Ratatöskr

Tutorial: Developing a plugin

This tutorial shows you how to write a plugin for Ratatöskr.

API documentation

In the API documentation of Ratatöskr, you will find detailed information about the classes and functions you can use to communicate with Ratatöskr.

The documentation of the plugin API is probably the most important piece of documentation for you, since it describes the RatatoeskrPlugin class you have to extend from to write your plugin.

This server also hosts the documentation of the STE template engine.

Needed tools

In order to package your plugins, you will need r7r-plugin-packer.php, a command line tool. We will discuss later in this tutorial how to use it. You will need PHP 5.3 or newer installed on your computer to run this script.

Alternatively you can use the online packer, but it is usually more comfortable to use the command line tool, because you can store the parameters in a shell script or a batch file.

Of course you also need a text editor.

Step 1: Enable debugmode

(This is a optional step, but it makes your life easier.)

Login to the backend of your Ratatöskr installation. Go to Administration > Settings and toggle the Debugmode value to Enabled.

This will give you more useful error messages, in case your plugin crashes.

It is also recommended not to use your production installation of Ratatöskr, just in case you mess something up.

Step 2: Writing a simple Hello World plugin

Our first plugin will be a plugin that gives the user a new STE-Tag that returns "Hello World!".

Fire up your text editor and type this in:

<?php

class HelloWorldPlugin extends RatatoeskrPlugin
{
    public function my_ste_tag($ste, $params, $sub)
    {
        return "Hello World!";
    }

    public function init()
    {
        $this->register_ste_tag("hello_world", array($this, "my_ste_tag"));
    }
}

?>

Now let us have a look, what this does:

  • class HelloWorldPlugin extends RatatoeskrPlugin

    This declares the class, where our plugin lives in. Every plugin class must extend from the abstract class RatatoeskrPlugin.

  • public function my_ste_tag($ste, $params, $sub)

    This is the function of our new STE-Tag. A STE-Tag function always takes three parameters:

    • $ste – The current STE instance.
    • $params – An associative array of tag parameters.
    • $sub – A callable function representing the tag's content, needs the STE instance as a parameter and returns the executed STE code.

    At the moment we do not use these parameters, since our tag will just return a static text. We will see the usage of these parameters in step 4.

  • return "Hello World!";

    This obvoiusly returns the text "Hello World!" and passes it as the output to the template engine.

  • public function init()

    The function init will be called, after Ratatöskr has loaded the plugin.

  • $this->register_ste_tag("hello_world", array($this, "my_ste_tag"));

    This will register the STE-Tag hello_world using the function my_ste_tag of this class object ($this).

Step 3: Packaging your plugin

Get r7r-plugin-packer.php. You can also use the online packer, which behaves similar.

Open a command line and start r7r-plugin-packer.php with these parameters:

Mandatory parameters:

  • --output=FILE – r7r-plugin-packer will store the package to the file called FILE.
  • --codefile=FILE – The PHP file with the plugin code.
  • --classname=CLASS – The name of the plugin class.
  • --pluginname=NAME – The name of our plugin.
  • --author=AUTHOR – The author of the name. If you do not want to publish your real name, you can use your screenname or something like that.
  • --versiontext=VER – A text that describes this version, something like "v.0.1 Beta 1".
  • --versioncount=COUNT – A number that should be increased every time you publish a new release. This is really important, if you want to publish your plugin on a repository.
  • --api=APIVERSION – The current API version.
  • --shortdesc=TEXT – A short description of your plugin.

Optional parameters:

  • --updatepath=URL – A URL that points to a file that contains information about the current version. Will be overwritten and automatically generated by the repository software.
  • --web=URL – The webpage of the plugin.
  • --licensefile=FILE – A file that contains license information about your plugin.
  • --helpfile=FILE – A HTML file that contains some documentation of your plugin and helps the user to use it.
  • --custompub=DIR — A directory that contains files that should later be available for everyone via HTTP.
  • --custompriv=DIR – A directory that contains files that your plugin needs but should not be public.
  • --tpldir=DIR – A directory that contains templates for your plugin.

So the command for our hello world plugin should be something like this:
r7r-plugin-packer.php --output=hello-world.rpk --codefile=plugin.php --classname=HelloWorldPlugin --pluginname=HelloWorld --author=Me --versiontext=v.1.0 --versioncount=1 --api=5 --shortdesc="An awesome Hello World plugin."

Since this is a lot to type, you probably should create a batch file or a shell script to automate this.

Now you have your first plugin. Isn't that awesome?

You can use the new Tag in your templates using <ste:hello_world />.

Excursion: My plugin crashed Ratatöskr!

If your plugin crashed immediantly after installation, usually you just need to go to the backend and then to Plugins > Plugin List and you should be able to delete it there.

If deletion failed, try to enforce the deletion using the drop-down menu next to the delete button.

If everything fails, you need to temporary disable plugins. For this, open the file config.php of your Ratatöskr installation and find the line
define("PLUGINS_ENABLED", True);
and set it to False:
define("PLUGINS_ENABLED", False);
Then you should be able to delete the plugin. Do not forget to enable the plugins after this.

Step 4: Extending our STE-Tag

Just one "Hello World!" is not enough. We want a parameter that let us define, how many "Hello World!"s we want.

Lets implement this! Here is our new my_ste_tag function:

public function my_ste_tag($ste, $params, $sub)
{
    if(isset($params["count"]))
        $count = $params["count"];
    else
        $count = 1;

    $return = "";
    for($i = 0; $i < $params["count"]; $i++)
        $return .= "Hello World!\n";
    return $return;
}

If the parameter count is set, we will return "Hello World!" count times, otherwise we will set count to 1 and do the same thing.

Now we want to see how the $sub parameter works. For this we want our tag to set a template variable that we get from the parameters to "Hello World!" plus the current number and then call the contents of the tag.

Example:

<ul>
<ste:hello_world count="3" var="foo">
    <li>$foo</li>
</ste:hello_world>
</ul>

This should then result into this:

<ul>
    <li>Hello World! 0</li>
    <li>Hello World! 1</li>
    <li>Hello World! 2</li>
</ul>

Let's implement this! Here is our new my_ste_tag function:

public function my_ste_tag($ste, $params, $sub)
{
    if(!isset($params["var"]))
        throw new \ste\RuntimeError("Parameter `var` must be set.");

    if(isset($params["count"]))
        $count = $params["count"];
    else
        $count = 1;

    $return = "";
    for($i = 0; $i < $params["count"]; $i++)
    {
        $ste->set_var_by_name($params["var"], "Hello World! " . $i);
        $return .= $sub($ste);
    }
    return $return;
}

The first new thing is this:

if(!isset($params["var"]))
    throw new \ste\RuntimeError("Parameter `var` must be set.");

This will warn the user (if debugmode was enabled, otherwise nothing will be displayed for this tag), if no value for var was passed.

\ste\RuntimeError has also a more "powerful" sibling: \ste\FatalRuntimeError. It should be used, if the template will not longer be able to execute after something bad happened. A \ste\FatalRuntimeError will always result in a "500 Internal Server Error" and will therefore stop the execution of Ratatöskr. So only use this, if it is really necessary!

Let's have a closer look at the loop:

$return = "";
for($i = 0; $i < $params["count"]; $i++)
{
    $ste->set_var_by_name($params["var"], "Hello World! " . $i);
    $return .= $sub($ste);
}
return $return;

$ste->set_var_by_name($params["var"], "Hello World! " . $i); will set the template variable with the name provided by the var parameter to "Hello World! " plus the current loop iteration. If you need to get the value of a template variable by it's name, you can use $ste->get_var_by_name(name). You can also use the associative array $ste->vars that holds all template variables. You should prefer this to the other two methods if you want to access a variable with a static name. The two functions mentioned above are useful, because they will automatically resolve array accesses for you.

$return .= $sub($ste); will execute the tags content and append the output to our return variable.

Step 5: Our private data storage

Every plugin will automatically have an own persistent key-value-storage.

The variable $this->kvstorage of your plugin class behaves almost like a regular associative array, but it's values will be saved to database and will therefore stay there, even if the current Ratatöskr execution is finished.

Usually you would use this for storing config data (we will later discuss how to create a configuration page). But now we want a simple plugin that will display a number and increase it everytime it is called.

Here is the implementation:

class SenselessCounterPlugin extends RatatoeskrPlugin
{
    public function my_ste_tag($ste, $params, $sub)
    {
        $return = $this->kvstorage["counter"];
        $this->kvstorage["counter"] = $this->kvstorage["counter"] + 1;
        return $return;
    }

    public function init()
    {
        $this->register_ste_tag("senseless_counter", array($this, "my_ste_tag"));
    }

    public function install()
    {
        $this->kvstorage["counter"] = 0;
    }
}

Do you remember that I said, that $this->kvstorage almost behaves like a regular associative array? Well, here is already the "almost" part: Usually you would probably do something like $this->kvstorage["counter"]++ instead of $this->kvstorage["counter"] = $this->kvstorage["counter"] + 1;. This is unfortunatly not possible, since no direct operation on a array value is possible. Also it can only store things that PHP's serialize function can process (which is pretty much everything).

The rest of the my_ste_tag function should be obvious. Also init should now look familiar to you.

But install is new. The install function will always be called, after the plugin is installed.

Here we initialize the value of $this->kvstorage["counter"] to 0.

Excursion: Special functions

In step 5 we introduced the setup function and we already use the init function. Both are special functions that will be called at some special events.

Here are all of these special functions:

  • init – Will be called after the plugin is loaded.
  • atexit – Will be called when Ratatöskr exits cleanly.
  • install – Will be called after installation.
  • uninstall – Will be called if your plugin is uninstalled.
  • update – If your plugin was updated, this function will be called.

Step 6: A configuration page for our plugin

It would be cool, if we could reset our counter, but not everyone should be able to do this. Only an administrator. So we will write a small configuration page for the backend that will be able to reset the counter.

First, we will need a template for this page. We will create a new directory for our template (use the --tpldir parameter of the plugin packer to include your templates) and create a file called my_backend_page.html with this content:

<ste:load name="/systemtemplates/master.html" />
<ste:block name="content">
    <ste:default_success />

    <form action="$rel_path_to_pluginpage" method="post">
        <p>Current counter value: $counter</p>
        <p>Reset counter: <input type="submit" name="reset_counter" value="Reset" /></p>
    </form>
</ste:block>

As you probably guessed, this is a STE template. Let's look at some special stuff here:

  • <ste:load name="/systemtemplates/master.html" /> – This will load the backend's master template, so our page inherits the general style of the backend and the menus.
  • <ste:block name="content"> ... </ste:block> – Overwrite the content block of the master template.
  • <ste:default_success /> – If the template variable $success is set, this will automatically display it and give it the typical Ratatöskr success look. There is also <ste:default_error /> and <ste:default_notice />.
  • $rel_path_to_pluginpage – This is a URL that will always point to our plugn page.

Okay, now we need to modify our plugin code:

class SenselessCounterPlugin extends RatatoeskrPlugin
{
    public function my_ste_tag($ste, $params, $sub)
    {
        $return = $this->kvstorage["counter"];
        $this->kvstorage["counter"] = $this->kvstorage["counter"] + 1;
        return $return;
    }

    public function my_backend_page(&$data, $url_now, &$url_next)
    {
        /* Prepare the pluginpage. This automatically selects the correct menu entry and set the title. */
        $this->prepare_backend_pluginpage();

        /* Set $url_next to an empty array, to stop url handling after this function */
        $url_next = array();

        /* Should we reset the counter? */
        if(isset($_POST["reset_counter"]))
        {
            /* Reset the counter and give the user a feedback, that the counter was resetted. */
            $this->kvstorage["counter"] = 0;
            $this->ste->vars["success"] = "Counter resetted.";
        }

        /* Pass the counter value to the template. Pro tip: always pass the values as the last step before displaying the template, this will guarantee that, if the user has changed something, the new value will be loaded. */
        $this->ste->vars["counter"] = $this->kvstorage["counter"];

        /* Call our template. $this->get_template_dir will return the correct path to our template directory, we have delivered with this plugin package. */
        echo $this->ste->exectemplate($this->get_template_dir() . "/my_backend_page.html");
    }

    public function init()
    {
        $this->register_ste_tag("senseless_counter", array($this, "my_ste_tag"));
        $this->register_backend_pluginpage("Reset SenselessCounter", array($this, "my_backend_page"));
    }

    public function install()
    {
        $this->kvstorage["counter"] = 0;
    }
}

We have a new function: my_backend_page. This is a function that url_process can handle. The parameter &$data can be used to pass data between multiple url handler functions. $url_nowis the current url fragment. &$url_next is a array of url fragments that still need to be travelled. Set it to empty array and url_process will stop processiong the url.

The rest of the function is already documented with comments and should therefore be easy to understand.

In the init function we registered this function as a pluginpage for the backend. "Reset SenselessCounter" will be the title / menu entry of our pluginpage.

And with these fairly easy methods you can create your own configuration page that is embedded in the backend.

Excursion: Using the database

When you want to access data from the database, you should use the classes that models.php provides. They give you an easy and safe access to the database.

If you really need to access the database with raw SQL commands (think twice before you decide to do so!), do not use mysql_query directly. Ratatöskr gives you a function called qdb, that should defeat SQL injections if used correctly. You can use format signs like %d for numbers and %s for strings and then provide the values to be inserted for these in the parameters. It will automatically escape all strings that are provided like this for you.

Step 7: Other functions of the RatatoeskrPlugin class

Here I will describe the rest of the functions that RatatoeskrPlugin provides to you:

  • get_custompriv_dir() – Gives you the path to the directory with your custom private files that you provided with the package (using the --custompriv option for the packer).
  • get_custompub_dir() – Gives you the path to the directory with your custom public files that you provided with the package (using the --custompub option for the packer).
  • get_custompub_url() – The URL to your custom public files.
  • register_url_handler($name, $fx) – Register a URL handler function ($fx) to be used for the public (i.e. you do not need to be in the backend for this) URL $name.
  • register_textprocessor($name, $fx, $visible_in_backend) – Register a new textprocessor. This is a function $fx that takes an input string and returns HTML. $name is the name of the textprocessor. If $visible_in_backend is True (default), the textprocessor will appear in the drop-down menus of the backend.
  • register_comment_validator($fx) – Register a function $fx that checks the $_POST values after a comment was sent, if the comment should be accepted. If the comment should not be accepted, throw a CommentRejected exception.
  • register_on_comment_store($fx) – Register a function $fx that will be called, if a comment was posted and already accepted. The function must accept a Comment object as only parameter.

Step 8: Publish your plugin

You want to publish your plugin? Awesome! Here are some rules / tips:

  • You really should use a developer prefix that you can use to avoid naming conflicts. There is a thread in the Ratatöskr forum that contains a list of all already assigned developer prefixes and where you can request a prefix.
  • If you submit your plugin to our community repository people can find your plugins easilly and you will be able to distribute updates.
  • You might also want to announce your plugin, we have a category in our forum for this.
  • Why not make it open source and upload the sources to a code hoster like GitHub? Other people that like your plugin can then easilly contribute to your plugin and make it even more awesome. (Also the pluginpackages provide no protection to your source code, so closed-source would not make that much sense...)

Write a comment

Your name:

Your E-Mailaddress:

Your comment (Markdown format):

Markdown