Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Portfolio API: Difference between revisions

From MoodleDocs
m (Add warning to the top of the page)
 
(40 intermediate revisions by 8 users not shown)
Line 1: Line 1:
This page describes the specification for a future feature, currently being worked on for [[Roadmap|Moodle 2.0]]. This spec is STILL UNDER CONSTRUCTION.
{{Warning|This page describes the original specification for the portfolios feature. The actual implementation currently existing in Moodle may differ from this specification. When adding support for portfolios to your plugins, you can find the page [[Adding a Portfolio Button to a page]] useful.}}
{{Moodle_2.0}}


==Overview==
==Overview==
Line 27: Line 28:
===Plugins and libraries===
===Plugins and libraries===


There will be two separate types of plugins  
There will be one type of plugins  


# Portfolio (eg Mahara/Elgg/OSP/Facebook/Download) - this will be portfolio/type/xxx
# Portfolio (eg Mahara/Elgg/OSP/Facebook/Download) - this will be portfolio/type/xxx
# Transport (eg mnet/http/scp/cp/dav etc) - these will be reused across different portfolio plugins


Then there will be different formats that plugins will support (and the part of moodle exporting content must support as well), eg IMS, moodle native, mahara native, pdf, enrypted pdf. These will have good libraries supporting them.
The transport layer (eg mnet/http/scp/cp/dav etc) or clients (eg box.net/flickr) will be written as libraries, to be shared by both repository and portfolio.


The reason for this is that I can see for abstracting the transport layer to its own plugin type is that it's pretty likely that the same external system might talk both portfolio and repository.
Then there will be different formats that plugins will support (and the part of moodle exporting content must support as well), eg IMS, moodle native, mahara native, pdf, encrypted pdf. These will have good libraries supporting them.


===Admin===
===Admin===
Line 49: Line 49:
===Exporting===
===Exporting===


*User is viewing a page that calls portfolio_add_button().  This checks to see if there are any configured portfolio plugin instances, and also (maybe) any permissions related to portfolios, and what the user's portfolio settings are, and then displays either a single 'add to portfolio' button, or a drop down menu of the available systems with the add button.
*User is viewing a page that calls new portfolio_add_button().  This checks to see if there are any configured portfolio plugin instances, and also (maybe) any permissions related to portfolios, and what the user's portfolio settings are, and then displays either a single 'add to portfolio' button, or a drop down menu of the available systems with the add button.
*When this button is pressed, the user is redirected to portfolio/add.php, with some post data containing the responsible area (activity module or something like course or blog) callback file and function.
*When this button is pressed, the user is redirected to portfolio/add.php, with some post data containing the responsible area (activity module or something like course or blog) callback file and callback arguments, as well as optionally some information about what type of content it is.
*On this page, the user is presented with a form to enter metadata about the item, and configure any options. At this point if there are multiple formats available for export (based on the intersection of what the plugin and module support), the user can select which format they want.  The plugin and module can both export mform elements for this page.  The user can at this point also select to send the data and wait (with a warning it might take awhile), or queue it for processing if it's larger.
*On this page, the user is presented with a form to enter metadata about the item, and configure any options. At this point if there are multiple formats available for export (based on the intersection of what the plugin and module support), the user can select which format they want.  The plugin and module can both export mform elements for this page.  The user can at this point also select to send the data and wait (with a warning it might take awhile), or queue it for processing if it's larger.  This is determined by the size of the content to be exported.
*When the user has submitted the form, they are displayed a summary of what they're about to export, with 'confirm' and 'edit' buttons.  Edit just relaunches the form, and confirm goes to the next step.
*When the user has submitted the form, they are displayed a summary of what they're about to export, with 'confirm' and 'cancel' buttons.  Cancel cancels the request, and cleans up any temporary data, and returns the user to where they came from, while confirm goes to the next step.
*At this point, the portfolio plugin (or transport plugin) might need to take control for a step. For example, facebook or flickr might require the user to log in for the first time and confirm moodle is allowed to access their API
*At any point, the portfolio plugin might need to take control for a step. For example, facebook or flickr might require the user to log in for the first time and confirm moodle is allowed to access their API.
*When the user has confirmed their summary, a 'portfolio_send' event will be triggered.  At this point, one of two things happen.   
*When the user has confirmed their summary, a 'portfolio_send' event will be triggered.  At this point, one of two things happen.   
# If the user has elected to wait, the 'instant' event is fired, and when the caller gets control again, it displays the status to the user.
# If the user has elected to wait, the 'instant' event is fired, and when the caller gets control again, it displays the status to the user.
# If the user has elected to queue, the delayed event is fired and the user is notified.
# If the user has elected to queue, the delayed event is fired and the user is notified.
*The user is given the option to continue to their portfolio, or return to where they were
*The user is given the option to continue to their portfolio, or return to where they were
*
*When the event is handled (either through the cron or instant event), the following happens:
*When the event is handled (either through the cron or instant event), the following happens:
*The event handler is invoked. This is almost certainly going to be a function in lib/portfoliolib.php rather than any handler in the portfolio plugins.  The event handler in turn calls the original callback that was passed to portfolio/add.php (module//lib.php or course/lib.php or something), which prepares the data in the format the user selected and returns control to the event handler.
*The event handler is invoked. This reawakens the transfer and defers control to the caller and then the portfolio to prepare and send the package.
*At this point we have all the metadata we need, and we have the content to send, so it's time to start transport negotiation, which happens in stages. Note that some plugins might not implement all stages, and 3 and 4 might be interchangeable in order, depending on the external system.
*When this is complete, we return control to the event handler (which, if it's an 'instant' one, will return true to the caller.
#request
#authentication
#content
#metadata (some control is passed to the portfolio plugin for packaging - either to just write out an xml file and zip up the whole package, or send a series of requests)
#completion
*When this is complete, we return control to the event handler (which, if it's an 'instant' one, will return true to the caller, which will be the ajax request)


===Storage===
===Storage===


Obviously during this process, state is going to be lost between webserver requests and also between user input and event handling. Some data will be stored in the user's session (the result of the metadata form, for example) and some in temporary files on the file system.  these will be stored in dataroot/temp/portfolio/$userid-$timestamp/
Obviously during this process, state is going to be lost between webserver requests and also between user input and event handling. All of the data is stored in the database, in the form of a serialized (and base64 encoded) representation of the exporter, plugin and caller objects.
 
Files are also going to be written during the preparation stage of the export, and these are stored in a special portfolio area using the new files api.


===Access/Permissions===
===Access/Permissions===
* Portfolio plugin must be allocatable per context (site/course/group etc)
* The calling code is responsible for performing the permission checks necessary before asking to display any button, but during the export the portfolio code will call a check_permissions function on the caller object.
* How can we assume all users will have an account on the remote system? We could try and silently authenticate them (or at least see if it's possible to) in the background at the first call to portfolio_add_button (and then cache the result in the session)
* I would really like to be able to make some portfolio instances available to some roles but this has fallen out of scope.
* Check MNet policy - it assumes everyone with the role can have an account (assuming they_sso_in or whatever it's called is on) - this doesn't scale though, other transport mechanisms and or plugin types might not follow this assumption.  I think this really must be defined by the portfolio plugin only, not the transport plugin.
 


===Event API===
===Event API===


It is probably worth using the event api to handle the sending of data, mostly just because it provides a handy way of detached processing.   However, any portfolio_send event will be 'owned' by particular "instances" of plugins - and each plugin that subscribes to portfolio_send event (all of them by definition) will have to check if the event data belongs to any of their instances, which is potentially quite messy.
The portfolio code uses the event api to handle queued events and there is one entry point for this that reawakens the transfer objects and resumes the transfer. Additionally, portfolio plugins can subscribe to events like any other part of moodle.
 
 




==Technical==
==Technical==


===abstract portfolio base class: portfolio_plugin_base===
===Abstract Portfolio Baseclass: portfolio_plugin_base===


Mixes providing some basic functionality by means of its own functions, with a number of abstract functions plugins must implement, and with some functions that plugins can also optionally override.
Mixes providing some basic functionality by means of its own functions, with a number of abstract functions plugins must implement, and with some functions that plugins can also optionally override.
Line 93: Line 86:
See also: [[Writing_a_Portfolio_Plugin]] for a full list of all methods you must/can/shouldn't override, as well as associated instructions for what else you need to do to create a new portfolio plugin
See also: [[Writing_a_Portfolio_Plugin]] for a full list of all methods you must/can/shouldn't override, as well as associated instructions for what else you need to do to create a new portfolio plugin


===abstract caller base class : portfolio_caller_base===
===Abstract Caller Baseclass : portfolio_caller_base===


Whenever somewhere in Moodle wants an 'add to portfolio' button, they must subclass this.
Whenever somewhere in Moodle wants an 'add to portfolio' button, they must subclass this.
Line 101: Line 94:
===Database Tables===
===Database Tables===


The actual information about plugins that are installed is just stored in mdl_config.
The actual information about plugins that are installed is just stored in mdl_config_plugin.


Additionally, as we're configuring instances of plugins, rather than just one config set per plugin, we're not using mdl_config_plugin, but instead our own set of tables:
Additionally, as we're configuring instances of plugins, rather than just one config set per plugin, we're not using mdl_config_plugin, but instead our own set of tables:


====portfolio_instance:====
'''portfolio_instance:'''
 
{| border="1" cellpadding="2" cellspacing="0"
{| border="1" cellpadding="2" cellspacing="0"
|-
|-
Line 129: Line 123:
|}
|}


====portfolio_instance_config:====
 
'''portfolio_instance_config:'''
 
{| border="1" cellpadding="2" cellspacing="0"
{| border="1" cellpadding="2" cellspacing="0"
|-
|-
Line 144: Line 140:
|(pseudo)fk to portfolio_instance
|(pseudo)fk to portfolio_instance
|-
|-
It cannot, however, be responsible for how external systems deal with this case. The different plugins can do what they can. For example, mahara will create new files rather than overwrite. The box.net plugin will try very hard to rename files to avoid collisions.
|name
|name
|varchar(255)
|varchar(255)
Line 153: Line 150:
|}
|}


====portfolio_instance_user:====
 
'''portfolio_instance_user:'''
 
{| border="1" cellpadding="2" cellspacing="0"
{| border="1" cellpadding="2" cellspacing="0"
|-
|-
Line 181: Line 180:
|}
|}


===portfolio plugins===
'''portfolio_log:'''
 
{| border="1" cellpadding="2" cellspacing="0"
|-
|'''Field'''
|'''Datatype'''
|'''Comment'''
|-
|id
|integer
|sequence
|-
|userid
|integer
|(pseudo) fk to mdl_user
|-
|time
|integer
|unix timestamp of transfer
|-
|portfolio
|integer
|(pseudo) fk to mdl_portfolio_instance
|-
|caller_class
|varchar(150)
|name of caller class (used in the case of duplicates to display information)
|-
|caller_file
|varchar(255)
|file that contains the definition of caller_class
|-
|caller_sha1
|varchar(255)
|sha1 information of export
|}
 
'''portfolio_tempdata'''
 
{| border="1" cellpadding="2" cellspacing="0"
|-
|'''Field'''
|'''Datatype'''
|'''Comment'''
|-
|id
|integer
|sequence
|-
|data
|text
|serialized representation of export data
|-
|expirytime
|integer
|time this data (and the transfer) expires (and the record (and associated files)) will be deleted
|-
|userid
|integer
|psuedo fk to mdl_user
|}
 
All plugins can also implement their own database tables as needed, by creating a db/install.xml and db/upgrade.php inside portfolio/type/xxx/ (See [[Writing_a_Portfolio_Plugin]]) for more information.
 
===Portfolio Plugins===


#mahara (will be done for the initial implementation)
#[[Mahara_Portfolio_Plugin|mahara]] (will be done for the initial implementation)
#download (should be done for the initial implementation)  
#download (will be done for the initial implementation)
#box.net (will be done for the initial implementation)
#flickr (Nico has been writing this but it is incomplete)
#googledocs (I think DanP has been writing this)


transport types and formats should be able to be found in a shared location for multiple plugins of both portfolio and repository type to use, but also might be specific to one type of plugin which means that moodle should support looking in multiple locations for these plugins. (eg mahara native format will be in the mahara portfolio plugin, but pdf format will be in a shared library)
transport types and formats should be able to be found in a shared location for multiple plugins of both portfolio and repository type to use, but also might be specific to one type of plugin which means that moodle should support looking in multiple locations for these plugins. (eg mahara native format would be in the mahara portfolio plugin, but pdf format will be in a shared library)


===transport types===
===Possible Transport Types===


# mnet (will be done for the initial implementation)
# mnet (will be done for the initial implementation as part of the Mahara Portfolio Plugin)
# download (just uses send_file_* functions)
# http
# http
# filesystem (could be local/nfs/samba whatever) (cp)
# filesystem (could be local/nfs/samba whatever) (cp)
Line 197: Line 264:
# open social? (http://code.google.com/apis/opensocial/)
# open social? (http://code.google.com/apis/opensocial/)


===formats===
===Possible Export Formats===
 
(Note that we don't necessarily want more than one of these for the initial implementation)


(note that we don't necessarily want more than one of these for the initial implementation)
* implemented now:
# html
# image
# video
# plaintext
# 'file' (fallback)


* possibly implemented in the future
# pdf
# pdf
# encrypted pdf
# encrypted pdf
# ims?
# ims?
# leap/piop? (http://wiki.cetis.ac.uk/LEAP_2.0)
# [[LEAP2A_Portfolio_Implementation|LEAP2A]]
# moodle native?
# moodle native?
# mahara native?
# mahara native?
# Dublin Core (Enovation implemented this)
# Dublin Core (Enovation implemented this)
 
===Testing===
===Testing===


Nico to help (see Development Approach for TDD-related approach)
At this stage, the portfoliolib and button objects have tests, and the callers have tests to check whether their sha1 generation remains consistent appropriately.  This includes the implicit testing of constructing the caller objects (which verifies the callback arguments).
 
The plugins are not currently tested and even if they were we would not be able to test interaction with the remote system.
 
==MNET==
 
This section has moved to [[MNET_Roadmap]]
 
See also [[MNET_API]] for the documentation of xmlrpc functions
 
==Duplication of Data==
 
Moodle will keep track of what content it transfers and when.  It keeps a sha1 has of the data, so that if the user tries to export the same content, Moodle can warn the user.
 
 
It cannot, however, be responsible for how external systems deal with this case. The different plugins can do what they can.  For example, mahara will create new files rather than overwrite.  The box.net plugin will try very hard to rename files to avoid collisions.
 
==Save points in Moodle==
 
moved to http://tracker.moodle.org/browse/MDL-15758 during development
 
==Still TODO==
 
There are a few things still I have not been able to complete for various reasons (generally reliance on other parts of the system, eg Files API).  There are bugs for all of these, but reproduced here for completeness:
 
* MDL-16406 - waiting on QA (Jerome)
* MDL-16048 - waiting on QA (Nico)
* MDL-16313 - this just didn't get far enough up my list and I'm still not sure how relevant it is.
* MDL-15777 - reliance on Files API - data fields that subclass data_field_file need to be extracted and copied separately into the export area using copy_existing_file - this is essentially done but I still think picture should subclass file. More info in MDL-16493
* MDL-15777 - reliance on Files API - data module can only export as CSV even though plain export also supports ods/xls as those two libraries are not updated to the new Files API.  More info in MDL-15911
* MDL-16326 - reliance on Files API - 'file' resource module has not been updated to use Files API, so exporting from this type is not yet implemented (currently HTML and plaintext only)
* MDL-16175 - (currently) unreasonable reliance on exceptions.  Especially for queued events, currently if a portfolio transfer is woken up at cron to be completed and an error happens in mnet (eg one remote site is down or misconfigured), the entire cronjob will die as mnet functions call print_error, which calls die(). This essentially means cron will stay broken (for all of moodle) until that transfer expires.  This is not really a bug in portfolio code, but it definitely exacerbates an already brittle situation.
 
===Unit Test TODO===
 
Currently there's quite a few tests implemented, but outstanding are tests that rely on the generator to create files for the callers.  As the generator gets updated to create this data, the portfolio unit tests will start throwing exceptions in the portfolio_exporter_text->copy_existing_file method so that it will become obvious when this needs to be updated as the tests will start failing (they are currently passing)
 
==Current exhaustive list of export scenarios==
 
===mod/assignment===
 
====upload single file====
 
This is pretty straightforward.  It should display the export icon next to the single file (no large form/button), and the export should respect the mime-based subtypes (_IMAGE, _VIDEO etc)
 
====upload multiple files====
 
This one is a little more complex. You should get an export icon next to individual files, and, additionally, if there is more than one, an export form at the bottom (which will export all files).  Exporting multiple files will always stop mime detection and fallback to _FILE format.
 
====online text====
 
Displays the full form at the bottom of the page. Should export as format _HTML.
 
===mod/chat===
 
These should all export as format _HTML, and contain no references back to Moodle (eg user profile images, which won't be able to be seen necessarily)
 
====Session page====
 
Should export entire session.
 
====Report page per session====
 
Should export entire session.
 
====Report page all sessions====
 
Should concatenate all sessions together.
 
===mod/data===
 
====Single entry export====
 
Should export as HTML.  Any files should be included along with the html.  If there is only one field in the entry and it is a file or image, the mimetype should be respected, and the export format should be based on that (eg _IMAGE)
 
====Whole database instance export====
 
Should export as CSV.  Files are not included (This is the same as the other CSV export)
 
===mod/forum===
 
====Whole discussion====


===Interaction between parts of the system===
The export format is FILE, attachments come alongside discussion.html - this could be improved later to be HTML if there are no attachments.


There needs to be a nice way for the different parts (portfolio libraries and scripts, portfolio plugins, and calling modules) to interact.
====Single post====


As I start writing code, I'm starting to think that modules (or indeeed any place in moodle) that wants to implement the ability to export its data to a portfolio should implement a class to handle it. I originally just started thinking that a callback function was enough, but now it has turned into possible multiple callback functions, and imo, the best way to manage this is that rather than just document the list of available callback functions, we should actually enforce this at the *code* level, by the use of abstract functions in a parent class (or even an interface actually) for the module authors to implement.  This may seem heavyweight, but really it would only be a small class with one or two functions in it.  Additionally it allows for namespacing of portfolio related functions in the module.
The export format is _HTML.


Also it makes a nice way to store state between requests - I can just store objects in the session.
====Single post with attachments====


==MNET==
The export format is FILE as it's mixed, and attachments come alongside post.html.
 
====Single attachment====
 
Mimetypes should be respected and the export format should be based on them (eg _IMAGE)
 
===mod/glossary===
 
====Single entry export====


Currently mnet has the concept of 'applications', but it doesn't know anything about what sort of services different applications provide.  Mnet knows about Mahara, and it doesn't know that Mahara can't handle incoming enrolments, for example.
Should export the entry as HTML.
For the portfolio API, it would be good to be able to have an extra 'service' under the mnet config, called portfolio, which we can subscribe to (note this is also useful for repos - which probably want publish AND subscribe).


I propose we add a new database table, mnet_application_service, which joins mnet_application and mnet_service and has publishable and subscribable columns, and change the UI to be aware of it.
====Whole glossary export====


==Duplication of data==
Should export the glossary as CSV - similar to a current glossary export.
Moodle will keep track of what content it transfers and when.  It'll remember the url of the original export location (alphabetisising the url parameters and removing the sessionkey) and a sha1 hash of the data, so that if the user tries to export the same content, Moodle can warn the user (along with a notification of whether it has changed or not).


At this point we can build support into the API for plugins to offer to differentiate between 'replace' and 'add', but won't implement it for any we write for the initial implementation.
===mod/resource===


==Assumptions==
====HTML resource====


* using the event api
Should export as _HTML.
* we want multiple instances (both to configure and to send to at the same time)


==Outstanding questions==
====text resource====
*which parts of moodle will have this button to start with (in order of necessity)
*Dependance of transport plugins - eg filesystem might depend on http for sending request/finished pings


==Development Approach==
Should export as _TEXT.
* create three base classes
* create mahara portfolio plugin
* create mnet transport plugin
* create dummy content plugin (until we decided on a format)
* create portfolio/add.php and portfolio/lib.php
* start adding calls to the portfolio_add_button function to various places in moodle, with their callbacks.
* create a dummy listener in mahara until the mahara side is specified


After a conversation with Nico about testing, we decided the most sensible ordering to start with would be:
====file resource====


# Write portfolio/add.php
Not implemented yet. Blocked by FILES API. Should respect the mimetype of the file and export in the appropriate format.
# Write *skeleton functions* to support it in portfolio/lib.php and the class structure
# Write tests for those methods that are necessary
# Implement the functions to make the tests pass
# Keep adding module implementations


==See also==
==See also==
* [[Core APIs]]
* [[Repository API]]
* [[Repository API]]
* [[File API]]
* [[File API]]
* MDL-14591 - Portfolio API Meta issue
[[Category:Portfolios]]
[[Category:API]]
[[ja: 開発:ポートフォリオAPI]]

Latest revision as of 09:57, 6 October 2016

Warning: This page describes the original specification for the portfolios feature. The actual implementation currently existing in Moodle may differ from this specification. When adding support for portfolios to your plugins, you can find the page Adding a Portfolio Button to a page useful.


Moodle 2.0


Overview

The Portfolio API is a core set of interfaces that all Moodle code will/should use so that we can easily publish files to all kinds of external document repository systems.

It's important to remember that portfolios are generally treated as WRITE-ONLY. All we are doing in Moodle is grabbing stuff and pushing it out to somewhere. Management of the files and further combining/reflecting is done through the native interface provided by the portfolio system. Reading of files from a repository is handled by the Repository API.

A typical user story:

  1. When portfolios are enabled, every page or major piece of content in Moodle has a little "Save" button beside it.
  2. User clicks one of these buttons
  3. User is able to choose from a list of configured portfolios (this step will be skipped if there's only one).
  4. User may be asked to define the format of the captured content (eg pdf, IMS LD, HTML, XML ...)
  5. User may be asked to define some metadata to go with the captured content (some will be generated automatically).
  6. The content and metadata is COPIED to the external portfolio system
  7. User has an option to "Return to the page you left" or "Visit their portfolio".

Note this will be just as useful for teachers as for students.

The formatting possibilities will vary depending on the context of the button and the type of external portfolios. So for example, the "Save" button on the course page would allow the user to capture the whole course in IMS LD or Moodle backup format, which you would not have on a forum page.

Architecture

Here is how it will work:

Plugins and libraries

There will be one type of plugins

  1. Portfolio (eg Mahara/Elgg/OSP/Facebook/Download) - this will be portfolio/type/xxx

The transport layer (eg mnet/http/scp/cp/dav etc) or clients (eg box.net/flickr) will be written as libraries, to be shared by both repository and portfolio.

Then there will be different formats that plugins will support (and the part of moodle exporting content must support as well), eg IMS, moodle native, mahara native, pdf, encrypted pdf. These will have good libraries supporting them.

Admin

It is important to be allowed to have multiple instances of (some) plugins. The workflow for adding a new one is:

  • Admin navigates to portfolio config
  • Selects from the list of available portfolio plugins, and clicks 'add a new external portfolio' (some may be disabled if there is an instance already and the plugin doesn't support multiple instances)
  • Configure the plugin - select which transport and content types to use if there are multiple supported and installed, urls, authentication keys, etc.
  • Set permissions (maybe) - handled by roles.

This is not necessary for every type of portfolio, because many will just require the user to authenticate directly and if we do ever want to retain settings for each user we just use user preferences.

Exporting

  • User is viewing a page that calls new portfolio_add_button(). This checks to see if there are any configured portfolio plugin instances, and also (maybe) any permissions related to portfolios, and what the user's portfolio settings are, and then displays either a single 'add to portfolio' button, or a drop down menu of the available systems with the add button.
  • When this button is pressed, the user is redirected to portfolio/add.php, with some post data containing the responsible area (activity module or something like course or blog) callback file and callback arguments, as well as optionally some information about what type of content it is.
  • On this page, the user is presented with a form to enter metadata about the item, and configure any options. At this point if there are multiple formats available for export (based on the intersection of what the plugin and module support), the user can select which format they want. The plugin and module can both export mform elements for this page. The user can at this point also select to send the data and wait (with a warning it might take awhile), or queue it for processing if it's larger. This is determined by the size of the content to be exported.
  • When the user has submitted the form, they are displayed a summary of what they're about to export, with 'confirm' and 'cancel' buttons. Cancel cancels the request, and cleans up any temporary data, and returns the user to where they came from, while confirm goes to the next step.
  • At any point, the portfolio plugin might need to take control for a step. For example, facebook or flickr might require the user to log in for the first time and confirm moodle is allowed to access their API.
  • When the user has confirmed their summary, a 'portfolio_send' event will be triggered. At this point, one of two things happen.
  1. If the user has elected to wait, the 'instant' event is fired, and when the caller gets control again, it displays the status to the user.
  2. If the user has elected to queue, the delayed event is fired and the user is notified.
  • The user is given the option to continue to their portfolio, or return to where they were
  • When the event is handled (either through the cron or instant event), the following happens:
  • The event handler is invoked. This reawakens the transfer and defers control to the caller and then the portfolio to prepare and send the package.
  • When this is complete, we return control to the event handler (which, if it's an 'instant' one, will return true to the caller.

Storage

Obviously during this process, state is going to be lost between webserver requests and also between user input and event handling. All of the data is stored in the database, in the form of a serialized (and base64 encoded) representation of the exporter, plugin and caller objects.

Files are also going to be written during the preparation stage of the export, and these are stored in a special portfolio area using the new files api.

Access/Permissions

  • The calling code is responsible for performing the permission checks necessary before asking to display any button, but during the export the portfolio code will call a check_permissions function on the caller object.
  • I would really like to be able to make some portfolio instances available to some roles but this has fallen out of scope.


Event API

The portfolio code uses the event api to handle queued events and there is one entry point for this that reawakens the transfer objects and resumes the transfer. Additionally, portfolio plugins can subscribe to events like any other part of moodle.


Technical

Abstract Portfolio Baseclass: portfolio_plugin_base

Mixes providing some basic functionality by means of its own functions, with a number of abstract functions plugins must implement, and with some functions that plugins can also optionally override.

See also: Writing_a_Portfolio_Plugin for a full list of all methods you must/can/shouldn't override, as well as associated instructions for what else you need to do to create a new portfolio plugin

Abstract Caller Baseclass : portfolio_caller_base

Whenever somewhere in Moodle wants an 'add to portfolio' button, they must subclass this.

See also: Adding_a_Portfolio_Button_to_a_page for a full list of all methods you must/can/shouldn't override as well as the associated instructions for how to call portfolio_add_button.

Database Tables

The actual information about plugins that are installed is just stored in mdl_config_plugin.

Additionally, as we're configuring instances of plugins, rather than just one config set per plugin, we're not using mdl_config_plugin, but instead our own set of tables:

portfolio_instance:

Field Datatype Comment
id integer sequence
plugin varchar(50) name of plugin (should match directory in portfolio/type)
name varchar(255) name of this plugin instance
visible smallint 0 or 1


portfolio_instance_config:

It cannot, however, be responsible for how external systems deal with this case. The different plugins can do what they can. For example, mahara will create new files rather than overwrite. The box.net plugin will try very hard to rename files to avoid collisions.
Field Datatype Comment
id integer sequence
instance integer (pseudo)fk to portfolio_instance
name varchar(255) config name
value text config value


portfolio_instance_user:

Field Datatype Comment
id integer sequence
instance integer (pseudo)fk to portfolio_instance
userid integer (pseudo)fk to mdl_user
name varchar(255) config name
value text config value

portfolio_log:

Field Datatype Comment
id integer sequence
userid integer (pseudo) fk to mdl_user
time integer unix timestamp of transfer
portfolio integer (pseudo) fk to mdl_portfolio_instance
caller_class varchar(150) name of caller class (used in the case of duplicates to display information)
caller_file varchar(255) file that contains the definition of caller_class
caller_sha1 varchar(255) sha1 information of export

portfolio_tempdata

Field Datatype Comment
id integer sequence
data text serialized representation of export data
expirytime integer time this data (and the transfer) expires (and the record (and associated files)) will be deleted
userid integer psuedo fk to mdl_user

All plugins can also implement their own database tables as needed, by creating a db/install.xml and db/upgrade.php inside portfolio/type/xxx/ (See Writing_a_Portfolio_Plugin) for more information.

Portfolio Plugins

  1. mahara (will be done for the initial implementation)
  2. download (will be done for the initial implementation)
  3. box.net (will be done for the initial implementation)
  4. flickr (Nico has been writing this but it is incomplete)
  5. googledocs (I think DanP has been writing this)

transport types and formats should be able to be found in a shared location for multiple plugins of both portfolio and repository type to use, but also might be specific to one type of plugin which means that moodle should support looking in multiple locations for these plugins. (eg mahara native format would be in the mahara portfolio plugin, but pdf format will be in a shared library)

Possible Transport Types

  1. mnet (will be done for the initial implementation as part of the Mahara Portfolio Plugin)
  2. download (just uses send_file_* functions)
  3. http
  4. filesystem (could be local/nfs/samba whatever) (cp)
  5. ssh based (eg scp - should find and re-use the elgg block code as it deals with using ssh keys nicely)
  6. webdav
  7. open social? (http://code.google.com/apis/opensocial/)

Possible Export Formats

(Note that we don't necessarily want more than one of these for the initial implementation)

  • implemented now:
  1. html
  2. image
  3. video
  4. plaintext
  5. 'file' (fallback)
  • possibly implemented in the future
  1. pdf
  2. encrypted pdf
  3. ims?
  4. LEAP2A
  5. moodle native?
  6. mahara native?
  7. Dublin Core (Enovation implemented this)

Testing

At this stage, the portfoliolib and button objects have tests, and the callers have tests to check whether their sha1 generation remains consistent appropriately. This includes the implicit testing of constructing the caller objects (which verifies the callback arguments).

The plugins are not currently tested and even if they were we would not be able to test interaction with the remote system.

MNET

This section has moved to MNET_Roadmap

See also MNET_API for the documentation of xmlrpc functions

Duplication of Data

Moodle will keep track of what content it transfers and when. It keeps a sha1 has of the data, so that if the user tries to export the same content, Moodle can warn the user.


It cannot, however, be responsible for how external systems deal with this case. The different plugins can do what they can. For example, mahara will create new files rather than overwrite. The box.net plugin will try very hard to rename files to avoid collisions.

Save points in Moodle

moved to http://tracker.moodle.org/browse/MDL-15758 during development

Still TODO

There are a few things still I have not been able to complete for various reasons (generally reliance on other parts of the system, eg Files API). There are bugs for all of these, but reproduced here for completeness:

  • MDL-16406 - waiting on QA (Jerome)
  • MDL-16048 - waiting on QA (Nico)
  • MDL-16313 - this just didn't get far enough up my list and I'm still not sure how relevant it is.
  • MDL-15777 - reliance on Files API - data fields that subclass data_field_file need to be extracted and copied separately into the export area using copy_existing_file - this is essentially done but I still think picture should subclass file. More info in MDL-16493
  • MDL-15777 - reliance on Files API - data module can only export as CSV even though plain export also supports ods/xls as those two libraries are not updated to the new Files API. More info in MDL-15911
  • MDL-16326 - reliance on Files API - 'file' resource module has not been updated to use Files API, so exporting from this type is not yet implemented (currently HTML and plaintext only)
  • MDL-16175 - (currently) unreasonable reliance on exceptions. Especially for queued events, currently if a portfolio transfer is woken up at cron to be completed and an error happens in mnet (eg one remote site is down or misconfigured), the entire cronjob will die as mnet functions call print_error, which calls die(). This essentially means cron will stay broken (for all of moodle) until that transfer expires. This is not really a bug in portfolio code, but it definitely exacerbates an already brittle situation.

Unit Test TODO

Currently there's quite a few tests implemented, but outstanding are tests that rely on the generator to create files for the callers. As the generator gets updated to create this data, the portfolio unit tests will start throwing exceptions in the portfolio_exporter_text->copy_existing_file method so that it will become obvious when this needs to be updated as the tests will start failing (they are currently passing)

Current exhaustive list of export scenarios

mod/assignment

upload single file

This is pretty straightforward. It should display the export icon next to the single file (no large form/button), and the export should respect the mime-based subtypes (_IMAGE, _VIDEO etc)

upload multiple files

This one is a little more complex. You should get an export icon next to individual files, and, additionally, if there is more than one, an export form at the bottom (which will export all files). Exporting multiple files will always stop mime detection and fallback to _FILE format.

online text

Displays the full form at the bottom of the page. Should export as format _HTML.

mod/chat

These should all export as format _HTML, and contain no references back to Moodle (eg user profile images, which won't be able to be seen necessarily)

Session page

Should export entire session.

Report page per session

Should export entire session.

Report page all sessions

Should concatenate all sessions together.

mod/data

Single entry export

Should export as HTML. Any files should be included along with the html. If there is only one field in the entry and it is a file or image, the mimetype should be respected, and the export format should be based on that (eg _IMAGE)

Whole database instance export

Should export as CSV. Files are not included (This is the same as the other CSV export)

mod/forum

Whole discussion

The export format is FILE, attachments come alongside discussion.html - this could be improved later to be HTML if there are no attachments.

Single post

The export format is _HTML.

Single post with attachments

The export format is FILE as it's mixed, and attachments come alongside post.html.

Single attachment

Mimetypes should be respected and the export format should be based on them (eg _IMAGE)

mod/glossary

Single entry export

Should export the entry as HTML.

Whole glossary export

Should export the glossary as CSV - similar to a current glossary export.

mod/resource

HTML resource

Should export as _HTML.

text resource

Should export as _TEXT.

file resource

Not implemented yet. Blocked by FILES API. Should respect the mimetype of the file and export in the appropriate format.

See also