Custom functionality can be added to Koha using its plugin system.

It can be a fun, and easy, way to get started with Koha development and can be used to customise your ILS to suit you!

What makes a Zip file a Koha plugin?

Plugins are bundled files in the form of a Zip file, using the .kpz extension.

There needs to be a base class, that inherits from Koha::Plugins::Base like this:

package Koha::Plugin::FancyPlugin;

use Modern::Perl;

use base qw(Koha::Plugins::Base);

our $VERSION = "{VERSION}";

our $metadata = {
    name            => 'Our fancy plugin',
    author          => 'Your name',
    description     => 'Some useful description, think of end users!',
    date_authored   => '2020-12-01',
    date_updated    => "1970-01-01",
    minimum_version => '19.1100000',
    maximum_version => undef,
    version         => $VERSION,
};

sub new {
    my ( $class, $args ) = @_;

    $args->{'metadata'} = $metadata;
    my $self = $class->SUPER::new($args);

    return $self;
}

This example is the minimum you need to have in your plugin class for it to be picked as a plugin in Koha.

The new() constructor simply adds metadata into its internal structure; this may be automated or moved into a distinct file in the future, but for now just clone the above example to get started.

The metadata structure is pretty straight-forward to read. The only attributes that have some semantics are minimum_version and maximum_version, which refer to the Koha version the plugin is expected to work on, and version. The latter is used when installing/updating the plugin for comparing with the currently installed version.

You will notice a placeholder, {VERSION}; We replace this at build time as using gulp.

Note: The astute of your will also have noticed that our date_updated is set to the epoch. This will also be replaced during build but a clear placeholder cannot yet be used due to a bug in Koha.

Using Gulp to automate builds

The communities prefered development environments KohaTestingDocker and KohaDevBox both include Gulp v4 and Node.js v12 so you can write automated build scripts using them.

The most basic gulpfile.js build file could look like this:

const { dest, series, src } = require('gulp');

const fs = require('fs');
const run = require('gulp-run');
const dateTime = require('node-datetime');
const Vinyl = require('vinyl');
const path = require('path');
const stream = require('stream');

const dt = dateTime.create();
const today = dt.format('Y-m-d');

const package_json = JSON.parse(fs.readFileSync('./package.json'));
const release_filename = `${package_json.name}-v${package_json.version}.kpz`;

const pm_name = 'FancyPlugin';
const pm_file = pm_name+'.pm';
const pm_file_path = path.join('Koha', 'Plugin');
const pm_file_path_full = path.join(pm_file_path, pm_file);
const pm_file_path_dist = path.join('dist', pm_file_path);
const pm_file_path_full_dist = path.join(pm_file_path_dist, pm_file);
const pm_bundle_path = path.join(pm_file_path, pm_name);

function build() {
    return run(`
        mkdir dist ;
        cp -r Koha dist/. ;
        sed -i -e "s/{VERSION}/${package_json.version}/g" ${pm_file_path_full_dist} ;
        sed -i -e "s/1970-01-01/${today}/g" ${pm_file_path_full_dist} ;
        cd dist ;
        zip -r ../${release_filename} ./Koha ;
        cd .. ;
        rm -rf dist ;
    `).exec();
};

exports.build = build;

Now, when we want to build the plugin for release it is as simple as running the following:

$ npm install # to install the dependencies
$ gulp build
[12:44:42] Using gulpfile /kohadevbox/koha_plugin/gulpfile.js
[12:44:42] Starting 'build'...
  adding: Koha/ (stored 0%)
  adding: Koha/Plugin/ (stored 0%)
  adding: Koha/Plugin/FancyPlugin/ (stored 0%)
  adding: Koha/Plugin/FancyPlugin.pm (deflated 56%)
[12:44:43] Finished 'build' after 309 ms
$ ls
gulpfile.js  Koha  koha-plugin-fancyplugin-v0.0.1.kpz
node_modules  package.json  package-lock.json  README.md

This will produce a .kpz file that is suitable for installing in Koha.

If you use the above gulpfile.js as a template, take a look at the following variables; they need to match the structure for your plugin:

const pm_name = 'FancyPlugin';
const pm_file = pm_name+'.pm';
const pm_file_path = path.join('Koha', 'Plugin');

You can even go a step further and add these to your CI system for Github or Gitlab. There will definitely be a post about it!

Our first example: Adding CSS to our OPAC

Koha includes a number of Hooks that plugins can utilise to add customisation and functionality into the system.

We will use the opac_head hook here to add an additional stylesheet to the OPAC to turn the background pink.

Implementing a hook is just a matter of implementing a method, with a name matching your chosen hook, in your plugin class. Depending on the hook you’ve chosen to utilise, your method will be passed varying input and you will need to return a structure that conforms to what the hook expects.

In our example, we will use the opac_head hook which simply expects the a HTML string as it’s return value. This can be handy, for example, if you are adding style for something your plugin is adding to the UI. You could require the user to manually code to OPACUserCSS or OPACUserJS preferences, but adding it using our hook leads to a cleaner solution without having to involved the user with additional setup steps.

Lets look at our hook method:

sub opac_head {
    my ( $self ) = @_;

    return q{
      <style>
        body {background-color: pink;}
      </style>
    };
}

The only thing we need to do now, is releasing it!.. but that’s for another day.