Tags:
development1Add my vote for this tag plugin1Add my vote for this tag create new tag
, view all tags
ALERT! NOTE: This is a SupplementalDocument topic which is not included with the official TWiki distribution. Please help maintain high quality documentation by fixing any errors or incomplete content. Put questions and suggestions concerning the documentation of this topic in the comments section below! Use the Support web for problems you are having using TWiki.

How to make a simple TWiki Plugin

Introduction

This topic is written for the absolute beginner that would like to write a simple plugin for TWiki that implements a new TWiki variable.

At first it seems a bit intimidating but it is actually quite easy once you are passed the first few steps.

This TWiki topic will help you through all the steps in a real "TWiki Plugin for dummies style".

It is a supplement to the already existing and very good topics TWikiPlugins and SpecifyingConfigurationItemsForExtensions

What is a TWiki Plugin?

A TWiki Plugin is some Perl code called when you either view, edit, or save a page.

Throughout the TWiki code a number of handlers are called. A handler is a call to a subroutine (function) in each installed plugin. If a Plugin has defined that particular handler it is run. If not, TWiki continues to the next plugin.

The order that plugin handlers are called is:

  • First, the order defined by the configure parameter {PluginsOrder}. By default SpreadSheetPlugin is the only plugin in this list so it gets called before any other plugin.
  • Then, alphabetical order. This means that it is not un-important what you name your plugin. AaaaaBeautifulPlugin, despite looking like a personal ad in a newspaper, will be executed before anything else.

There is not at this time a very good overview of which handler gets called when. TWiki:Codev/StepByStepRenderingOrder is currently the best we have but, as the author admits, it could be better. And it will be one day.

However this topic promised to describe simple plugins. And this is where we leave the handlers because there are simpler and often better ways to make a plugin than using a handler.

The other thing plugins can do is define new TWiki variables. If you look at very old plugins you will see that they handle the new variables in the commonTagsHandler. This is sometimes necessary still. But if your plugin simply defines a TWiki Variable and turns it into something smart then you should not use the commonTagsHandler. Instead use the newer and much more efficient TWiki::Func::registerTagHandler method.

The TWiki::Func::registerTagHandler ensures that:

  • Your new twiki variable is treated and executed same way as a normal internal TWiki variable. Left to right, inside out.
    Example in %FOUR{"%ONE{}%"}% %FIVE{"%TWO{}% and "%THREE{}%"}% the variables are rendered in the order of the numeric words.
  • The code executes much faster and only if the new plugin defined TWiki variable is used inside a given topic

This topic will explain how to write a simple plugin that uses the TWiki::Func::registerTagHandler only.

The minimum requirements of a plugin

For a plugin to work it must:

  • Have a Perl code file in lib/TWiki/Plugins called something that ends with Plugin.pm. Example MyownPlugin.pm
    • The plugin module file must contain an initPlugin subroutine which is successful in running and returns 1.
    • Must have a line saying package TWiki::Plugins::MyownPlugin; using MyownPlugin as example
    • use vars qw( $VERSION ); statement and a line defining $VERSION. However you should use the normal default which is to declare these global variables
      use vars qw( $VERSION $RELEASE $SHORTDESCRIPTION $debug $pluginName $NO_PREFS_IN_TOPIC );
      and defining them all. I will show how later.
  • Have a topic in data/TWiki with the same name as the plugin. MyownPlugin.txt. This topic can be totally blank but it has to exist.

DumbPlugin part 1

So let us write a plugin called DumbPlugin because it is absolutely dumb and does absolutely nothing.

First we make a naked lib/Plugins/DumbPlugin.pm

package TWiki::Plugins::DumbPlugin;
use strict;
use vars qw( $VERSION $RELEASE $SHORTDESCRIPTION $debug $pluginName $NO_PREFS_IN_TOPIC );
$VERSION = '0.1';
$RELEASE = '0.1';
$SHORTDESCRIPTION = 'Dumb plugin that does nothing at all';
$NO_PREFS_IN_TOPIC = 0;
$pluginName = 'DumbPlugin';

sub initPlugin {
    my( $topic, $web, $user, $installWeb ) = @_;

    return 1;
}

and a very shaved down plugin topic

---+ !Dumb Plugin

I do nothing

---++ Syntax Rules

I have no syntax

---++ Settings

   * Set SHORTDESCRIPTION = A really dumb plugin

---++ Plugin Info

|  Plugin Author: | TWiki:Main.KennethLavrsen |
|  Copyright: | ? 2008, Kenneth Lavrsen |
|  License: | GPL ([[http://www.gnu.org/copyleft/gpl.html][GNU General Public License]]) |
|  Plugin Version: | 16 Aug 2008 |
|  Change History: | <!-- versions below in reverse order -->&nbsp; |
|  16 Aug 2008: | Initial version |
|  TWiki Dependency: | $TWiki::Plugins::VERSION 1.1 |
|  CPAN Dependencies: | none |
|  Other Dependencies: | none |
|  Perl Version: | 5.006 |
|  TWiki:Plugins/Benchmark: | %TWIKIWEB%.GoodStyle N/A, %TWIKIWEB%.FormattedSearch N/A, %TOPIC% N/A |
|  Plugin Home: | http://TWiki.org/cgi-bin/view/Plugins/%TOPIC% |
|  Feedback: | http://TWiki.org/cgi-bin/view/Plugins/%TOPIC%Dev |
|  Appraisal: | http://TWiki.org/cgi-bin/view/Plugins/%TOPIC%Appraisal |

__Related Topics:__ %TWIKIWEB%.TWikiPlugins, %TWIKIWEB%.DeveloperDocumentationCategory, %TWIKIWEB%.AdminDocumentationCategory, %TWIKIWEB%.TWikiPreferences

That's it! Now let's activate the plugin and try it out:

  1. Open the configure script in the browser. Under Plugins find your DumbPlugin and activate it.
  2. Now when you look up the InstalledPlugins topic in the TWiki web you will see the DumbPlugin listed, hopefully without any errors...

That is your minimum plugin. That was easy, wasn't it? You, just needed to know how to get started, and then it is not really difficult.

In next section we will make the dumb plugin a little less dumb.

DumbPlugin part 2 - Hello World

Let us make the plugin register a new TWiki variable called HELLOWORLD which returns "Hello World".

We do two things:

  1. In the initPlugin we add
    TWiki::Func::registerTagHandler( 'HELLOWORLD', \&_HELLOWORLD );
  2. We create a new subroutine called _HELLOWORLD which is the subroutine that gets called when TWiki sees a %HELLOWORLD% tag.

We could call the subroutine anything we want but it is convention to name an internal function with an initial underscore. And why not name the routine the same as your variable wink

So here is the hello world example:

package TWiki::Plugins::DumbPlugin;
use strict;
use vars qw( $VERSION $RELEASE $SHORTDESCRIPTION $debug $pluginName $NO_PREFS_IN_TOPIC );
$VERSION = '0.1';
$RELEASE = '0.1';
$SHORTDESCRIPTION = 'Dumb plugin that does nothing at all';
$NO_PREFS_IN_TOPIC = 0;
$pluginName = 'DumbPlugin';

sub initPlugin {
    my( $topic, $web, $user, $installWeb ) = @_;

    TWiki::Func::registerTagHandler( 'HELLOWORLD', \&_HELLOWORLD );

    return 1;
}

sub _HELLOWORLD {
    my($session, $params, $theTopic, $theWeb) = @_;
    
    return "Hello World";
}

Now try putting a %HELLOWORLD% in a topic. You should see the variable replaced by the text, "Hello World".

DumbPlugin part 2 - Hello Parameters

So far our dumb plugin has not been able to do more than we could have done with a Set in a topic. But we have to learn to crawl before we can walk: let's take the first real steps by adding a new TWiki variable called HELLOSOMEONE and giving it some options.

It is a good idea not to invent new strange syntaxes for TWiki variables. The standard syntax is %VARIABLE{"some value" parameter1="some text" parameter2="some other text"}%. By always using this syntax your users will see your new variables work like all the internal variables. The TWiki::Func::registerTagHandler handles all this for you in a very easy way.

When the variable handler is called it is called with a parameter which is a reference to the TWiki::Attrs object containing all the given parameters. Sounds like voodoo? Don't worry. It is to me to. But it is dead easy to use. Just follow these examples.

When you define your variable handler always use this syntax like I used in the examples already

sub _HELLOWORLD {
my($session, $params, $theTopic, $theWeb) = @_;

The $session parameter a reference to the TWiki session object. You will often ignore this in simple plugins.

The $params parameter is the reference to a TWiki::Attrs object containing parameters. This can be used as a simple hash that maps parameter names to values, with _DEFAULT being the name for the default parameter. Let's see how simple this is to use by examples:

Someone has written this in a topic: %HELLOSOMEONE{"Mom" someoneelse="Dad" yetanother="Sister"}%

my $hellovariable = $params->{_DEFAULT}; would set $hellovariable = Mom

my $some = $params->{someoneelse}; would set $hellovariable = Mom

my $yet = $params->{yetanother}; would set $yet = Sister

my $ehm = $params->{ehm}; would leave $ehm = undefined because it is not defined in the HELLOSOMEONE variable.

You will often find that not all variables are always used. You will end up with errors or at least a flood of warnings in the Apache log if you do not always give some good default value for when a parameter is not used.

my $ehm = $params->{ehm} || ' '; is a simple way to ensure $ehm is defined to an empty string.

The last two parameters the variable handler is called with are straight forward the current topic and web name.

OK. Let us enhance the dumb plugin so it registers a new variable HELLOSOMEONE.

package TWiki::Plugins::DumbPlugin;
use strict;
use vars qw( $VERSION $RELEASE $SHORTDESCRIPTION $debug $pluginName $NO_PREFS_IN_TOPIC );
$VERSION = '0.1';
$RELEASE = '0.1';
$SHORTDESCRIPTION = 'Dumb plugin that does nothing at all';
$NO_PREFS_IN_TOPIC = 0;
$pluginName = 'DumbPlugin';

sub initPlugin {
    my( $topic, $web, $user, $installWeb ) = @_;

    TWiki::Func::registerTagHandler( 'HELLOWORLD', \&_HELLOWORLD );
    TWiki::Func::registerTagHandler( 'HELLOSOMEONE', \&_HELLOSOMEONE );

    return 1;
}

sub _HELLOWORLD {
    my($session, $params, $theTopic, $theWeb) = @_;
    
    return "Hello World";
}

sub _HELLOSOMEONE {
    my($session, $params, $theTopic, $theWeb) = @_;
    
    my $defaulttext = $params->{_DEFAULT} || '';
    my $someoneelse = $params->{someoneelse} || '';
    my $yetanother = $params->{yetanother} || '';
    
    my $text = '';
    $text .= " $defaulttext" if $defaulttext;
    $text .= " and" if ($text && $someoneelse);
    $text .= " $someoneelse" if $someoneelse;
    $text .= " and" if ($text && $yetanother );
    $text .= " $yetanother" if $yetanother;
    $text = "Hello" . $text;
    
    return $text;
}

Our dumb plugin can now say hello to up to 3 people. And you can leave out anyone and still get a sensible result.

Examples:

  • %HELLOSOMEONE{"Mom" someoneelse="Dad" yetanother="Sister"}%
  • %HELLOSOMEONE{someoneelse="Dad" yetanother="Sister"}%

It is still a very dumb plugin but I think you can build on from here making much more advanced stuff.

Plugin Preference Settings

You can define plugin settings two ways:

  1. You can define a new option in configure
  2. You can define a setting in the plugin topic

There are advantages and disadvantages of the two methods.

Options defined in configure have a huge speed advantage over options defined in the plugin topic. So if your value is a static value that only an admin would ever change and only once - define it as a configure value.

If the value is the path for an external script used by the plugin then for sure you should define the setting as a configure value for security reasons.

If the setting is one you will need to redefine on a web basis or a setting users will benefit from being able to alter maybe even at topic level then you need to use a topic defined setting.

Plugin setting defined in configure

Adding a configure setting is very simple.

Your plugin must simply include a file lib/TWiki/Plugins/DumbPlugin/Config.spec (using DumbPlugin as example) with a definition of the setting. See section "Structure of a Config.spec file" in TWikiPlugins for a good description how to do this.

Example

#---+ Plugins
#---++ DumbPlugin
# The Dumb Plugin can just say Hello
# **STRING 30**
# Options the Hello word
$TWiki::cfg{Plugins}{DumbPlugin}{Greeting} = 'Hello';

After having defined the value in configure the plugin can now access the value simply by

$greeting = $TWiki::cfg{Plugins}{DumbPlugin}{Greeting} || "Hello";

See also TWiki:TWiki/SpecifyingConfigurationItemsForExtensions for a good detailed description of how to define the plugin configure files.

If you ONLY use configure defined variables then you can define

$NO_PREFS_IN_TOPIC = 1;

Instead of setting it 0 as shown in the previous examples. This makes the plugin not trying to find settings in topics. You win some performance by doing this.

Plugin settings defined in topics

You can define the setting directly in the Plugin topic:

  • Set GREETING = Hello

You can also define it in Main.TWikiPreferences:

  • Set DUMBPLUGIN_GREETING = Allo

The Main.TWikiPreferences overrides the setting in the Plugin topic.

To use this setting throughout the plugin simple add a global variable like this where we add $greeting

use vars qw( $VERSION $RELEASE $SHORTDESCRIPTION $debug $pluginName $NO_PREFS_IN_TOPIC $greeting );

and in initPlugin subroutine add

$greeting = TWiki::Func::getPreferencesValue( 'DUMBPLUGIN_GREETING' ) || "Hello";

Use EmptyPlugin as your starting point

I have shown how to make the most basic plugin without any comments or any extras. But in the real world you should not start from scratch.

Instead start your plugin based on the distributed plugin called EmptyPlugin. This plugin is also quite dumb to start with but it contains a lot of documentation in the shape of comments that helps you getting the job done. It also contains all the handlers that exist in TWiki except the subroutines for these have been renamed by prefexing them DISABLE_. If you need to use a handler simply rename is by removing the DISABLE_ prefix and you are set to go. Even if you only use the TWiki::Func::registerTagHandler it is still a good idea to always start with the EmptyPlugin for later extensions.

Always use the official API

You will soon need to do stuff which is more advanced than plain Perl and you will need information from the rest of TWiki or you will need to store data in files.

ALWAYS ALWAYS use the TWiki::Func module as your interface to TWiki. TWiki::Func is the official plugin API for TWiki. It is the only part of TWiki you can trust not to be changed - only enhanced - from TWiki version to TWiki version. If you want your plugin to work after next TWiki upgrade always use only the functions in TWiki::Func.

If you need to use code from elsewhere in TWiki because your needed function is not in TWiki::Func then it is better to copy the few lines of code to your plugin than to directly call other TWiki modules. Same with accessing data in the TWiki Session object. Get the data through TWiki::Func even if it is also available in the Session object because it will very well have changed in the next TWiki version.

Executing external programs and accessing the file system

Plugins are often used to fetch data by calling external programs. This is easy to do in Perl. It is also very dangerous if you do not take care.

ALERT! Never trust any data you get from the browser. Never trust that the 3 values you put in a form are the only values your program will see. There are web development plugins for Firefox that enables even small kids to submit forms with values you never intended should be possible. Always filter the input from the browser. If you have to call different external scripts based on user inputs always make "if" statements testing for those 2-3 possible values and ignore everything else. Never let data from user input be part of a filename you open unless you have filtered off anything that is not plain alphanumerical characters.

It is not difficult to write a safe plugin that takes user data as long as you think like an attacker and avoid using user inputs directly for the names of files or external programs.

Also TWiki::Meta and TWiki::Sandbox are considered part of the official API. The 3 APIs are described in TWikiFuncDotPm, TWikiMetaDotPm, and TWikiSandboxDotPm. The TWiki:Codev.PluginsApiPolicies describes the official TWiki API policy.

Please publish your plugin

You got TWiki for free. You have access to more than 300 plugins for free. If you write a plugin, even a simple one, please consider contributing it back to the community. The TWikiCommunity appreciates this very much!

What about those handlers?

That will be the subject for a new How To tutorial which will eventually be added to the suite of supplemental documents.

We hope this topic was useful to you. Please provide feedback on how we can improve it further and please feel free to directly edit this topic.

Related topics: TWikiPlugins, TWikiPluginsSupplement

-- Contributors: KennethLavrsen



Comments & Questions about this Supplemental Document Topic

Thank you Kenneth for contributing this supplemental document!

-- PeterThoeny - 02 Sep 2008

Please use the Support forum if you have questions about TWiki features. This comment section is about the documentation of this topic.
Edit | Attach | Watch | Print version | History: r7 < r6 < r5 < r4 < r3 | Backlinks | Raw View | Raw edit | More topic actions
Topic revision: r7 - 2015-04-25 - EmilStepniewski
 
  • Learn about TWiki  
  • Download TWiki
This site is powered by the TWiki collaboration platform Powered by Perl Hosted by OICcam.com Ideas, requests, problems regarding TWiki? Send feedback. Ask community in the support forum.
Copyright © 1999-2017 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.