Introduction to Oempro Plug-In development - Part I

We have recently published a step-by-step plugin development article on our blog. Click here to learn how to develop an Oempro plugin.

Introduction

Oempro is a powerful, feature packed list management and email marketing software for both your own needs as well as for providing email marketing service to your customers. But we are not big fans of making software even more complicated by adding several new features in every version release. Instead, we prefer to keep Oempro stable with a base feature set and provide additional features to anyone interested in those features only. Any Oempro owner can extend functionalities of Oempro by adding new plugins to their Oempro installations.

Oempro has a powerful and highly extendable plugin engine which lets you to add any kind of new feature set (or change the work-flow of existing features). You are almost limitless on plugins you can develop for Oempro. However, we are aware that this is not a one-man show. To encourage developers just like you, we do our best to prepare you a detailed plugin development manual, sharing every single step, every single plugin development techniques in this manual.

This manual is updated frequently based on developer feedback, recently added new hooks, etc. Please bookmark this page and check it frequently for the most up-to-date information.

We are looking forward to hear from you about your suggestions which will help us to make the plugin system even more flexible. If you think that you have a great idea for the plugin system, or if you think that you need new hooks or menu items (explained later in this manual), just get in touch with us, by email or from our Facebook developers group.

The Plugin Developers Community

Come and join us, let’s improve the plugin engine of Oempro together and let us help you develop amazing Oempro plugins.

The mail list

Subscribe to our developer mail list to get notified about the most recent updates on Oempro plugin ecosystem. We will be sending you status update emails time to time about;

  • new plugin hooks
  • new plugin engine features
  • the most recent plugin development techniques

You can always opt-out from our mail list by clicking the unsubscription link inside our emails.

The Facebook group

Come and join our Facebook Oempro Plugin Developers Group:
https://www.facebook.com/groups/oempro.plugin.developers/

We discuss building awesome Oempro plugins and special techniques with developers.

Getting started

Oempro is written in PHP language with MySQL database backend. The plugin engine of Oempro is based on classes. Basically, an Oempro plugin consists of at least one class. Based on the plugin project you are working on, it may contain several files, but you can even write a simple plugin with a small PHP class.

To help you get started faster, we prepared a boilerplate and framework plugin for you. You can take this plugin as an example and start coding your own Oempro plugin. Until you get used to how plugins work in Oempro, this example plugin will be very helpful and a good starting point for you. It contains several examples like adding a menu item to the user interface, registering a hook to a listener, etc.

The plugin boilerplate and framework

We host the boilerplate plugin framework on Github, which can be accessed anytime. Here’s the direct link to the Github project page:

https://github.com/octeth/oempro_plugin_framework

Feel free to fork it, improve it and push it. We are excited to make it even more feature packed for you.

This boilerplate example plugin includes many features that will give you an idea about how things work in plugin system, such as;

  • enabling, disabling plugins
  • initiating plugin database tables on enable
  • loading the plugin
  • authentication and privilege system
  • model-view-controller approach
  • adding menu items to the Oempro user interface
  • adding new screens to the Oempro user interface
  • events
  • and more…

Installing the boilerplate framework

Simply visit Github project page and clone it to /plugins/ directory inside Oempro directory on your server. Once it’s cloned, be sure that you have cloned it to the following directory:

/oempro/plugins/plugin_framework/

Once the “plugin_framework” directory is created and example plugin project is cloned to this directory, you are ready to give it a try. Now, login to your Oempro administrator area and click “Settings” link. Then click “Plugins” link on the left menu. You should see the “Plugin Framework” on the list as “disabled”:

Now, click “Enable” next to Plugin Framework. Once the plugin is enabled, it will add example menu items to;

  • Administrator settings left side menu
  • Administrator top menu (drop down menu)
  • User area top menu

You can play with the plugin framework.

Let’s dig into the source code and learn how those menu items, screens and all other functionalities are done.

Plugin directory structure

First, let’s check the directory structure of a plugin. Simply, go into the /plugins/ directory inside Oempro. You will see directories which belongs to different plugins. Go into “plugin_framework” plugin directory.

Each plugin in Oempro has its own directory under /plugins/ directory. Don’t forget to prefix your plugin(s) with a unique code. For example, we use “oct” as prefix on our plugins.

In order to create a basic plugin, you only need to create the plugin directory and then place the .php file inside which has the same name as the plugin directory. For example;

plugins/my_plugin/my_plugin.php

In order to keep your plugin files organized, we have grouped similar files under sub-directories (something like categorization).

Plugin Directory Structure
cli
If your plugin contains any PHP scripts which needs to be executed in CLI group them under this directory
css
This is the directory which should contain CSS files for the plugin user interfaces (views)
images
Group images inside this directory
js
JavaScript files (such as jQuery or your user interface JavaScripts) should be stored in this directory
languages
Your plugin language files (at least the default language file) should be stored inside this directory
libraries
All third party classes functions and other included PHP files should be put into this directory
models
Models are classes which interact with your Oempro database
templates
This directory contains user interface files (views)

In addition to these directories, there are some files inside the “plugin_framework” directory.

Plugin Files
plugin_framework.php
This is the main plugin file. The name of this file should be the same with the plugin directory. Otherwise your plugin will not be recognized in Oempro
main.php
This is optional. You can write your plugin code inside the main plugin file (plugin_framework.php in this example) or you can split the code to the main.php. This is very useful if you are going to encrypt your plugin PHP scripts with Ioncube.

In order to start building your own plugin, simply follow these easy steps:

  1. Decide a name for your plugin. Example: My Plugin
  2. Decide a plugin code for your plugin. Example: my_plugin (it should be lowercase with no spaces. Alphanumeric characters and underscore)
  3. Clone the “/plugins/plugin_framework” directory and rename it to your own code. Example: /plugins/my_plugin
  4. Rename the plugin_framework.php file to my_plugin.php inside your plugin directory
  5. Edit main.php and change the name of the class to “my_plugin”

Once you edit the main.php file, you will notice that every single class property and method are well-commented.

Default plugin methods

Below, you can find the list of plugin methods which will be executed by Oempro on certain conditions. When using these methods in your plugin, replace “yyy” with your plugin code (ex: enable_my_plugin(), disable_my_plugin(), load_my_plugin())

In the future, we are considering to make these default functions much easy to implement, such as removing the plugin code from function names. However, we will retain the backward compatibility.

Default plugin methods
__construct()
Never use the constructor. Plugin class is called statically therefore object is not constructed.

enable_yyy()
This method will be called when the Oempro administrator clicks “Enable” link next to the plugin in “Settings > Plugin”. Use this method to setup plugin options create plugin database tables etc.

disable_yyy()
This method will be called when the Oempro administrator clicks “Disable” link next to the plugin in “Settings > Plugin”. Use this method to remove plugin related data files database tables and plugin options.

load_yyy()
This method will be called every time an Oempro page is loaded/accessed if the plugin is enabled. In this method you will usually set the language register hooks menu items etc.

Simply edit main.php file inside plugin_framework plugin and you will find some useful examples for each method.

Don’t forget to replace “yyy” with your own plugin code.

Basic MVC approach in Oempro plugins

It’s up to you how to develop an Oempro plugin. However, MVC is the best approach to keep your application organized and easy-to-maintain (and expand) in the future.

In the plugin framework example, you will find example MVC approach. Simply edit the “main.php” and take a look at controller methods (methods with “ui_” prefix), view files (files under “templates” directory) and models (files under “models” directory.

Plugin features

Below, you will find detailed descriptions and examples for specific approaches when developing an Oempro plugin.

Adding a new menu item

If you are developing a plugin which has user interface, then you will probably need to place menu items to admin and/or user area to let your users access your plugin.

In your plugin loader method (load_yourplugincode), you need to define menu item function, just like setting up hooks. For an example, edit plugin_framework ‘s main.php file and search for:

parent::RegisterMenuHook(self::$PluginCode, ‘set_menu_items’);

As it can be seen on the above example, Oempro will get the list of menu items from “set_menu_items” method in your plugin class. Search for “set_menu_items” method in plugin_framework class:

In the “set_menu_items” method, you will see an array including locations and menu items:

$ArrayMenuItems[] = array(
    'MenuLocation' => 'Admin.Settings', 
    'MenuID' => 'Plugin Framework', 
    'MenuLink' => Core::InterfaceAppURL() . '/' . self::$PluginCode . '/admin_settings/', 
    'MenuTitle' => self::$ArrayLanguage['Screen']['0001']
);

Menu item array

MenuLocation
This is the menu container on the user interface. For a list of available menu item locations take a look at “Menu item locations” reference in the article.

MenuID
Give a unique ID to your menu item. It will be a good approach to prefix the ID with your plugin code.

MenuLink
This is the link where user will be redirect upon he clicks to the menu item you inserted. For linking to a controller in your plugin simply use the following structure:

Core::InterfaceAppURL() . '/' . self::$PluginCode . '/controller_name/'

MenuTitle
This is the title which will be shown to the user. You can set it as a string or get the name from the language file of your plugin or Oempro.

Adding a new API command

With Oempro 4.7, it became very easy to add new API commands to Oempro’s core API. In your plugin loader method (load_yourplugincode), you need to register the API command name and its properties. For example:

parent::RegisterAPIHook('MyApi.Get', self::$PluginCode, array('user'));

What this does is to register a new API command (MyApi.Get) to Oempro’s core API which is accesible through only USER authentication. Here is the RegisterAPIHook function signature and its details:

parent::RegisterAPIHook($commandName (String), $pluginCode (String), $allowedAccountTypes (Array));

RegisterAPIHook

$commandName (String)
This is the name of your new API command. Your plugin users will run your new API command with this name.

$pluginCode (String)
This is your plugin code

$allowedAccountTypes (Array)
Which account types may use this new API command? This array answers the question. You can pass multiple account types. Possible values are “user” “admin” “subscriber” and “client”.

After registering your new API command, you need add a method for this API command to your plugin class. The name of the method must be in the following format (assuming your new API command is “MyApi.Get”):

public function api_myapi_get($parameters = array())

All dots are replaced with an underscore, all lowercase and prefixed with “api_”. $paramters array contains all the parameters sent to the API command, it is an associative array. Within this method you must return an array with the following structure:

array("Success" => true, "ErrorCode" => 0);

Depending on your conditions, you can return FALSE as success and an error code. Also you can add as many as new keys to this array. But be sure that you return an array with a “Success” and “ErrorCode” keys. Here is an example (assuming your new API command is “MyApi.Get”):

public function api_myapi_get($parameters) {
    return array(
        "Success" => true,
        "ErrorCode" => 0,
        "hello" => 'world!'
    );
}

And here is full basic example:

class octapitest extends Plugins {
    public function load_octapitest() {
        parent::RegisterAPIHook('MyApi.Get', 'octapitest', array('user'));
    }

    public function api_myapi_get($parameters) {
        return array(‘Success’ => true, ‘ErrorCode’ => 0, ‘hello’ => ‘world!’);
    }
}

Plugin hooks: Actions and Filters

Hooks are “triggers”. At certain points during the Oempro application execution, you can trigger your plugin methods to perform specific processes. For example, just before starting to send an email campaign, you can perform a final check on the email content and stop the email campaign delivery. Or you can monitor user logins and block an IP address if suspected situation is detected, such as too many failed login attempts.

There are two hook types in Oempro:

Hook types explained

Filter Hooks
Filter hooks are used to perform an operation for the passed-in parameters and return them in the same order. In this way you can manipulate the system variables. For example you can add a prefix to the email campaign subject.

Action Hooks
Action hooks may pass-in parameters but it will not accept them from your plugin method. Action hooks can be used to control the process or perform “internal” operations. For example you can cancel an email campaign delivery or log user logins to your plugin database table.

You can hook to a filter hook just like in the example below:

parent::RegisterHook('Filter', 'UserGroup.Update.FieldValidator', self::$PluginCode, 'usergroup_update_fieldvalidator', 10, 1);

In the example above, we hooked to “UserGroup.Update.FieldValidator” filter hook which gets triggered whenever admin submits the user group form in admin area. “usergroup_update_fieldvalidator” method inside your plugin class will be executed.

“10″ represents the priority. Just leave it as “10″ for now, this is a parameter which will be used in the future.

“1″ means the number of parameters accepted by your plugin hook method

parent::RegisterHook(‘Action|Filter’, ‘HookListener’, self::$PluginCode, ‘YourPluginMethod’, 10, NumberOfParameters);

For a list of available filter hooks, please take a look at “Filter hook listeners” section on this manual.

Your plugin hook method should be similar to the one written below as an example:

public function usergroup_update_fieldvalidator($ArrayFormRules)
    {
        $ArrayFormRules[] = array(
            'field' => 'PluginFramework[DeliveryLimit]',
            'label' => self::$ArrayLanguage['Screen']['0003'],
            'rules' => 'required|numeric',
        );
    return array($ArrayFormRules);
    }

As you can see above, we hooked to “UserGroup.Update.FieldValidator” filter hook. This filter hook sends and received one parameter. We received it in our plugin hook method, performed the appropriate action and then returned it in the same order in an array (take a look at the return).

Hook registrations must be set in “load” method of your plugin class. Otherwise, they may not be registered correctly.

Action hooks are just the same as filter hooks with one difference. They do not return the passed-in parameters. Action hooks are ideal for “internal” processing, such as activity logging, stopping an email campaign, sending a notification email to the admin.

Multi Language Support

In order to make your plugin “multi-language” ready, first check the example in plugin framework script. To make your script ready, follow these steps:

  1. Create “languages” directory inside your plugin directory
  2. Create “en” language directory (or any other ISO 2-letter language code) inside “languages” directory
  3. Inside “en” directory, create info.txt file. The contents should be similar to:

Language Code: EN
Language Name: English

  1. Create “en.inc.php” file inside “en” directory. If you have created a different ISO 2-letter language code, the file name should be the same.

  2. The contents of en.inc.php file should be similar to;

    $ArrayPlugInLanguageStrings = array();
    $ArrayPlugInLanguageStrings[‘Screen’][‘0001’] = ‘My Plugin’;
    $ArrayPlugInLanguageStrings[‘Screen’][‘0002’] = ‘Example text’;

  3. You can set unlimited amount of language strings in this language file. Just make the array keys unique.

Okay, your language file is ready. Now, let’s load it in your plugin. In your plugin loader method (load_xxx() where xxx represents your plugin code), add the following code:

$Language = Database::$Interface->GetOption(self::$PluginCode . '_Language');
if (count($Language) == 0) {
    Database::$Interface->SaveOption(self::$PluginCode . '_Language', 'en');
    $Language = 'en';
} else {
    $Language = $Language[0]['OptionValue'];
}

$ArrayPlugInLanguageStrings = array();
if (file_exists(PLUGIN_PATH . self::$PluginCode . ‘/languages/’ . strtolower($Language) . ‘/’ . strtolower($Language) . ‘.inc.php’) == true) {
    include_once(PLUGIN_PATH . self::$PluginCode . ‘/languages/’ . strtolower($Language) . ‘/’ . strtolower($Language) . ‘.inc.php’);
} 
else {
    include_once(PLUGIN_PATH . self::$PluginCode . ‘/languages/en/en.inc.php’);
}

self::$ArrayLanguage = $ArrayPlugInLanguageStrings;
unset($ArrayPlugInLanguageStrings);

Now, the correct language pack will be loaded during your plugin loading process. You can use the loaded language strings in your plugin methods and view files just like the below example:

Using Oempro’s language strings;

ApplicationHeader::$ArrayLanguageStrings['Screen']['0966'])

Using your plugin’s language strings;

self::$ArrayLanguage['Screen']['0001']

MVC approach

You are free to design your plugin code in anyway you want. However, we do believe that designing your plugin with a MVC (Model-View-Controller) approach will make it easier to maintain in the future. To help you get started with MVC approach, we have developed a basic but highly effective MVC backend for you. You can examine the plugin framework source code to have a better idea on this topic.

Controllers

Controllers are methods which get executed whenever a user interface of your plugin is accessed by the user. Controllers perform specific actions such as event handling (save settings, send email, etc.) and loads the view (user interface). Inside controllers, no database queries should be included. Controller method names should start with “ui_” prefix, otherwise they will not be able to executed through the Oempro user interface.

Models

Model files store methods which perform database related processes, such saving account profile, creating a new email, etc.

Views

Views are user interface files. No event handling and database operations should be done in view files.

Plugin updates and new version releases

Currently, Oempro’s upgrade system is not compatible with plugins. Therefore, once you have an update for your plugin, simply change the $PluginVersion to the most recent version and re-submit to the plugin store.

Licensing and protection

Currently, we are able to license-protect and encrypt our plugins. Unfortunately, this licensing engine is currently not available for third party developers. We have plans to make it available for third party developers so that you can both protect the source code of your plugin and sell it to other Oempro owners and bind it to specific Oempro licenses.

We are working on a powerful and robust plugin licensing engine, once it becomes ready for deployment, we will invite all Oempro plugin developers to give it a try.

If the plugin you have developed is going to be distributed through our Plugin Store, then we will be happy to implement our own licensing engine to your plugin during the distribution process. Please contact us for more information.

Plugin source code encryption

If you want, you can encrypt the source code of your plugin with Ioncube. The only thing that you should be careful is, the main plugin file shouldn’t be encrypted. That’s why we recommend you to include main.php file inside the plugin file and keep it simple. Except the plugin file, all other PHP files can be encrypted inside your plugin.

If the plugin you have developed is going to be distributed through our Plugin Store, then we will be happy to encrypt your plugin source code (ioncube) before distributing it. Please contact us for more information.

Oempro Plugin Store

In order to make plugin distribution easy across ten thousands of Oempro owners, we have prepared a central plugin distribution place on our website: Oempro Plugin Store.

You will be able to submit your plugins to our Plugin Store and have opportunity to promote/distribute your plugin to thousands of Oempro users. In order to be sure that each plugin in our Plugin Store complies with our terms and conditions (such as privacy and security), we will be testing every single submitted plugin.

Octeth Plugin Store terms and guidelines will be announced in the coming weeks on our website. Please stay tuned.