Writing ColdFusion Plugins

This website contains links to software which is either no longer maintained or will be supported only until the end of 2019 (CKFinder 2). For the latest documentation about current CKSource projects, including software like CKEditor 4/CKEditor 5, CKFinder 3, Cloud Services, Letters, Accessibility Checker, please visit the new documentation website.

If you look for an information about very old versions of CKEditor, FCKeditor and CKFinder check also the CKEditor forum, which was closed in 2015. If not, please head to StackOverflow for support.

(Created page with ''''Note: the instruction below is valid only for ColdFusion 8 and later.''' {{CKFinder 2.x Writing Plugins Introduction}} == Creating a plugin == ==== Step1: Create plugin fold…')
 
 
(21 intermediate revisions by the same user not shown)
Line 1: Line 1:
'''Note: the instruction below is valid only for ColdFusion 8 and later.'''
+
<div style="border:1px solid #000; padding:6px; margin-bottom:10px">'''Note: the instruction below is valid only for ColdFusion 8 and later.'''</div>
 
+
__TOC__{{CKFinder 2.x Writing Plugins Introduction}}
{{CKFinder 2.x Writing Plugins Introduction}}
 
 
== Creating a plugin ==
 
== Creating a plugin ==
  
Line 14: Line 13:
 
==== Step3: Add plugin definition ====
 
==== Step3: Add plugin definition ====
  
// Let's create an empty PHP file for now
+
// Let's create an empty file for now
  
 
==== Step4: Enable plugin ====
 
==== Step4: Enable plugin ====
Line 20: Line 19:
 
Include your plugin with include() in the ColdFusion configuration file (config.cfm):  
 
Include your plugin with include() in the ColdFusion configuration file (config.cfm):  
  
<source lang="xml">
+
<source>
 
<cfscript>
 
<cfscript>
 
include( "plugins/myplugin/plugin.cfm" );
 
include( "plugins/myplugin/plugin.cfm" );
Line 26: Line 25:
 
</source>
 
</source>
  
== PHP plugin system ==
+
== ColdFusion plugin system ==
  
 
=== Hooks ===
 
=== Hooks ===
Line 32: Line 31:
 
CKFinder provides several hooks that can be used to extend the functionality of the CKFinder application. Assigning a function (also known as an event handler) to a hook will cause that function to be called at the appropriate point in the main CKFinder code, to perform whatever additional task(s) the developer thinks would be useful at that point. Each hook can have multiple handlers assigned to it, in which case it will call the functions in the order that they are assigned.
 
CKFinder provides several hooks that can be used to extend the functionality of the CKFinder application. Assigning a function (also known as an event handler) to a hook will cause that function to be called at the appropriate point in the main CKFinder code, to perform whatever additional task(s) the developer thinks would be useful at that point. Each hook can have multiple handlers assigned to it, in which case it will call the functions in the order that they are assigned.
  
Hooks should be assigned in a plugin file. The easiest way to assign a function to a hook is:
+
Hooks should be assigned in a plugin file. To assign a function to a hook, add an array to config.hooks:
<source lang="php">
+
<source>
$config['Hooks']['event'][] = 'function';
+
<cfset hook = arrayNew(1)>
</source>
+
<cfset hook[1] = "BeforeExecuteCommand">    <!--- Hook name --->
 
+
<cfset hook[2] = "CKFinderPluginSaveFile">   <!--- Function name --->
However, usually you'll provide a class instance and a method name.
+
<cfset ArrayAppend(config.hooks, hook)>
 
 
<source lang="php">
 
// Resulting function call: $object->someMethod($param1, $param2);
 
$config['Hooks']['event'][] = array($object, 'someMethod');
 
 
</source>
 
</source>
  
 
==== Available hooks ====
 
==== Available hooks ====
<table border="1">
+
{{CKFinder_2.x hooks table}}
<tr>
 
<th style="width:150px">Hook</th>
 
<th>Since</th>
 
<th>Description</th>
 
</tr>
 
<tr>
 
<td>[[/AfterFileUpload|AfterFileUpload]]</td>
 
<td>2.0</td>
 
<td>Executed after successfull file upload.</td>
 
</tr>
 
<tr>
 
<td>[[/BeforeExecuteCommand|BeforeExecuteCommand]]</td>
 
<td>2.0</td>
 
<td>Executed before a server side command is executed.</td>
 
</tr>
 
<tr>
 
<td>[[/InitCommand|InitCommand]]</td>
 
<td>2.0</td>
 
<td>Executed straight before sending the result of the Init command.</td>
 
</tr>
 
</table>
 
  
 
=== Enabling JavaScript plugin ===
 
=== Enabling JavaScript plugin ===
  
 
Sometimes a server side plugin might come with a client side (JavaScript) plugin, that should be automatically enabled when the server side plugin is enabled.<br>
 
Sometimes a server side plugin might come with a client side (JavaScript) plugin, that should be automatically enabled when the server side plugin is enabled.<br>
The <code>$config['Plugins']</code> variable is a special array that does that.
+
The <code>config.plugins</code> variable is a special array that does that.
  
 
To enable a plugin, simply add it's name to this array.
 
To enable a plugin, simply add it's name to this array.
<source lang="php">
+
<source>
$config['Plugins'][] = 'myplugin';
+
<cfset ArrayAppend(config.plugins, 'myplugin')>
 
</source>
 
</source>
  
Line 82: Line 56:
 
config.extraPlugins = 'myplugin';
 
config.extraPlugins = 'myplugin';
 
</source>
 
</source>
but the advantage of defining it in plugin.php is that it will be enabled only when the server side plugin is also enabled.
+
but the advantage of defining it in plugin.cfm is that it will be enabled only when the server side plugin is also enabled.
 +
 
 +
{{CKFinder_2.x Sample Plugin Introduction|ext=cfm}}
  
== Sample plugin: FileSize (adding new server side command - complete example) ==
+
For the sake of simplicity, we'll first create a simple '''plugin.cfm''' that just does exactly what we need:
==== Introduction ====
+
<source>
Probably the best way to learn new things is to write a code, so let's write a plugin that adds a new server side command to the connector. We'll create a command that returns a size of a given file.
+
<!--- (Step 2) Our event handler --->
 +
<cffunction name="CKFinderPluginFileSize" returntype="Boolean" output="true">
 +
  <cfargument name="command" required="true" type="String">
 +
  <!--- Register the "FileSize" command --->
 +
  <cfif command eq "FileSize">
 +
    <cftry>
 +
      <!--- (Step 4) Our custom component that will do the rest --->
 +
      <cfset oObject = CreateObject("component", "FileSize")>
 +
      <!---
 +
      The sendResponse method is defined in XmlCommandHandlerBase, it creates
 +
      a basic XML response and calls the buildXml()method
 +
      --->
 +
      <cfset oObject.sendResponse()>
 +
    </cftry>
 +
    <!--- false = stop further execution --->
 +
    <cfreturn false>
 +
  <cfelse>
 +
    <cfreturn true>
 +
  </cfif>
 +
</cffunction>
  
<div style="border:1px solid #000; padding:6px; margin-bottom:10px">If you're unfamiliar with CKFinder architecture, please take a look at [[CKFinder_2.x/Server_Side_Integration|Server Side Integration documentation]]. Alternatively, just bear in mind that CKFinder is an AJAX application and that the connection between the user interface running in a browser and the server connector is simply made of AJAX calls to the connector. Check AJAX calls in Firebug to better understand CKFinder.</div>
+
<!--- (Step 3) The function assigned to a hook must be available in the REQUEST scope --->
 +
<cfset REQUEST.CKFinderPluginFileSize = CKFinderPluginFileSize>
  
The FileSize plugin should provide a new command named "FileSize", in other words, calling:
+
<!--- (Step 1) Register the CKFinderPluginFileSize function to be called by the BeforeExecuteCommand hook. --->
<pre>
+
<cfset hook = arrayNew(1)>
/ckfinder/core/connector/php/connector.php?command=FileSize&type=Files&currentFolder=%2F&fileName=foobar.jpg
+
<cfset hook[1] = "BeforeExecuteCommand">
</pre>
+
<cfset hook[2] = "CKFinderPluginFileSize">
(assuming that foobar.jpg exists)
+
<cfset ArrayAppend(config.hooks, hook)>
should return a valid XML response:
+
<cfset ArrayAppend(config.plugins, 'myplugin')>
<source lang="xml"><Connector resourceType="Files">
 
<Error number="0"/>
 
<CurrentFolder path="/" url="/ckfinder/userfiles/files/" acl="255"/>
 
<FileSize size="5647"/> <------
 
</Connector>
 
 
</source>
 
</source>
  
For the sake of simplicity, we'll first create a simple '''plugin.php''' that just does exactly what we need:
+
==== Step1: Register an event handler ====
<source lang="php">
+
<source>
<?php
+
<cfset hook = arrayNew(1)>
 
+
<cfset hook[1] = "BeforeExecuteCommand">
// (2) Include base XML command handler
+
<cfset hook[2] = "CKFinderPluginFileSize">
require_once CKFINDER_CONNECTOR_LIB_DIR . "/CommandHandler/XmlCommandHandlerBase.php";
+
<cfset ArrayAppend(config.hooks, hook)>
 
 
// Since we will send a XML response, we'll extend the XmlCommandHandlerBase
 
class CKFinder_Connector_CommandHandler_FileSize extends CKFinder_Connector_CommandHandler_XmlCommandHandlerBase
 
{
 
    // (4) The buildXml method is used to construct an XML response
 
    function buildXml()
 
    {
 
        $fileName = $_GET["fileName"];
 
        $filePath = CKFinder_Connector_Utils_FileSystem::combinePaths($this->_currentFolder->getServerPath(), $fileName);
 
 
 
        // (5) Adding a <FileSize> element to the XML response.
 
        $oNode = new Ckfinder_Connector_Utils_XmlNode("FileSize");
 
        $oNode->addAttribute("size", filesize($filePath));
 
        $this->_connectorNode->addChild($oNode);
 
    }
 
 
 
    // (3) Register the "FileSize" command
 
    function onBeforeExecuteCommand( &$command )
 
    {
 
        if ( $command == 'FileSize' )
 
        {
 
            // The sendResponse method is defined in XmlCommandHandlerBase, it creates
 
            // a basic XML response and calls the buildXml()method
 
            $this->sendResponse();
 
            // false = stop further execution.
 
            return false;
 
        }
 
 
 
        return true ;
 
    }
 
}
 
 
 
$CommandHandler_FileSize = new CKFinder_Connector_CommandHandler_FileSize();
 
// (1) Register the onBeforeExecuteCommand method to be called by the BeforeExecuteCommand hook.
 
$config['Hooks']['BeforeExecuteCommand'][] = array($CommandHandler_FileSize, "onBeforeExecuteCommand");
 
 
</source>
 
</source>
  
==== Step1: Register an event handler ====
+
==== Step2: Create an event handler ====
<source lang="php">
+
Our event handler defined in (1) is called "CKFinderPluginFileSize", so let's define such function.
$config['Hooks']['BeforeExecuteCommand'][] = array($CommandHandler_FileSize, "onBeforeExecuteCommand");
+
<source>
 +
<cffunction name="CKFinderPluginFileSize" returntype="Boolean" output="true">
 +
// ...
 
</source>
 
</source>
  
==== Step2: Create a class ====
+
==== Step3: Add function to REQUEST scope ====
If your plugin returns an XML response, you'll usually just extend the XmlCommandHandlerBase class to reduce the amount of code to write, however it is not a requirement.
+
Our event handler must be available in the REQUEST scope.
<source lang="php">
 
// (2) Include base XML command handler
 
require_once CKFINDER_CONNECTOR_LIB_DIR . "/CommandHandler/XmlCommandHandlerBase.php";
 
  
// Since we will send a XML response, we'll extend the XmlCommandHandlerBase
+
==== Step4: Create a class ====
class CKFinder_Connector_CommandHandler_FileSize extends CKFinder_Connector_CommandHandler_XmlCommandHandlerBase
+
Instead of writing everything in plugin.cfm, we'll create another file, called '''FileSize.cfc''', where we'll add the rest of the code (full source code is available below).
</source>
 
  
==== Step3: Create an event handler ====
+
If your plugin returns an XML response, you'll usually just extend the XmlCommandHandlerBase component to reduce the amount of code to write, however it is not a requirement.
Our event handler defined in (1) is called "onBeforeExecuteCommand", so let's define such function.
+
<source>
<source lang="php">
+
<cfcomponent output="false" extends="CKFinder_Connector.CommandHandler.XmlCommandHandlerBase">
function onBeforeExecuteCommand( &$command )
 
// ...
 
 
</source>
 
</source>
  
==== Step4: Create the buildXml method ====
+
==== Step5: Create the buildXml method ====
In the <code>onBeforeExecuteCommand</code> method we have called <code>sendResponse</code>. The <code>sendResponse method is defined</code> in XmlCommandHandlerBase class, it creates a basic XML response and calls the <code>buildXml</code> method, which we need to define.
+
In the <code>CKFinderPluginFileSize</code> function we have called <code>sendResponse</code>. The <code>sendResponse method is defined</code> in XmlCommandHandlerBase class, it creates a basic XML response and calls the <code>buildXml</code> method, which we need to define in FileSize.cfc.
<source lang="php">
+
<source>
function buildXml()
+
<cffunction access="public" name="buildXml" hint="send XML response" returntype="boolean" description="send response" output="false">
 
// ...
 
// ...
 
</source>
 
</source>
  
==== Step5: Construct the XML response ====
+
==== Step6: Construct the XML response ====
 
The main task of the buildXml method is to construct an XML response, so we're doing it below:  
 
The main task of the buildXml method is to construct an XML response, so we're doing it below:  
<source lang="php">
+
<source>
$oNode = new Ckfinder_Connector_Utils_XmlNode("FileSize");
+
nodeFileSize = XMLElemNew(THIS.xmlObject, "FileSize");
$oNode->addAttribute("size", $size);
+
nodeFileSize.xmlAttributes["size"] = myDir.Size;
$this->_connectorNode->addChild($oNode);
+
ArrayAppend(THIS.xmlObject["Connector"].xmlChildren, nodeFileSize);
 
</source>
 
</source>
  
 
==== Full source code ====
 
==== Full source code ====
The full source code of a plugin with all necessary security checks: (save the code as '''plugin.php''' in "plugins/myplugin" folder)
+
The full source code of a plugin with all necessary security checks:
  
<source lang="php">
+
===== plugin.cfm =====
<?php
+
<source>
 +
<!--- (Step 2) Our event handler --->
 +
<cffunction name="CKFinderPluginFileSize" returntype="Boolean" output="true">
 +
  <cfargument name="command" required="true" type="String">
 +
  <!--- Register the "FileSize" command --->
 +
  <cfif command eq "FileSize">
 +
    <cftry>
 +
      <!--- (Step 4) Our custom component that will do the rest --->
 +
      <cfset oObject = CreateObject("component", "FileSize")>
 +
      <!---
 +
      The sendResponse method is defined in XmlCommandHandlerBase, it creates
 +
      a basic XML response and calls the buildXml()method
 +
      --->
 +
      <cfset oObject.sendResponse()>
 +
      <cfcatch type="ckfinder">
 +
        <!--- If an exception occurred, send an error in the XML format --->
 +
        <cfscript>
 +
          oCommandHandler_XmlCommandHandler =  
 +
                APPLICATION.CreateCFC("CKFinder_Connector.CommandHandler.XmlCommandHandlerBase").Init();
 +
          oCommandHandler_XmlCommandHandler.sendError(#CFCATCH.ErrorCode#);
 +
        </cfscript>
 +
      </cfcatch>
 +
    </cftry>
 +
    <!--- false = stop further execution --->
 +
    <cfreturn false>
 +
  <cfelse>
 +
    <cfreturn true>
 +
  </cfif>
 +
</cffunction>
  
// A simple protection against calling this file directly.
+
<!--- (Step 3) The function assigned to a hook must be available in the REQUEST scope --->
if (!defined('IN_CKFINDER')) exit;
+
<cfset REQUEST.CKFinderPluginFileSize = CKFinderPluginFileSize>
  
// Include base XML command handler
+
<!--- Make sure that both arrays exist --->
require_once CKFINDER_CONNECTOR_LIB_DIR . "/CommandHandler/XmlCommandHandlerBase.php";
+
<cfif not structkeyexists(config, "plugins")>
 +
  <cfset config.plugins = arrayNew(1)>
 +
</cfif>
 +
<cfif not structkeyexists(config, "hooks")>
 +
  <cfset config.hooks = arrayNew(1)>
 +
</cfif>
  
// Since we will send a XML response, we'll reuse the XmlCommandHandler
+
<!--- (Step 1) Register the CKFinderPluginFileSize function to be called by the BeforeExecuteCommand hook. --->
class CKFinder_Connector_CommandHandler_FileSize extends CKFinder_Connector_CommandHandler_XmlCommandHandlerBase
+
<cfset hook = arrayNew(1)>
{
+
<cfset hook[1] = "BeforeExecuteCommand">
    // The buildXml method is used to construct an XML response
+
<cfset hook[2] = "CKFinderPluginFileSize">
    function buildXml()
+
<cfset ArrayAppend(config.hooks, hook)>
    {
+
<cfset ArrayAppend(config.plugins, 'myplugin')>
        // A "must have", checking whether the connector is enabled and the basic parameters (like current folder) are safe.
+
</source>
        $this->checkConnector();
 
        $this->checkRequest();
 
 
 
        // Checking ACL permissions, we're just getting an information about a file, so FILE_VIEW permission seems to be ok.
 
        if (!$this->_currentFolder->checkAcl(CKFINDER_CONNECTOR_ACL_FILE_VIEW)) {
 
            $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED);
 
        }
 
 
 
        // Make sure we actually received a file name
 
        if (!isset($_GET["fileName"])) {
 
            $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_NAME);
 
        }
 
  
        $fileName = CKFinder_Connector_Utils_FileSystem::convertToFilesystemEncoding($_GET["fileName"]);
+
===== FileSize.cfc =====
        $resourceTypeInfo = $this->_currentFolder->getResourceTypeConfig();
+
<source lang="cfm">
 +
<!--- Since we will send a XML response, we'll extend the XmlCommandHandlerBase --->
 +
<!--- (Step 4) Extend base XML command handler --->
 +
<cfcomponent output="false" extends="CKFinder_Connector.CommandHandler.XmlCommandHandlerBase">
  
        // Use the resource type configuration object to check whether the extension of a file to check is really allowed.
+
<!--- (Step 5) The buildXml method is used to construct an XML response --->
        if (!$resourceTypeInfo->checkExtension($fileName)) {
+
<cffunction access="public" name="buildXml" hint="send XML response" returntype="boolean" output="false">
            $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_EXTENSION);
+
  <cfset var fileName = URL.FileName>
        }
+
  <cfset var fileSystem = APPLICATION.CreateCFC("Utils.FileSystem") />
 +
  <cfset var coreConfig = APPLICATION.CreateCFC("Core.Config")>
 +
  <cfset var currentFolderServerPath = THIS.currentFolder.getServerPath()>
 +
  <cfset var filePath = fileSystem.CombinePaths(currentFolderServerPath, fileName)>
  
        // Make sure that the file name is really ok and has not been sent by a hacker
+
  <cfif not REQUEST.CheckAuthentication()>
        if (!CKFinder_Connector_Utils_FileSystem::checkFileName($fileName) || $resourceTypeInfo->checkIsHiddenFile($fileName)) {
+
    <cfthrow errorCode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_CONNECTOR_DISABLED#" type="ckfinder" />
            $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST);
+
  </cfif>
        }
 
  
        $filePath = CKFinder_Connector_Utils_FileSystem::combinePaths($this->_currentFolder->getServerPath(), $fileName);
+
  <cfif not THIS.currentFolder.checkAcl(REQUEST.constants.CKFINDER_CONNECTOR_ACL_FILE_VIEW) >
 +
    <cfthrow errorcode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED#" type="ckfinder" />
 +
  </cfif>
  
        if (!file_exists($filePath) || !is_file($filePath)) {
+
  <cfif not fileSystem.checkFileName(fileName) or coreConfig.checkIsHiddenFile(fileName)>
            $this->_errorHandler->throwError(CKFINDER_CONNECTOR_ERROR_FILE_NOT_FOUND);
+
    <cfthrow errorcode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST#" type="ckfinder">
        }
+
  </cfif>
  
        $size = filesize($filePath);
+
  <cfset result = THIS.currentFolder.checkExtension(fileName)>
 +
  <cfif not result[1]>
 +
    <cfthrow errorcode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST#" type="ckfinder">
 +
  </cfif>
  
        // *** The main part of this plugin ****
+
  <cfif not fileexists(filePath)>
        // Adding a <FileSize> element to the XML response.
+
    <cfthrow errorcode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_FILE_NOT_FOUND#">
        $oNode = new Ckfinder_Connector_Utils_XmlNode("FileSize");
+
  </cfif>
        $oNode->addAttribute("size", $size);
 
        $this->_connectorNode->addChild($oNode);
 
    }
 
  
    // Register the "FileSize" command
+
  <cfdirectory name="myDir" action="list" directory="#currentFolderServerPath#" filter="#fileName#">
    function onBeforeExecuteCommand( &$command )
+
  <!--- (Step 6) Adding a <FileSize> element to the XML response. --->
    {
+
  <cfset nodeFileSize = XMLElemNew(THIS.xmlObject, "FileSize") >
        if ( $command == 'FileSize' )
+
  <cfset nodeFileSize.xmlAttributes["size"] = myDir.Size >
        {
+
  <cfset ArrayAppend(THIS.xmlObject["Connector"].xmlChildren, nodeFileSize) >
            // The sendResponse method is defined in XmlCommandHandlerBase, it creates
 
            // a basic XML response and calls the buildXml()method
 
            $this->sendResponse();
 
            // false = stop further execution.
 
            return false;
 
        }
 
  
        return true ;
+
  <cfreturn true>
    }
+
</cffunction>
}
 
  
$CommandHandler_FileSize = new CKFinder_Connector_CommandHandler_FileSize();
+
</cfcomponent>
// Register the onBeforeExecuteCommand method to be called by the BeforeExecuteCommand hook.
 
$config['Hooks']['BeforeExecuteCommand'][] = array($CommandHandler_FileSize, "onBeforeExecuteCommand");
 
// (Optional) Register a javascript plugin named "myplugin"
 
$config['Plugins'][] = 'myplugin';
 
 
</source>
 
</source>
  
.. and the client side (JavaScript) plugin that will call the FileSize command:
+
{{CKFinder_2.x myplugin code}}
(save the code as '''plugin.js''' in "plugins/myplugin" folder)
+
{{#CUSTOMTITLE:Writing ColdFusion Plugins}}
<source>
 
CKFinder.addPlugin( 'myplugin', function( api ) {
 
api.addFileContextMenuOption( { label : 'File Size', command : "FileSize" } , function( api, file )
 
{
 
api.connector.sendCommand( 'FileSize', { fileName : api.getSelectedFile().name }, function( xml )
 
{
 
if ( xml.checkError() )
 
return;
 
 
 
var size = xml.selectSingleNode( 'Connector/FileSize/@size' );
 
api.openMsgDialog( "The exact size of a file is: " + size.value + " bytes");
 
} );
 
});
 
});
 
</source>
 
{{#CUSTOMTITLE:Writing PHP Plugins}}
 

Latest revision as of 15:31, 25 May 2010

Note: the instruction below is valid only for ColdFusion 8 and later.
CKFinder functionality can be extended with server-side plugins. Although the full source code of the CKFinder server connector is available and can be modified in any way desired, a much better way of enhancing the CKFinder connector is to create a plugin.

The main advantages of plugins are:

  • Upgrades are much easier.
  • The plugin code is stored in a single place.
  • Plugins can be easily disabled when they are not needed anymore.

Common use cases:

  • Adding a new server-side command (i.e. fileditor and imageresize plugin).
  • Working with uploaded files (i.e. watermark plugin).
  • Extending information returned by the Init command (i.e. imageresize plugin).

Creating a plugin

Step1: Create plugin folder

Create a directory for your plugin inside of the "plugins" directory (by default CKFinder comes with three plugins: dummy, fileeditor, imagreresize). Let's use "myplugin" as the plugin name and use the same name for our new folder.

Step2: Create plugin file

Inside of your plugin's folder ("myplugin") create an empty file named plugin.cfm.

Step3: Add plugin definition

// Let's create an empty file for now

Step4: Enable plugin

Include your plugin with include() in the ColdFusion configuration file (config.cfm):

<cfscript>
include( "plugins/myplugin/plugin.cfm" );
</cfscript>

ColdFusion plugin system

Hooks

CKFinder provides several hooks that can be used to extend the functionality of the CKFinder application. Assigning a function (also known as an event handler) to a hook will cause that function to be called at the appropriate point in the main CKFinder code, to perform whatever additional task(s) the developer thinks would be useful at that point. Each hook can have multiple handlers assigned to it, in which case it will call the functions in the order that they are assigned.

Hooks should be assigned in a plugin file. To assign a function to a hook, add an array to config.hooks:

<cfset hook = arrayNew(1)>
<cfset hook[1] = "BeforeExecuteCommand">     <!--- Hook name --->
<cfset hook[2] = "CKFinderPluginSaveFile">   <!--- Function name --->
<cfset ArrayAppend(config.hooks, hook)>

Available hooks

Hook Since Description
AfterFileUpload 2.0 Executed after successful file upload.
BeforeExecuteCommand 2.0 Executed before a server side command is executed.
InitCommand 2.0 Executed straight before sending the result of the Init command.

Enabling JavaScript plugin

Sometimes a server side plugin might come with a client side (JavaScript) plugin, that should be automatically enabled when the server side plugin is enabled.
The config.plugins variable is a special array that does that.

To enable a plugin, simply add it's name to this array.

<cfset ArrayAppend(config.plugins, 'myplugin')>

This code is virtually equal to specyfying in config.js:

config.extraPlugins = 'myplugin';

but the advantage of defining it in plugin.cfm is that it will be enabled only when the server side plugin is also enabled.

Sample plugin: FileSize (adding new server side command - complete example)

Introduction

Probably the best way to learn new things is to write a code, so let's write a plugin that adds a new server side command to the connector. We'll create a command that returns a size of a given file.

If you're unfamiliar with CKFinder architecture, please take a look at Server Side Integration documentation. Alternatively, just bear in mind that CKFinder is an AJAX application and that the connection between the user interface running in a browser and the server connector is simply made of AJAX calls to the connector. Check AJAX calls in Firebug to better understand CKFinder.

The FileSize plugin should provide a new command named "FileSize", in other words, calling:

/ckfinder/core/connector/cfm/connector.cfm?command=FileSize&type=Files&currentFolder=%2F&fileName=foobar.jpg

(assuming that foobar.jpg exists) should return a valid XML response:

<Connector resourceType="Files">
<Error number="0"/>
<CurrentFolder path="/" url="/ckfinder/userfiles/files/" acl="255"/>
<FileSize size="5647"/>
</Connector>

For the sake of simplicity, we'll first create a simple plugin.cfm that just does exactly what we need:

<!--- (Step 2) Our event handler --->
<cffunction name="CKFinderPluginFileSize" returntype="Boolean" output="true">
  <cfargument name="command" required="true" type="String">
  <!--- Register the "FileSize" command --->
  <cfif command eq "FileSize">
    <cftry>
      <!--- (Step 4) Our custom component that will do the rest --->
      <cfset oObject = CreateObject("component", "FileSize")>
      <!---
      The sendResponse method is defined in XmlCommandHandlerBase, it creates 
      a basic XML response and calls the buildXml()method 
      --->
      <cfset oObject.sendResponse()>
    </cftry>
    <!--- false = stop further execution --->
    <cfreturn false>
  <cfelse>
    <cfreturn true>
  </cfif>
</cffunction>

<!--- (Step 3) The function assigned to a hook must be available in the REQUEST scope --->
<cfset REQUEST.CKFinderPluginFileSize = CKFinderPluginFileSize>

<!--- (Step 1) Register the CKFinderPluginFileSize function to be called by the BeforeExecuteCommand hook. --->
<cfset hook = arrayNew(1)>
<cfset hook[1] = "BeforeExecuteCommand">
<cfset hook[2] = "CKFinderPluginFileSize">
<cfset ArrayAppend(config.hooks, hook)>
<cfset ArrayAppend(config.plugins, 'myplugin')>

Step1: Register an event handler

<cfset hook = arrayNew(1)>
<cfset hook[1] = "BeforeExecuteCommand">
<cfset hook[2] = "CKFinderPluginFileSize">
<cfset ArrayAppend(config.hooks, hook)>

Step2: Create an event handler

Our event handler defined in (1) is called "CKFinderPluginFileSize", so let's define such function.

<cffunction name="CKFinderPluginFileSize" returntype="Boolean" output="true">
// ...

Step3: Add function to REQUEST scope

Our event handler must be available in the REQUEST scope.

Step4: Create a class

Instead of writing everything in plugin.cfm, we'll create another file, called FileSize.cfc, where we'll add the rest of the code (full source code is available below).

If your plugin returns an XML response, you'll usually just extend the XmlCommandHandlerBase component to reduce the amount of code to write, however it is not a requirement.

<cfcomponent output="false" extends="CKFinder_Connector.CommandHandler.XmlCommandHandlerBase">

Step5: Create the buildXml method

In the CKFinderPluginFileSize function we have called sendResponse. The sendResponse method is defined in XmlCommandHandlerBase class, it creates a basic XML response and calls the buildXml method, which we need to define in FileSize.cfc.

<cffunction access="public" name="buildXml" hint="send XML response" returntype="boolean" description="send response" output="false">
// ...

Step6: Construct the XML response

The main task of the buildXml method is to construct an XML response, so we're doing it below:

nodeFileSize = XMLElemNew(THIS.xmlObject, "FileSize");
nodeFileSize.xmlAttributes["size"] = myDir.Size;
ArrayAppend(THIS.xmlObject["Connector"].xmlChildren, nodeFileSize);

Full source code

The full source code of a plugin with all necessary security checks:

plugin.cfm
<!--- (Step 2) Our event handler --->
<cffunction name="CKFinderPluginFileSize" returntype="Boolean" output="true">
  <cfargument name="command" required="true" type="String">
  <!--- Register the "FileSize" command --->
  <cfif command eq "FileSize">
    <cftry>
      <!--- (Step 4) Our custom component that will do the rest --->
      <cfset oObject = CreateObject("component", "FileSize")>
      <!---
      The sendResponse method is defined in XmlCommandHandlerBase, it creates 
      a basic XML response and calls the buildXml()method 
      --->
      <cfset oObject.sendResponse()>
      <cfcatch type="ckfinder">
        <!--- If an exception occurred, send an error in the XML format --->
        <cfscript>
          oCommandHandler_XmlCommandHandler = 
                APPLICATION.CreateCFC("CKFinder_Connector.CommandHandler.XmlCommandHandlerBase").Init();
          oCommandHandler_XmlCommandHandler.sendError(#CFCATCH.ErrorCode#);
        </cfscript>
      </cfcatch>
    </cftry>
    <!--- false = stop further execution --->
    <cfreturn false>
  <cfelse>
    <cfreturn true>
  </cfif>
</cffunction>

<!--- (Step 3) The function assigned to a hook must be available in the REQUEST scope --->
<cfset REQUEST.CKFinderPluginFileSize = CKFinderPluginFileSize>

<!--- Make sure that both arrays exist --->
<cfif not structkeyexists(config, "plugins")>
  <cfset config.plugins = arrayNew(1)>
</cfif>
<cfif not structkeyexists(config, "hooks")>
  <cfset config.hooks = arrayNew(1)>
</cfif>

<!--- (Step 1) Register the CKFinderPluginFileSize function to be called by the BeforeExecuteCommand hook. --->
<cfset hook = arrayNew(1)>
<cfset hook[1] = "BeforeExecuteCommand">
<cfset hook[2] = "CKFinderPluginFileSize">
<cfset ArrayAppend(config.hooks, hook)>
<cfset ArrayAppend(config.plugins, 'myplugin')>
FileSize.cfc
<!--- Since we will send a XML response, we'll extend the XmlCommandHandlerBase --->
<!--- (Step 4) Extend base XML command handler --->
<cfcomponent output="false" extends="CKFinder_Connector.CommandHandler.XmlCommandHandlerBase">

<!--- (Step 5) The buildXml method is used to construct an XML response --->
<cffunction access="public" name="buildXml" hint="send XML response" returntype="boolean" output="false">
  <cfset var fileName = URL.FileName>
  <cfset var fileSystem = APPLICATION.CreateCFC("Utils.FileSystem") />
  <cfset var coreConfig = APPLICATION.CreateCFC("Core.Config")>
  <cfset var currentFolderServerPath = THIS.currentFolder.getServerPath()>
  <cfset var filePath = fileSystem.CombinePaths(currentFolderServerPath, fileName)>

  <cfif not REQUEST.CheckAuthentication()>
    <cfthrow errorCode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_CONNECTOR_DISABLED#" type="ckfinder" />
  </cfif>

  <cfif not THIS.currentFolder.checkAcl(REQUEST.constants.CKFINDER_CONNECTOR_ACL_FILE_VIEW) >
    <cfthrow errorcode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_UNAUTHORIZED#" type="ckfinder" />
  </cfif>

  <cfif not fileSystem.checkFileName(fileName) or coreConfig.checkIsHiddenFile(fileName)>
    <cfthrow errorcode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST#" type="ckfinder">
  </cfif>

  <cfset result = THIS.currentFolder.checkExtension(fileName)>
  <cfif not result[1]>
    <cfthrow errorcode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_INVALID_REQUEST#" type="ckfinder">
  </cfif>

  <cfif not fileexists(filePath)>
    <cfthrow errorcode="#REQUEST.constants.CKFINDER_CONNECTOR_ERROR_FILE_NOT_FOUND#">
  </cfif>

  <cfdirectory name="myDir" action="list" directory="#currentFolderServerPath#" filter="#fileName#">
  <!--- (Step 6) Adding a <FileSize> element to the XML response. --->
  <cfset nodeFileSize = XMLElemNew(THIS.xmlObject, "FileSize") >
  <cfset nodeFileSize.xmlAttributes["size"] = myDir.Size >
  <cfset ArrayAppend(THIS.xmlObject["Connector"].xmlChildren, nodeFileSize) >

  <cfreturn true>
</cffunction>

</cfcomponent>
plugin.js

.. and the client side (JavaScript) plugin that will call the FileSize command: (save the code as plugin.js in "plugins/myplugin" folder)

CKFinder.addPlugin( 'myplugin', function( api ) {
	api.addFileContextMenuOption( { label : 'File Size', command : "FileSize" } , function( api, file )
	{
		api.connector.sendCommand( 'FileSize', { fileName : api.getSelectedFile().name }, function( xml )
		{
			if ( xml.checkError() )
				return;

			var size = xml.selectSingleNode( 'Connector/FileSize/@size' );
			api.openMsgDialog( "", "The exact size of a file is: " + size.value + " bytes");
		} );
	});
});

This page was last edited on 25 May 2010, at 15:31.