XML manipulation of the EPrints Workflow using a Bazaar Package

From EPrints Documentation
Revision as of 14:53, 3 December 2010 by DaveTarrant (Talk | contribs)

Jump to: navigation, search

Manipulation of built in EPrint files, such as the Workflows, is currently performed using the postinst method which is run after the rest of package is installed.

Likewise when the package is removed, the prerm method needs to call exactly the opposite set of operations to clean itself up before full removal.

As we go along we'll see that if you don't cleanup behind you, you won't even be able to re-install your same package again as the installer will detect conflicting data already in existence.

Pre-Requisites

To edit EPrints XML we need an epm package containing a spec file which points to a plug-in where we are going to put out postinst and prerm methods.

Below is a simple framework for your plug-in (it's a Screen plug-in so needs to go in the cfg/plugins/EPrints/Plugin/Screen/Admin directory). Remember to name it applicably and match this name with the one in the spec file.

 package EPrints::Plugin::Screen::Admin::MYNAME;
 
 @ISA = ( 'EPrints::Plugin::Screen' );
  
 use strict;
 
 sub new
 {
       my( $class, %params ) = @_;
 
       my $self = $class->SUPER::new(%params);
 
       $self->{actions} = [qw/ postinst prerm /];
 
       return $self;
 } 
 
 # Can this screen be viewed?
 sub can_be_viewed
 {
       my( $self ) = @_;
 
       return 0;
 }
 
 # Do we have a prerm method which needs executing?
 sub allow_action_prerm
 {
       my ( $self ) = @_;
 
       return 1;
 }
 
 # Do we have a postinst method which needs executing?
 sub allow_action_postinst
 {
       my( $self ) = @_;
 
       return 1;
 }
 
 # The postinst routine
 sub action_postinst
 {
       my ( $self ) = @_;
 
       my $repository = $self->{repository}->get_repository();
       
       # Everthing went OK (why the hell did I code this the wrong way round?)
       return (0,undef);
       # Something went wrong
       # return (1,"MUPPET!!!")
 }
 
 # The prerm routine
 sub action_prerm
 {
       my ( $self ) = @_;
 
       my $repository = $self->{repository}->get_repository();
 
       return (0,undef);
 
 }
 
 # A render method for the screen, which we don't need unless we allow this screen to be viewed.
 # If this screen is visible you return an html fragment, or redirect somewhere else, like a config file :)
 sub render 
 {
       my ( $self ) = @_;
 
       return undef;
 }
 
 1;


Remember how to force remove a broken package

 perl bin/epm archive_name remove package_name --force

Exercise 1

In this exercise we are going to do some simple workflow editing exercises. These follow the standard EPrints training materials which teach you how to manually edit the workflow. Here we are going to do all the same stuff with API calls.

Adding a Divisions Stage to the Workflow

Basically we are simply editing XML so all we need to do is tell the repository which file to manipulate and then give it the stuff add to this file.

So to the postinst routine we need to add the following lines. Note that you need to manually specify your package_name

 my $filename = $repository->config( "config_path" )."/workflows/eprint/default.xml";
 
 # Gap for the stuff to be defined we need to add
 
 my $rc = 0;
 
 foreach my $child ( $node->getChildNodes() ) {
        $rc = EPrints::XML::add_to_xml( $filename,$child,"package_name" );
 }

At the same time then it is worth adding the equivalent remove lines to the prerm method. Again you need to specify your package name.

 my $filename = $repository->config( "config_path" )."/workflows/eprint/default.xml";
 
 EPrints::XML::remove_package_from_xml( $filename, "package_name" );

Note that the remove call does not return a state as it will be able to remove anything, but it won't error if there is nothing to remove. Thus all return states are OK (this may change if someone can think of an exception)

Lastly we need to actually tell the postinst method the block of XML to add.

 my $string = '
<xml>
       <flow>
               <stage ref="divisions"/>
       </flow>
       <stage name="divisions">
               <component type="Field::Subject">
                       <field ref="divisions" required="yes" />
               </component>
       </stage>
</xml>
';
 
 my $xml = EPrints::XML::parse_string( undef, $string );
 $xml = EPrints::XML::_remove_blank_nodes($xml);
 my $node = $xml->getFirstChild();

Here you can see that we can simply specify the XML as a string, then parse it. After it is parsed we then remove all the blank nodes to just leave the XML. All pretty printing is done by the API, not by you.

The XML represents the exact same tree as in the workflow XML file we are attempting to edit. You can view this by clicking the "View Configuration" button on the "Config. Tools" tab of the admin screen. Once here you will need to scroll down to find the file you specified (in this case /workflows/eprint/default.xml). Since it represents the same tree, only nodes which don't exist are added and marked as belonging to your package. This marker is how they are removed.

Alternatively you can build an XML tree using the EPrints::XML methods like make_element("flow") etc. At this point (unless you added blank nodes) you won't need the remove line, but it doesn't hurt to leave it in there.

Finally make sure you have all your variables like $rc (return code) defined and then this screen should be ready.

Divisions Phrases

For the divisions screen to work fully you will need to define a number of phrases.

As usual you need a new file, basically take a copy of cfg/lang/en/phrases/zz_webcfg in cfg/lang/en/phrases/package_name.

The clear all the defined phrases from this file and add the follow 2:

 <epp:phrase id="metapage_title_divisions">Divisions</epp:phrase>
 <epp:phrase id="Plugin/InputForm/Component/Field/Subject:divisions_search_bar"><div class="ep_subjectinput_searchbar">Search for division: <epc:pin name="input"/> <epc:pin name="search_button"/> <epc:pin name="clear_button"/></div></epp:phrase>

Package and Test

Finally, package all these files up and test in from the Bazaar. Note that in the spec file you need to set your configuration file to Admin::PackageName or whatever your screen is called.

You can test this package by click the "manage deposits" link in the admin toolbar and adding a new item. You should see a divisions tab.

Exercise 2

Change this package to add a conditional element somewhere, for example:

 <epc:if test="type = 'book'">
    <field ref="pres_type"/>
 </epc:if>

Experiment with this and use the "view configuration" to see what your changes do to the workflow file each time.

Clue: The correct place to put this code block is under the <component type="Field::Multi"> tag which you can wrap the above block in.

Exercise 3

Here we are going to define a whole new type "map" and a different set of stages for it.

This exercise is also one of manual customisation exercises we run.

Add a new depoist type

This stage involves the editing of the repository namedsets, specifically the eprints namedset.

Again this used to be an entirely manual process however it can now be done with an API call via the postinst/prerm methods:

In postinst:

 # Load your namedset, in this case the eprint namedset
 my $namedset = EPrints::NamedSet->new( "eprint",repository => $repository );
 
 # Add a new option (string type, offset) 
 $namedset->add_option( "map", 0 );
 
 # Save the namedset so it persists between reboots/reloads etc
 $namedset->write;

In prerm we need the corresponding remove calls!

 my $namedset = EPrints::NamedSet->new( "eprint",repository => $repository );
 
 $namedset->remove_option( "map" );
 
 $namedset->write;

Each namedset item also needs some phrase so in our phrase file we need to add phrases for "eprint_typename_map" and "eprint_optdetails_type_map". Lets set these to "Map" and "A map or chart." respectively.

Adding a new stage and simple map workflow

In this section we are going to define a whole new workflow for our "map" type, so if someone selects this as their EPrint type they won't see upload (files), subjects and divisions, just a new state we going to call "map_stage".

Of course in order to customise the workflow we need to handle if/else statements. Helpfully there is a clever API call that does it for you.

All we have to define in the types we want to match and the stages that these types have. Everything that doesn't match thus goes in the else condition.

So in our postinst lets now define some variables

 #our matching types 
 my @types = ( "map" );
 
 #the stages
 my @stages = ( "map_stage" );

Then we add these to our workflow with a similar API call to before (note the inclusion of the package_name!)

 $rc = EPrints::Workflow::add_workflow_flow( $repository, $filename, "package_name", \@types, \@stages );

We can then use the xml string which we defined before to add the new stage:

 my $string = '
 <xml>
       <stage name="map_stage">
              <component id="map_c1">
                    <field ref="place_of_pub" />
                    <field ref="publisher" required="yes" />
                    <field ref="date" />
                    <field ref="date_type" />
              </component>
              <component id="map_c2">
                    <field ref="id_number" />
              </component>
              <component id="map_c3">
                    <field ref="official_url"/>
              </component>      
       </stage>
 </xml>
 ';

Package and Test

Finally, package all these files up and test in from the Bazaar.

You can test this package by click the "manage deposits" link in the admin toolbar and adding a new item. You should now have a new eprint type on the first screen called "map" and selecting this will give you a much shorter workflow containing only a few fields.

Are all your phrases defined?