NewsFeaturesDownloadsDevelopmentSupportAbout Us

Plugin Development

From LifeType Wiki


Contents

Plugin structure

Plugins in Lifetype are self-contained bits of code, templates and locales that implement additional functionality in Lifetype without overloading the core.

Plugins are stored in the plugins/ folder and all should follow a structure similar to this:

  plugins/
    helloworld/
    smileys/
      icons/
      locale/
      templates/
      class/
        action/
        dao/
         view/
    ...

The locale and templates folders should contain all templates and translations provided by the plugin.

The class subfolder in the plugin main folder should mimic the structure of the core class folder. Not only because the structure looks cleaner, but also because by putting our custom action*.class.php classes in there will allow the core plugin loader to load them dynamically at run time, there will be no need to include_once(...) them at the top of our plugin*.class.php file.

With regards to the naming scheme of plugins, the main rule is that if our plugin is called "smileys", the class that extends from PluginBase and that implements the plugin's main class *must* be called *pluginsmileys.class.php* and must reside in the plugins' first level folder, and the same level than the locale/ and templates/ folder. Example:

 plugins/
   smileys/
     pluginsmileys.class.php
     locale/
       locale_en_UK.php
     templates/
       ...
     icons/
       ...
     class/
        action/
          pluginsmileysconfigaction.class.php
          pluginsmileysupdateconfigaction.class.php

Otherwise, the plugin manager class will not be able to autoload your plugin!

PluginBase

PluginBase is the core class that defines the plugin interface within Lifetype. All new plugins must extend and implement this interface so that they can interact with the core.

PluginBase public methods

The following public methods must be implemented when developing new plugins.

  • PluginBase::PluginBase( $source ): All plugins must use the constructor to initialize at least the PluginBase::$id public attribute. This attribute is required by the plugin manager/loader to uniquely identify each plugin. It is enough to initialize this attribute with the plugin's name as the value. Plugins are also expected to follow the $source parameter, which indicates from where this plugin is being initialized. There are currently two possible value: blog and admin. The first one will tell the plugin that it's index.php initalizing the plugin, while the second one will tell the plugin that it's admin.php being the caller. This should be used by plugins to determine which data to load and initialize, so for example admin actions and menu entries should not be initialized when $source is blog, and by doing so we will be able to save some additional memory. In case it is needed, the value of the $source parameter can be retrieved later on with the PluginBase::getSource() method. Example:
function PluginMyPlugin( $source )
{
   $this->PluginBase( $source );

   $this->id = "myplugin";

   if( $source == "admin" ) {
     $this->registerAdminAction( ... );
     $this->registerAdminAction( ... );
     $this->addMenuEntry( ... );
   }
    ...
}


  • PluginBase::getPluginConfigurationKeys(): Please see The Global Plugin Settings framework below for more information.
  • PluginBase::register(): This method is called by the plugin manager class the first time the plugin is loaded. It should be used to initialize one-time actions only, such as creating database tables needed by the plugin's logic.

Public Members:

  • PluginBase::$blogInfo: This is a read-only attribute that contains a pointer to the BlogInfo object representing the blog that is currently executing the plugin.
  • PluginBase::$userInfo: This is a read-only attribute that contains a pointer to the UserInfo object representing the user that is currently executing the plugin. It only has a meaningful value when the plugin is being run from the administration interface.
  • PluginBase::$id: This is a writable attribute that contains a unique string identifying the plugin. The plugin manager class expects this value to be set when loading the plugin, or else an exception will be thrown poiting out the error.
  • PluginBase::$desc: This is a writable attribute that contains a brief description about the plugin. It will be displayed in the "Plugin Centre" page.
  • PluginBase::$author: This is a writable attribute that contains the name of the author(s) of the plugin. It will be displayed in the "Plugin Centre" page.
  • PluginBase::$version: This is a writable attribute that contains the current version of the plugin. It will be displayed in the "Plugin Centre" page. Please update this value every time you release a version of a plugin, so that users are aware of what version they are running.

Events

One of the main features of the new plugin framework is that plugins are now able to capture events (and if needed, events can also be thrown)

Events are messages thrown by the LifeType core to report about certain situations. For example, the core will throw an event every time a post is loaded, or every time a comment is received, and so forth. In order to attach our plugin to an event, we can use the PluginBase::registerNotification( $eventType ) method:

 class PluginSmileys extends PluginBase
 {
   function PluginSmileys()
   {
     ...
     $this->registerNotification( EVENT_POST_LOADED );
     ...
   }
 }


Doing so we are telling the PluginManager class to let our plugin know every time the EVENT_POST_LOADED is thrown.

In order to implement the event handler, we have to use the PluginBase::process( $eventType, $params ) method, which will be called every time one of the events to which we attached is thrown. This method will receive the event identifier (an integer) and an associative array containing several different parameters that will depend on the event. For example an EVENT_POST_LOADED event will come with a reference to the article that was just loaded (a reference to an Article object), but an EVENT_USER_REGISTER event will come with a reference to the user that was just registered (a reference to an UserInfo object):

 class PluginSmileys extends PluginBase
 {
   function PluginSmileys()
   {
     ...
     $this->registerNotification( EVENT_POST_LOADED );
     ...
   }
 
   function process( $eventType, $params )
   {
     print("The event type is = $eventType, and the parameters are:");
     foreach( $params as $key => $value ) {
       print("$key = $value");
     }
 
     return true;
   }
 }

The value returned by the process() method is not taken into account by neither the plugin manager nor the core class that threw the event.

For a detailed list of event codes and the parameters that accompany them, please see List of Events

One thing to keep in mind is that our plugins and code are not restricted to only the events listed in the previous link. That is the list of standard events thrown by the LifeType core classes, but new event codes can be added at any time. Of course those events will not be picked by and used by the core classes but other plugins can register and "listen" to them. In LifeType, events are nothing more than an integer identifying the event and an array with the event parameters. As long as a unique event identifier is used, any event can be thrown.

The PluginBase has a method called notifyEvent that takes two parameters: the first one is the event identifier and the second one is an associative array with the event parameters. If you would like other plugins to listen to this event and modify the parameters, then pass these parameters by reference (use the '&' modifier) See the documentation for PluginBase::notifyEvent for more informaiton.

Action classes can also throw their own custom events by using the same method, see AdminAction::notifyEvent and BlogAction::notifyEvent.

In any other class (such as a class inheriting from Object or Model), we can still throw events manually by implementing this code:

 $pm =& PluginManager::getPluginManager();
 $pm->setBlogInfo( $this->_blogInfo );
 $pm->notifyEvent( YOUR_EVENT_IDENTIFER, Array( "your" => &$a, "parameters" => &$b ));

The only thing that we need to keep in mind is that we need a reference to a BlogInfo object, so thats plugins have a context. Each plugin needs to know which blog is throwing the event or in other words, which blog is the current active one. As long as such reference is available, it will be possible to throw events.

Filters

Filters are structures that don't have much in common with plugins, but the only way a new filter can be dynamically attached to LifeType's pipeline is via a plugin.

See Filters and the Pipeline to get more information about what filters are good for, and how developers can create their own filters.

In order to register a filter from our plugin, we can use the PluginBase::registerFilter($filterClass) method to attach a custom filter to the pipeline:

 class PluginSmileys extends PluginBase
 {
   function PluginSmileys()
   {
     ...
     $this->registerFilter( "myTestFilter" );
     ...
   }
   ...
 }

Registration of new filters should always be performed in the plugin constructor, though LifeType will not stop you from doing it anywhere else.

Localizing plugins

It is not mandatory to provide localized plugins, but it definately increases the user experience when all the pages and messages are shown in their correct languages.

Plugins can now be 100% localized as any other component of LifeType. In order to do so, the plugin must first of all specify which locales it defines and place the correct locale_xx_YY.php files under the plugins/xxxx/locale/ folder, where 'xxxxx' is the plugin identifier.

Plugins define the locales that they provide by using the _locales_ attribute of the PluginBase class. This attribute should be set to an array containing the list of locale codes that this plugin provides. For example if our plugin comes with the messages in English, Spanish and German, we should add the following line to the constructor of our plugin:

 class PluginSmileys extends PluginBase
 {
   function PluginSmileys()
   {
     ...
     $this->locales = Array( "en_UK", "es_ES", "de_DE" );
     ...
   }
   ...
 }

The we would need to place the files locale_en_UK.php, locale_es_ES.php and locale_de_DE.php under the folder plugins/smileys/locale. The PluginManager, at plugin initialization will take care of reading the locale files and add the strings so that they are available for all pages in the site. If the blog is running in a language that is not provided with the plugin, LifeType will use the first locale that was specified in the array. As LifeType serves primarily an english-speaking (reading) audience, it is recommended to place "en_UK" as the first locale (and provide a locale_en_UK.php file)

Setting Default Configuration Values

Most of the plugins provide a simple configuration page to customize some of its parameters. Setting default values in the view class for the plugin works by adding the default value as a parameter to the getValue() method. If the configuration key does not return a value, the default is returned (and thus may be "set" instead).

//Example
define(DEFAULT_VALUE, "MyConfigValue");

$value = $blogSettings->getValue("plugin_myplugin_confkey", DEFAULT_VALUE);

(available since LifeType 1.2.1)

Templates

Plugins, as any other code in Lifetype, will provide their own actions and views. Views need their own templates and templates are free to use as many as they wish.

Template files should be under the templates/ folder in the plugin own folder:

  plugins/
    smileys/
      pluginsmileys.class.php
      locale/
        locale_en_UK.php
      templates/
        ...
      icons/
        ...

In order to load a template file for a view within a plugin, we have to use the AdminPluginTemplatedView for admin actions and PluginTemplatedView for blog actions.

class MyPluginView extends AdminPluginTemplatedView {
  function MyPluginView( $blogInfo )
  {
    $this->AdminPluginTemplatedView( $blogInfo, "myplugin", "mytemplatename" );
    ...
  }

These two views take three parameters:

  • $blogInfo: Like any other View class, a reference to the currently running blog is needed. This will be provided by the Action class calling this view so all we have to do is pass it on.
  • $pluginId: This is the plugin identifier, the same one that was used as the value for the PluginBase::$id attribute. The template loader will use it to identify the folder from which the template file should be loaded.
  • $templateName: This is the name of the template file that should be loaded. There is no need to include the path to it, the template loaded will figure it out on its own based on the plugin identifier.

Adding new menu entries to the admin interface

NOTE: needs to be updated

Use the PluginBase::addMenuEntry($menuPath,$menuKey,$url,$localeId,$orPerms,$andPerms,$siteAdmin) method to add a custom entry into the admin interface. Take a look at the Menu API to get more familiar with dynamic menus and how they work.

 class PluginSmileys extends PluginBase
 {
   function PluginSmileys()
   {
     ...
     $this->addMenuEntry( "/menu/blogSettings/blogSettings", "smileySettings", "admin.php?op=smileys", "Smileys Settings", Array( "manage_plugins"), Array(), false );
     ...
    }
    ...
  }

The code above would add an entry under "Blog Settings", called "smileySettings", pointing to "admin.php?op=smileys", with a certain text, and would be accesible by any user in the blog (no restrictions)

Registering new Actions

Plugins are allowed to register new actions dynamically to implement to implement their functionalities.

In order to register a new action in the admin interface, use the PluginBase::registerAdminAction($actionKey,$actionClass) method. In order to register an action for the public side of the blog, use the PluginBase::registerBlogaction($actionKey,$actionClass)

  class PluginSmileys extends PluginBase
  {
    function PluginSmileys()
    {
      ...
      $this->registerAdminAction( "pluginSmileysSettings", "AdminPluginSmileysSettingsAction" );
      $this->registerAdminAction( "updatePluginSmileysSettings", "AdminUpdatePluginSmileysSettingsAction" );
      ...
    }

After registering our actions, using URLs such as http://.../admin.php?op=pluginSmileysSettings and http://.../admin.php?op=updatePluginSmileysSettings will tell the controller to load the action classes specified by these keys.

  class PluginAdvancedSearch extends PluginBase
  {
    function PluginAdvancedSearch ()
    {
      ...
      $this->registerBlogAction( "advancedSearch", "PluginAdvancedSearchAction" );
      ...
    }

Plugin action classes should be placed in the plugins/xxx/class/action/ folder, where 'xxx' is the plugin identifier. If this rule is followed, the action class loader will be able to automatically load the class, instead of requiring us to preload all of the action classes via include_once() at the top of our plugin class.

The Global Plugin Settings framework

Introduction

The main requirement for this feature to work is that the plugin settings are all prefixed with a plugin_xxxx_ prefix. This is necessary so that the BlogSettings::getValue() method can transparently detect whether it's being requested the value of a plugin setting and route the request accordingly.

Exposing the public plugin settings

The PluginBase::getPluginConfigurationKeys() method should return an array listing all the global attributes/settings of the plugin. Not all settings should be global, as for example with password settings. However in other cases like enabled/disabled settings, it does make sense to export these setting so that it can be globally set.

The format of the returned array is as follows:

Array(
  Array( 
    "name" => "plugin_xxxx_name",
    "type" => "boolean/string/integer/text/list/radio",
    "validator" => "new ValidatorClass",
    "options" => Array( "option1", "option2", "option3" )
   ),
   Array(
     ...
   )
)

This is how the Authimage plugin implements its method PluginAuthImage::getPluginConfigurationKeys();

return( Array(
  Array( "name" => "plugin_authimage_enabled", "type" => "boolean" ),
  Array( "name" => "plugin_authimage_length", "validator" => new IntegerValidator(), "type" => "integer" ),
  Array( "name" => "plugin_authimage_key", "validator" => new StringValidator(), "type" => "string" ),
  Array( "name" => "plugin_authimage_test_dropdown", "type" => "list", "options" => Array( "1" => "option 1", "2" => "option 2", "3" => "option 3" )),
  Array( "name" => "plugin_authimage_test_textarea", "type" => "text", "validator" => new StringValidator())
));

User interface

The last step when integrating support for the global plugin settings framework is to modify the user interface. Depending on how the site administatror configures some of the plugins, it could be that some of the parameters are not editable. In order not to confuse the end user, we have to modify all forms where these parameters can be modify so that these fields appear grayed out or disabled, to no te that they do not accept input.

At the user interface level, the Smarty block function user_cannot_override will check whether the given plugin key can be modified or not by users. If it cannot be modifed, the text between the starting and opening tags of the block will be added to the template. In most of the cases the input field or checkbox will be disabled via the "disabled" attribute.

{user_cannot_override key=plugin_key_name}
  Code or HTML code that should be output
  if the plugin key cannot be overridden.
{/user_cannot_override}


This is an example from the plugin configuration page of the 'authimage' plugin:

<input class="checkbox" 
       type="checkbox" 
       name="pluginEnabled" id="pluginEnabled" 
       {if $pluginEnabled}checked="checked"{/if}
       value="1" 
{user_cannot_override key=plugin_authimage_enabled}disabled="disabled"{/user_cannot_override} />
{$locale->tr("authimage_plugin_enabled")}

Permissions and access control

As of Lifetype 1.2, plugins can create new permissions and control access to their functionalities via the permission framework. The whole framework is described in Permissions, please refer to that page for more information.

Access control

Permission-based access control is implemented by calling the AdminAction::requirePermission($permName) and AdminAction::requireAdminPermission($permName). These two methods will inform the controller class about what blog permission or admin permission is required to perform this action.

At the very minimum, all Action classes that are part of a plugin should require the "manage_plugin" or "manage_admin_plugin" permission in order to be executed, unless we want the plugin functionalities to be available to all users with access to the blog.

The "manage_plugin" permission should be required for non-admin functionalities, while "manage_admin_plugin" should be required for admin-only functionalities.

In order to create new permissions for our plugin, please see the next section.

Creating new permissions

By creating new permissions, we can allow site administrators to have finer control over which plugins can be accessed by which users. Othewrise the catch-all manage_plugins permission will be used by default.