CWIS Developer Documentation
Advanced CWIS Plugin Implementation

Plugin Events

Just as CWIS provides the ability for you to run your code when certain events occur, you may want to provide the ability for others to run their code when events happen within your plugin. To do this you'll need to declare these events via your Plugin::DeclareEvents() method:

function DeclareEvents()
{
return array(
"MyPlugin_EVENT_MY_FIRST_EVENT" => ApplicationFramework::EVENTTYPE_DEFAULT,
"MyPlugin_EVENT_MY_SECOND_EVENT" => ApplicationFramework::EVENTTYPE_CHAIN,
);
}

By convention, plugin event names should begin with the base plugin name (e.g. MyPlugin), followed by an underscore and the word EVENT (MyPlugin_EVENT), and then underscore-separated terms in all upper case describing the event (MyPlugin_EVENT_MY_FIRST_EVENT).

At the point in the code where the event occurs, you'll then signal the event, to give anyone who has hooked into it the chance to run their code:

global $AF;
$AF->SignalEvent("MyPlugin_EVENT_MY_FIRST_EVENT", array(
"EventFirstParameter" => $ValueForFirstParam,
"EventSecondParameter" => $ValueForSecondParam
));

The second argument to ApplicationFramework::SignalEvent() allows you to pass parameters to any hooked event handlers, as most of the events listed on the CWIS Event Callbacks page do.

For EVENTTYPE_DEFAULT events any value returned from the event handlers is ignored, however with EVENTTYPE_CHAIN events the return value is a (possibly) modified version of the parameter(s):

global $AF;
$SignalResult = $AF->SignalEvent("MyPlugin_EVENT_MY_SECOND_EVENT", array(
"EventFirstParameter" => $ValueForFirstParam
));
$PossiblyModifiedFirstParam = $SignalResult["EventFirstParameter"];

This is why EVENTTYPE_CHAIN events are often used to provide the opportunity for other plugins to modify a value before it is displayed or otherwise used.

Plugin Pages

For more complex plugins that require their own pages (perhaps for complex configuration settings or to display results), you can include PHP and/or HTML files as part of your plugin, and link to them via a path-independent mechanism.

To implement a plugin-specific PHP file, you would place all of your plugin files into a subdirectory named with your plugin base name (e.g. MyPlugin) and then put your plugin class file (MyPlugin.php) and any PHP files (PageOne.php, PageTwo.php) into that directory.

To link to your plugin pages within HTML, use the page base name (PageOne) prefixed with P_ and your plugin base name (MyPlugin):

<a href="index.php?P=P_MyPlugin_PageOne">

This will also load a similarly-named HTML file (MyPlugin/PageOne.html) at the appropriate time, if one is available.

Plugin Objects

For more complex plugins that include their own object classes, the object files should be one class per file, named after the object class. For example, class MyPlugin_MyObject would go in MyPlugin/MyPlugin_MyObject.php.

To prevent class name collisions, plugin object class names should be prepended with the plugin name and an underscore (e.g. MyPlugin_MyObject). (This somewhat awkward requirement will likely disappear when namespace support becomes widely available with PHP 5.3.)

Following these guidelines, classes for any enabled plugins will be automatically loaded when instances of the object are created, as occurs with native CWIS objects; no require() statements are needed.

Database Usage

Plugins that require storage beyond that provided by the built-in configuration value support (described in Implementing CWIS Plugins), may make use of the Database class. However to prevent name collision, any tables created should have the base plugin prepended to their name, followed by an underscore:

CREATE TABLE IF NOT EXIST MyPlugin_MyDatabaseTable (
ItemId INT NOT NULL AUTO_INCREMENT,
ItemData TEXT,
INDEX (ItemId)
);

Tables are normally created in a plugin's Plugin::Install() method, and destroyed (dropped) in the plugin's Plugin::Uninstall() method (depending on the setting of the $RemoveData parameter).

When upgrading to a new plugin version that requires changes to the database schema, those are normally made in the plugin's Plugin::Upgrade() method:

function Upgrade($PreviousVersion)
{
if (version_compare($PreviousVersion, "1.1.0"))
{
$Queries = array(
"ALTER TABLE MyPlugin_MyData
ADD COLUMN ItemCount INT;",
"CREATE TABLE IF NOT EXISTS MyPlugin_MoreData (
ItemId INT NOT NULL,
ItemData TEXT,
INDEX (ItemId));",
);
$SqlErrorsToIgnore = array(
"/ALTER\s+TABLE\s+[a-z\_]+\s+ADD\s+COLUMN/i"
=> "/Duplicate column name/i",
);
$this->DB->SetQueryErrorsToIgnore($SqlErrorsToIgnore);
foreach ($Queries as $Query)
{
$Result = $this->DB->Query($Query);
if ($Result === FALSE) { break; }
}
$this->DB->SetQueryErrorsToIgnore(NULL);
return ($Result === FALSE) ? "Database upgrade error. (SQL: "
.$Query." ERROR: ".$this->DB->QueryErrMsg().")" : NULL;
}
}

(Ignoring database errors that result from commands being run multiple times isn't necessary in this case, since the plugin manager should ensure the upgrade method will only be run once, but it provides a useful example of how to implement fault tolerance for database commands.)