portfolio: MDL-21030 - leap2a portfolio format support.

This commit includes:

- leap2a portfolio format, and xml writer
- proof of concept implementation in forum and assignment modules
- a lot of refactoring of the portfolio formats in general:
    - addition of "abstract" formats - this is necessary for plugins to be able to support groups of formats
    - addition of the idea of portfolio formats conflicting with eachother - eg richhtml & plainhtml

it touches modules other than assignment and forum, because the format api
changed and now each place in moodle that exports portfolio content has to deal
with the formats it supports slightly differently.

At the moment the Mahara portfolio still doesn't support this format, because I
haven't done the Mahara side yet. The "file download" plugin supports it
though.

Still todo:

- Add support for the other places in Moodle (glossary, data, etc)
- Write tests, once the rest of the portfolio tests have been updated to use the new DB mocking stuff
- Fix a bunch of TODOs
This commit is contained in:
Penny Leach 2009-12-03 14:26:37 +00:00
parent 9cbced1d2e
commit 59dd457e4b
26 changed files with 994 additions and 111 deletions

View file

@ -1821,7 +1821,6 @@ class assignment_base {
}
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();
}
}
@ -3395,10 +3394,7 @@ class assignment_portfolio_caller extends portfolio_module_caller_base {
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() {
@ -3406,6 +3402,26 @@ class assignment_portfolio_caller extends portfolio_module_caller_base {
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();
}
@ -3441,6 +3457,10 @@ class assignment_portfolio_caller extends portfolio_module_caller_base {
public static function display_name() {
return get_string('modulename', 'assignment');
}
public static function base_supported_formats() {
return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
}
}
/**

View file

@ -110,6 +110,7 @@ class assignment_online extends assignment_base {
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>';
@ -285,11 +286,19 @@ class assignment_online extends assignment_base {
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) {

View file

@ -368,7 +368,7 @@ class assignment_upload extends assignment_base {
}
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();
}
}

View file

@ -1272,7 +1272,7 @@ class chat_portfolio_caller extends portfolio_module_caller_base {
/**
* @return array
*/
public static function supported_formats() {
public static function base_supported_formats() {
return array(PORTFOLIO_FORMAT_PLAINHTML);
}
/**

View file

@ -101,7 +101,7 @@ if (array_key_exists('portfolio', $formdata) && !empty($formdata['portfolio']))
// 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);
}

View file

@ -33,6 +33,9 @@ class mod_data_export_form extends moodleform {
$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');
@ -56,10 +59,10 @@ class mod_data_export_form extends moodleform {
}
$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');

View file

@ -2915,7 +2915,6 @@ class data_portfolio_caller extends portfolio_module_caller_base {
$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';
@ -3104,7 +3103,6 @@ class data_portfolio_caller extends portfolio_module_caller_base {
* @param array $fields
* @param object $record
* @uses PORTFOLIO_FORMAT_PLAINHTML
* @uses PORTFOLIO_FORMAT_FILE
* @uses PORTFOLIO_FORMAT_RICHHTML
* @return array
*/
@ -3119,10 +3117,14 @@ class data_portfolio_caller extends portfolio_module_caller_base {
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) {

View file

@ -3508,7 +3508,7 @@ function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=fa
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);
@ -7978,7 +7978,8 @@ class forum_portfolio_caller extends portfolio_module_caller_base {
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
*/
@ -8033,11 +8034,11 @@ class forum_portfolio_caller extends portfolio_module_caller_base {
$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();
@ -8050,14 +8051,17 @@ class forum_portfolio_caller extends portfolio_module_caller_base {
$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
@ -8082,35 +8086,103 @@ class forum_portfolio_caller extends portfolio_module_caller_base {
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);
return $this->get('exporter')->write_new_file($content, 'discussion.html', $manifest);
$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);
$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
}
$this->copy_files($this->multifiles, $this->attachment);
$post = $this->prepare_post($this->post);
$this->get('exporter')->write_new_file($post, 'post.html', $manifest);
}
/**
* 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);
}
}
$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) {
@ -8119,7 +8191,7 @@ class forum_portfolio_caller extends portfolio_module_caller_base {
}
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) {
@ -8134,7 +8206,7 @@ class forum_portfolio_caller extends portfolio_module_caller_base {
* @param int $post
* @return string
*/
private function prepare_post($post) {
private function prepare_post($post, $fileoutputextras=null) {
global $DB;
static $users;
if (empty($users)) {
@ -8175,12 +8247,12 @@ class forum_portfolio_caller extends portfolio_module_caller_base {
$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>";
}
@ -8238,6 +8310,10 @@ class forum_portfolio_caller extends portfolio_module_caller_base {
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);
}
}
/**

View file

@ -73,7 +73,7 @@ echo $OUTPUT->box_start('glossarydisplay generalbox');
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();

View file

@ -985,6 +985,18 @@ function glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode='',$h
&& 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 .= "&nbsp;&nbsp;"; // just to make up a little the output in Mozilla ;)
@ -2666,7 +2678,7 @@ function glossary_supports($feature) {
* @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;
@ -2736,8 +2748,10 @@ class glossary_csv_portfolio_caller extends portfolio_module_caller_base {
$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
@ -2751,6 +2765,10 @@ class glossary_csv_portfolio_caller extends portfolio_module_caller_base {
public static function display_name() {
return get_string('modulename', 'glossary');
}
public static function base_supported_formats() {
return array(PORTFOLIO_FORMAT_FILE);
}
}
/**
@ -2758,7 +2776,7 @@ class glossary_csv_portfolio_caller extends portfolio_module_caller_base {
* @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;
@ -2790,7 +2808,14 @@ class glossary_entry_portfolio_caller extends portfolio_module_caller_base {
// 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
@ -2821,13 +2846,22 @@ class glossary_entry_portfolio_caller extends portfolio_module_caller_base {
$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);
}
}