CKFinder uses two types of plugins:
Below you can find information and examples for CKFinder ASP.NET connector plugins. For details about JavaScript plugins please refer to the Creating CKFinder 3.x JavaScript Plugins documentation.
Manual installation requires downloading plugin source and unpacking it inside the CKFinder plugins
directory. The directory structure needs to follow the following pattern:
plugins
└── PluginName
├── PluginName.dll
└── PluginDependency.dll
After installation the plugin has to be enabled in the CKFinder configuration file. See Plugins for details.
Plugins usually offer a few configuration options that can be set in the main CKFinder configuration file. Please check plugin documentation for details.
Default CKFinder behavior can be changed and extended with custom plugins. There are a few constraints that a plugin must meet in order to be recognized as valid:
Below is an example of a properly defined plugin:
using System.Collections.Generic;
using System.ComponentModel.Composition;
using CKSource.CKFinder.Connector.Core;
using CKSource.CKFinder.Connector.Core.Plugins;
[Export(typeof(IPlugin))]
public class MyPlugin : IPlugin
{
public void Initialize(
IComponentResolver componentResolver,
IReadOnlyDictionary<string, IReadOnlyCollection<string>> options)
{
}
}
Note: A plugin may implement the IDisposable interface if it needs to clean up resources during unload.
Each CKFinder plugin has to implement the IPlugin interface:
public interface IPlugin
{
void Initialize(
IComponentResolver componentResolver,
IReadOnlyDictionary<string, IReadOnlyCollection<string>> options);
}
It has one method that is invoked at the application start. In this method you should register all your event handlers and custom commands.
Plugins are configured in their Initialize method.
Options for plugins are passed in the ConnectorBuilder.AddPlugin method or as a collection of <option />
elements in the <plugins />
section of the configuration file.
If you are using XML configuration and need to supply multiple values for a key, just add options with the same key.
The plugin receives a dictionary with a collection of values as a parameter in the Initialize method: IReadOnlyDictionary<string, IReadOnlyCollection<string>> options
. It is up to the developer to check if options contain valid values. If an option is not provided at all, the dictionary will return an empty collection of values.
For example, let us assume you want to create a plugin named ImageWatermark
. The plugin should work in the following way:
The basic code structure of the ImageWatermark
plugin class can look as below:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using CKSource.CKFinder.Connector.Core;
using CKSource.CKFinder.Connector.Core.Events;
using CKSource.CKFinder.Connector.Core.Events.Messages;
using CKSource.CKFinder.Connector.Core.Nodes;
using CKSource.CKFinder.Connector.Core.Plugins;
using CKSource.CKFinder.Connector.Core.ResizedImages;
using ImageProcessor;
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Imaging;
[Export(typeof(IPlugin))]
public class WatermarkPlugin : IPlugin, IDisposable
{
private Image _watermarkImage;
private object _fileUploadSubscription;
private IEventAggregator _eventAggregator;
/*
* Initialize the plugin.
* This method is called just once when the plugin is being loaded.
*/
public void Initialize(
IComponentResolver componentResolver,
IReadOnlyDictionary<string, IReadOnlyCollection<string>> options)
{
/*
* Get the path to the watermark image.
* Warning: For simplicity this code is unsafe and will throw InvalidOperationException
* if watermarkPath was not provided in the options.
*/
var watermarkImagePath = options["watermarkPath"].First();
_watermarkImage = Image.FromFile(watermarkImagePath);
/*
* Resolve IEventAggregator.
* IEventAggregator is a singleton and it is safe to resolve it at any time in the plugin.
*/
_eventAggregator = componentResolver.Resolve<IEventAggregator>();
/*
* Subscribe to the FileUploadEvent event.
*/
_fileUploadSubscription = _eventAggregator.Subscribe<FileUploadEvent>(
next => async messageContext => await OnFileUpload(messageContext, next));
}
/*
* Dispose resources.
* This method will be called when the plugin implements the IDisposable interface and the plugin needs to be unloaded.
*/
public void Dispose()
{
/*
* Unsubscribe from the FileUploadEvent event.
*/
_eventAggregator.Unsubscribe(_fileUploadSubscription);
/*
* Free memory used by the watermark image.
*/
_watermarkImage.Dispose();
}
/*
* The FileUploadEvent handler.
*/
private async Task OnFileUpload(
MessageContext<FileUploadEvent> messageContext,
EventHandlerFunc<FileUploadEvent> next)
{
var stream = messageContext.Message.Stream;
/*
* Resolve IImageSettings.
* IImageSettings contains CKFinder's internal settings for images.
*/
var imageSettings = messageContext.ComponentResolver.Resolve<IImageSettings>();
/*
* Save the original position of the stream.
*/
var originalPosition = stream.Position;
/*
* Create ImageFactory.
* ImageFactory is a third-party component. See http://imageprocessor.org/ for more information.
*/
using (var imageFactory = new ImageFactory())
{
try
{
/*
* Load the image from the stream.
*/
imageFactory.Load(stream);
/*
* Restore the original position of the stream.
*/
stream.Position = originalPosition;
}
catch (ImageFormatException)
{
/*
* This is not an image or the format is not supported.
* We do not care about this now, just call the next event handler in
* the chain and return to caller.
*/
await next(messageContext);
return;
}
/*
* Apply the watermark image.
*/
imageFactory.Overlay(new ImageLayer
{
Image = _watermarkImage, Opacity = 50, Size = imageFactory.Image.Size
});
/*
* Determine the encoding format based on the file name.
*/
var format = ImageFormats.GetFormatForFile(messageContext.Message.File);
format.Quality = imageSettings.Quality;
/*
* Apply the encoding format and save the modified image to the stream.
*/
imageFactory.Format(format);
imageFactory.Save(stream);
}
/*
* Call the next event handler in the chain.
*/
await next(messageContext);
}
}
You can find a complete working example of the ImageWatermark
plugin on GitHub.