���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home3/cpr76684/public_html/portfolio.tar
���ѧ٧ѧ�
exceptions.php 0000644 00000012011 15151222010 0007416 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * This file contains all the portfolio exception classes. * * @package core_portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, Martin Dougiamas * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Top level portfolio exception. * * Sometimes caught and re-thrown as portfolio_export_exception * @see portfolio_export_exception * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_exception extends moodle_exception {} /** * Exception to throw during an export - will clean up session and tempdata * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_export_exception extends portfolio_exception { /** * Constructor. * * @param portfolio_exporter $exporter instance of portfolio_exporter (will handle null case) * @param string $errorcode language string key * @param string $module language string module (optional, defaults to moodle) * @param string $continue url to continue to (optional, defaults to wwwroot) * @param object $a language string data (optional, defaults to null) */ public function __construct($exporter, $errorcode, $module=null, $continue=null, $a=null) { global $CFG; // This static variable is necessary because sometimes the code below // which tries to obtain a continue link can cause one of these // exceptions to be thrown. This would create an infinite loop (until // PHP hits its stack limit). Using this static lets us make the // nested constructor finish immediately without attempting to call // methods that might fail. static $inconstructor = false; if (!$inconstructor && !empty($exporter) && $exporter instanceof portfolio_exporter) { $inconstructor = true; try { if (empty($continue)) { $caller = $exporter->get('caller'); if (!empty($caller) && $caller instanceof portfolio_caller_base) { $continue = $exporter->get('caller')->get_return_url(); } } // this was previously only called if we were in cron, // but I think if there's always an exception, we should clean up // rather than force the user to resolve the export later. $exporter->process_stage_cleanup(); } catch(Exception $e) { // Ooops, we had an exception trying to get caller // information. Ignore it. } $inconstructor = false; } parent::__construct($errorcode, $module, $continue, $a); } } /** * Exception for callers to throw when they have a problem. * * Usually caught and rethrown as portfolio_export_exception * @see portfolio_export_exception * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_caller_exception extends portfolio_exception {} /** * Exception for portfolio plugins to throw when they have a problem. * * Usually caught and rethrown as portfolio_export_exception * @see portfolio_export_exception * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_plugin_exception extends portfolio_exception {} /** * Exception for interacting with the button class * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_button_exception extends portfolio_exception {} /** * Leap2a exception - for invalid api calls * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_leap2a_exception extends portfolio_exception {} exporter.php 0000644 00000107411 15151222010 0007116 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * This file contains the class definition for the exporter object. * * @package core_portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * Martin Dougiamas <http://dougiamas.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * The class that handles the various stages of the actual export * and the communication between the caller and the portfolio plugin. * * This is stored in the database between page requests in serialized base64 encoded form * also contains helper methods for the plugin and caller to use (at the end of the file) * @see get_base_filearea - where to write files to * @see write_new_file - write some content to a file in the export filearea * @see copy_existing_file - copy an existing file into the export filearea * @see get_tempfiles - return list of all files in the export filearea * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * Martin Dougiamas <http://dougiamas.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_exporter { /** @var portfolio_caller_base the caller object used during the export */ private $caller; /** @var portfolio_plugin_base the portfolio plugin instanced used during the export */ private $instance; /** @var bool if there has been no config form displayed to the user */ private $noexportconfig; /** * @var stdClass the user currently exporting content always $USER, * but more conveniently placed here */ private $user; /** * @var string the file to include that contains the class defintion of * the portfolio instance plugin used to re-waken the object after sleep */ public $instancefile; /** * @var string the component that contains the class definition of * the caller object used to re-waken the object after sleep */ public $callercomponent; /** @var int the current stage of the export */ private $stage; /** @var bool whether something (usually the portfolio plugin) has forced queuing */ private $forcequeue; /** * @var int id of this export matches record in portfolio_tempdata table * and used for itemid for file storage. */ private $id; /** @var array of stages that have had the portfolio plugin already steal control from them */ private $alreadystolen; /** * @var stored_file files that the exporter has written to this temp area keep track of * this in case of duplicates within one export see MDL-16390 */ private $newfilehashes; /** * @var string selected exportformat this is also set in * export_config in the portfolio and caller classes */ private $format; /** @var bool queued - this is set after the event is triggered */ private $queued = false; /** @var int expiry time - set the first time the object is saved out */ private $expirytime; /** * @var bool deleted - this is set during the cleanup routine so * that subsequent save() calls can detect it */ private $deleted = false; /** * Construct a new exporter for use * * @param portfolio_plugin_base $instance portfolio instance (passed by reference) * @param portfolio_caller_base $caller portfolio caller (passed by reference) * @param string $callercomponent the name of the callercomponent */ public function __construct($instance, portfolio_caller_base $caller, $callercomponent) { $this->instance = $instance; $this->caller = $caller; if ($instance) { $this->instancefile = 'portfolio/' . $instance->get('plugin') . '/lib.php'; $this->instance->set('exporter', $this); } $this->callercomponent = $callercomponent; $this->stage = PORTFOLIO_STAGE_CONFIG; $this->caller->set('exporter', $this); $this->alreadystolen = array(); $this->newfilehashes = array(); } /** * Generic getter for properties belonging to this instance * <b>outside</b> the subclasses like name, visible etc. * * @param string $field property's name * @return portfolio_format|mixed */ public function get($field) { if ($field == 'format') { return portfolio_format_object($this->format); } else if ($field == 'formatclass') { return $this->format; } if (property_exists($this, $field)) { return $this->{$field}; } $a = (object)array('property' => $field, 'class' => get_class($this)); throw new portfolio_export_exception($this, 'invalidproperty', 'portfolio', null, $a); } /** * Generic setter for properties belonging to this instance * <b>outside</b> the subclass like name, visible, etc. * * @param string $field property's name * @param mixed $value property's value * @return bool * @throws portfolio_export_exception */ public function set($field, &$value) { if (property_exists($this, $field)) { $this->{$field} =& $value; if ($field == 'instance') { $this->instancefile = 'portfolio/' . $this->instance->get('plugin') . '/lib.php'; $this->instance->set('exporter', $this); } $this->dirty = true; return true; } $a = (object)array('property' => $field, 'class' => get_class($this)); throw new portfolio_export_exception($this, 'invalidproperty', 'portfolio', null, $a); } /** * Sets this export to force queued. * Sometimes plugins need to set this randomly * if an external system changes its mind * about what's supported */ public function set_forcequeue() { $this->forcequeue = true; } /** * Process the given stage calling whatever functions are necessary * * @param int $stage (see PORTFOLIO_STAGE_* constants) * @param bool $alreadystolen used to avoid letting plugins steal control twice. * @return bool whether or not to process the next stage. this is important as the function is called recursively. */ public function process_stage($stage, $alreadystolen=false) { $this->set('stage', $stage); if ($alreadystolen) { $this->alreadystolen[$stage] = true; } else { if (!array_key_exists($stage, $this->alreadystolen)) { $this->alreadystolen[$stage] = false; } } if (!$this->alreadystolen[$stage] && $url = $this->instance->steal_control($stage)) { $this->save(); redirect($url); // does not return } else { $this->save(); } $waiting = $this->instance->get_export_config('wait'); if ($stage > PORTFOLIO_STAGE_QUEUEORWAIT && empty($waiting)) { $stage = PORTFOLIO_STAGE_FINISHED; } $functionmap = array( PORTFOLIO_STAGE_CONFIG => 'config', PORTFOLIO_STAGE_CONFIRM => 'confirm', PORTFOLIO_STAGE_QUEUEORWAIT => 'queueorwait', PORTFOLIO_STAGE_PACKAGE => 'package', PORTFOLIO_STAGE_CLEANUP => 'cleanup', PORTFOLIO_STAGE_SEND => 'send', PORTFOLIO_STAGE_FINISHED => 'finished' ); $function = 'process_stage_' . $functionmap[$stage]; try { if ($this->$function()) { // if we get through here it means control was returned // as opposed to wanting to stop processing // eg to wait for user input. $this->save(); $stage++; return $this->process_stage($stage); } else { $this->save(); return false; } } catch (portfolio_caller_exception $e) { portfolio_export_rethrow_exception($this, $e); } catch (portfolio_plugin_exception $e) { portfolio_export_rethrow_exception($this, $e); } catch (portfolio_export_exception $e) { throw $e; } catch (Exception $e) { debugging(get_string('thirdpartyexception', 'portfolio', get_class($e))); debugging($e); portfolio_export_rethrow_exception($this, $e); } } /** * Helper function to return the portfolio instance * * @return portfolio_plugin_base subclass */ public function instance() { return $this->instance; } /** * Helper function to return the caller object * * @return portfolio_caller_base subclass */ public function caller() { return $this->caller; } /** * Processes the 'config' stage of the export * * @return bool whether or not to process the next stage. this is important as the control function is called recursively. */ public function process_stage_config() { global $OUTPUT, $CFG; $pluginobj = $callerobj = null; if ($this->instance->has_export_config()) { $pluginobj = $this->instance; } if ($this->caller->has_export_config()) { $callerobj = $this->caller; } $formats = portfolio_supported_formats_intersect($this->caller->supported_formats(), $this->instance->supported_formats()); $expectedtime = $this->instance->expected_time($this->caller->expected_time()); if (count($formats) == 0) { // something went wrong, we should not have gotten this far. throw new portfolio_export_exception($this, 'nocommonformats', 'portfolio', null, array('location' => get_class($this->caller), 'formats' => implode(',', $formats))); } // even if neither plugin or caller wants any config, we have to let the user choose their format, and decide to wait. if ($pluginobj || $callerobj || count($formats) > 1 || ($expectedtime != PORTFOLIO_TIME_LOW && $expectedtime != PORTFOLIO_TIME_FORCEQUEUE)) { $customdata = array( 'instance' => $this->instance, 'id' => $this->id, 'plugin' => $pluginobj, 'caller' => $callerobj, 'userid' => $this->user->id, 'formats' => $formats, 'expectedtime' => $expectedtime, ); require_once($CFG->libdir . '/portfolio/forms.php'); $mform = new portfolio_export_form('', $customdata); if ($mform->is_cancelled()){ $this->cancel_request(); } else if ($fromform = $mform->get_data()){ if (!confirm_sesskey()) { throw new portfolio_export_exception($this, 'confirmsesskeybad'); } $pluginbits = array(); $callerbits = array(); foreach ($fromform as $key => $value) { if (strpos($key, 'plugin_') === 0) { $pluginbits[substr($key, 7)] = $value; } else if (strpos($key, 'caller_') === 0) { $callerbits[substr($key, 7)] = $value; } } $callerbits['format'] = $pluginbits['format'] = $fromform->format; $pluginbits['wait'] = $fromform->wait; if ($expectedtime == PORTFOLIO_TIME_LOW) { $pluginbits['wait'] = 1; $pluginbits['hidewait'] = 1; } else if ($expectedtime == PORTFOLIO_TIME_FORCEQUEUE) { $pluginbits['wait'] = 0; $pluginbits['hidewait'] = 1; $this->forcequeue = true; } $callerbits['hideformat'] = $pluginbits['hideformat'] = (count($formats) == 1); $this->caller->set_export_config($callerbits); $this->instance->set_export_config($pluginbits); $this->set('format', $fromform->format); return true; } else { $this->print_header(get_string('configexport', 'portfolio')); echo $OUTPUT->box_start(); $mform->display(); echo $OUTPUT->box_end(); echo $OUTPUT->footer(); return false; } } else { $this->noexportconfig = true; $format = array_shift($formats); $config = array( 'hidewait' => 1, 'wait' => (($expectedtime == PORTFOLIO_TIME_LOW) ? 1 : 0), 'format' => $format, 'hideformat' => 1 ); $this->set('format', $format); $this->instance->set_export_config($config); $this->caller->set_export_config(array('format' => $format, 'hideformat' => 1)); if ($expectedtime == PORTFOLIO_TIME_FORCEQUEUE) { $this->forcequeue = true; } return true; // do not break - fall through to confirm } } /** * Processes the 'confirm' stage of the export * * @return bool whether or not to process the next stage. this is important as the control function is called recursively. */ public function process_stage_confirm() { global $CFG, $DB, $OUTPUT; $previous = $DB->get_records( 'portfolio_log', array( 'userid' => $this->user->id, 'portfolio' => $this->instance->get('id'), 'caller_sha1' => $this->caller->get_sha1(), ) ); if (isset($this->noexportconfig) && empty($previous)) { return true; } $strconfirm = get_string('confirmexport', 'portfolio'); $baseurl = $CFG->wwwroot . '/portfolio/add.php?sesskey=' . sesskey() . '&id=' . $this->get('id'); $yesurl = $baseurl . '&stage=' . PORTFOLIO_STAGE_QUEUEORWAIT; $nourl = $baseurl . '&cancel=1'; $this->print_header(get_string('confirmexport', 'portfolio')); echo $OUTPUT->box_start(); echo $OUTPUT->heading(get_string('confirmsummary', 'portfolio'), 3); $mainsummary = array(); if (!$this->instance->get_export_config('hideformat')) { $mainsummary[get_string('selectedformat', 'portfolio')] = get_string('format_' . $this->instance->get_export_config('format'), 'portfolio'); } if (!$this->instance->get_export_config('hidewait')) { $mainsummary[get_string('selectedwait', 'portfolio')] = get_string(($this->instance->get_export_config('wait') ? 'yes' : 'no')); } if ($previous) { $previousstr = ''; foreach ($previous as $row) { $previousstr .= userdate($row->time); if ($row->caller_class != get_class($this->caller)) { if (!empty($row->caller_file)) { portfolio_include_callback_file($row->caller_file); } else if (!empty($row->caller_component)) { portfolio_include_callback_file($row->caller_component); } else { // Ok, that's weird - this should never happen. Is the apocalypse coming? continue; } $previousstr .= ' (' . call_user_func(array($row->caller_class, 'display_name')) . ')'; } $previousstr .= '<br />'; } $mainsummary[get_string('exportedpreviously', 'portfolio')] = $previousstr; } if (!$csummary = $this->caller->get_export_summary()) { $csummary = array(); } if (!$isummary = $this->instance->get_export_summary()) { $isummary = array(); } $mainsummary = array_merge($mainsummary, $csummary, $isummary); $table = new html_table(); $table->attributes['class'] = 'generaltable exportsummary'; $table->data = array(); foreach ($mainsummary as $string => $value) { $table->data[] = array($string, $value); } echo html_writer::table($table); echo $OUTPUT->confirm($strconfirm, $yesurl, $nourl); echo $OUTPUT->box_end(); echo $OUTPUT->footer(); return false; } /** * Processes the 'queueornext' stage of the export * * @return bool whether or not to process the next stage. this is important as the control function is called recursively. */ public function process_stage_queueorwait() { global $DB; $wait = $this->instance->get_export_config('wait'); if (empty($wait)) { $DB->set_field('portfolio_tempdata', 'queued', 1, array('id' => $this->id)); $this->queued = true; return $this->process_stage_finished(true); } return true; } /** * Processes the 'package' stage of the export * * @return bool whether or not to process the next stage. this is important as the control function is called recursively. * @throws portfolio_export_exception */ public function process_stage_package() { // now we've agreed on a format, // the caller is given control to package it up however it wants // and then the portfolio plugin is given control to do whatever it wants. try { $this->caller->prepare_package(); } catch (portfolio_exception $e) { throw new portfolio_export_exception($this, 'callercouldnotpackage', 'portfolio', null, $e->getMessage()); } catch (file_exception $e) { throw new portfolio_export_exception($this, 'callercouldnotpackage', 'portfolio', null, $e->getMessage()); } try { $this->instance->prepare_package(); } catch (portfolio_exception $e) { throw new portfolio_export_exception($this, 'plugincouldnotpackage', 'portfolio', null, $e->getMessage()); } catch (file_exception $e) { throw new portfolio_export_exception($this, 'plugincouldnotpackage', 'portfolio', null, $e->getMessage()); } return true; } /** * Processes the 'cleanup' stage of the export * * @param bool $pullok normally cleanup is deferred for pull plugins until after the file is requested from portfolio/file.php * if you want to clean up earlier, pass true here (defaults to false) * @return bool whether or not to process the next stage. this is important as the control function is called recursively. */ public function process_stage_cleanup($pullok=false) { global $CFG, $DB; if (!$pullok && $this->get('instance') && !$this->get('instance')->is_push()) { return true; } if ($this->get('instance')) { // might not be set - before export really starts $this->get('instance')->cleanup(); } $DB->delete_records('portfolio_tempdata', array('id' => $this->id)); $fs = get_file_storage(); $fs->delete_area_files(SYSCONTEXTID, 'portfolio', 'exporter', $this->id); $this->deleted = true; return true; } /** * Processes the 'send' stage of the export * * @return bool whether or not to process the next stage. this is important as the control function is called recursively. */ public function process_stage_send() { // send the file try { $this->instance->send_package(); } catch (portfolio_plugin_exception $e) { // not catching anything more general here. plugins with dependencies on other libraries that throw exceptions should catch and rethrow. // eg curl exception throw new portfolio_export_exception($this, 'failedtosendpackage', 'portfolio', null, $e->getMessage()); } // only log push types, pull happens in send_file if ($this->get('instance')->is_push()) { $this->log_transfer(); } return true; } /** * Log the transfer * * this should only be called after the file has been sent * either via push, or sent from a pull request. */ public function log_transfer() { global $DB; $l = array( 'userid' => $this->user->id, 'portfolio' => $this->instance->get('id'), 'caller_file'=> '', 'caller_component' => $this->callercomponent, 'caller_sha1' => $this->caller->get_sha1(), 'caller_class' => get_class($this->caller), 'continueurl' => $this->instance->get_static_continue_url(), 'returnurl' => $this->caller->get_return_url(), 'tempdataid' => $this->id, 'time' => time(), ); $DB->insert_record('portfolio_log', $l); } /** * In some cases (mahara) we need to update this after the log has been done * because of MDL-20872 * * @param string $url link to be recorded to portfolio log */ public function update_log_url($url) { global $DB; $DB->set_field('portfolio_log', 'continueurl', $url, array('tempdataid' => $this->id)); } /** * Processes the 'finish' stage of the export * * @param bool $queued let the process to be queued * @return bool whether or not to process the next stage. this is important as the control function is called recursively. */ public function process_stage_finished($queued=false) { global $OUTPUT; $returnurl = $this->caller->get_return_url(); $continueurl = $this->instance->get_interactive_continue_url(); $extras = $this->instance->get_extra_finish_options(); $key = 'exportcomplete'; if ($queued || $this->forcequeue) { $key = 'exportqueued'; if ($this->forcequeue) { $key = 'exportqueuedforced'; } } $this->print_header(get_string($key, 'portfolio'), false); self::print_finish_info($returnurl, $continueurl, $extras); echo $OUTPUT->footer(); return false; } /** * Local print header function to be reused across the export * * @param string $headingstr full language string * @param bool $summary (optional) to print summary, default is set to true * @return void */ public function print_header($headingstr, $summary=true) { global $OUTPUT, $PAGE; $titlestr = get_string('exporting', 'portfolio'); $headerstr = get_string('exporting', 'portfolio'); $PAGE->set_title($titlestr); $PAGE->set_heading($headerstr); echo $OUTPUT->header(); echo $OUTPUT->heading($headingstr); if (!$summary) { return; } echo $OUTPUT->box_start(); echo $OUTPUT->box_start(); echo $this->caller->heading_summary(); echo $OUTPUT->box_end(); if ($this->instance) { echo $OUTPUT->box_start(); echo $this->instance->heading_summary(); echo $OUTPUT->box_end(); } echo $OUTPUT->box_end(); } /** * Cancels a potfolio request and cleans up the tempdata * and redirects the user back to where they started * * @param bool $logreturn options to return to porfolio log or caller return page * @return void * @uses exit */ public function cancel_request($logreturn=false) { global $CFG; if (!isset($this)) { return; } $this->process_stage_cleanup(true); if ($logreturn) { redirect($CFG->wwwroot . '/user/portfoliologs.php'); } redirect($this->caller->get_return_url()); exit; } /** * Writes out the contents of this object and all its data to the portfolio_tempdata table and sets the 'id' field. * * @return void */ public function save() { global $DB; if (empty($this->id)) { $r = (object)array( 'data' => base64_encode(serialize($this)), 'expirytime' => time() + (60*60*24), 'userid' => $this->user->id, 'instance' => (empty($this->instance)) ? null : $this->instance->get('id'), ); $this->id = $DB->insert_record('portfolio_tempdata', $r); $this->expirytime = $r->expirytime; $this->save(); // call again so that id gets added to the save data. } else { if (!$r = $DB->get_record('portfolio_tempdata', array('id' => $this->id))) { if (!$this->deleted) { //debugging("tried to save current object, but failed - see MDL-20872"); } return; } $r->data = base64_encode(serialize($this)); $r->instance = (empty($this->instance)) ? null : $this->instance->get('id'); $DB->update_record('portfolio_tempdata', $r); } } /** * Rewakens the data from the database given the id. * Makes sure to load the required files with the class definitions * * @param int $id id of data * @return portfolio_exporter */ public static function rewaken_object($id) { global $DB, $CFG; require_once($CFG->libdir . '/filelib.php'); require_once($CFG->libdir . '/portfolio/exporter.php'); require_once($CFG->libdir . '/portfolio/caller.php'); require_once($CFG->libdir . '/portfolio/plugin.php'); if (!$data = $DB->get_record('portfolio_tempdata', array('id' => $id))) { // maybe it's been finished already by a pull plugin // so look in the logs if ($log = $DB->get_record('portfolio_log', array('tempdataid' => $id))) { self::print_cleaned_export($log); } throw new portfolio_exception('invalidtempid', 'portfolio'); } $exporter = unserialize(base64_decode($data->data)); if ($exporter->instancefile) { require_once($CFG->dirroot . '/' . $exporter->instancefile); } if (!empty($exporter->callerfile)) { portfolio_include_callback_file($exporter->callerfile); } else if (!empty($exporter->callercomponent)) { portfolio_include_callback_file($exporter->callercomponent); } else { return; // Should never get here! } $exporter = unserialize(serialize($exporter)); if (!$exporter->get('id')) { // workaround for weird case // where the id doesn't get saved between a new insert // and the subsequent call that sets this field in the serialised data $exporter->set('id', $id); $exporter->save(); } return $exporter; } /** * Helper function to create the beginnings of a file_record object * to create a new file in the portfolio_temporary working directory. * Use write_new_file or copy_existing_file externally * @see write_new_file * @see copy_existing_file * * @param string $name filename of new record * @return object */ private function new_file_record_base($name) { return (object)array_merge($this->get_base_filearea(), array( 'filepath' => '/', 'filename' => $name, )); } /** * Verifies a rewoken object. * Checks to make sure it belongs to the same user and session as is currently in use. * * @param bool $readonly if we're reawakening this for a user to just display in the log view, don't verify the sessionkey * @throws portfolio_exception */ public function verify_rewaken($readonly=false) { global $USER, $CFG; if ($this->get('user')->id != $USER->id) { // make sure it belongs to the right user throw new portfolio_exception('notyours', 'portfolio'); } if (!$readonly && $this->get('instance') && !$this->get('instance')->allows_multiple_exports()) { $already = portfolio_existing_exports($this->get('user')->id, $this->get('instance')->get('plugin')); $already = array_keys($already); if (array_shift($already) != $this->get('id')) { $a = (object)array( 'plugin' => $this->get('instance')->get('plugin'), 'link' => $CFG->wwwroot . '/user/portfoliologs.php', ); throw new portfolio_exception('nomultipleexports', 'portfolio', '', $a); } } if (!$this->caller->check_permissions()) { // recall the caller permission check throw new portfolio_caller_exception('nopermissions', 'portfolio', $this->caller->get_return_url()); } } /** * Copies a file from somewhere else in moodle * to the portfolio temporary working directory * associated with this export * * @param stored_file $oldfile existing stored file object * @return stored_file|bool new file object */ public function copy_existing_file($oldfile) { if (array_key_exists($oldfile->get_contenthash(), $this->newfilehashes)) { return $this->newfilehashes[$oldfile->get_contenthash()]; } $fs = get_file_storage(); $file_record = $this->new_file_record_base($oldfile->get_filename()); if ($dir = $this->get('format')->get_file_directory()) { $file_record->filepath = '/'. $dir . '/'; } try { $newfile = $fs->create_file_from_storedfile($file_record, $oldfile->get_id()); $this->newfilehashes[$newfile->get_contenthash()] = $newfile; return $newfile; } catch (file_exception $e) { return false; } } /** * Writes out some content to a file * in the portfolio temporary working directory * associated with this export. * * @param string $content content to write * @param string $name filename to use * @param bool $manifest whether this is the main file or an secondary file (eg attachment) * @return stored_file */ public function write_new_file($content, $name, $manifest=true) { $fs = get_file_storage(); $file_record = $this->new_file_record_base($name); if (empty($manifest) && ($dir = $this->get('format')->get_file_directory())) { $file_record->filepath = '/' . $dir . '/'; } return $fs->create_file_from_string($file_record, $content); } /** * Zips all files in the temporary directory * * @param string $filename name of resulting zipfile (optional, defaults to portfolio-export.zip) * @param string $filepath subpath in the filearea (optional, defaults to final) * @return stored_file|bool resulting stored_file object, or false */ public function zip_tempfiles($filename='portfolio-export.zip', $filepath='/final/') { $zipper = new zip_packer(); list ($contextid, $component, $filearea, $itemid) = array_values($this->get_base_filearea()); if ($newfile = $zipper->archive_to_storage($this->get_tempfiles(), $contextid, $component, $filearea, $itemid, $filepath, $filename, $this->user->id)) { return $newfile; } return false; } /** * Returns an arary of files in the temporary working directory * for this export. * Always use this instead of the files api directly * * @param string $skipfile name of the file to be skipped * @return array of stored_file objects keyed by name */ public function get_tempfiles($skipfile='portfolio-export.zip') { $fs = get_file_storage(); $files = $fs->get_area_files(SYSCONTEXTID, 'portfolio', 'exporter', $this->id, 'sortorder, itemid, filepath, filename', false); if (empty($files)) { return array(); } $returnfiles = array(); foreach ($files as $f) { if ($f->get_filename() == $skipfile) { continue; } $returnfiles[$f->get_filepath() . $f->get_filename()] = $f; } return $returnfiles; } /** * Returns the context, filearea, and itemid. * Parts of a filearea (not filepath) to be used by * plugins if they want to do things like zip up the contents of * the temp area to here, or something that can't be done just using * write_new_file, copy_existing_file or get_tempfiles * * @return array contextid, filearea, itemid are the keys. */ public function get_base_filearea() { return array( 'contextid' => SYSCONTEXTID, 'component' => 'portfolio', 'filearea' => 'exporter', 'itemid' => $this->id, ); } /** * Wrapper function to print a friendly error to users * This is generally caused by them hitting an expired transfer * through the usage of the backbutton * * @uses exit */ public static function print_expired_export() { global $CFG, $OUTPUT, $PAGE; $title = get_string('exportexpired', 'portfolio'); $PAGE->navbar->add(get_string('exportexpired', 'portfolio')); $PAGE->set_title($title); $PAGE->set_heading($title); echo $OUTPUT->header(); echo $OUTPUT->notification(get_string('exportexpireddesc', 'portfolio')); echo $OUTPUT->continue_button($CFG->wwwroot); echo $OUTPUT->footer(); exit; } /** * Wrapper function to print a friendly error to users * * @param stdClass $log portfolio_log object * @param portfolio_plugin_base $instance portfolio instance * @uses exit */ public static function print_cleaned_export($log, $instance=null) { global $CFG, $OUTPUT, $PAGE; if (empty($instance) || !$instance instanceof portfolio_plugin_base) { $instance = portfolio_instance($log->portfolio); } $title = get_string('exportalreadyfinished', 'portfolio'); $PAGE->navbar->add($title); $PAGE->set_title($title); $PAGE->set_heading($title); echo $OUTPUT->header(); echo $OUTPUT->notification(get_string('exportalreadyfinished', 'portfolio')); self::print_finish_info($log->returnurl, $instance->resolve_static_continue_url($log->continueurl)); echo $OUTPUT->continue_button($CFG->wwwroot); echo $OUTPUT->footer(); exit; } /** * Wrapper function to print continue and/or return link * * @param string $returnurl link to previos page * @param string $continueurl continue to next page * @param array $extras (optional) other links to be display. */ public static function print_finish_info($returnurl, $continueurl, $extras=null) { if ($returnurl) { echo '<a href="' . $returnurl . '">' . get_string('returntowhereyouwere', 'portfolio') . '</a><br />'; } if ($continueurl) { echo '<a href="' . $continueurl . '">' . get_string('continuetoportfolio', 'portfolio') . '</a><br />'; } if (is_array($extras)) { foreach ($extras as $link => $string) { echo '<a href="' . $link . '">' . $string . '</a><br />'; } } } } constants.php 0000644 00000013670 15151222010 0007265 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * This file contains all the defined constants to do with portfolios. * * @package core_portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, Martin Dougiamas * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); //EXPORT STAGE CONSTANTS /** * PORTFOLIO_STAGE_CONFIG - display a form to the user this one might not be * used if neither the plugin, or the caller has any config. */ define('PORTFOLIO_STAGE_CONFIG', 1); /** * PORTFOLIO_STAGE_CONFIRM - summarise the form and ask for confirmation * if we skipped PORTFOLIO_STAGE_CONFIG, * just confirm the send. */ define('PORTFOLIO_STAGE_CONFIRM', 2); /** * PORTFOLIO_STAGE_QUEUEORWAIT - either queue the event and skip to PORTFOLIO_STAGE_FINISHED */ define('PORTFOLIO_STAGE_QUEUEORWAIT', 3); /** * PORTFOLIO_STAGE_PACKAGE - package up the various bits during this stage both the caller * and the plugin get their package methods called */ define('PORTFOLIO_STAGE_PACKAGE', 4); /** * PORTFOLIO_STAGE_SEND - the portfolio plugin must send the file */ define('PORTFOLIO_STAGE_SEND', 5); /** * PORTFOLIO_STAGE_CLEANUP - cleanup the temporary area */ define('PORTFOLIO_STAGE_CLEANUP', 6); /** * PORTFOLIO_STAGE_FINISHED - display the "finished notification" */ define('PORTFOLIO_STAGE_FINISHED', 7); // EXPORT FORMAT CONSTANTS // These should always correspond to a string in the portfolio module, called format_{$value} /** * PORTFOLIO_FORMAT_FILE - the most basic fallback format. this should always be supported * in remote system.s */ define('PORTFOLIO_FORMAT_FILE', 'file'); /** * PORTFOLIO_FORMAT_MBKP - the plugin needs to be able to write a complete backup * the caller need to be able to export the particular XML bits to insert * into moodle.xml (?and the file bits if necessary) */ define('PORTFOLIO_FORMAT_MBKP', 'mbkp'); /** * PORTFOLIO_FORMAT_RICHHTML - like html but with attachments. */ define('PORTFOLIO_FORMAT_RICHHTML', 'richhtml'); /** * PORTFOLIO_FORMAT_PLAINHTML - a single html representation - no attachments */ define('PORTFOLIO_FORMAT_PLAINHTML', 'plainhtml'); /** * PORTFOLIO_FORMAT_IMAGE - subtype of file */ define('PORTFOLIO_FORMAT_IMAGE', 'image'); /** * PORTFOLIO_FORMAT_VIDEO - subtype of file */ define('PORTFOLIO_FORMAT_VIDEO', 'video'); /** * PORTFOLIO_FORMAT_TEXT - subtype of file */ define('PORTFOLIO_FORMAT_TEXT', 'text'); /** * PORTFOLIO_FORMAT_PDF - subtype of file */ define('PORTFOLIO_FORMAT_PDF', 'pdf'); /** * PORTFOLIO_FORMAT_DOCUMENT - subtype of file */ define('PORTFOLIO_FORMAT_DOCUMENT', 'document'); /** * PORTFOLIO_FORMAT_SPREADSHEET - subtype of file */ define('PORTFOLIO_FORMAT_SPREADSHEET', 'spreadsheet'); /** * PORTFOLIO_FORMAT_PRESENTATION - subtype of file */ define('PORTFOLIO_FORMAT_PRESENTATION', 'presentation'); /** * PORTFOLIO_FORMAT_RICH - just used to say, "we support all these" */ define('PORTFOLIO_FORMAT_RICH', 'rich'); /** * PORTFOLIO_FORMAT_LEAP2A - supported by mahara and and others {http://wiki.cetis.ac.uk/LEAP_2.0} */ define('PORTFOLIO_FORMAT_LEAP2A', 'leap2a'); // EXPORT TIME LEVELS // These should correspond to a string in the portfolio module, called time_{$value} /** * PORTFOLIO_TIME_LOW - no delay. don't even offer the user the option * of not waiting for the transfer */ define('PORTFOLIO_TIME_LOW', 'low'); /** * PORTFOLIO_TIME_MODERATE - a small delay. user can still easily opt to * watch this transfer and wait. */ define('PORTFOLIO_TIME_MODERATE', 'moderate'); /** * PORTFOLIO_TIME_HIGH - slow. the user really should not be given the option * to choose this. */ define('PORTFOLIO_TIME_HIGH', 'high'); /** * PORTFOLIO_TIME_FORCEQUEUE - very slow, or immediate transfers not supported */ define('PORTFOLIO_TIME_FORCEQUEUE', 'queue'); // BUTTON FORMATS // Available ways to add the portfolio export to a page /** * PORTFOLIO_ADD_FULL_FORM - a whole form, containing a drop down menu (where necessary) * and a submit button */ define('PORTFOLIO_ADD_FULL_FORM', 1); /** * PORTFOLIO_ADD_ICON_FORM - a whole form, containing a drop down menu (where necessary) * but has an icon instead of a button to submit */ define('PORTFOLIO_ADD_ICON_FORM', 2); /** * PORTFOLIO_ADD_ICON_LINK - just an icon with a link around it (yuk, as will result in a long url * only use where necessary) */ define('PORTFOLIO_ADD_ICON_LINK', 3); /** * PORTFOLIO_ADD_TEXT_LINK - just some text with a link around it (yuk, as will result in a long url * only use where necessary) */ define('PORTFOLIO_ADD_TEXT_LINK', 4); /** * PORTFOLIO_ADD_FAKE_URL - hacky way to turn the button class into a url to redirect to * this replaces the old portfolio_fake_add_url function */ define('PORTFOLIO_ADD_FAKE_URL', 5); /** * PORTFOLIO_ADD_MOODULE_URL - hacky way to turn the button class into a moodle_url to redirect to * this replaces the old portfolio_fake_add_url function */ define('PORTFOLIO_ADD_MOODLE_URL', 6); plugin.php 0000644 00000073640 15151222010 0006552 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * This file contains the base classes for portfolio plugins to inherit from: * * portfolio_plugin_pull_base and portfolio_plugin_push_base * which both in turn inherit from portfolio_plugin_base. * {@link http://docs.moodle.org/dev/Writing_a_Portfolio_Plugin} * * @package core_portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, * Martin Dougiamas <http://dougiamas.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * The base class for portfolio plugins. * * All plugins must subclass this * either via portfolio_plugin_pull_base or portfolio_plugin_push_base * @see portfolio_plugin_pull_base * @see portfolio_plugin_push_base * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class portfolio_plugin_base { /** @var bool whether this object needs writing out to the database */ protected $dirty; /** @var integer id of instance */ protected $id; /** @var string name of instance */ protected $name; /** @var string plugin this instance belongs to */ protected $plugin; /** @var bool whether this instance is visible or not */ protected $visible; /** @var array admin configured config use {@link set_config} and {@get_config} to access */ protected $config; /** @var array user config cache. keyed on userid and then on config field => value use {@link get_user_config} and {@link set_user_config} to access. */ protected $userconfig; /** @var array export config during export use {@link get_export_config} and {@link set export_config} to access. */ protected $exportconfig; /** @var stdClass user currently exporting data */ protected $user; /** @var stdClass a reference to the exporter object */ protected $exporter; /** * Array of formats this portfolio supports * the intersection of what this function returns * and what the caller supports will be used. * Use the constants PORTFOLIO_FORMAT_* * * @return array list of formats */ public function supported_formats() { return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICH); } /** * Override this if you are supporting the 'file' type (or a subformat) * but have restrictions on mimetypes * * @param string $mimetype file type or subformat * @return bool */ public static function file_mime_check($mimetype) { return true; } /** * How long does this reasonably expect to take.. * Should we offer the user the option to wait.. * This is deliberately nonstatic so it can take filesize into account * * @param string $callertime - what the caller thinks * the portfolio plugin instance * is given the final say * because it might be (for example) download. */ public abstract function expected_time($callertime); /** * Is this plugin push or pull. * If push, cleanup will be called directly after send_package * If not, cleanup will be called after portfolio/file.php is requested */ public abstract function is_push(); /** * Returns the user-friendly name for this plugin. * Usually just get_string('pluginname', 'portfolio_something') */ public static function get_name() { throw new coding_exception('get_name() method needs to be overridden in each subclass of portfolio_plugin_base'); } /** * Check sanity of plugin. * If this function returns something non empty, ALL instances of your plugin * will be set to invisble and not be able to be set back until it's fixed * * @return string|int|bool - string = error string KEY (must be inside portfolio_$yourplugin) or 0/false if you're ok */ public static function plugin_sanity_check() { return 0; } /** * Check sanity of instances. * If this function returns something non empty, the instance will be * set to invislbe and not be able to be set back until it's fixed. * * @return int|string|bool - string = error string KEY (must be inside portfolio_$yourplugin) or 0/false if you're ok */ public function instance_sanity_check() { return 0; } /** * Does this plugin need any configuration by the administrator? * If you override this to return true, * you <b>must</b> implement admin_config_form. * @see admin_config_form * * @return bool */ public static function has_admin_config() { return false; } /** * Can this plugin be configured by the user in their profile? * If you override this to return true, * you <b>must</b> implement user_config_form * @see user_config_form * * @return bool */ public function has_user_config() { return false; } /** * Does this plugin need configuration during export time? * If you override this to return true, * you <b>must</b> implement export_config_form. * @see export_config_form * * @return bool */ public function has_export_config() { return false; } /** * Just like the moodle form validation function. * This is passed in the data array from the form * and if a non empty array is returned, form processing will stop. * * @param array $data data from form. */ public function export_config_validation(array $data) {} /** * Just like the moodle form validation function. * This is passed in the data array from the form * and if a non empty array is returned, form processing will stop. * * @param array $data data from form. */ public function user_config_validation(array $data) {} /** * Sets the export time config from the moodle form. * You can also use this to set export config that * isn't actually controlled by the user. * Eg: things that your subclasses want to keep in state * across the export. * Keys must be in get_allowed_export_config * This is deliberately not final (see googledocs plugin) * @see get_allowed_export_config * * @param array $config named array of config items to set. */ public function set_export_config($config) { $allowed = array_merge( array('wait', 'hidewait', 'format', 'hideformat'), $this->get_allowed_export_config() ); foreach ($config as $key => $value) { if (!in_array($key, $allowed)) { $a = (object)array('property' => $key, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', null, $a); } $this->exportconfig[$key] = $value; } } /** * Gets an export time config value. * Subclasses should not override this. * * @param string $key field to fetch * @return null|string config value */ public final function get_export_config($key) { $allowed = array_merge( array('hidewait', 'wait', 'format', 'hideformat'), $this->get_allowed_export_config() ); if (!in_array($key, $allowed)) { $a = (object)array('property' => $key, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', null, $a); } if (!array_key_exists($key, $this->exportconfig)) { return null; } return $this->exportconfig[$key]; } /** * After the user submits their config, * they're given a confirm screen * summarising what they've chosen. * This function should return a table of nice strings => values * of what they've chosen * to be displayed in a table. * * @return bool */ public function get_export_summary() { return false; } /** * Called after the caller has finished having control * of its prepare_package function. * This function should read all the files from the portfolio * working file area and zip them and send them or whatever it wants. * get_tempfiles to get the list of files. * @see get_tempfiles * */ public abstract function prepare_package(); /** * This is the function that is responsible for sending * the package to the remote system, * or whatever request is necessary to initiate the transfer. * * @return bool success */ public abstract function send_package(); /** * Once everything is done and the user * has the finish page displayed to them. * The base class takes care of printing them * "return to where you are" or "continue to portfolio" links. * This function allows for exta finish options from the plugin * * @return bool */ public function get_extra_finish_options() { return false; } /** * The url for the user to continue to their portfolio * during the lifecycle of the request */ public abstract function get_interactive_continue_url(); /** * The url to save in the log as the continue url. * This is passed through resolve_static_continue_url() * at display time to the user. * * @return string */ public function get_static_continue_url() { return $this->get_interactive_continue_url(); } /** * Override this function if you need to add something on to the url * for post-export continues (eg from the log page). * Mahara does this, for example, to start a jump session. * * @param string $url static continue url * @return string */ public function resolve_static_continue_url($url) { return $url; } /** * mform to display to the user in their profile * if your plugin can't be configured by the user, * @see has_user_config. * Don't bother overriding this function * * @param moodleform $mform passed by reference, add elements to it */ public function user_config_form(&$mform) {} /** * mform to display to the admin configuring the plugin. * If your plugin can't be configured by the admin, * @see has_admin_config * Don't bother overriding this function. * This function can be called statically or non statically, * depending on whether it's creating a new instance (statically), * or editing an existing one (non statically) * * @param moodleform $mform passed by reference, add elements to it. */ public static function admin_config_form(&$mform) {} /** * Just like the moodle form validation function, * this is passed in the data array from the form * and if a non empty array is returned, form processing will stop. * * @param array $data data from form. */ public static function admin_config_validation($data) {} /** * mform to display to the user exporting data using this plugin. * If your plugin doesn't need user input at this time, * @see has_export_config. * Don't bother overrideing this function * * @param moodleform $mform passed by reference, add elements to it. */ public function export_config_form(&$mform) {} /** * Override this if your plugin doesn't allow multiple instances * * @return bool */ public static function allows_multiple_instances() { return true; } /** * If at any point the caller wants to steal control, * it can, by returning something that isn't false * in this function * The controller will redirect to whatever url * this function returns. * Afterwards, you can redirect back to portfolio/add.php?postcontrol=1 * and post_control is called before the rest of the processing * for the stage is done, * @see post_control * * @param int $stage to steal control *before* (see constants PARAM_STAGE_*} * @return bool */ public function steal_control($stage) { return false; } /** * After a plugin has elected to steal control, * and control returns to portfolio/add.php|postcontrol=1, * this function is called, and passed the stage that was stolen control from * and the request (get and post but not cookie) parameters. * This is useful for external systems that need to redirect the user back * with some extra data in the url (like auth tokens etc) * for an example implementation, see googledocs portfolio plugin. * * @param int $stage the stage before control was stolen * @param array $params a merge of $_GET and $_POST */ public function post_control($stage, $params) { } /** * This function creates a new instance of a plugin * saves it in the database, saves the config * and returns it. * You shouldn't need to override it * unless you're doing something really funky * * @param string $plugin portfolio plugin to create * @param string $name name of new instance * @param array $config what the admin config form returned * @return object subclass of portfolio_plugin_base */ public static function create_instance($plugin, $name, $config) { global $DB, $CFG; $new = (object)array( 'plugin' => $plugin, 'name' => $name, ); if (!portfolio_static_function($plugin, 'allows_multiple_instances')) { // check we don't have one already if ($DB->record_exists('portfolio_instance', array('plugin' => $plugin))) { throw new portfolio_exception('multipleinstancesdisallowed', 'portfolio', '', $plugin); } } $newid = $DB->insert_record('portfolio_instance', $new); require_once($CFG->dirroot . '/portfolio/' . $plugin . '/lib.php'); $classname = 'portfolio_plugin_' . $plugin; $obj = new $classname($newid); $obj->set_config($config); $obj->save(); return $obj; } /** * Construct a plugin instance. * Subclasses should not need to override this unless they're doing something special * and should call parent::__construct afterwards. * * @param int $instanceid id of plugin instance to construct * @param mixed $record stdclass object or named array - use this if you already have the record to avoid another query * @return portfolio_plugin_base */ public function __construct($instanceid, $record=null) { global $DB; if (!$record) { if (!$record = $DB->get_record('portfolio_instance', array('id' => $instanceid))) { throw new portfolio_exception('invalidinstance', 'portfolio'); } } foreach ((array)$record as $key =>$value) { if (property_exists($this, $key)) { $this->{$key} = $value; } } $this->config = new StdClass; $this->userconfig = array(); $this->exportconfig = array(); foreach ($DB->get_records('portfolio_instance_config', array('instance' => $instanceid)) as $config) { $this->config->{$config->name} = $config->value; } $this->init(); return $this; } /** * Called after __construct - allows plugins to perform initialisation tasks * without having to override the constructor. */ protected function init() { } /** * A list of fields that can be configured per instance. * This is used for the save handlers of the config form * and as checks in set_config and get_config. * * @return array array of strings (config item names) */ public static function get_allowed_config() { return array(); } /** * A list of fields that can be configured by the user. * This is used for the save handlers in the config form * and as checks in set_user_config and get_user_config. * * @return array array of strings (config field names) */ public function get_allowed_user_config() { return array(); } /** * A list of fields that can be configured by the user. * This is used for the save handlers in the config form * and as checks in set_export_config and get_export_config. * * @return array array of strings (config field names) */ public function get_allowed_export_config() { return array(); } /** * Saves (or updates) the config stored in portfolio_instance_config. * You shouldn't need to override this unless you're doing something funky. * * @param array $config array of config items. */ public final function set_config($config) { global $DB; foreach ($config as $key => $value) { // try set it in $this first try { $this->set($key, $value); continue; } catch (portfolio_exception $e) { } if (!in_array($key, $this->get_allowed_config())) { $a = (object)array('property' => $key, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidconfigproperty', 'portfolio', null, $a); } if (!isset($this->config->{$key})) { $DB->insert_record('portfolio_instance_config', (object)array( 'instance' => $this->id, 'name' => $key, 'value' => $value, )); } else if ($this->config->{$key} != $value) { $DB->set_field('portfolio_instance_config', 'value', $value, array('name' => $key, 'instance' => $this->id)); } $this->config->{$key} = $value; } } /** * Gets the value of a particular config item * * @param string $key key to fetch * @return null|mixed the corresponding value */ public final function get_config($key) { if (!in_array($key, $this->get_allowed_config())) { $a = (object)array('property' => $key, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidconfigproperty', 'portfolio', null, $a); } if (isset($this->config->{$key})) { return $this->config->{$key}; } return null; } /** * Get the value of a config item for a particular user. * * @param string $key key to fetch * @param int $userid id of user (defaults to current) * @return string the corresponding value * */ public final function get_user_config($key, $userid=0) { global $DB; if (empty($userid)) { $userid = $this->user->id; } if ($key != 'visible') { // handled by the parent class if (!in_array($key, $this->get_allowed_user_config())) { $a = (object)array('property' => $key, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invaliduserproperty', 'portfolio', null, $a); } } if (!array_key_exists($userid, $this->userconfig)) { $this->userconfig[$userid] = (object)array_fill_keys(array_merge(array('visible'), $this->get_allowed_user_config()), null); foreach ($DB->get_records('portfolio_instance_user', array('instance' => $this->id, 'userid' => $userid)) as $config) { $this->userconfig[$userid]->{$config->name} = $config->value; } } if ($this->userconfig[$userid]->visible === null) { $this->set_user_config(array('visible' => 1), $userid); } return $this->userconfig[$userid]->{$key}; } /** * Sets config options for a given user. * * @param array $config array containing key/value pairs to set * @param int $userid userid to set config for (defaults to current) * */ public final function set_user_config($config, $userid=0) { global $DB; if (empty($userid)) { $userid = $this->user->id; } foreach ($config as $key => $value) { if ($key != 'visible' && !in_array($key, $this->get_allowed_user_config())) { $a = (object)array('property' => $key, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invaliduserproperty', 'portfolio', null, $a); } if (!$existing = $DB->get_record('portfolio_instance_user', array('instance'=> $this->id, 'userid' => $userid, 'name' => $key))) { $DB->insert_record('portfolio_instance_user', (object)array( 'instance' => $this->id, 'name' => $key, 'value' => $value, 'userid' => $userid, )); } else if ($existing->value != $value) { $DB->set_field('portfolio_instance_user', 'value', $value, array('name' => $key, 'instance' => $this->id, 'userid' => $userid)); } $this->userconfig[$userid]->{$key} = $value; } } /** * Generic getter for properties belonging to this instance * <b>outside</b> the subclasses * like name, visible etc. * * @param string $field property name * @return array|string|int|boolean value of the field */ public final function get($field) { // This is a legacy change to the way files are get/set. // We now only set $this->file to the id of the \stored_file. So, we need to convert that id back to a \stored_file here. if ($field === 'file') { return $this->get_file(); } if (property_exists($this, $field)) { return $this->{$field}; } $a = (object)array('property' => $field, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', null, $a); } /** * Generic setter for properties belonging to this instance * <b>outside</b> the subclass * like name, visible, etc. * * @param string $field property's name * @param string $value property's value * @return bool */ public final function set($field, $value) { // This is a legacy change to the way files are get/set. // Make sure we never save the \stored_file object. Instead, use the id from $file->get_id() - set_file() does this for us. if ($field === 'file') { $this->set_file($value); return true; } if (property_exists($this, $field)) { $this->{$field} =& $value; $this->dirty = true; return true; } $a = (object)array('property' => $field, 'class' => get_class($this)); if ($this->get('exporter')) { throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', null, $a); } throw new portfolio_exception('invalidproperty', 'portfolio', null, $a); // this happens outside export (eg admin settings) } /** * Saves stuff that's been stored in the object to the database. * You shouldn't need to override this * unless you're doing something really funky. * and if so, call parent::save when you're done. * * @return bool */ public function save() { global $DB; if (!$this->dirty) { return true; } $fordb = new StdClass(); foreach (array('id', 'name', 'plugin', 'visible') as $field) { $fordb->{$field} = $this->{$field}; } $DB->update_record('portfolio_instance', $fordb); $this->dirty = false; return true; } /** * Deletes everything from the database about this plugin instance. * You shouldn't need to override this unless you're storing stuff * in your own tables. and if so, call parent::delete when you're done. * * @return bool */ public function delete() { global $DB; $DB->delete_records('portfolio_instance_config', array('instance' => $this->get('id'))); $DB->delete_records('portfolio_instance_user', array('instance' => $this->get('id'))); $DB->delete_records('portfolio_tempdata', array('instance' => $this->get('id'))); $DB->delete_records('portfolio_instance', array('id' => $this->get('id'))); $this->dirty = false; return true; } /** * Perform any required cleanup functions * * @return bool */ public function cleanup() { return true; } /** * Whether this plugin supports multiple exports in the same session * most plugins should handle this, but some that require a redirect for authentication * and then don't support dynamically constructed urls to return to (eg box.net) * need to override this to return false. * This means that moodle will prevent multiple exports of this *type* of plugin * occurring in the same session. * * @return bool */ public static function allows_multiple_exports() { return true; } /** * Return a string to put at the header summarising this export * by default, just the plugin instance name * * @return string */ public function heading_summary() { return get_string('exportingcontentto', 'portfolio', $this->name); } } /** * Class to inherit from for 'push' type plugins * * Eg: those that send the file via a HTTP post or whatever * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class portfolio_plugin_push_base extends portfolio_plugin_base { /** * Get the capability to push * * @return bool */ public function is_push() { return true; } } /** * Class to inherit from for 'pull' type plugins. * * Eg: those that write a file and wait for the remote system to request it * from portfolio/file.php. * If you're using this you must do $this->set('file', $file) so that it can be served. * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class portfolio_plugin_pull_base extends portfolio_plugin_base { /** @var int $file the id of a single file */ protected $file; /** * return the enablelity to push * * @return bool */ public function is_push() { return false; } /** * The base part of the download file url to pull files from * your plugin might need to add &foo=bar on the end * @see verify_file_request_params * * @return string the url */ public function get_base_file_url() { global $CFG; return $CFG->wwwroot . '/portfolio/file.php?id=' . $this->exporter->get('id'); } /** * Before sending the file when the pull is requested, verify the request parameters. * These might include a token of some sort of whatever * * @param array $params request parameters (POST wins over GET) */ public abstract function verify_file_request_params($params); /** * Called from portfolio/file.php. * This function sends the stored file out to the browser. * The default is to just use send_stored_file, * but other implementations might do something different, * for example, send back the file base64 encoded and encrypted * mahara does this but in the response to an xmlrpc request * rather than through file.php */ public function send_file() { $file = $this->get('file'); if (!($file instanceof stored_file)) { throw new portfolio_export_exception($this->get('exporter'), 'filenotfound', 'portfolio'); } // don't die(); afterwards, so we can clean up. send_stored_file($file, 0, 0, true, array('dontdie' => true)); $this->get('exporter')->log_transfer(); } /** * Sets the $file instance var to the id of the supplied \stored_file. * This helper allows the $this->get('file') call to return a \stored_file, but means that we only ever record an id reference * in the $file instance var. * * @param \stored_file $file The stored_file instance. * @return void */ protected function set_file(\stored_file $file) { $fileid = $file->get_id(); if (empty($fileid)) { debugging('stored_file->id should not be empty'); $this->file = null; } else { $this->file = $fileid; } } /** * Gets the \stored_file object from the file id in the $file instance var. * * @return stored_file|null the \stored_file object if it exists, null otherwise. */ protected function get_file() { if (!$this->file) { return null; } // The get_file_by_id call can return false, so normalise to null. $file = get_file_storage()->get_file_by_id($this->file); return ($file) ? $file : null; } } formats.php 0000644 00000045427 15151222010 0006731 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * This file contains all the class definitions of the export formats. * * They are implemented in php classes rather than just a simpler hash * Because it provides an easy way to do subtyping using php inheritance. * * @package core_portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, * Martin Dougiamas <http://dougiamas.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Base class to inherit from. * * Do not use this anywhere in supported_formats * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, * Martin Dougiamas <http://dougiamas.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * */ abstract class portfolio_format { /** * Array of mimetypes this format supports * * @throws coding_exception */ public static function mimetypes() { throw new coding_exception('mimetypes() method needs to be overridden in each subclass of portfolio_format'); } /** * For multipart formats, eg html with attachments, * we need to have a directory to place associated files from * inside the zip file. This is the name of that directory * * @throws coding_exception */ public static function get_file_directory() { throw new coding_exception('get_file_directory() method needs to be overridden in each subclass of portfolio_format'); } /** * Given a file, return a snippet of markup in whatever format * to link to that file. * Usually involves the path given by get_file_directory. * This is not supported in subclasses of portfolio_format_file * since they're all just single files. * @see get_file_directory * * @param stored_file $file file information object * @param array $options array of options to pass. can contain: * attributes => hash of existing html attributes (eg title, height, width, etc) * * @throws coding_exception */ public static function file_output($file, $options=null) { throw new coding_exception('file_output() method needs to be overridden in each subclass of portfolio_format'); } /** * Create portfolio tag * * @param stored_file $file file information object * @param string $path file path * @param array $attributes portfolio attributes * @return string */ public static function make_tag($file, $path, $attributes) { $srcattr = 'href'; $tag = 'a'; $content = $file->get_filename(); if (in_array($file->get_mimetype(), portfolio_format_image::mimetypes())) { $srcattr = 'src'; $tag = 'img'; $content = ''; } $attributes[$srcattr] = $path; // this will override anything we might have been passed (which is good) $dom = new DomDocument(); $elem = null; if ($content) { $elem = $dom->createElement($tag, $content); } else { $elem = $dom->createElement($tag); } foreach ($attributes as $key => $value) { $elem->setAttribute($key, $value); } $dom->appendChild($elem); return $dom->saveXML($elem); } /** * Whether this format conflicts with the given format. * This is used for the case where an export location * "generally" supports something like FORMAT_PLAINHTML * but then in a specific export case, must add attachments, * which means that FORMAT_RICHHTML is supported in that case, * which implies removing support for FORMAT_PLAINHTML. * Note that conflicts don't have to be bi-directional * (eg FORMAT_PLAINHTML conflicts with FORMAT_RICHHTML * but not the other way around) and things within the class hierarchy * are resolved automatically anyway. * This is really just between subclasses of format_rich * and subclasses of format_file. * * @param string $format one of the FORMAT_XX constants * @return bool */ public static function conflicts($format) { return false; } } /** * The most basic type - pretty much everything is a subtype * * @package core_portfolio * @category portfolio * @copyright 2009 Penny Leach <penny@catalyst.net.nz>, Martin Dougiamas * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_file extends portfolio_format { /** * Array of mimetypes this format supports * * @return array */ public static function mimetypes() { return array(); } /** * For multipart formats, eg html with attachments, * we need to have a directory to place associated files from * inside the zip file. This is the name of that directory * * @return bool */ public static function get_file_directory() { return false; } /** * Given a file, return a snippet of markup in whatever format * to link to that file. * Usually involves the path given by get_file_directory. * This is not supported in subclasses of portfolio_format_file * since they're all just single files. * @see get_file_directory * * @param stored_file $file informations object * @param array $options array of options to pass. can contain: * attributes => hash of existing html attributes (eg title, height, width, etc) */ public static function file_output($file, $options=null) { throw new portfolio_exception('fileoutputnotsupported', 'portfolio'); } } /** * Image format, subtype of file. * * @package core_portfolio * @category portfolio * @copyright 2009 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_image extends portfolio_format_file { /** * Return all mimetypes that use image.gif (eg all images) * * @return string */ public static function mimetypes() { return file_get_typegroup('type', 'image'); } /** * Whether this format conflicts with the given format. * This is used for the case where an export location * "generally" supports something like FORMAT_PLAINHTML * but then in a specific export case, must add attachments, * which means that FORMAT_RICHHTML is supported in that case, * which implies removing support for FORMAT_PLAINHTML. * Note that conflicts don't have to be bi-directional * (eg FORMAT_PLAINHTML conflicts with FORMAT_RICHHTML * but not the other way around) and things within the class hierarchy * are resolved automatically anyway. * This is really just between subclasses of format_rich * and subclasses of format_file. * * @param string $format one of the FORMAT_XX constants * @return bool */ public static function conflicts($format) { return ($format == PORTFOLIO_FORMAT_RICHHTML || $format == PORTFOLIO_FORMAT_PLAINHTML); } } /** * HTML format * * Could be used for an external cms or something in case we want to be really specific. * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_plainhtml extends portfolio_format_file { /** * Return html mimetype * * @return array */ public static function mimetypes() { return array('text/html'); } /** * Whether this format conflicts with the given format. * This is used for the case where an export location * "generally" supports something like FORMAT_PLAINHTML * but then in a specific export case, must add attachments, * which means that FORMAT_RICHHTML is supported in that case, * which implies removing support for FORMAT_PLAINHTML. * Note that conflicts don't have to be bi-directional * (eg FORMAT_PLAINHTML conflicts with FORMAT_RICHHTML * but not the other way around) and things within the class hierarchy * are resolved automatically anyway. * This is really just between subclasses of format_rich * and subclasses of format_file. * * @param string $format one of the FORMAT_XX constants * @return bool */ public static function conflicts($format) { return ($format == PORTFOLIO_FORMAT_RICHHTML || $format == PORTFOLIO_FORMAT_FILE); } } /** * Video format * * For portfolio plugins that support videos specifically * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_video extends portfolio_format_file { /** * Return video mimetypes * * @return array */ public static function mimetypes() { return file_get_typegroup('type', 'video'); } } /** * Class for plain text format. * * Not sure why we would need this yet, * but since resource module wants to export it... we can * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_text extends portfolio_format_file { /** * Return plain text mimetypes * * @return array */ public static function mimetypes() { return array('text/plain'); } /** * Whether this format conflicts with the given format. * This is used for the case where an export location * "generally" supports something like FORMAT_PLAINHTML * but then in a specific export case, must add attachments, * which means that FORMAT_RICHHTML is supported in that case, * which implies removing support for FORMAT_PLAINHTML. * Note that conflicts don't have to be bi-directional * (eg FORMAT_PLAINHTML conflicts with FORMAT_RICHHTML * but not the other way around) and things within the class hierarchy * are resolved automatically anyway. * This is really just between subclasses of format_rich * and subclasses of format_file. * * @param string $format one of the FORMAT_XX constants * @return bool */ public static function conflicts($format ) { return ($format == PORTFOLIO_FORMAT_PLAINHTML || $format == PORTFOLIO_FORMAT_RICHHTML); } } /** * Base class for rich formats. * * These are multipart - eg things with attachments * * @package core_portfolio * @category portfolio * @copyright 2009 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class portfolio_format_rich extends portfolio_format { /** * Return rich text mimetypes * * @return array */ public static function mimetypes() { return array(); } } /** * Richhtml - html with attachments. * * The most commonly used rich format * eg inline images * * @package core_portfolio * @category portfolio * @copyright 2009 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_richhtml extends portfolio_format_rich { /** * For multipart formats, eg html with attachments, * we need to have a directory to place associated files from * inside the zip file. this is the name of that directory * * @return string */ public static function get_file_directory() { return 'site_files/'; } /** * Given a file, return a snippet of markup in whatever format * to link to that file. * Usually involves the path given by get_file_directory. * This is not supported in subclasses of portfolio_format_file * since they're all just single files. * @see get_file_directory * * @param stored_file $file information for existing file * @param array $options array of options to pass. can contain: * attributes => hash of existing html attributes (eg title, height, width, etc) * @return string */ public static function file_output($file, $options=null) { $path = self::get_file_directory() . $file->get_filename(); $attributes = array(); if (!empty($options['attributes']) && is_array($options['attributes'])) { $attributes = $options['attributes']; } return self::make_tag($file, $path, $attributes); } /** * Whether this format conflicts with the given format. * This is used for the case where an export location * "generally" supports something like FORMAT_PLAINHTML * but then in a specific export case, must add attachments, * which means that FORMAT_RICHHTML is supported in that case, * which implies removing support for FORMAT_PLAINHTML. * Note that conflicts don't have to be bi-directional * (eg FORMAT_PLAINHTML conflicts with FORMAT_RICHHTML * but not the other way around) and things within the class hierarchy * are resolved automatically anyway. * This is really just between subclasses of format_rich * and subclasses of format_file. * * @todo MDL-31305 - revisit the conflict with file, since we zip here * @param string $format one of the FORMAT_XX constants * @return bool */ public static function conflicts($format) { // TODO revisit the conflict with file, since we zip here return ($format == PORTFOLIO_FORMAT_PLAINHTML || $format == PORTFOLIO_FORMAT_FILE); } } /** * Class used for leap2a format * * @package core_portfolio * @category portfolio * @copyright 2009 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_leap2a extends portfolio_format_rich { /** * For multipart formats, eg html with attachments, * we need to have a directory to place associated files from * inside the zip file. this is the name of that directory * * @return string */ public static function get_file_directory() { return 'files/'; } /** * Return the file prefix * * @return string */ public static function file_id_prefix() { return 'storedfile'; } /** * Return the link to a file * * @param stored_file $file information for existing file * @param array $options array of options to pass. can contain: * attributes => hash of existing html attributes (eg title, height, width, etc) * @return string */ public static function file_output($file, $options=null) { $id = ''; if (!is_array($options)) { $options = array(); } if (!array_key_exists('entry', $options)) { $options['entry'] = true; } if (!empty($options['entry'])) { $path = 'portfolio:' . self::file_id_prefix() . $file->get_id(); } else { $path = self::get_file_directory() . $file->get_filename(); } $attributes = array(); if (!empty($options['attributes']) && is_array($options['attributes'])) { $attributes = $options['attributes']; } $attributes['rel'] = 'enclosure'; return self::make_tag($file, $path, $attributes); } /** * Generate portfolio_format_leap2a * * @param stdclass $user user information object * @return portfolio_format_leap2a_writer */ public static function leap2a_writer(stdclass $user=null) { global $CFG; if (empty($user)) { global $USER; $user = $USER; } require_once($CFG->libdir . '/portfolio/formats/leap2a/lib.php'); return new portfolio_format_leap2a_writer($user); } /** * Return the manifest name * * @return string */ public static function manifest_name() { return 'leap2a.xml'; } } // later.... a moodle plugin might support this. // it's commented out in portfolio_supported_formats so cannot currently be used. //class portfolio_format_mbkp extends portfolio_format_rich {} /** * 'PDF format', subtype of file. * * For portfolio plugins that support PDFs specifically. * * @package core_portfolio * @category portfolio * @copyright 2009 Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_pdf extends portfolio_format_file { /** * Return pdf mimetypes * * @return array */ public static function mimetypes() { return array('application/pdf'); } } /** * 'Document format', subtype of file. * * For portfolio plugins that support documents specifically. * * @package core_portfolio * @category portfolio * @copyright 2009 Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_document extends portfolio_format_file { /** * Return documents mimetypes * * @return array of documents mimetypes */ public static function mimetypes() { return file_get_typegroup('type', 'document'); } } /** * 'Spreadsheet format', subtype of file. * * For portfolio plugins that support spreadsheets specifically. * * @package core_portfolio * @category portfolio * @copyright 2009 Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_spreadsheet extends portfolio_format_file { /** * Return spreadsheet spreadsheet mimetypes * * @return array of documents mimetypes */ public static function mimetypes() { return file_get_typegroup('type', 'spreadsheet'); } } /** * 'Presentation format', subtype of file. * * For portfolio plugins that support presentation specifically. * * @package core_portfolio * @category portfolio * @copyright 2009 Dan Poltawski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_presentation extends portfolio_format_file { /** * Return presentation documents mimetypes * * @return array presentation document mimetypes */ public static function mimetypes() { return file_get_typegroup('type', 'presentation'); } } caller.php 0000644 00000045406 15151222010 0006515 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * This file contains the base classes that are extended to create portfolio export functionality. * * For places in moodle that want to * add export functionality to subclass from {@link http://docs.moodle.org/dev/Adding_a_Portfolio_Button_to_a_page} * * @package core_portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, Martin Dougiamas * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Base class for callers * * @link See http://docs.moodle.org/dev/Adding_a_Portfolio_Button_to_a_page * @see also portfolio_module_caller_base * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class portfolio_caller_base { /** @var stdClass course active during the call */ protected $course; /** @var array configuration used for export. Use set_export_config and get_export_config to access */ protected $exportconfig = array(); /** @var stdclass user currently exporting content */ protected $user; /** @var stdClass a reference to the exporter object */ protected $exporter; /** @var array can be optionally overridden by subclass constructors */ protected $supportedformats; /** @var stored_file single file exports configuration*/ protected $singlefile; /** @var stored_file|object set this for multi file exports */ protected $multifiles; /** @var string set this for generated-file exports */ protected $intendedmimetype; /** * Create portfolio_caller object * * @param array $callbackargs argument properties */ public function __construct($callbackargs) { $expected = call_user_func(array(get_class($this), 'expected_callbackargs')); foreach ($expected as $key => $required) { if (!array_key_exists($key, $callbackargs)) { if ($required) { $a = (object)array('arg' => $key, 'class' => get_class($this)); throw new portfolio_caller_exception('missingcallbackarg', 'portfolio', null, $a); } continue; } $this->{$key} = $callbackargs[$key]; } } /** * If this caller wants any additional config items, * they should be defined here. * * @param moodleform $mform passed by reference, add elements to it. * @param portfolio_plugin_base $instance subclass of portfolio_plugin_base */ public function export_config_form(&$mform, $instance) {} /** * Whether this caller wants any additional * config during export (eg options or metadata) * * @return bool */ public function has_export_config() { return false; } /** * Just like the moodle form validation function, * this is passed in the data array from the form * and if a non empty array is returned, form processing will stop. * * @param array $data data from form. */ public function export_config_validation($data) {} /** * How long does this reasonably expect to take.. * Should we offer the user the option to wait..? * This is deliberately nonstatic so it can take filesize into account * the portfolio plugin can override this. * (so for example even if a huge file is being sent, * the download portfolio plugin doesn't care ) */ public abstract function expected_time(); /** * Helper method to calculate expected time for multi or single file exports * * @return string file time expectation */ public function expected_time_file() { if ($this->multifiles) { return portfolio_expected_time_file($this->multifiles); } else if ($this->singlefile) { return portfolio_expected_time_file($this->singlefile); } return PORTFOLIO_TIME_LOW; } /** * Function to build navigation */ public abstract function get_navigation(); /** * Helper function to get sha1 */ public abstract function get_sha1(); /** * Helper function to calculate the sha1 for multi or single file exports * * @return string sha1 file exports */ public function get_sha1_file() { if (empty($this->singlefile) && empty($this->multifiles)) { throw new portfolio_caller_exception('invalidsha1file', 'portfolio', $this->get_return_url()); } if ($this->singlefile) { return $this->singlefile->get_contenthash(); } $sha1s = array(); foreach ($this->multifiles as $file) { $sha1s[] = $file->get_contenthash(); } asort($sha1s); return sha1(implode('', $sha1s)); } /** * Generic getter for properties belonging to this instance * <b>outside</b> the subclasses * like name, visible etc. * * @param string $field property's name * @return mixed * @throws portfolio_export_exception */ public function get($field) { if (property_exists($this, $field)) { return $this->{$field}; } $a = (object)array('property' => $field, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a); } /** * Generic setter for properties belonging to this instance * <b>outside</b> the subclass * like name, visible, etc. * * @param string $field property's name * @param mixed $value property's value * @return bool * @throws moodle_exception */ public final function set($field, &$value) { if (property_exists($this, $field)) { $this->{$field} =& $value; $this->dirty = true; return true; } $a = (object)array('property' => $field, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a); } /** * Stores the config generated at export time. * Subclasses can retrieve values using * @see get_export_config * * @param array $config formdata */ public final function set_export_config($config) { $allowed = array_merge( array('wait', 'hidewait', 'format', 'hideformat'), $this->get_allowed_export_config() ); foreach ($config as $key => $value) { if (!in_array($key, $allowed)) { $a = (object)array('property' => $key, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a); } $this->exportconfig[$key] = $value; } } /** * Returns a particular export config value. * Subclasses shouldn't need to override this * * @param string $key the config item to fetch * @return null|mixed of export configuration */ public final function get_export_config($key) { $allowed = array_merge( array('wait', 'hidewait', 'format', 'hideformat'), $this->get_allowed_export_config() ); if (!in_array($key, $allowed)) { $a = (object)array('property' => $key, 'class' => get_class($this)); throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a); } if (!array_key_exists($key, $this->exportconfig)) { return null; } return $this->exportconfig[$key]; } /** * Similar to the other allowed_config functions * if you need export config, you must provide * a list of what the fields are. * Even if you want to store stuff during export * without displaying a form to the user, * you can use this. * * @return array array of allowed keys */ public function get_allowed_export_config() { return array(); } /** * After the user submits their config, * they're given a confirm screen * summarising what they've chosen. * This function should return a table of nice strings => values * of what they've chosen * to be displayed in a table. * * @return bool */ public function get_export_summary() { return false; } /** * Called before the portfolio plugin gets control. * This function should copy all the files it wants to * the temporary directory, using copy_existing_file * or write_new_file * * @see copy_existing_file() * @see write_new_file() */ public abstract function prepare_package(); /** * Helper function to copy files into the temp area * for single or multi file exports. * * @return stored_file|bool */ public function prepare_package_file() { if (empty($this->singlefile) && empty($this->multifiles)) { throw new portfolio_caller_exception('invalidpreparepackagefile', 'portfolio', $this->get_return_url()); } if ($this->singlefile) { return $this->exporter->copy_existing_file($this->singlefile); } foreach ($this->multifiles as $file) { $this->exporter->copy_existing_file($file); } } /** * Array of formats this caller supports. * * @return array list of formats */ public final function supported_formats() { $basic = $this->base_supported_formats(); if (empty($this->supportedformats)) { $specific = array(); } else if (!is_array($this->supportedformats)) { debugging(get_class($this) . ' has set a non array value of member variable supported formats - working around but should be fixed in code'); $specific = array($this->supportedformats); } else { $specific = $this->supportedformats; } return portfolio_most_specific_formats($specific, $basic); } /** * Base supported formats * * @throws coding_exception */ public static function base_supported_formats() { throw new coding_exception('base_supported_formats() method needs to be overridden in each subclass of portfolio_caller_base'); } /** * This is the "return to where you were" url */ public abstract function get_return_url(); /** * Callback to do whatever capability checks required * in the caller (called during the export process */ public abstract function check_permissions(); /** * Clean name to display to the user about this caller location */ public static function display_name() { throw new coding_exception('display_name() method needs to be overridden in each subclass of portfolio_caller_base'); } /** * Return a string to put at the header summarising this export. * By default, it just display the name (usually just 'assignment' or something unhelpful * * @return string */ public function heading_summary() { return get_string('exportingcontentfrom', 'portfolio', $this->display_name()); } /** * Load data */ public abstract function load_data(); /** * Set up the required files for this export. * This supports either passing files directly * or passing area arguments directly through * to the files api using file_storage::get_area_files * * @param mixed $ids one of: * - single file id * - single stored_file object * - array of file ids or stored_file objects * - null * @return void */ public function set_file_and_format_data($ids=null /* ..pass arguments to area files here. */) { $args = func_get_args(); array_shift($args); // shift off $ids if (empty($ids) && count($args) == 0) { return; } $files = array(); $fs = get_file_storage(); if (!empty($ids)) { if (is_numeric($ids) || $ids instanceof stored_file) { $ids = array($ids); } foreach ($ids as $id) { if ($id instanceof stored_file) { $files[] = $id; } else { $files[] = $fs->get_file_by_id($id); } } } else if (count($args) != 0) { if (count($args) < 4) { throw new portfolio_caller_exception('invalidfileareaargs', 'portfolio'); } $files = array_values(call_user_func_array(array($fs, 'get_area_files'), $args)); } switch (count($files)) { case 0: return; case 1: { $this->singlefile = $files[0]; return; } default: { $this->multifiles = $files; } } } /** * The button-location always knows best * what the formats are... so it should be trusted. * * @todo MDL-31298 - re-analyze set_formats_from_button comment * @param array $formats array of PORTFOLIO_FORMAT_XX * @return void */ public function set_formats_from_button($formats) { $base = $this->base_supported_formats(); if (count($base) != count($formats) || count($base) != count(array_intersect($base, $formats))) { $this->supportedformats = portfolio_most_specific_formats($formats, $base); return; } // in the case where the button hasn't actually set anything, // we need to run through again and resolve conflicts // TODO revisit this comment - it looks to me like it's lying $this->supportedformats = portfolio_most_specific_formats($formats, $formats); } /** * Adds a new format to the list of supported formats. * This functions also handles removing conflicting and less specific * formats at the same time. * * @param string $format one of PORTFOLIO_FORMAT_XX * @return void */ protected function add_format($format) { if (in_array($format, $this->supportedformats)) { return; } $this->supportedformats = portfolio_most_specific_formats(array($format), $this->supportedformats); } /** * Gets mimetype * * @return string */ public function get_mimetype() { if ($this->singlefile instanceof stored_file) { return $this->singlefile->get_mimetype(); } else if (!empty($this->intendedmimetype)) { return $this->intendedmimetype; } } /** * Array of arguments the caller expects to be passed through to it. * This must be keyed on the argument name, and the array value is a boolean, * whether it is required, or just optional * eg array( * id => true, * somethingelse => false * ) */ public static function expected_callbackargs() { throw new coding_exception('expected_callbackargs() method needs to be overridden in each subclass of portfolio_caller_base'); } /** * Return the context for this export. used for $PAGE->set_context * * @param moodle_page $PAGE global page object */ public abstract function set_context($PAGE); } /** * Base class for module callers. * * This just implements a few of the abstract functions * from portfolio_caller_base so that caller authors * don't need to. * {@link http://docs.moodle.org/dev/Adding_a_Portfolio_Button_to_a_page} * @see also portfolio_caller_base * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class portfolio_module_caller_base extends portfolio_caller_base { /** @var object coursemodule object. set this in the constructor like $this->cm = get_coursemodule_from_instance('forum', $this->forum->id); */ protected $cm; /** @var int cmid */ protected $id; /** @var stdclass course object */ protected $course; /** * Navigation passed to print_header. * Override this to do something more specific than the module view page * like adding more links to the breadcrumb. * * @return array */ public function get_navigation() { // No extra navigation by default, link to the course module already included. $extranav = array(); return array($extranav, $this->cm); } /** * The url to return to after export or on cancel. * Defaults value is set to the module 'view' page. * Override this if it's deeper inside the module. * * @return string */ public function get_return_url() { global $CFG; return $CFG->wwwroot . '/mod/' . $this->cm->modname . '/view.php?id=' . $this->cm->id; } /** * Override the parent get function * to make sure when we're asked for a course, * We retrieve the object from the database as needed. * * @param string $key the name of get function * @return stdClass */ public function get($key) { if ($key != 'course') { return parent::get($key); } global $DB; if (empty($this->course)) { $this->course = $DB->get_record('course', array('id' => $this->cm->course)); } return $this->course; } /** * Return a string to put at the header summarising this export. * by default, this function just display the name and module instance name. * Override this to do something more specific * * @return string */ public function heading_summary() { return get_string('exportingcontentfrom', 'portfolio', $this->display_name() . ': ' . $this->cm->name); } /** * Overridden to return the course module context * * @param moodle_page $PAGE global PAGE */ public function set_context($PAGE) { $PAGE->set_cm($this->cm); } } forms.php 0000644 00000030173 15151222010 0006374 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * This file contains all the form definitions used by the portfolio code. * * @package core_portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, * Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); // make sure we include moodleform first! require_once ($CFG->libdir.'/formslib.php'); /** * During-export config form. * * This is the form that is actually used while exporting. * Plugins and callers don't get to define their own class * as we have to handle form elements from both places * See the docs here for more information: * http://docs.moodle.org/dev/Writing_a_Portfolio_Plugin#has_export_config * http://docs.moodle.org/dev/Adding_a_Portfolio_Button_to_a_page#has_export_config * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class portfolio_export_form extends moodleform { /** * prepare form */ public function definition() { $mform =& $this->_form; $mform->addElement('hidden', 'stage', PORTFOLIO_STAGE_CONFIG); $mform->addElement('hidden', 'id', $this->_customdata['id']); $mform->addElement('hidden', 'instance', $this->_customdata['instance']->get('id')); $mform->setType('instance', PARAM_INT); $mform->setType('stage', PARAM_INT); $mform->setType('id', PARAM_INT); if (array_key_exists('formats', $this->_customdata) && is_array($this->_customdata['formats'])) { if (count($this->_customdata['formats']) > 1) { $options = array(); foreach ($this->_customdata['formats'] as $key) { $options[$key] = get_string('format_' . $key, 'portfolio'); } $mform->addElement('select', 'format', get_string('availableformats', 'portfolio'), $options); } else { $f = array_shift($this->_customdata['formats']); $mform->addElement('hidden', 'format', $f); $mform->setType('format', PARAM_RAW); } } // only display the option to wait or not if it's applicable if (array_key_exists('expectedtime', $this->_customdata) && $this->_customdata['expectedtime'] != PORTFOLIO_TIME_LOW && $this->_customdata['expectedtime'] != PORTFOLIO_TIME_FORCEQUEUE) { $radioarray = array(); $radioarray[] = $mform->createElement('radio', 'wait', '', get_string('wait', 'portfolio'), 1); $radioarray[] = $mform->createElement('radio', 'wait', '', get_string('dontwait', 'portfolio'), 0); $mform->addGroup($radioarray, 'radioar', get_string('wanttowait_' . $this->_customdata['expectedtime'], 'portfolio') , array(' '), false); $mform->setDefault('wait', 0); } else { if ($this->_customdata['expectedtime'] == PORTFOLIO_TIME_LOW) { $mform->addElement('hidden', 'wait', 1); } else { $mform->addElement('hidden', 'wait', 0); } $mform->setType('wait', PARAM_INT); } if (array_key_exists('plugin', $this->_customdata) && is_object($this->_customdata['plugin'])) { $this->_customdata['plugin']->export_config_form($mform, $this->_customdata['userid']); } if (array_key_exists('caller', $this->_customdata) && is_object($this->_customdata['caller'])) { $this->_customdata['caller']->export_config_form($mform, $this->_customdata['instance'], $this->_customdata['userid']); } $this->add_action_buttons(true, get_string('next')); } /** * Validate portfolio export form * * @param stdClass $data portfolio information from form data * @return array */ public function validation($data, $files) { $errors = array(); if (array_key_exists('plugin', $this->_customdata) && is_object($this->_customdata['plugin'])) { $pluginerrors = $this->_customdata['plugin']->export_config_validation($data); if (is_array($pluginerrors)) { $errors = $pluginerrors; } } if (array_key_exists('caller', $this->_customdata) && is_object($this->_customdata['caller'])) { $callererrors = $this->_customdata['caller']->export_config_validation($data); if (is_array($callererrors)) { $errors = array_merge($errors, $callererrors); } } return $errors; } } /** * Admin config form. * * This form is extendable by plugins who want the admin to be able to configure more than just the name of the instance. * This is NOT done by subclassing this class, see the docs for portfolio_plugin_base for more information: * {@link http://docs.moodle.org/dev/Writing_a_Portfolio_Plugin#has_admin_config} * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class portfolio_admin_form extends moodleform { /** @var object to hold porfolio instance configuration */ protected $instance; /** @var string plugin name*/ protected $plugin; /** @var string portfolio plugin name*/ protected $portfolio; /** @var string plugin availability*/ protected $action; /** @var int portfolio plugin visibility*/ protected $visible; /** * prepare form */ public function definition() { global $CFG; $this->plugin = $this->_customdata['plugin']; $this->instance = (isset($this->_customdata['instance']) && is_subclass_of($this->_customdata['instance'], 'portfolio_plugin_base')) ? $this->_customdata['instance'] : null; $this->portfolio = $this->_customdata['portfolio']; $this->action = $this->_customdata['action']; $this->visible = $this->_customdata['visible']; $mform =& $this->_form; $strrequired = get_string('required'); $mform->addElement('hidden', 'pf', $this->portfolio); $mform->setType('pf', PARAM_ALPHA); $mform->addElement('hidden', 'action', $this->action); $mform->setType('action', PARAM_ALPHA); $mform->addElement('hidden', 'visible', $this->visible); $mform->setType('visible', PARAM_INT); $mform->addElement('hidden', 'plugin', $this->plugin); $mform->setType('plugin', PARAM_PLUGIN); if (!$this->instance) { $insane = portfolio_instance_sanity_check($this->instance); } else { $insane = portfolio_plugin_sanity_check($this->plugin); } if (isset($insane) && is_array($insane)) { $insane = array_shift($insane); } if (isset($insane) && is_string($insane)) { // something went wrong, warn... $mform->addElement('warning', 'insane', null, get_string($insane, 'portfolio_' . $this->plugin)); } $mform->addElement('text', 'name', get_string('name'), 'maxlength="100" size="30"'); $mform->addRule('name', $strrequired, 'required', null, 'client'); $mform->setType('name', PARAM_TEXT); // let the plugin add the fields they want (either statically or not) if (portfolio_static_function($this->plugin, 'has_admin_config')) { require_once($CFG->libdir . '/portfolio/plugin.php'); require_once($CFG->dirroot . '/portfolio/' . $this->plugin . '/lib.php'); $classname = 'portfolio_plugin_' . $this->plugin; $classname::admin_config_form($mform); } // and set the data if we have some. if ($this->instance) { $data = array('name' => $this->instance->get('name')); foreach ($this->instance->get_allowed_config() as $config) { $data[$config] = $this->instance->get_config($config); } $this->set_data($data); } else { $this->set_data(array('name' => portfolio_static_function($this->plugin, 'get_name'))); } $this->add_action_buttons(true, get_string('save', 'portfolio')); } /** * Validate admin config form * * @param stdObject $data form data * @return array */ public function validation($data, $files) { global $DB; $errors = array(); if ($DB->count_records('portfolio_instance', array('name' => $data['name'], 'plugin' => $data['plugin'])) > 1) { $errors = array('name' => get_string('err_uniquename', 'portfolio')); } $pluginerrors = array(); $pluginerrors = portfolio_static_function($this->plugin, 'admin_config_validation', $data); if (is_array($pluginerrors)) { $errors = array_merge($errors, $pluginerrors); } return $errors; } } /** * User config form. * * This is the form for letting the user configure an instance of a plugin. * In order to extend this, you don't subclass this in the plugin.. * see the docs in portfolio_plugin_base for more information: * {@link http://docs.moodle.org/dev/Writing_a_Portfolio_Plugin#has_user_config} * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class portfolio_user_form extends moodleform { /** @var object user porfolio instance */ protected $instance; /** @var int hold user id */ protected $userid; /** * prepare form */ public function definition() { $this->instance = $this->_customdata['instance']; $this->userid = $this->_customdata['userid']; $this->_form->addElement('hidden', 'config', $this->instance->get('id')); $this->_form->setType('config', PARAM_INT); $this->instance->user_config_form($this->_form, $this->userid); $data = array(); foreach ($this->instance->get_allowed_user_config() as $config) { $data[$config] = $this->instance->get_user_config($config, $this->userid); } $this->set_data($data); $this->add_action_buttons(true, get_string('save', 'portfolio')); } /** * User user config form. * * @param stdClass $data form data */ public function validation($data, $files) { $errors = $this->instance->user_config_validation($data); } } /** * Form that just contains the dropdown menu of available instances. * * This is not used by portfolio_add_button, but on the first step of the export, * if the plugin instance has not yet been selected. * * @package core_portfolio * @category portfolio * @copyright 2008 Penny Leach <penny@catalyst.net.nz> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_instance_select extends moodleform { /** @var portfolio_caller_base plugin instance */ private $caller; /** * The required basic elements to the form. */ function definition() { $this->caller = $this->_customdata['caller']; $options = $this->_customdata['options']; $mform =& $this->_form; $mform->addElement('select', 'instance', get_string('selectplugin', 'portfolio'), $options); $mform->addElement('hidden', 'id', $this->_customdata['id']); $mform->setType('id', PARAM_INT); $this->add_action_buttons(true, get_string('next')); } } formats/leap2a/lib.php 0000644 00000052226 15151222010 0010636 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * This file contains the LEAP2a writer used by portfolio_format_leap2a * * @package core_portfolio * @copyright 2009 Penny Leach (penny@liip.ch), Martin Dougiamas * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * */ defined('MOODLE_INTERNAL') || die(); /** * Object to encapsulate the writing of leap2a. * * Should be used like: * $writer = portfolio_format_leap2a::leap2a_writer($USER); * $entry = new portfolio_format_leap2a_entry('forumpost6', $title, 'leap2', 'somecontent') * $entry->add_link('something', 'has_part')->add_link('somethingelse', 'has_part'); * .. etc * $writer->add_entry($entry); * $xmlstr = $writer->to_xml(); * * @todo MDL-31287 - find a way to ensure that all referenced files are included * @package core_portfolio * @category portfolio * @copyright 2009 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_leap2a_writer { /** @var DomDocument the domdocument object used to create elements */ private $dom; /** @var DOMElement the top level feed element */ private $feed; /** @var stdClass the user exporting data */ private $user; /** @var string the id of the feed - this is unique to the user and date and used for portfolio ns as well as feed id */ private $id; /** @var array the entries for the feed - keyed on id */ private $entries = array(); /** * Constructor - usually generated from portfolio_format_leap2a::leap2a_writer($USER); * * @todo MDL-31302 - add exporter and format * @param stdclass $user the user exporting (almost always $USER) */ public function __construct(stdclass $user) { // todo something else - exporter, format, etc global $CFG; $this->user = $user; $this->exporttime = time(); $this->id = $CFG->wwwroot . '/portfolio/export/leap2a/' . $this->user->id . '/' . $this->exporttime; $this->dom = new DomDocument('1.0', 'utf-8'); $this->feed = $this->dom->createElement('feed'); $this->feed->setAttribute('xmlns', 'http://www.w3.org/2005/Atom'); $this->feed->setAttribute('xmlns:rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); $this->feed->setAttribute('xmlns:leap2', 'http://terms.leapspecs.org/'); $this->feed->setAttribute('xmlns:categories', 'http://wiki.leapspecs.org/2A/categories'); $this->feed->setAttribute('xmlns:portfolio', $this->id); // this is just a ns for ids of elements for convenience $this->dom->appendChild($this->feed); $this->feed->appendChild($this->dom->createElement('id', $this->id)); $this->feed->appendChild($this->dom->createElement('title', get_string('leap2a_feedtitle', 'portfolio', fullname($this->user)))); $this->feed->appendChild($this->dom->createElement('leap2:version', 'http://www.leapspecs.org/2010-07/2A/')); $generator = $this->dom->createElement('generator', 'Moodle'); $generator->setAttribute('uri', $CFG->wwwroot); $generator->setAttribute('version', $CFG->version); $this->feed->appendChild($generator); $author = $this->dom->createElement('author'); $author->appendChild($this->dom->createElement('name', fullname($this->user))); $author->appendChild($this->dom->createElement('email', $this->user->email)); $author->appendChild($this->dom->CreateElement('uri', $CFG->wwwroot . '/user/view.php?id=' . $this->user->id)); $this->feed->appendChild($author); // header done, we can start appending entry elements now } /** * Adds a entry to the feed ready to be exported * * @param portfolio_format_leap2a_entry $entry new feed entry to add * @return portfolio_format_leap2a_entry */ public function add_entry(portfolio_format_leap2a_entry $entry) { if (array_key_exists($entry->id, $this->entries)) { if (!($entry instanceof portfolio_format_leap2a_file)) { throw new portfolio_format_leap2a_exception('leap2a_entryalreadyexists', 'portfolio', '', $entry->id); } } $this->entries[$entry->id] = $entry; return $entry; } /** * Select an entry that has previously been added into the feed * * @param portfolio_format_leap2a_entry|string $selectionentry the entry to make a selection (id or entry object) * @param array $ids array of ids this selection includes * @param string $selectiontype for selection type, see: http://wiki.cetis.ac.uk/2009-03/LEAP2A_categories/selection_type */ public function make_selection($selectionentry, $ids, $selectiontype) { $selectionid = null; if ($selectionentry instanceof portfolio_format_leap2a_entry) { $selectionid = $selectionentry->id; } else if (is_string($selectionentry)) { $selectionid = $selectionentry; } if (!array_key_exists($selectionid, $this->entries)) { throw new portfolio_format_leap2a_exception('leap2a_invalidentryid', 'portfolio', '', $selectionid); } foreach ($ids as $entryid) { if (!array_key_exists($entryid, $this->entries)) { throw new portfolio_format_leap2a_exception('leap2a_invalidentryid', 'portfolio', '', $entryid); } $this->entries[$selectionid]->add_link($entryid, 'has_part'); $this->entries[$entryid]->add_link($selectionid, 'is_part_of'); } $this->entries[$selectionid]->add_category($selectiontype, 'selection_type'); if ($this->entries[$selectionid]->type != 'selection') { debugging(get_string('leap2a_overwritingselection', 'portfolio', $this->entries[$selectionid]->type)); $this->entries[$selectionid]->type = 'selection'; } } /** * Helper function to link some stored_files into the feed and link them to a particular entry * * @param portfolio_format_leap2a_entry $entry feed object * @param array $files array of stored_files to link */ public function link_files($entry, $files) { foreach ($files as $file) { $fileentry = new portfolio_format_leap2a_file($file->get_filename(), $file); $this->add_entry($fileentry); $entry->add_link($fileentry, 'related'); $fileentry->add_link($entry, 'related'); } } /** * Validate the feed and all entries */ private function validate() { foreach ($this->entries as $entry) { // first call the entry's own validation method // which will throw an exception if there's anything wrong $entry->validate(); // now make sure that all links are in place foreach ($entry->links as $linkedid => $rel) { // the linked to entry exists if (!array_key_exists($linkedid, $this->entries)) { $a = (object)array('rel' => $rel->type, 'to' => $linkedid, 'from' => $entry->id); throw new portfolio_format_leap2a_exception('leap2a_nonexistantlink', 'portfolio', '', $a); } // and contains a link back to us if (!array_key_exists($entry->id, $this->entries[$linkedid]->links)) { } // we could later check that the reltypes were properly inverse, but nevermind for now. } } } /** * Return the entire feed as a string. * Then, it calls for validation * * @return string feeds' content in xml */ public function to_xml() { $this->validate(); foreach ($this->entries as $entry) { $entry->id = 'portfolio:' . $entry->id; $this->feed->appendChild($entry->to_dom($this->dom, $this->user)); } return $this->dom->saveXML(); } } /** * This class represents a single leap2a entry. * * You can create these directly and then add them to the main leap feed object * * @package core_portfolio * @category portfolio * @copyright 2009 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_leap2a_entry { /** @var string entry id - something like forumpost6, must be unique to the feed */ public $id; /** @var string title of the entry */ public $title; /** @var string leap2a entry type */ public $type; /** @var string optional author (only if different to feed author) */ public $author; /** @var string summary - for split long content */ public $summary; /** @var mixed main content of the entry. can be html,text,or xhtml. for a stored_file, use portfolio_format_leap2a_file **/ public $content; /** @var int updated date - unix timestamp */ public $updated; /** @var int published date (ctime) - unix timestamp */ public $published; /** @var array the required fields for a leap2a entry */ private $requiredfields = array( 'id', 'title', 'type'); /** @var array extra fields which usually should be set (except author) but are not required */ private $optionalfields = array('author', 'updated', 'published', 'content', 'summary'); /** @var array links from this entry to other entries */ public $links = array(); /** @var array attachments to this entry */ public $attachments = array(); /** @var array categories for this entry */ private $categories = array(); /** * Constructor. All arguments are required (and will be validated) * http://wiki.cetis.ac.uk/2009-03/LEAP2A_types * * @param string $id unique id of this entry. * could be something like forumpost6 for example. * This <b>must</b> be unique to the entire feed. * @param string $title title of the entry. This is pure atom. * @param string $type the leap type of this entry. * @param mixed $content the content of the entry. string (xhtml/html/text) */ public function __construct($id, $title, $type, $content=null) { $this->id = $id; $this->title = $title; $this->type = $type; $this->content = $this->__set('content', $content); } /** * Override __set to do proper dispatching for different things. * Only allows the optional and required leap2a entry fields to be set * * @param string $field property's name * @param mixed $value property's value * @return mixed */ public function __set($field, $value) { // detect the case where content is being set to be a file directly if ($field == 'content' && $value instanceof stored_file) { throw new portfolio_format_leap2a_exception('leap2a_filecontent', 'portfolio'); } if (in_array($field, $this->requiredfields) || in_array($field, $this->optionalfields)) { return $this->{$field} = $value; } throw new portfolio_format_leap2a_exception('leap2a_invalidentryfield', 'portfolio', '', $field); } /** * Validate this entry. * At the moment this just makes sure required fields exist * but it could also check things against a list, for example * * @todo MDL-31303 - add category with a scheme 'selection_type' */ public function validate() { foreach ($this->requiredfields as $key) { if (empty($this->{$key})) { throw new portfolio_format_leap2a_exception('leap2a_missingfield', 'portfolio', '', $key); } } if ($this->type == 'selection') { if (count($this->links) == 0) { throw new portfolio_format_leap2a_exception('leap2a_emptyselection', 'portfolio'); } //TODO make sure we have a category with a scheme 'selection_type' } } /** * Add a link from this entry to another one. * These will be collated at the end of the export (during to_xml) * and validated at that point. This function does no validation * {@link http://wiki.cetis.ac.uk/2009-03/LEAP2A_relationships} * * @param portfolio_format_leap2a_entry|string $otherentry portfolio_format_leap2a_entry or its id * @param string $reltype (no leap2: ns required) * @param string $displayorder (optional) * @return portfolio_format_leap2a_entry the current entry object. This is so that these calls can be chained * eg $entry->add_link('something6', 'has_part')->add_link('something7', * 'has_part'); */ public function add_link($otherentry, $reltype, $displayorder=null) { if ($otherentry instanceof portfolio_format_leap2a_entry) { $otherentry = $otherentry->id; } if ($otherentry == $this->id) { throw new portfolio_format_leap2a_exception('leap2a_selflink', 'portfolio', '', (object)array('rel' => $reltype, 'id' => $this->id)); } // add on the leap2: ns if required if (!in_array($reltype, array('related', 'alternate', 'enclosure'))) { $reltype = 'leap2:' . $reltype; } $this->links[$otherentry] = (object)array('rel' => $reltype, 'order' => $displayorder); return $this; } /** * Add a category to this entry * {@link http://wiki.cetis.ac.uk/2009-03/LEAP2A_categories} * "tags" should just pass a term here and no scheme or label. * They will be automatically normalised if they have spaces. * * @param string $term eg 'Offline' * @param string $scheme (optional) eg resource_type * @param string $label (optional) eg File */ public function add_category($term, $scheme=null, $label=null) { // "normalise" terms and set their label if they have spaces // see http://wiki.cetis.ac.uk/2009-03/LEAP2A_categories#Plain_tags for more information if (empty($scheme) && strpos($term, ' ') !== false) { $label = $term; $term = str_replace(' ', '-', $term); } $this->categories[] = (object)array( 'term' => $term, 'scheme' => $scheme, 'label' => $label, ); } /** * Create an entry element and append all the children * And return it rather than adding it to the dom. * This is handled by the main writer object. * * @param DomDocument $dom use this to create elements * @param stdClass $feedauthor object of author(user) info * @return DOMDocument */ public function to_dom(DomDocument $dom, $feedauthor) { $entry = $dom->createElement('entry'); $entry->appendChild($dom->createElement('id', $this->id)); $entry->appendChild($dom->createElement('title', $this->title)); if ($this->author && $this->author->id != $feedauthor->id) { $author = $dom->createElement('author'); $author->appendChild($dom->createElement('name', fullname($this->author))); $entry->appendChild($author); } // selectively add uncomplicated optional elements foreach (array('updated', 'published') as $field) { if ($this->{$field}) { $date = date(DATE_ATOM, $this->{$field}); $entry->appendChild($dom->createElement($field, $date)); } } if (empty($this->content)) { $entry->appendChild($dom->createElement('content')); } else { $content = $this->create_xhtmlish_element($dom, 'content', $this->content); $entry->appendChild($content); } if (!empty($this->summary)) { $summary = $this->create_xhtmlish_element($dom, 'summary', $this->summary); $entry->appendChild($summary); } $type = $dom->createElement('rdf:type'); $type->setAttribute('rdf:resource', 'leap2:' . $this->type); $entry->appendChild($type); foreach ($this->links as $otherentry => $l) { $link = $dom->createElement('link'); $link->setAttribute('rel', $l->rel); $link->setAttribute('href', 'portfolio:' . $otherentry); if ($l->order) { $link->setAttribute('leap2:display_order', $l->order); } $entry->appendChild($link); } $this->add_extra_links($dom, $entry); // hook for subclass foreach ($this->categories as $category) { $cat = $dom->createElement('category'); $cat->setAttribute('term', $category->term); if ($category->scheme) { $cat->setAttribute('scheme', 'categories:' .$category->scheme . '#'); } if ($category->label && $category->label != $category->term) { $cat->setAttribute('label', $category->label); } $entry->appendChild($cat); } return $entry; } /** * Try to load whatever is in $content into xhtml and add it to the dom. * Failing that, load the html, escape it, and set it as the body of the tag. * Either way it sets the type attribute of the top level element. * Moodle should always provide xhtml content, but user-defined content can't be trusted * * @todo MDL-31304 - convert <html><body> </body></html> to xml * @param DomDocument $dom the dom doc to use * @param string $tagname usually 'content' or 'summary' * @param string $content the content to use, either xhtml or html. * @return DomDocument */ private function create_xhtmlish_element(DomDocument $dom, $tagname, $content) { $topel = $dom->createElement($tagname); $maybexml = true; if (strpos($content, '<') === false && strpos($content, '>') === false) { $maybexml = false; } // try to load content as xml $tmp = new DomDocument(); if ($maybexml && @$tmp->loadXML('<div>' . $content . '</div>')) { $topel->setAttribute('type', 'xhtml'); $content = $dom->importNode($tmp->documentElement, true); $content->setAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); $topel->appendChild($content); // if that fails, it could still be html } else if ($maybexml && @$tmp->loadHTML($content)) { $topel->setAttribute('type', 'html'); $topel->nodeValue = $content; // TODO figure out how to convert this to xml // TODO because we end up with <html><body> </body></html> wrapped around it // which is annoying // either we already know it's text from the first check // or nothing else has worked anyway } else { $topel->nodeValue = $content; $topel->setAttribute('type', 'text'); return $topel; } return $topel; } /** * Hook function for subclasses to add extra links (like for files) * * @param DomDocument $dom feed object * @param DomDocument $entry feed added link */ protected function add_extra_links($dom, $entry) {} } /** * Subclass of entry, purely for dealing with files * * @package core_portfolio * @category portfolio * @copyright 2009 Penny Leach * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class portfolio_format_leap2a_file extends portfolio_format_leap2a_entry { /** @var file_stored for the dealing file */ protected $referencedfile; /** * Overridden constructor to set up the file. * * @param string $title title of the entry * @param stored_file $file file storage instance */ public function __construct($title, stored_file $file) { $id = portfolio_format_leap2a::file_id_prefix() . $file->get_id(); parent::__construct($id, $title, 'resource'); $this->referencedfile = $file; $this->published = $this->referencedfile->get_timecreated(); $this->updated = $this->referencedfile->get_timemodified(); $this->add_category('offline', 'resource_type'); } /** * Implement the hook to add extra links to attach the file in an enclosure * * @param DomDocument $dom feed object * @param DomDocument $entry feed added link */ protected function add_extra_links($dom, $entry) { $link = $dom->createElement('link'); $link->setAttribute('rel', 'enclosure'); $link->setAttribute('href', portfolio_format_leap2a::get_file_directory() . $this->referencedfile->get_filename()); $link->setAttribute('length', $this->referencedfile->get_filesize()); $link->setAttribute('type', $this->referencedfile->get_mimetype()); $entry->appendChild($link); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�