$string['participants'] = 'Participants';
$string['picture'] = 'Picture';
$string['pleaseaddsome'] = 'Please create some below or <a href=\"$a\">choose a predefined set</a> to get started.';
-$string['portfolionotfile'] = 'Export to a portfolio rather than a file (csv only)';
+$string['portfolionotfile'] = 'Export to a portfolio rather than a file (csv and leap2a only)';
$string['presetinfo'] = 'Saving as a preset will publish this template. Other users may be able to use it in their databases.';
$string['presets'] = 'Presets';
$string['radiobutton'] = 'Radio buttons';
$string['format_mbkp'] = 'Moodle Backup Format';
$string['format_video'] = 'Video';
$string['format_text'] = 'Plain Text';
+$string['format_leap2a'] = 'LEAP2A portfolio format';
$string['hidden'] = 'Hidden';
$string['highfilesizethreshold'] = 'High transfer filesize';
$string['highfilesizethresholddesc'] = 'Filesizes over this threshold will be considered to take a high amount of time to transfer';
--- /dev/null
+<?php
+
+$string['entryalreadyexists'] = 'You tried to add a LEAP2A entry with an id ($a) that already exists in this feed';
+$string['feedtitle'] = 'LEAP2A export from Moodle for $a';
+$string['invalidentryfield'] = 'You tried to set an entry field that didn\'t exist ($a) or you can\'t set directly';
+$string['invalidentryid'] = 'You tried to access an entry by an id that didn\'t exist ($a)';
+$string['missingfield'] = 'Required LEAP2A entry field $a missing';
+$string['nonexistantlink'] = 'A LEAP2A entry ($a->from) tried to link to a non existing entry ($a->to) with rel $a->rel';
+$string['overwritingselection'] = 'Overwriting the original type of an entry ($a) to selection in make_selection';
+$string['selflink'] = 'A LEAP2A entry ($a->id) tried to link to itself with rel $a->rel';
* as this function <b>must</b> be called statically.
*
* @return array list of formats
+ *
*/
- public static function supported_formats($caller=null) {
- if ($caller && $formats = $caller->get('supportedformats')) {
- if (is_array($formats)) {
- return $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($caller) . ' has set a non array value of member variable supported formats - working around but should be fixed in code');
- return array($formats);
+ $specific = array($formats);
+ } else {
+ $specific = $this->supportedformats;
}
- return array(PORTFOLIO_FORMAT_FILE);
+ return portfolio_most_specific_formats($specific, $basic);
}
+ public abstract static function base_supported_formats();
/**
* this is the "return to where you were" url
}
}
} else if (count($args) != 0) {
- if (count($args) != 3) {
+ if (count($args) < 3) {
throw new portfolio_caller_exception('invalidfileareaargs', 'portfolio');
}
$files = array_values(call_user_func_array(array($fs, 'get_area_files'), $args));
case 0: return;
case 1: {
$this->singlefile = $files[0];
- $this->supportedformats = array(portfolio_format_from_file($this->singlefile));
return;
}
default: {
}
}
+ /**
+ * the button-location always knows best
+ * what the formats are... so it should be trusted.
+ *
+ * @param array $formats array of PORTFOLIO_FORMAT_XX
+ */
+ public function set_formats_from_button($formats) {
+ $base = $this->base_supported_formats();
+ if (count($base) != count($formats)
+ || count($base) != count(array_intersect($base, $formats))) {
+ return $this->supportedformats = portfolio_most_specific_formats($formats, $base);
+ }
+ // in the case where the button hasn't actually set anything,
+ // we need to run through again and resolve conflicts
+ $removed = array();
+ foreach ($formats as $f1key => $f1) {
+ if (in_array($f1, $removed)) {
+ continue;
+ }
+ $f1obj = portfolio_format_object($f1);
+ foreach ($formats as $f2key => $f2) {
+ if (in_array($f2, $removed)) {
+ continue;
+ }
+ if ($f1obj->conflicts($f2)) {
+ unset($formats[$f2key]);
+ $removed[] = $f2;
+ }
+ }
+ }
+ $this->supportedformats = $formats;
+ }
+
+ /**
+ * adds a new format to the list of supported formats.
+ * 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;
+ }
+ $formatobj = portfolio_format_object($format);
+ foreach ($this->supportedformats as $key => $f) {
+ $f2obj = portfolio_format_object($f);
+ if ($formatobj->conflicts($f)) {
+ unset($this->supportedformats[$key]);
+ }
+ $class = get_class($f2obj);
+ if ($formatobj instanceof $class) {
+ unset($this->supportedformats[$key]);
+ }
+ }
+ $this->supportedformats[] = $format;
+ }
+
/**
* 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,
*/
define('PORTFOLIO_FORMAT_PRESENTATION', 'presentation');
+/**
+ * abstract - just used to say, "we support all these"
+ */
+define('PORTFOLIO_FORMAT_RICH', 'rich');
+
+/**
+ * leap2a http://wiki.cetis.ac.uk/LEAP_2.0
+ * supported by mahara and and others
+ */
+define('PORTFOLIO_FORMAT_LEAP2A', 'leap2a');
+
// ************************************************** //
// EXPORT TIME LEVELS
// these should correspond to a string
* exception for interacting with the button class
*/
class portfolio_button_exception extends portfolio_exception {}
+
+/**
+ * leap2a exception - for invalid api calls
+ */
+class portfolio_format_leap2a_exception extends portfolio_exception {}
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};
if ($this->caller->has_export_config()) {
$callerobj = $this->caller;
}
- $formats = portfolio_supported_formats_intersect($this->caller->supported_formats($this->caller), $this->instance->supported_formats());
+ $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.
* given a file, return a snippet of markup in whatever format
* to link to that file.
* usually involves the path given by {@link get_file_directory}
+ * this is not supported in subclasses of portfolio_format_file
+ * since they're all just single files.
*/
public static function file_output($file) {
- return '';
+ throw new portfolio_exception('fileoutputnotsupported', 'portfolio');
+ }
+
+ /**
+ * 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 boolean
+ */
+ public static function conflicts($format) {
+ return false;
}
}
* in case we want to be really specific.
*/
class portfolio_format_plainhtml extends portfolio_format_file {
+
public static function mimetypes() {
return array('text/html');
}
+
+ public static function conflicts($format) {
+ return ($format == PORTFOLIO_FORMAT_RICHHTML);
+ }
}
/**
public static function mimetypes() {
return array('text/plain');
}
+
+ 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
*/
-class portfolio_format_rich {
+abstract class portfolio_format_rich {
public static function mimetypes() {
return array(null);
}
+
+ public static function conflicts($format) {
+ return false;
+ }
+
+ /**
+ * given a file, return a snippet of markup in whatever format
+ * to link to that file.
+ * usually involves the path given by {@link get_file_directory}
+ * this is not supported in subclasses of portfolio_format_file
+ * since they're all just single files.
+ *
+ * @param stored_file $file the file to link to
+ * @param mixed $extras any extra arguments
+ */
+ public static abstract function file_output($file, $extras=null);
+
}
/**
public static function get_file_directory() {
return 'site_files';
}
- public static function file_output($file) {
+ public static function file_output($file, $extras=null) {
$path = self::get_file_directory() . '/' . $file->get_filename();
if (in_array($file->get_mimetype(), portfolio_format_image::mimetypes())) {
return '<img src="' . $path . '" alt="' . $file->get_filename() . '" />';
}
return '<a href="' . $path . '">' . $file->get_filename() . '</a>';
}
+ 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 portfolio_format_leap extends portfolio_format_rich { }
+class portfolio_format_leap2a extends portfolio_format_rich {
+
+ public static function get_file_directory() {
+ return 'files/';
+ }
+
+ /**
+ * return the link to a file
+ *
+ * @param stored_file $file
+ * @param boolean $entry whether the file is a LEAP2A entry or just a bundled file (default false)
+ */
+ public static function file_output($file, $entry=false) {
+ $id = '';
+ if ($entry) {
+ $id = 'portfolio:file' . $file->get_id;
+ } else {
+ $id = self::get_file_directory() . '/' . $file->get_filename();
+ }
+ return '<a rel="enclosure" href="' . $id . '">' . $file->get_filename() . '</a>';
+ }
+
+ 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);
+ }
+
+ 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 {}
+//class portfolio_format_mbkp extends portfolio_format_rich {}
/**
* 'PDF format', subtype of file.
--- /dev/null
+<?php
+/**
+ * Moodle - Modular Object-Oriented Dynamic Learning Environment
+ * http://moodle.org
+ * Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package moodle
+ * @subpackage portfolio
+ * @author Penny Leach <penny@liip.ch>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL
+ * @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
+ *
+ * This file contains the LEAP2a writer used by portfolio_format_leap2a
+ */
+
+/**
+ * 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, 'leaptype', 'somecontent')
+ * $entry->add_link('something', 'has_part')->add_link('somethingelse', 'has_part');
+ * .. etc
+ * $writer->add_entry($entry);
+ * $xmlstr = $writer->to_xml();
+ *
+ * @TODO find a way to ensure that all referenced files are included
+ */
+class portfolio_format_leap2a_writer {
+
+ /** the domdocument object used to create elements */
+ private $dom;
+ /** the top level feed element */
+ private $feed;
+ /** the user exporting data */
+ private $user;
+ /** 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;
+ /** the entries for the feed - keyed on id */
+ private $entries = array();
+
+ /**
+ * constructor - usually generated from portfolio_format_leap2a::leap2a_writer($USER);
+ *
+ * @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:leap', 'http://wiki.cetis.ac.uk/2009-03/LEAP2A_predicates#');
+ $this->feed->setAttribute('xmlns:leaptype', 'http://wiki.cetis.ac.uk/2009-03/LEAP2A_types#');
+ $this->feed->setAttribute('xmlns:categories', 'http://wiki.cetis.ac.uk/2009-03/LEAP2A_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('feedtitle', 'portfolio_format_leap2a', fullname($this->user))));
+
+ $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 the entry to add
+ */
+ public function add_entry(portfolio_format_leap2a_entry $entry) {
+ if (array_key_exists($entry->id, $this->entries)) {
+ throw new portfolio_format_leap2a_exception('entryalreadyexists', 'portfolio_format_leap2a', '', $entry->id);
+ }
+ $this->entries[$entry->id] = $entry;
+ return $entry;
+ }
+
+ /**
+ * make an entry that has previously been added into the feed into a selection.
+ *
+ * @param mixed $selectionentry the entry to make a selection (id or entry object)
+ * @param array $ids array of ids this selection includes
+ * @param string $selectiontype 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('invalidentryid', 'portfolio_format_leap2a', '', $selectionid);
+ }
+ foreach ($ids as $entryid) {
+ if (!array_key_exists($entryid, $this->entries)) {
+ throw new portfolio_format_leap2a_exception('invalidentryid', 'portfolio_format_leap2a', '', $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('overwritingselection', 'portfolio_format_leap2a', $this->entries[$selectionid]->type));
+ $this->entries[$selectionid]->type = 'selection';
+ }
+ }
+
+ /**
+ * 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('nonexistantlink', 'portfolio_format_leap2a', '', $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
+ * calls validate() first on everything
+ *
+ * @return string
+ */
+ public function to_xml() {
+ $this->validate();
+ foreach ($this->entries as $entry) {
+ $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
+ */
+class portfolio_format_leap2a_entry {
+
+ /** entry id - something like forumpost6, must be unique to the feed **/
+ public $id;
+ /** title of the entry **/
+ public $title;
+ /** leap2a entry type **/
+ public $type;
+ /** optional author (only if different to feed author) **/
+ public $author;
+ /** summary - for split long content **/
+ public $summary;
+ /** main content of the entry. can be html,text,xhtml or a stored_file **/
+ public $content;
+ /** updated date - unix timestamp */
+ public $updated;
+ /** published date (ctime) - unix timestamp */
+ public $published;
+
+ /** used internally for file content **/
+ private $contentsrc;
+ /** used internally for file content **/
+ private $referencedfile;
+
+ /** the required fields for a leap2a entry */
+ private $requiredfields = array( 'id', 'title', 'type');
+
+ /** extra fields which usually should be set (except author) but are not required */
+ private $optionalfields = array('author', 'updated', 'published', 'content', 'summary');
+
+ /** links from this entry to other entries */
+ public $links = array();
+
+ /** attachments to this entry */
+ public $attachments = 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) or stored_file
+ */
+ 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
+ */
+ 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) {
+ return $this->set_content_file($value);
+ }
+ if (in_array($field, $this->requiredfields) || in_array($field, $this->optionalfields)) {
+ return $this->{$field} = $value;
+ }
+ throw new portfolio_format_leap2a_exception('invalidentryfield', 'portfolio_format_leap2a', '', $field);
+ }
+
+ /**
+ * sets the content of this entry to have a source
+ * this will take care of namespacing the filepath
+ * to the final path in the resulting zip file.
+ *
+ * @param stored_file $file the file to link to
+ * @param boolean $overridetype (default true) will set the entry rdf type to resource,
+ * overriding what was previously set.
+ * will be ignored if type is empty already
+ */
+ public function set_content_file(stored_file $file, $overridetype=true) {
+ $this->contentsrc = portfolio_format_leap2a::get_file_directory() . $file->get_filename();
+ if (empty($overridetype) || empty($this->type)) {
+ $this->type = 'resource';
+ }
+ $this->referencedfile = $file;
+ }
+
+ /**
+ * validate this entry.
+ * at the moment this just makes sure required fields exist
+ * but it could also check things against a list, for example
+ */
+ public function validate() {
+ foreach ($this->requiredfields as $key) {
+ if (empty($this->{$key})) {
+ throw new portfolio_format_leap2a_exception('missingfield', 'portfolio_format_leap2a', '', $key);
+ }
+ }
+ if ($this->type == 'selection') {
+ if (count($this->links) == 0) {
+ throw new portfolio_format_leap2a_exception('emptyselection', 'portfolio_format_leap2a');
+ }
+ //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
+ * http://wiki.cetis.ac.uk/2009-03/LEAP2A_relationships
+ *
+ * @param mixed $otherentry portfolio_format_leap2a_entry or its id
+ * @param string $reltype (no leap: ns required)
+ *
+ * @return 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('selflink', 'portfolio_format_leap2a', '', (object)array('rel' => $reltype, 'id' => $this->id));
+ }
+ // add on the leap: ns if required
+ if (!in_array($reltype, array('related', 'alternate', 'enclosure'))) {
+ $rel = 'leap:' . $reltype;
+ }
+
+ $this->links[$otherentry] = (object)array('rel' => $reltype, 'order' => $displayorder);
+
+ return $this;
+ }
+
+ /**
+ * add an attachment to the feed.
+ * adding the file to the files area has to be handled outside this class separately.
+ *
+ * @param stored_file $file the file to add
+ */
+ public function add_attachment(stored_file $file) {
+ $this->attachments[$file->get_id()] = $file;
+ }
+
+ /**
+ * add a category to this entry
+ * http://wiki.cetis.ac.uk/2009-03/LEAP2A_categories
+ *
+ * @param string $term eg 'Offline'
+ * @param string $scheme (optional) eg resource_type
+ * @param string $label (optional) eg File
+ *
+ * "tags" should just pass a term here and no scheme or label.
+ * they will be automatically normalised if they have spaces.
+ */
+ 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
+ *
+ * @return DomElement
+ */
+ 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}) {
+ // TODO get the proper date format
+ $entry->appendChild($dom->createElement($field, $this->{$field}));
+ }
+ }
+ // deal with referenced files first since it's simple
+ if ($this->contentsrc) {
+ $content = $dom->createElement('content');
+ $content->setAttribute('src', $this->contentsrc);
+ $content->setAttribute('type', $this->referencedfile->get_mimetype());
+ $entry->appendChild($content);
+ } else 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', 'leaptype:' . $this->type);
+ $entry->appendChild($type);
+
+ foreach ($this->links as $otherentry => $l) {
+ $link = $dom->createElement('link');
+ $link->setAttribute('rel', $l->rel);
+ $link->setAttribute('href', $otherentry);
+ if ($l->order) {
+ $link->setAttribute('leap:display_order', $l->order);
+ }
+ $entry->appendChild($link);
+ }
+ foreach ($this->attachments as $id => $file) {
+ $link = $dom->createElement('link');
+ $link->setAttribute('rel', 'enclosure');
+ $link->setAttribute('href', portfolio_format_leap2a::get_file_directory() . $file->get_filename());
+ $link->setAttribute('length', $file->get_filesize());
+ $entry->appendChild($link);
+ }
+ foreach ($this->categories as $category) {
+ $cat = $dom->createElement('category');
+ $cat->setAttribute('term', $category->term);
+ if ($category->scheme) {
+ $cat->setAttribute('scheme', $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
+ *
+ * @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 DomElement
+ */
+ 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;
+ }
+}
function definition() {
$this->caller = $this->_customdata['caller'];
- $options = portfolio_instance_select(
- portfolio_instances(),
- $this->caller->supported_formats($this->caller),
- get_class($this->caller),
- $this->caller->get('singlefile'),
- 'instance',
- true,
- true
- );
- // TODO maybe add on some information to the user if they're already exporting
- // and some of the options were skipped because they are for plugins that don't support
- // multiple exports per session
- if (empty($options)) {
- debugging('noavailableplugins', 'portfolio');
- return false;
- }
+ $options = $this->_customdata['options'];
$mform =& $this->_form;
$mform->addElement('select', 'instance', get_string('selectplugin', 'portfolio'), $options);
$mform->addElement('hidden', 'id', $this->_customdata['id']);
* @return array list of formats
*/
public static function supported_formats() {
- return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML);
+ return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICH);
}
/**
* sets the available export formats for this content
* this function will also poll the static function in the caller class
* and make sure we're not overriding a format that has nothing to do with mimetypes
- * eg if you pass IMAGE here but the caller can export LEAP it will keep LEAP as well.
+ * eg if you pass IMAGE here but the caller can export LEAP2A it will keep LEAP2A as well.
* see portfolio_most_specific_formats for more information
*
- * @param array $formats if the calling code knows better than the static method on the calling class (supported_formats)
+ * @param array $formats if the calling code knows better than the static method on the calling class (base_supported_formats)
* eg, if it's going to be a single file, or if you know it's HTML, you can pass it here instead
* this is almost always the case so you should always use this.
* {@see portfolio_format_from_file} for how to get the appropriate formats to pass here for uploaded files.
if (empty($this->callbackclass)) {
throw new portfolio_button_exception('noclassbeforeformats', 'portfolio');
}
- $callerformats = call_user_func(array($this->callbackclass, 'supported_formats'));
+ $callerformats = call_user_func(array($this->callbackclass, 'base_supported_formats'));
$this->formats = portfolio_most_specific_formats($formats, $callerformats);
}
+ public function reset_formats() {
+ $this->set_formats();
+ }
+
+
/**
* if we already know we have exactly one file,
* bypass set_formats and just pass the file
* so we can detect the formats by mimetype.
*
- * @param stored_file $file
+ * @param stored_file $file file to set the format from
+ * @param mixed $extraformats any additional formats other than by mimetype
+ * eg leap2a etc
*/
- public function set_format_by_file(stored_file $file) {
+ public function set_format_by_file(stored_file $file, $extraformats=null) {
$this->file = $file;
- $this->formats = array(portfolio_format_from_file($file));
+ if (is_string($extraformats)) {
+ $this->set_formats(array(portfolio_format_from_file($file), $extraformats));
+ } else if (is_array($extraformats)) {
+ $this->set_formats(array_merge(array(portfolio_format_from_file($file)), $extraformats));
+ } else {
+ $this->set_formats(portfolio_format_from_file($file));
+ }
}
/*
$formoutput .= "\n" . '<input type="hidden" name="callbackfile" value="' . $this->callbackfile . '" />';
$formoutput .= "\n" . '<input type="hidden" name="callbackclass" value="' . $this->callbackclass . '" />';
$formoutput .= "\n" . '<input type="hidden" name="course" value="' . (!empty($COURSE) ? $COURSE->id : 0) . '" />';
+ $formoutput .= "\n" . '<input type="hidden" name="callerformats" value="' . implode(',', $this->formats) . '" />';
$linkoutput .= 'callbackfile=' . $this->callbackfile . '&callbackclass='
- . $this->callbackclass . '&course=' . (!empty($COURSE) ? $COURSE->id : 0);
+ . $this->callbackclass . '&course=' . (!empty($COURSE) ? $COURSE->id : 0)
+ . '&callerformats=' . implode(',', $this->formats);
$selectoutput = '';
if (count($this->instances) == 1) {
$tmp = array_values($this->instances);
PORTFOLIO_FORMAT_SPREADSHEET => 'portfolio_format_spreadsheet',
PORTFOLIO_FORMAT_PRESENTATION => 'portfolio_format_presentation',
/*PORTFOLIO_FORMAT_MBKP, */ // later
- /*PORTFOLIO_FORMAT_LEAP, */ // also later
+ PORTFOLIO_FORMAT_LEAP2A => 'portfolio_format_leap2a',
+ PORTFOLIO_FORMAT_RICH => 'portfolio_format_rich',
);
}
$intersection = array();
foreach ($callerformats as $cf) {
if (!array_key_exists($cf, $allformats)) {
- debugging(get_string('invalidformat', 'portfolio', $cf));
+ if (!portfolio_format_is_abstract($cf)) {
+ debugging(get_string('invalidformat', 'portfolio', $cf));
+ }
continue;
}
$cfobj = new $allformats[$cf]();
foreach ($pluginformats as $p => $pf) {
if (!array_key_exists($pf, $allformats)) {
- debugging(get_string('invalidformat', 'portfolio', $pf));
+ if (!portfolio_format_is_abstract($pf)) {
+ debugging(get_string('invalidformat', 'portfolio', $pf));
+ }
unset($pluginformats[$p]); // to avoid the same warning over and over
continue;
}
return $intersection;
}
+/**
+ * tiny helper to figure out whether a portfolio format is abstract
+ *
+ * @param string $format the format to test
+ *
+ * @retun bool
+ */
+function portfolio_format_is_abstract($format) {
+ if (class_exists($format)) {
+ $class = $format;
+ } else if (class_exists('portfolio_format_' . $format)) {
+ $class = 'portfolio_format_' . $format;
+ } else {
+ $allformats = portfolio_supported_formats();
+ if (array_key_exists($format, $allformats)) {
+ $class = $allformats[$format];
+ }
+ }
+ if (empty($class)) {
+ return true; // it may as well be, we can't instantiate it :)
+ }
+ $rc = new ReflectionClass($class);
+ return $rc->isAbstract();
+}
+
/**
* return the combination of the two arrays of formats with duplicates in terms of specificity removed
+* and also removes conflicting formats
* use case: a module is exporting a single file, so the general formats would be FILE and MBKP
* while the specific formats would be the specific subclass of FILE based on mime (say IMAGE)
* and this function would return IMAGE and MBKP
*/
function portfolio_most_specific_formats($specificformats, $generalformats) {
$allformats = portfolio_supported_formats();
+ if (empty($specificformats)) {
+ return $generalformats;
+ } else if (empty($generalformats)) {
+ return $specificformats;
+ }
foreach ($specificformats as $f) {
// look for something less specific and remove it, ie outside of the inheritance tree of the current formats.
if (!array_key_exists($f, $allformats)) {
- throw new portfolio_button_exception('invalidformat', 'portfolio', $f);
+ if (!portfolio_format_is_abstract($pf)) {
+ throw new portfolio_button_exception('invalidformat', 'portfolio', $f);
+ }
}
$fobj = new $allformats[$f];
foreach ($generalformats as $key => $cf) {
$cfclass = $allformats[$cf];
- if ($fobj instanceof $cfclass) {
- unset($generalformats[$cf]);
+ if ($fobj instanceof $cfclass && $cfclass != get_class($fobj)) {
+ debugging("unsetting $key $cf because it's not specific enough ($f is better)");
+ unset($generalformats[$key]);
+ }
+ // check for conflicts
+ if ($fobj->conflicts($cf)) {
+ debugging("unsetting $key $cf because it conflicts with $f");
+ unset($generalformats[$key]);
}
}
+ //debugging('inside loop');
+ //print_object($generalformats);
}
- return array_merge(array_values($specificformats), array_values($generalformats));
+
+ //debugging('final formats');
+ $finalformats = array_unique(array_merge(array_values($specificformats), array_values($generalformats)));
+ //print_object($finalformats);
+ return $finalformats;
}
/**
* @param string $classfile file containing the callback class definition
* @param array $callbackargs arguments to pass to the callback class
*/
-function portfolio_fake_add_url($instanceid, $classname, $classfile, $callbackargs) {
+function portfolio_fake_add_url($instanceid, $classname, $classfile, $callbackargs, $formats) {
global $CFG;
- $url = $CFG->wwwroot . '/portfolio/add.php?instance=' . $instanceid . '&callbackclass=' . $classname . '&callbackfile=' . $classfile . '&sesskey=' . sesskey();
+ $url = $CFG->wwwroot . '/portfolio/add.php?instance=' . $instanceid . '&callbackclass=' . $classname . '&callbackfile=' . $classfile . '&sesskey=' . sesskey();
if (is_object($callbackargs)) {
$callbackargs = (array)$callbackargs;
return $url;
}
foreach ($callbackargs as $key => $value) {
- $url .= '&ca_' . $key . '=' . urlencode($value);
+ $url .= '&ca_' . $key . '=' . urlencode($value);
}
+ $url .= '&callerformats=' . implode(',', $formats);
return $url;
}
public static function expected_callbackargs() {
return array();
}
+ public static function base_supported_formats() {
+ return array(PORTFOLIO_FORMAT_RICH, PORTFOLIO_FORMAT_FILE);
+ }
}
class portfolio_exporter_test extends portfolio_exporter {
}
if (count($files) > 1 && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
$button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id));
- $button->set_formats(PORTFOLIO_PORMAT_FILE);
$output .= '<br />' . $button->to_html();
}
}
throw new portfolio_caller_exception('notexportable', 'portfolio', $this->get_return_url());
}
- $this->set_file_and_format_data($this->fileid, $this->assignment->context->id, 'assignment_submission', $this->user->id);
- if (empty($this->supportedformats) && is_callable(array($this->assignment, 'portfolio_supported_formats'))) {
- $this->supportedformats = $this->assignment->portfolio_supported_formats();
- }
+ $this->set_file_and_format_data($this->fileid, $this->assignment->context->id, 'assignment_submission', $this->user->id, 'timemodified', false);
}
public function prepare_package() {
if (is_callable(array($this->assignment, 'portfolio_prepare_package'))) {
return $this->assignment->portfolio_prepare_package($this->exporter, $this->user->id);
}
+ if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
+ $leapwriter = $this->exporter->get('format')->leap2a_writer();
+ $files = array();
+ if ($this->singlefile) {
+ $files[] = $this->singlefile;
+ } elseif ($this->multifiles) {
+ $files = $this->multifiles;
+ } else {
+ throw new portfolio_caller_exception('invalidpreparepackagefile', 'portfolio', $this->get_return_url());
+ }
+ $baseid = 'assignment' . $this->assignment->assignment->assignmenttype . $this->assignment->assignment->id . 'submission';
+ foreach ($files as $file) {
+ $id = $baseid . $file->get_id();
+ $entry = new portfolio_format_leap2a_entry($id, $file->get_filename(), 'resource', $file);
+ $entry->add_category('offline', 'resource_type');
+ $leapwriter->add_entry($entry);
+ $this->exporter->copy_existing_file($file);
+ }
+ return $this->exporter->write_new_file($leapwriter->to_xml(), $this->exporter->get('format')->manifest_name(), true);
+ }
return $this->prepare_package_file();
}
public static function display_name() {
return get_string('modulename', 'assignment');
}
+
+ public static function base_supported_formats() {
+ return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
+ }
}
/**
echo format_text($text, $submission->data2);
$button = new portfolio_add_button();
$button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id), '/mod/assignment/lib.php');
+ $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML); //TODO this might have files?
$button->render();
} else if (!has_capability('mod/assignment:submit', $context)) { //fix for #4604
echo '<div style="text-align:center">'. get_string('guestnosubmit', 'assignment').'</div>';
function portfolio_prepare_package($exporter, $userid=0) {
$submission = $this->get_submission($userid);
- $exporter->write_new_file(format_text($submission->data1, $submission->data2), 'assignment.html', false);
- }
-
- function portfolio_supported_formats() {
- return array(PORTFOLIO_FORMAT_PLAINHTML);
+ $html = format_text($submission->data1, $submission->data2);
+ if ($exporter->get('formatclass') == PORTFOLIO_FORMAT_PLAINHTML) {
+ return $exporter->write_new_file($html, 'assignment.html', false);
+ } else if ($exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
+ $leapwriter = $exporter->get('format')->leap2a_writer();
+ $entry = new portfolio_format_leap2a_entry('assignmentonline' . $this->assignment->id, $this->assignment->name, 'resource', $html); // TODO entry?
+ $entry->add_category('web', 'resource_type');
+ $leapwriter->add_entry($entry);
+ return $exporter->write_new_file($leapwriter->to_xml(), $exporter->get('format')->manifest_name(), true);
+ //TODO attached files?!
+ } else {
+ die('wtf ;' . $exporter->get('formatclass'));
+ }
}
function extend_settings_navigation($node) {
}
if (count($files) > 1 && has_capability('mod/assignment:exportownsubmission', $this->context)) {
$button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id), '/mod/assignment/lib.php');
- $button->set_formats(PORTFOLIO_FORMAT_FILE);
+ $button->reset_formats(); // reset what we set before, since it's multi-file
$output .= $button->to_html();
}
}
/**
* @return array
*/
- public static function supported_formats() {
+ public static function base_supported_formats() {
return array(PORTFOLIO_FORMAT_PLAINHTML);
}
/**
// fake portfolio callback stuff and redirect
$formdata['id'] = $cm->id;
$formdata['exporttype'] = 'csv'; // force for now
- $url = portfolio_fake_add_url($formdata['portfolio'], 'data_portfolio_caller', '/mod/data/lib.php', $formdata);
+ $url = portfolio_fake_add_url($formdata['portfolio'], 'data_portfolio_caller', '/mod/data/lib.php', $formdata, array());
redirect($url);
}
$typesarray[] = &MoodleQuickForm::createElement('select', 'delimiter_name', null, $choices);
$typesarray[] = &MoodleQuickForm::createElement('radio', 'exporttype', null, get_string('excel', 'data'), 'xls');
$typesarray[] = &MoodleQuickForm::createElement('radio', 'exporttype', null, get_string('ods', 'data'), 'ods');
+ if ($CFG->enableportfolios) {
+ $typesarray[] = &MoodleQuickForm::createElement('radio', 'exporttype', null, get_string('format_leap2a', 'portfolio'), 'leap2a');
+ }
$mform->addGroup($typesarray, 'exportar', '', array(''), false);
$mform->addRule('exportar', null, 'required');
$mform->setDefault('exporttype', 'csv');
}
$this->add_checkbox_controller(1, null, null, 1);
require_once($CFG->libdir . '/portfoliolib.php');
- if (has_capability('mod/data:exportallentries', get_context_instance(CONTEXT_MODULE, $this->_cm->id))) {
+ if ($CFG->enableportfolios && has_capability('mod/data:exportallentries', get_context_instance(CONTEXT_MODULE, $this->_cm->id))) {
if ($portfoliooptions = portfolio_instance_select(
portfolio_instances(),
- call_user_func(array('data_portfolio_caller', 'supported_formats')),
+ call_user_func(array('data_portfolio_caller', 'base_supported_formats')),
'data_portfolio_caller', null, '', true, true)) {
$mform->addElement('header', 'notice', get_string('portfolionotfile', 'data') . ':');
$portfoliooptions[0] = get_string('none');
$this->exporttype = 'single';
list($formats, $files) = self::formats($this->fields, $this->singlerecord);
- $this->supportedformats = $formats;
if (count($files) == 1 && count($this->fields) == 1) {
$this->singlefile = $files[0];
$this->exporttype = 'singlefile';
* @param array $fields
* @param object $record
* @uses PORTFOLIO_FORMAT_PLAINHTML
- * @uses PORTFOLIO_FORMAT_FILE
* @uses PORTFOLIO_FORMAT_RICHHTML
* @return array
*/
if (count($includedfiles) == 1 && count($fields) == 1) {
$formats= array(portfolio_format_from_file($includedfiles[0]));
} else if (count($includedfiles) > 0) {
- $formats = array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML);
+ $formats = array(PORTFOLIO_FORMAT_RICHHTML);
}
return array($formats, $includedfiles);
}
+
+ public static function base_supported_formats() {
+ return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML);
+ }
}
function data_extend_navigation($navigation, $course, $module, $cm) {
if (empty($attachments)) {
$button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
} else {
- $button->set_formats(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML);
+ $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
}
$porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
private $forum;
private $discussion;
private $posts;
- private $keyedfiles;
+ private $keyedfiles; // just using multifiles isn't enough if we're exporting a full thread
+
/**
* @return array
*/
$modcontext = get_context_instance(CONTEXT_MODULE, $this->cm->id);
if ($this->post) {
- $this->set_file_and_format_data($this->attachment, $modcontext->id, 'forum_attachment', $this->post->id);
+ $this->set_file_and_format_data($this->attachment, $modcontext->id, 'forum_attachment', $this->post->id, 'timemodified', false);
if (!empty($this->multifiles)) {
$this->keyedfiles[$this->post->id] = $this->multifiles;
} else if (!empty($this->singlefile)) {
- $this->keyedfiles[$this->post->id] = $this->singlefile;
+ $this->keyedfiles[$this->post->id] = array($this->singlefile);
}
} else { // whole thread
$fs = get_file_storage();
$this->multifiles = array_merge($this->multifiles, array_values($this->keyedfiles[$post->id]));
}
}
- if ($this->attachment) {
- // do nothing
- } else if (!empty($this->multifiles) || !empty($this->singlefile)) {
- $this->supportedformats = array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML);
+ if (empty($this->multifiles) && !empty($this->singlefile)) {
+ $this->multifiles = array($this->singlefile); // copy_files workaround
+ }
+ // depending on whether there are files or not, we might have to change richhtml/plainhtml
+ if (!empty($this->multifiles)) {
+ $this->add_format(PORTFOLIO_FORMAT_RICHHTML);
} else {
- $this->supportedformats = array(PORTFOLIO_FORMAT_PLAINHTML);
+ $this->add_format(PORTFOLIO_FORMAT_PLAINHTML);
}
}
+
/**
* @global object
* @return string
return array($navlinks, $this->cm);
}
/**
+ * either a whole discussion
+ * a single post, with or without attachment
+ * or just an attachment with no post
+ *
* @global object
* @global object
* @uses PORTFOLIO_FORMAT_RICH
* @return mixed
*/
function prepare_package() {
- global $CFG, $SESSION;
- // either a whole discussion
- // a single post, with or without attachment
- // or just an attachment with no post
- $manifest = ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH);
- if (!$this->post) { // whole discussion
- $content = '';
+ global $CFG;
+
+ // set up the leap2a writer if we need it
+ $writingleap = false;
+ if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
+ $leapwriter = $this->exporter->get('format')->leap2a_writer();
+ $writingleap = true;
+ }
+ if ($this->attachment) { // simplest case first - single file attachment
+ $this->copy_files(array($this->singlefile), $this->attachment);
+ if ($writingleap) { // if we're writing leap, make the manifest to go along with the file
+ $entry = new portfolio_format_leap2a_entry($id, $this->attachment->get_filename(), 'resource', $this->attachment);
+ $entry->add_category('offline', 'resource_type');
+ $leapwriter->add_entry($entry);
+ return $this->exporter->write_new_file($leapwriter->to_xml(), $this->exporter->get('format')->manifest_name(), true);
+ }
+
+ } else if (empty($this->post)) { // exporting whole discussion
+ $content = ''; // if we're just writing HTML, start a string to add each post to
+ $ids = array(); // if we're writing leap2a, keep track of all entryids so we can add a selection element
foreach ($this->posts as $post) {
- $content .= '<br /><br />' . $this->prepare_post($post);
+ $posthtml = $this->prepare_post($post);
+ if ($writingleap) {
+ $ids[] = $this->prepare_post_leap2a($leapwriter, $post, $posthtml);
+ } else {
+ $content .= $posthtml . '<br /><br />';
+ }
+ }
+ $this->copy_files($this->multifiles);
+ $name = 'discussion.html';
+ $manifest = ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH);
+ if ($writingleap) {
+ // add on an extra 'selection' entry
+ $selection = new portfolio_format_leap2a_entry('forumdiscussion' . $this->discussionid, get_string('discussion', 'forum'), 'selection');
+ $leapwriter->add_entry($selection);
+ $leapwriter->make_selection($selection, $ids, 'Grouping');
+ $content = $leapwriter->to_xml();
+ $name = $this->exporter->get('format')->manifest_name();
+ }
+ $this->get('exporter')->write_new_file($content, $name, $manifest);
+
+ } else { // exporting a single post
+ $posthtml = $this->prepare_post($this->post);
+
+ $content = $posthtml;
+ $name = 'post.html';
+ $manifest = ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH);
+
+ if ($writingleap) {
+ $this->prepare_post_leap2a($leapwriter, $this->post, $posthtml);
+ $content = $leapwriter->to_xml();
+ $name = $this->exporter->get('format')->manifest_name();
}
$this->copy_files($this->multifiles);
- return $this->get('exporter')->write_new_file($content, 'discussion.html', $manifest);
+ $this->get('exporter')->write_new_file($content, $name, $manifest);
}
- if ($this->attachment) {
- return $this->copy_files(array($this->singlefile), $this->attachment); // all we need to do
+ }
+
+ /**
+ * helper function to add a leap2a entry element
+ * that corresponds to a single forum post,
+ * including any attachments
+ *
+ * the entry/ies are added directly to the leapwriter, which is passed by ref
+ *
+ * @param portfolio_format_leap2a_writer $leapwriter writer object to add entries to
+ * @param object $post the stdclass object representing the database record
+ * @param string $posthtml the content of the post (prepared by {@link prepare_post}
+ *
+ * @return int id of new entry
+ */
+ private function prepare_post_leap2a(portfolio_format_leap2a_writer $leapwriter, $post, $posthtml) {
+ $entry = new portfolio_format_leap2a_entry('forumpost' . $post->id, $post->subject, 'resource', $posthtml);
+ if (is_array($this->keyedfiles) && array_key_exists($post->id, $this->keyedfiles) && is_array($this->keyedfiles[$post->id])) {
+ foreach ($this->keyedfiles[$post->id] as $file) {
+ // copying the file into the package area is handled elsewhere
+ $entry->add_attachment($file);
+ }
}
- $this->copy_files($this->multifiles, $this->attachment);
- $post = $this->prepare_post($this->post);
- $this->get('exporter')->write_new_file($post, 'post.html', $manifest);
+ $entry->add_category('web', 'resource_type');
+ $leapwriter->add_entry($entry);
+ return $entry->id;
}
+
/**
* @param array $files
- * @param bool $justone
+ * @param mixed $justone false of id of single file to copy
* @return bool|void
*/
private function copy_files($files, $justone=false) {
}
foreach ($files as $f) {
if ($justone && $f->get_id() != $justone) {
- continue; // support multipe files later
+ continue;
}
$this->get('exporter')->copy_existing_file($f);
if ($justone && $f->get_id() == $justone) {
* @param int $post
* @return string
*/
- private function prepare_post($post) {
+ private function prepare_post($post, $fileoutputextras=null) {
global $DB;
static $users;
if (empty($users)) {
$output .= $formattedtext;
- if (is_array($this->keyedfiles) && array_key_exists($post->id, $this->keyedfiles) && is_array($this->keyedfiles[$post->id])) {
+ if (is_array($this->keyedfiles) && array_key_exists($post->id, $this->keyedfiles) && is_array($this->keyedfiles[$post->id]) && count($this->keyedfiles[$post->id]) > 0) {
$output .= '<div class="attachments">';
$output .= '<br /><b>' . get_string('attachments', 'forum') . '</b>:<br /><br />';
$format = $this->get('exporter')->get('format');
foreach ($this->keyedfiles[$post->id] as $file) {
- $output .= $format->file_output($file) . '<br/ >';
+ $output .= $format->file_output($file) . '<br/ >';
}
$output .= "</div>";
}
public static function display_name() {
return get_string('modulename', 'forum');
}
+
+ public static function base_supported_formats() {
+ return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_LEAP2A);
+ }
}
/**
if ($DB->count_records('glossary_entries', array('glossaryid' => $glossary->id))) {
require_once($CFG->libdir . '/portfoliolib.php');
$button = new portfolio_add_button();
- $button->set_callback_options('glossary_csv_portfolio_caller', array('id' => $cm->id), '/mod/glossary/lib.php');
+ $button->set_callback_options('glossary_full_portfolio_caller', array('id' => $cm->id), '/mod/glossary/lib.php');
$button->render();
}
echo $OUTPUT->box_end();
&& has_capability('mod/glossary:exportownentry', $context))) {
$button = new portfolio_add_button();
$button->set_callback_options('glossary_entry_portfolio_caller', array('id' => $cm->id, 'entryid' => $entry->id));
+
+ $filecontext = $context;
+ if ($entry->sourceglossaryid == $cm->instance) {
+ if ($maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
+ $filecontext = get_context_instance(CONTEXT_MODULE, $maincm->id);
+ }
+ }
+ $fs = get_file_storage();
+ if ($files = $fs->get_area_files($filecontext->id, 'glossary_attachment', $entry->id, "timemodified", false)) {
+ $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
+ }
+
$return .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
}
$return .= " "; // just to make up a little the output in Mozilla ;)
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class glossary_csv_portfolio_caller extends portfolio_module_caller_base {
+class glossary_full_portfolio_caller extends portfolio_module_caller_base {
private $glossary;
private $exportdata;
$categories[$cat->entryid][] = $cat->name;
}
}
+ // TODO detect format here
$csv = glossary_generate_export_csv($entries, $aliases, $categories);
return $this->exporter->write_new_file($csv, clean_filename($this->cm->name) . '.csv', false);
+ // TODO when csv, what do we do with attachments?!
}
/**
* @return bool
public static function display_name() {
return get_string('modulename', 'glossary');
}
+
+ public static function base_supported_formats() {
+ return array(PORTFOLIO_FORMAT_FILE);
+ }
}
/**
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class glossary_entry_portfolio_caller extends portfolio_module_caller_base {
+class glossary_entry_portfolio_caller extends portfolio_module_caller_base { // TODO files support
private $glossary;
private $entry;
// in case we don't have USER this will make the entry be printed
$this->entry->approved = true;
}
- $this->supportedformats = array(PORTFOLIO_FORMAT_PLAINHTML);
+ $context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
+ if ($this->entry->sourceglossaryid == $this->cm->instance) {
+ if ($maincm = get_coursemodule_from_instance('glossary', $this->entry->glossaryid)) {
+ $context = get_context_instance(CONTEXT_MODULE, $maincm->id);
+ }
+ }
+ $fs = get_file_storage();
+ $this->multifiles = $fs->get_area_files($context->id, 'glossary_attachment', $this->entry->id, "timemodified", false);
}
/**
* @return string
$entry = clone $this->entry;
glossary_print_entry($this->get('course'), $this->cm, $this->glossary, $entry, null, null, false);
$content = ob_get_clean();
- return $this->exporter->write_new_file($content, clean_filename($this->entry->concept) . '.html', false);
+ if ($this->multifiles) {
+ foreach ($this->multifiles as $file) {
+ $this->exporter->copy_existing_file($file);
+ }
+ }
+ return $this->exporter->write_new_file($content, clean_filename($this->entry->concept) . '.html', !empty($files));
}
/**
* @return string
*/
public function get_sha1() {
- return sha1(serialize($this->entry));
+ return sha1(serialize($this->entry) . $this->get_sha1_file());
+ }
+
+ public static function base_supported_formats() {
+ return array(PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML);
}
}
$postcontrol = optional_param('postcontrol', 0, PARAM_INT); // when returning from some bounce to an external system, this gets passed
$callbackfile = optional_param('callbackfile', null, PARAM_PATH); // callback file eg /mod/forum/lib.php - the location of the exporting content
$callbackclass = optional_param('callbackclass', null, PARAM_ALPHAEXT); // callback class eg forum_portfolio_caller - the class to handle the exporting content.
+$callerformats = optional_param('callerformats', null, PARAM_TAGLIST); // comma separated list of formats the specific place exporting content supports
require_login(); // this is selectively called again with $course later when we know for sure which one we're in.
$PAGE->set_url('/portfolio/add.php', array('id' => $dataid, 'sesskey' => sesskey()));
if ($cancelsure) {
$exporter->cancel_request($logreturn);
} else {
- $yesurl = $CFG->wwwroot . '/portfolio/add.php?id=' . $dataid . '&cancel=1&cancelsure=1&logreturn=' . $logreturn . '&sesskey=' . sesskey();
- $nourl = $CFG->wwwroot . '/portfolio/add.php?id=' . $dataid . '&sesskey=' . sesskey();
- if ($logreturn) {
- $nourl = $CFG->wwwroot . '/user/portfoliologs.php';
- }
$exporter->print_header('confirmcancel');
echo $OUTPUT->box_start();
- echo $OUTPUT->confirm(get_string('confirmcancel', 'portfolio'), $yesurl, $nourl);
+ $yesbutton = html_form::make_button($CFG->wwwroot . '/portfolio/add.php', array('id' => $dataid, 'cancel' => 1, 'cancelsure' => 1, 'logreturn' => $logreturn));
+ $nobutton = html_form::make_button($CFG->wwwroot . '/portfolio/add.php', array('id' => $dataid), get_string('no'));
+ if ($logreturn) {
+ $nobutton->url = $CFG->wwwroot . '/user/portfoliologs.php';
+ }
+ echo $OUTPUT->confirm(get_string('confirmcancel', 'portfolio'), $yesbutton, $nobutton);
echo $OUTPUT->box_end();
echo $OUTPUT->footer();
exit;
// we must be passed this from the caller, we cannot start a new export
// without knowing information about what part of moodle we come from.
if (empty($callbackfile) || empty($callbackclass)) {
+ debugging('no callback file or class');
portfolio_exporter::print_expired_export();
}
}
$caller = new $callbackclass($callbackargs);
$caller->set('user', $USER);
+ if ($formats = explode(',', $callerformats)) {
+ $caller->set_formats_from_button($formats);
+ }
$caller->load_data();
// this must check capabilities and either throw an exception or return false.
if (!$caller->check_permissions()) {
// in this case the exporter object and the caller object have been set up above
// so just make a little form to select the portfolio plugin instance,
// which is the last thing to do before starting the export.
- $mform = new portfolio_instance_select('', array('id' => $exporter->get('id'), 'caller' => $exporter->get('caller')));
+ //
+ // first check to make sure there is actually a point
+ $options = portfolio_instance_select(
+ portfolio_instances(),
+ $exporter->get('caller')->supported_formats(),
+ get_class($exporter->get('caller')),
+ $exporter->get('caller')->get('singlefile'),
+ 'instance',
+ true,
+ true
+ );
+ if (empty($options)) {
+ throw new portfolio_export_exception($exporter, 'noavailableplugins', 'portfolio');
+ } else if (count($options) == 1) {
+ // no point displaying a form, just redirect.
+ $instance = array_shift(array_keys($options));
+ redirect($CFG->wwwroot . '/portfolio/add.php?id= ' . $exporter->get('id') . '&instance=' . $instance . '&sesskey=' . sesskey());
+ }
+ $mform = new portfolio_instance_select('', array('id' => $exporter->get('id'), 'caller' => $exporter->get('caller'), 'options' => $options));
if ($mform->is_cancelled()) {
$exporter->cancel_request();
} else if ($fromform = $mform->get_data()){
public static function supported_formats() {
return array(PORTFOLIO_FORMAT_FILE);
+ // TODO remove above line once leap over mnet is tested
+ return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
}
public function expected_time($callertime) {
echo $OUTPUT->box_start();
-$queued = $DB->get_records('portfolio_tempdata', array('userid' => $USER->id), '', 'id, expirytime');
+$queued = $DB->get_records('portfolio_tempdata', array('userid' => $USER->id), 'expirytime DESC', 'id, expirytime');
if (count($queued) > 0) {
$table = new html_table();
$table->head = array(