Contents
- 1 Creating a plugin
- 2 ColdFusion plugin system
- 3 Sample plugin: FileSize (adding new server side command - complete example)
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
andimageresize
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.
The FileSize plugin should provide a new command named "FileSize", in other words, calling:
/ckfinder/core/connector/cfm/connector.cfm?command=FileSize&type=Files¤tFolder=%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"); } ); }); });