Difference between revisions of "XML manipulation of the EPrints Workflow using a Bazaar Package"

From EPrints Documentation
Jump to: navigation, search
(Add a new depoist type)
(Add a new deposit type)
 
(5 intermediate revisions by 2 users not shown)
Line 1: Line 1:
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.
+
[[Category:EPrints_Bazaar]]
  
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.
+
Manipulation of built in EPrint files, such as the Workflows, is currently performed using the enable method which is run as a package is being installed on a single repository.  
  
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.
+
Likewise when the package is removed, the disable method needs to call exactly the opposite set of operations to clean itself up before full removal.
 +
 
 +
Every package has an enable and disable method which is called, we simply need to subclass the EPMC screen and add our own enable and disable methods.  
  
 
=Pre-Requisites=
 
=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::EPMC plug-in so needs to go in the lib/plugins/EPrints/Plugin/Screen/EPMC directory). Remember to name it applicably and remember the EPMC::Package_Name when filling out your package specification (this is what calls it!).  
  
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.
+
NOTE: The following line:
  
   package EPrints::Plugin::Screen::Admin::MYNAME;
+
  $self->{package_name} = "package_name";
 +
 
 +
If your package name is "my_package", this line should read:
 +
 
 +
  $self->{package_name} = "my_package";
 +
 
 +
This way you don't need to type your package name in anywhere else in the code, You can just use $self->{package_name} which is the variable that contains the name.
 +
 
 +
   package EPrints::Plugin::Screen::EPMC::Package_Name;
 +
 
 +
  @ISA = ( 'EPrints::Plugin::Screen::EPMC' );
 
    
 
    
  @ISA = ( 'EPrints::Plugin::Screen' );
 
 
 
 
   use strict;
 
   use strict;
    
+
   # Make the plug-in
 
   sub new
 
   sub new
 
   {
 
   {
 
         my( $class, %params ) = @_;
 
         my( $class, %params ) = @_;
 
    
 
    
         my $self = $class->SUPER::new(%params);
+
         my $self = $class->SUPER::new( %params );
 
    
 
    
         $self->{actions} = [qw/ postinst prerm /];
+
         $self->{actions} = [qw( enable disable )];
          
+
         $self->{disable} = 0; # always enabled, even in lib/plugins
         $self->{package_name} = "package_name";
+
 
 +
         $self->{package_name} = "Package_Name";
 
    
 
    
 
         return $self;
 
         return $self;
   }  
+
   }
 
    
 
    
   # Can this screen be viewed?
+
   =item $screen->action_enable( [ SKIP_RELOAD ] )
  sub can_be_viewed
+
  {
+
        my( $self ) = @_;
+
 
    
 
    
        return 0;
+
   Enable the L<EPrints::DataObj::EPM> for the current repository.
   }
+
 
    
 
    
   # Do we have a prerm method which needs executing?
+
   If SKIP_RELOAD is true will not reload the repository configuration.
  sub allow_action_prerm
+
  {
+
        my ( $self ) = @_;
+
 
    
 
    
        return 1;
+
   =cut
   }
+
 
    
 
    
  # Do we have a postinst method which needs executing?
+
   sub action_enable
   sub allow_action_postinst
+
 
   {
 
   {
         my( $self ) = @_;
+
         my( $self, $skip_reload ) = @_;
 
    
 
    
         return 1;
+
         $self->SUPER::action_enable( $skip_reload );
  }
+
 
    
 
    
  # The postinst routine
+
         my $repo = $self->{repository};
  sub action_postinst
+
  {
+
         my ( $self ) = @_;
+
 
    
 
    
         my $repository = $self->{repository}->get_repository();
+
         # ADD STUFF HERE
       
+
 
        # Everthing went OK (why the hell did I code this the wrong way round?)
+
        $self->reload_config if !$skip_reload;
        return (0,undef);
+
        # Something went wrong
+
        # return (1,"MUPPET!!!")
+
 
   }
 
   }
 
    
 
    
   # The prerm routine
+
   =item $screen->action_disable( [ SKIP_RELOAD ] )
  sub action_prerm
+
  {
+
        my ( $self ) = @_;
+
 
    
 
    
        my $repository = $self->{repository}->get_repository();
+
  Disable the L<EPrints::DataObj::EPM> for the current repository.
 
    
 
    
        return (0,undef);
+
  If SKIP_RELOAD is true will not reload the repository configuration.
 
    
 
    
   }
+
   =cut
 
    
 
    
  # A render method for the screen, which we don't need unless we allow this screen to be viewed.
+
   sub action_disable
  # If this screen is visible you return an html fragment, or redirect somewhere else, like a config file :)
+
   sub render
+
 
   {
 
   {
         my ( $self ) = @_;
+
         my( $self, $skip_reload ) = @_;
 +
 
 +
        $self->SUPER::action_disable( $skip_reload );
 +
        my $repo = $self->{repository};
 +
 
 +
        # ADD STUFF HERE
 +
       
 +
        $self->reload_config if !$skip_reload;
 
    
 
    
        return undef;
 
 
   }
 
   }
 
    
 
    
 
   1;
 
   1;
 
=Remember how to force remove a broken package=
 
 
  perl bin/epm archive_name remove package_name --force
 
  
 
=Exercise 1=
 
=Exercise 1=
Line 101: Line 93:
 
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.  
 
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 '''don't''' need to manually specify your package_name  
+
So to the '''enable''' routine we need to add the following lines. Note that you '''don't''' need to manually specify your package_name  
  
 
   my $filename = $repository->config( "config_path" )."/workflows/eprint/default.xml";
 
   my $filename = $repository->config( "config_path" )."/workflows/eprint/default.xml";
Line 107: Line 99:
 
   # Gap for the stuff to be defined we need to add
 
   # Gap for the stuff to be defined we need to add
 
    
 
    
   my $rc = EPrints::XML::add_to_xml( $filename, $xml, $self->{package_name} );
+
   EPrints::XML::add_to_xml( $filename, $xml, $self->{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.  
+
At the same time then it is worth adding the equivalent remove lines to the '''disable''' method. Again you need to specify your package name.  
  
 
   my $filename = $repository->config( "config_path" )."/workflows/eprint/default.xml";
 
   my $filename = $repository->config( "config_path" )."/workflows/eprint/default.xml";
Line 116: Line 108:
 
   EPrints::XML::remove_package_from_xml( $filename, $self->{package_name} );
 
   EPrints::XML::remove_package_from_xml( $filename, $self->{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 enable method the block of XML to add.
  
Lastly we need to actually tell the postinst method the block of XML to add.
+
   my $xml = '
 
+
   my $string = '
+
 
   <workflow xmlns="http://eprints.org/ep3/workflow" xmlns:epc="http://eprints.org/ep3/control">
 
   <workflow xmlns="http://eprints.org/ep3/workflow" xmlns:epc="http://eprints.org/ep3/control">
 
         <flow>
 
         <flow>
Line 132: Line 122:
 
  </workflow>
 
  </workflow>
 
  ';
 
  ';
 
 
  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. We can then either parse this string and send a dom object to the add method, or simply send the string and let the add_to_xml method do it for us.  
 
Here you can see that we can simply specify the XML as a string. We can then either parse this string and send a dom object to the add method, or simply send the string and let the add_to_xml method do it for us.  
Line 146: Line 132:
  
 
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.  
 
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==
 
==Divisions Phrases==
Line 153: Line 137:
 
For the divisions screen to work fully you will need to define a number of 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.  
+
As usual you need a new file, basically take a copy of lib/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:
 
The clear all the defined phrases from this file and add the follow 2:
Line 162: Line 146:
 
=Package and Test=
 
=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.  
+
Finally, package all these files up and test in from the Bazaar. Note that in the spec file you need to set your Control Screen to EPMC::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.
 
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.
Line 186: Line 170:
 
This exercise is also one of manual customisation exercises we run.  
 
This exercise is also one of manual customisation exercises we run.  
  
==Add a new depoist type==
+
==Add a new deposit type==
  
 
This stage involves the editing of the repository namedsets, specifically the eprints namedset.  
 
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:
+
Again this used to be an entirely manual process however it can now be done with an API call via the enable/disable methods:
  
In '''postinst''':
+
In '''enable''':
 
    
 
    
 
   # Load your namedset, in this case the eprint namedset
 
   # Load your namedset, in this case the eprint namedset
Line 201: Line 185:
 
   $namedset->add_option( "map", $self->{package_name}, 0 );
 
   $namedset->add_option( "map", $self->{package_name}, 0 );
 
    
 
    
In '''prerm''' we need the corresponding remove calls!  
+
In '''disable''' we need the corresponding remove calls!  
  
 
   my $namedset = EPrints::NamedSet->new( "eprint",repository => $repository );
 
   my $namedset = EPrints::NamedSet->new( "eprint",repository => $repository );
Line 217: Line 201:
 
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.  
 
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
+
So in our '''enable''' lets now define some variables
  
 
   #our matching types  
 
   #our matching types  
Line 225: Line 209:
 
   my @stages = ( "map_stage" );
 
   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!)
+
Then we add these to our workflow with a similar API call to before.
  
   $rc = EPrints::Workflow::add_workflow_flow( $repository, $filename, "package_name", \@types, \@stages );
+
   $rc = EPrints::Workflow::add_workflow_flow( $repository, $filename, $self->{package_name}, \@types, \@stages );
  
 
We can then use the xml string which we defined before to add the new stage:
 
We can then use the xml string which we defined before to add the new stage:

Latest revision as of 15:25, 16 December 2011


Manipulation of built in EPrint files, such as the Workflows, is currently performed using the enable method which is run as a package is being installed on a single repository.

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

Every package has an enable and disable method which is called, we simply need to subclass the EPMC screen and add our own enable and disable methods.

Pre-Requisites

Below is a simple framework for your plug-in (it's a Screen::EPMC plug-in so needs to go in the lib/plugins/EPrints/Plugin/Screen/EPMC directory). Remember to name it applicably and remember the EPMC::Package_Name when filling out your package specification (this is what calls it!).

NOTE: The following line:

 $self->{package_name} = "package_name";

If your package name is "my_package", this line should read:

 $self->{package_name} = "my_package";

This way you don't need to type your package name in anywhere else in the code, You can just use $self->{package_name} which is the variable that contains the name.

 package EPrints::Plugin::Screen::EPMC::Package_Name;
 
 @ISA = ( 'EPrints::Plugin::Screen::EPMC' );
 
 use strict;
 # Make the plug-in
 sub new
 {
       my( $class, %params ) = @_;
 
       my $self = $class->SUPER::new( %params );
 
       $self->{actions} = [qw( enable disable )];
       $self->{disable} = 0; # always enabled, even in lib/plugins
 
       $self->{package_name} = "Package_Name";
 
       return $self;
 }
 
 =item $screen->action_enable( [ SKIP_RELOAD ] )
 
 Enable the L<EPrints::DataObj::EPM> for the current repository.
 
 If SKIP_RELOAD is true will not reload the repository configuration.
 
 =cut
 
 sub action_enable
 {
       my( $self, $skip_reload ) = @_;
 
       $self->SUPER::action_enable( $skip_reload );
 
       my $repo = $self->{repository};
 
       # ADD STUFF HERE
 
       $self->reload_config if !$skip_reload;
 }
 
 =item $screen->action_disable( [ SKIP_RELOAD ] )
 
 Disable the L<EPrints::DataObj::EPM> for the current repository.
 
 If SKIP_RELOAD is true will not reload the repository configuration.
 
 =cut
 
 sub action_disable
 {
       my( $self, $skip_reload ) = @_;
 
       $self->SUPER::action_disable( $skip_reload );
       my $repo = $self->{repository};
 
       # ADD STUFF HERE
       
       $self->reload_config if !$skip_reload;
 
 }
 
 1;

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 enable routine we need to add the following lines. Note that you don't 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
 
 EPrints::XML::add_to_xml( $filename, $xml, $self->{package_name} );


At the same time then it is worth adding the equivalent remove lines to the disable 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, $self->{package_name} );

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

 my $xml = '
 <workflow xmlns="http://eprints.org/ep3/workflow" xmlns:epc="http://eprints.org/ep3/control">
       <flow>
               <stage ref="divisions"/>
       </flow>
       <stage name="divisions">
               <component type="Field::Subject">
                       <field ref="divisions" required="yes" />
               </component>
       </stage>
</workflow>
';

Here you can see that we can simply specify the XML as a string. We can then either parse this string and send a dom object to the add method, or simply send the string and let the add_to_xml method do it for us.

If you want to parse it yourself you will need the following code block:

 my $xml = EPrints::XML::parse_string( undef, $string );
 

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.

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 lib/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 Control Screen to EPMC::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 is in the core stage <stage name="core">. You need to wrap the above code blog in both these tags.

If you are using the same package as before you will need to updated your cached version and then restart apache to make it work. This is a known bug and applies the the XML manipulation code only.

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 deposit 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 enable/disable methods:

In enable:

 # Load your namedset, in this case the eprint namedset
 my $namedset = EPrints::NamedSet->new( "eprint",repository => $repository );
 
 # Add a new option (string type, package_name, offset from top) 
 # Setting the offset to 0 will add the item at the top of list of eprint types in this case. 
 $namedset->add_option( "map", $self->{package_name}, 0 );
 

In disable we need the corresponding remove calls!

 my $namedset = EPrints::NamedSet->new( "eprint",repository => $repository );
 
 $namedset->remove_option( "map", $self->{package_name} );

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 enable 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.

 $rc = EPrints::Workflow::add_workflow_flow( $repository, $filename, $self->{package_name}, \@types, \@stages );

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

 my $string = '
 <workflow xmlns="http://eprints.org/ep3/workflow" xmlns:epc="http://eprints.org/ep3/control">
       <stage name="map_stage">
              <component type="Field::Multi">
                    <title>Publisher Details</title>
                    <field ref="place_of_pub" />
                    <field ref="publisher" required="yes" />
                    <field ref="date" />
                    <field ref="date_type" />
              </component>
              <component>
                    <field ref="id_number" />
              </component>
              <component>
                    <field ref="official_url"/>
              </component>      
       </stage>
 </workflow>
 ';

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?