Adding a Portfolio Button to a page

Revision as of 13:35, 20 August 2008 by Penny Leach (talk | contribs) (supported_formats (static))

Jump to: navigation, search

Introduction

Adding an 'Add to Portfolio' button to any page is relatively trivial, there are just two things that you need to do:

Write a subclass

You can either subclass portfolio_caller_base for the general case, or portfolio_module_caller_base if you're somewhere inside mod/

This sounds scary, but really it's not! It's a very small class. portfolio_caller_base has abstract functions that you must override, and some functions that you can override if you want to do something special. You should call it something like $module_portfolio_caller, or in a more complicated case (say assignment/type/upload, assignment_upload_portfolio_caller)

If you're adding the portfolio button somewhere in a module, it's better to subclass portfolio_module_caller_base, which implements 2 of the below abstract methods for you.

Methods you must override

__construct

When your object is constructed, the contents of whatever callback arguments you passed to portfolio_add_button are passed back to you here in an array, so your chance to set member variables or do whatever you need is in the constructor.

get_navigation

During the export screens, it's desirable to still have some sensible navigation that logically follows from the place the user was before they started the export process. This function should return components to pass to build_nagivation (extralinks and cm). portfolio_module_caller_base implements this for you

prepare_package

prepares the package up before control is passed to the portfolio plugin. You should copy any files (or write out any files) into the temporary directory provided, where they'll be found by the portfolio plugin

See also Adding_a_Portfolio_Button_to_a_page#setting portfolio internal

expected_time

You should return a constant here to indicate how long the transfer is expected to take. This should be based on the size of the file. There are three options, PORTFOLIO_TIME_LOW, PORTFOLIO_TIME_MODERATE, and PORTFOLIO_TIME_HIGH. The first means the user will not be asked if they want to wait for the transfer or not, they will just wait. The second and third mean they'll be given the option (and in the case of the third, advised not to). The portfolio plugin can override this if it wants (eg in the case of download, they always want to wait for the transfer)

check_permissions

portfolio/add.php will expect the caller to verify the user is allowed to export the given content. This function should perform any has_capability checks it needs to and return a booelan.

get_return_url

This is used for redirecting the user in the case of a cancelled export, or at the end of their export, they are offered the option of continuing back to where they were (what this function returns) or on to their portfolio. portfolio_module_caller_base implements this for you (but will use mod/modname/view.php)

display_name (static)

A nice language string for displaying the location of this export to the user (this is used to notify the user in case of duplicate exports that originated from different places in moodle (Eg exporting an assignment upload and a forum post attachment that are the same file)

get_sha1

Return a sha1 of the content being exported - used to detect duplicate exports later.

Methods you can override

supported_formats (static)

The formats this caller can support. At export time, both the plugin and the caller are polled for which formats they can support, and then the intersection is used to determine the export format. In the case that the intersection is greater than 1, the user is asked for their selection.

The available formats you can choose from are in portfolio_supported_formats and are constants PORTFOLIO_FORMAT_XXX. By default, the subclass defines PORTFOLIO_FORMAT_FILE.

This function is static, but if the caller object has been instantiated it is passed as an argument.

You can also set $this->supportedformats in the constructor if you want to change it at constructor time and the implementation in the base class will detect that case. Currently, there is an example of this in the forum code - attachments that are images are handled in this way.

has_export_config

If there's any addition config during the export process (for example, extra metadata), you can override this function to return true. If you do this, you must also override export_config_form and get_export_summary.

export_config_form

This function is called, and passed a moodle form object by reference to add elements to it.

export_config_validation

This follows the exact same format as the validation() function in the moodleform object.

get_allowed_export_config

If at any point, your caller is going to use set_export_config, you must implement this function to return an array of allowed config fields. (Note that you can set export time config even if you're not using interactive user config)

get_export_summary

If your plugin has overridden has_export_config, you must implement this to display nicely to the user on the confirmation screen. It should return a named array (keys are nice strings to describe the config, values are the config options)

Call portfolio_add_button in the appropriate place

Now that you've implemented this class, you just need to add the button. To do this, you require_once("$CFG->libdir/portfoliolib.php"); and call portfolio_add_button. It takes the following parameters:

$callbackclass

The name of the class you've made that subclassed portfolio_caller_base.

$callbackargs

An associative array of key=>value pairs you want passed to the constructor of your class. These must be primitives, as they are added as hidden form fields and cleaned to either PARAM_ALPHAEXT, PARAM_NUMERIC or PARAM_PATH. Weird stuff will happen if they're not compliant.

$callbackfile

This can be autodetected from the backtrace of where this function was called, but if your class definition isn't in the same file as the caller (eg if your caller is some .php script, but the class is in a lib.php file), you can pass it explicitly here.

$format

Whether you want a button, icon or link, and whether you want the dropdown menu of available plugins, and whether you want form tags. Options are PORTFOLIO_ADD_FULL_FORM (full form, with dropdown menu and submit button), PORTFOLIO_ADD_ICON_FORM (form with dropdown but icon rather than button), PORTFOLIO_ADD_ICON_LINK (just the icon, no form and no drop menu) and PORTFOLIO_ADD_TEXT_LINK (just a text link)

The last two (no dropdown) will introduce a whole new screen in the wizard first for the user to select the active portfolio plugin.

This argument is optional and defaults to PORTFOLIO_ADD_FULL_FORM.

$addstr

The string to use on the button, link and alt text of the icon. Optional and defaults to 'Add to Portfolio'

$return

Whether you want the output returned or echoed. Defaults to false (echo)

Other ways to integrate

If it's undesirable to add a form, there are a couple of things you can do. For an example, see the export tab of the 'data' module, which has already an export form, but just adds an option to export to portfolio rather than file:

require_once($CFG->libdir . '/portfoliolib.php');
if (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')),
          'data_portfolio_caller', '', true, true)) {
    $mform->addElement('header', 'notice', get_string('portfolionotfile', 'data') . ':');
    $portfoliooptions[0] = get_string('none');
    ksort($portfoliooptions);
    $mform->addElement('select', 'portfolio', 
       get_string('portfolio', 'portfolio'), $portfoliooptions);
  }
}

This code adds a select option to the existing export form containing the available portfolio instances.

Then in the form handler:

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);
  redirect($url);
}

The portfolio_fake_add_url function returns the url that you need to redirect to, that would normally be the result of portfolio_add_button form being submitted.

A few extra notes on the caller base class

protected $course

There is a protected member variable, $course, that subclasses can set (with $this->set('course', $course); ).

portfolio_add_button tries to look for a course object at the point that it's called, by doing a global $COURSE which is hackish but mostly works. This is also used to build the navigation during the export process. If for some reason, your navigation doesn't include the current course, you can set it like this. You shouldn't need to in the majority of cases.

serialization

The caller object is stored in the database in serialized form. The portfolio code works around this by loading the class definitions and then serializing and unserializing the objects again. However, if you're storing any real objects in your caller class, you will need to do this as well. See the assignment implementation for how this done (using php5's __wakeup function):

public function __wakeup() {
    require_once($this->assignmentfile);
    $this->assignment = unserialize(serialize($this->assignment));
}

setting portfolio internal

Sometimes during prepare_package, you need to call functions in the libraries to render content as HTML that would normally also call portfolio_add_button. This will result in 'you already have an export active in this session' error - to get around this, do something like

define('PORTFOLIO_INTERNAL', true);
modulename_print_some_htmlcontent();

Error handling

Your code should always throw exceptions of type portfolio_caller_exception. You should NOT call error or print_error or whatever, and also any third party exceptions should really be caught and rethrown although most of the time other exceptions will be caught too by the portfolio code (although this will trigger a warning in debug mode)