# HOWTO {#howto}

\tableofcontents

If you did not find the answer you are looking for, send us your question: https://cksource.com/contact

@section howto_folder_instance Different Folder per Instance

If you use several CKFinder instances, you can use different `id` attributes and pass them to the server connector requests.

~~~
CKFinder.start( { id: 'instanceNo1', pass: 'id' } );
CKFinder.start( { id: 'instanceNo2', pass: 'id' } );
~~~

On connector side you can get the name of the current instance with <code>$_GET['id']</code>, and use it for dynamic configuration modification. This way you can make each instance use its own root folder for the local file system backend.

~~~
$config['backends'][] = array(
    'name'    => 'default',
    'adapter' => 'local',
    'baseUrl' => getBaseUrlByInstance($_GET['id']),
    'root'    => getRootByInstance($_GET['id'])
);
~~~

For security reasons you should avoid using the instance name directly in the directory path and use a kind of a whitelist.
The `getRootByInstance()` function used in the configuration example above may look as follows:

~~~
function getRootByInstance($instanceId)
{
    $pathMap = array(
        'instanceNo1' => '/instance/no1/path',
        'instanceNo2' => '/instance/no2/path'
    );

    foreach ($pathMap as $id => $path) {
        if ($id === $instanceId) {
            return $path;
        }
    }

    throw new \Exception('Invalid instance id', Error::CUSTOM_ERROR); // If no valid instance id was passed.
}
~~~

@section howto_private_folders Private Folders per User

To create separate directories for users, you need to create a simple mechanism to map the current user to an appropriate directory path.
When building the directory path you should remember not to reveal any sensitive information, and do not use any insecure data that may lead to path traversal attacks. In this example a `sha1` hash of the current user name is used.

~~~
$userDirectory = sha1($user->getUsername());

$config['backends'][] = array(
    'name'    => 'default',
    'adapter' => 'local',
    'baseUrl' => 'http://example.com/ckfinder/userfiles/' . $userDirectory,
    'root'    => '/path/to/ckfinder/userfiles/' . $userDirectory
);
~~~

**Note**: When creating private directories for users you should also remember about [CKFinder internal directories](@ref configuration_options_privateDir), which should be separated, too.
~~~
$config['privateDir'] = array(
    'backend' => 'default',
    'tags'    => '/path/to/ckfinder/private/dir/.ckfinder/' . $userDirectory . '/tags',
    'logs'    => '/path/to/ckfinder/private/dir/.ckfinder/' . $userDirectory . '/logs',
    'cache'   => '/path/to/ckfinder/private/dir/.ckfinder/' . $userDirectory . '/cache',
    'thumbs'  => '/path/to/ckfinder/private/dir/.ckfinder/' . $userDirectory . '/cache/thumbs',
);
~~~
@section howto_files_outside_docroot Storing Files Outside Document Root

In some cases you may want to store CKFinder users' files in a folder that is not directly accessible from the web. You can do this by setting an explicit folder path for the backend with the `root` option:

~~~
$config['backends'][] = array(
    'name'    => 'default',
    'adapter' => 'local',
    'root'    => '/my/private/directory/path',
    'baseUrl' => '/download.php?path='
);
~~~

In the example above the `baseUrl` option was also modified, so the current file path in CKFinder will be appended as a `path` parameter. This way it is still possible to build a URL pointing to a file.
Access to files is managed by a PHP script `download.php` which may perform additional operations, like logging, authentication and security checks.

Alternatively, you can use the @ref command_proxy command introduced in version 3.1.0 of CKFinder:

~~~
$config['backends'][] = array(
    'name'            => 'default',
    'adapter'         => 'local',
    'root'            => '/my/private/directory/path',
    'useProxyCommand' => true
);
~~~

With above configuration all links to files will point to `Proxy` command, and files will be served by PHP connector.

An example link to a file if `useProxyCommand` option is enabled:

~~~~~~~~~~~~~
/ckfinder/core/connector/php/connector.php?command=Proxy&type=Files&currentFolder=/&fileName=foo.jpg
~~~~~~~~~~~~~

**Note**: If you decide to `useProxyCommand` option, all links generated by CKFinder will be dependent on connector to work properly.
    

@section howto_securing_userfiles Securing Userfiles Folder

On Apache you may disable the PHP engine in the destination folder where all uploaded files will be stored. If you chose a different user files folder, copy the `/ckfinder/userfiles/.htaccess` file with these settings from its default location and paste it into your destination folder.

CKFinder by default stores private files (like thumbnails, cache, logs) in a hidden `userfiles/.ckfinder` directory. These files may contain sensitive data, so best practice is to restrict access to them.
On an Apache server access to private folders is disabled by automatically created `.htaccess` files.
The best practice is to move CKFinder private folders out of the document root, to make them inaccessible from the web.

You can define a separate backend to be used for private data in the [private directories](@ref configuration_options_privateDir) configuration option:

~~~
$config['privateDir'] = array(
    'backend' => 'private_files',
    'tags'    => './ckfinder/tags',
    'logs'    => './ckfinder/logs',
    'cache'   => './ckfinder/cache',
    'thumbs'  => './ckfinder/cache/thumbs',
);

$config['backends'][] = array(
    'name'         => 'private_files',
    'adapter'      => 'local',
    'root'         => '/private/files/path',
    'chmodFiles'   => 0755,
    'chmodFolders' => 0755,
);
~~~

@section howto_disk_quota Disk Quota

In this example it is assumed that you have implemented your own logic for checking user storage quota with the `isQuotaAvailable()` method.
You can attach this logic to [before command events](@ref before_command_events) in CKFinder for commands that you want to check
(in case of checking quota: commands like `FileUpload`, `CopyFiles`, `ImageResize`, `CreateFolder`).

See the [DiskQuota plugin sample](https://github.com/ckfinder/ckfinder-docs-samples-php/tree/master/src/DiskQuota) for the complete source code implementing this functionality.

@section howto_logging_actions Logging User Actions

In this example the goal is to create a plugin for logging user actions to a file. This can be achieved using the [events](@ref events) system.
For the purpose of this example let us assume that all user actions corresponding to [intermediate events](@ref intermediate_events) are to be logged.
In order to do that, a simple event listener needs to be created and attached to the events that should be logged.

See the [UserActionsLogger plugin sample](https://github.com/ckfinder/ckfinder-docs-samples-php/tree/master/src/UserActionsLogger) for the complete source code implementing this functionality.

**Note**: The UserActionsLogger plugin is just an example. In a real application you should remember to take care of concurrent file access by multiple scripts, i.e. you should use file locking.

If the plugin was registered properly, you should see output similar to below in your log file.
~~~
[2015.02.13 13:22:54] - dummyUser1 : ckfinder.createFolder.create
[2015.02.13 13:22:59] - dummyUser1 : ckfinder.uploadFile.upload
[2015.02.13 13:22:59] - dummyUser1 : ckfinder.thumbnail.createThumbnail
[2015.02.13 13:23:04] - dummyUser1 : ckfinder.renameFile.rename
[2015.02.13 13:23:04] - dummyUser1 : ckfinder.thumbnail.createThumbnail
[2015.02.13 13:23:09] - dummyUser1 : ckfinder.moveFiles.move
[2015.02.13 13:23:10] - dummyUser1 : ckfinder.thumbnail.createThumbnail
[2015.02.13 13:23:14] - dummyUser1 : ckfinder.copyFiles.copy
[2015.02.13 13:23:16] - dummyUser1 : ckfinder.thumbnail.createThumbnail
[2015.02.13 13:23:20] - dummyUser1 : ckfinder.deleteFiles.delete
~~~

In the example above a generic listener was created to log very basic information about events. When a particular event is dispatched, a more specific event object is passed as the listener parameter. It contains more information about the current operation, like the path of the deleted file, uploaded file content etc.
Please refer to the [Events](@ref events) section for more detailed information about types of event object parameters passed for particular events.

@section howto_custom_commands Custom Commands

This example presents a simple command plugin that returns basic information about a file.

See the [GetFileInfo plugin sample](https://github.com/ckfinder/ckfinder-docs-samples-php/tree/master/src/GetFileInfo) for the complete source code implementing this functionality.

If this plugin is enabled, you can call an additional `GetFileInfo` command that returns some very basic
information about a file, like the size and the last modification timestamp. This behavior can be simply altered to return any 
other information about the file (for example EXIF data for images or ID3 tags for mp3 files).

**GetFileInfo**
<table class="command-desc">
    <tr>
        <td>Description</td>
        <td>
            Returns basic information about a file.
        </td>
    </tr>
    <tr>
        <td>Method</td>
        <td>
            `GET`
        </td>
    </tr>
    <tr>
        <td>Sample request</td>
        <td>
Get basic information about the `foo.png` file located in the `sub1` directory of the `Files` resource type.
~~~
/ckfinder/core/connector/php/connector.php?command=GetFileInfo&type=Files&currentFolder=/sub1/&fileName=foo.png
~~~
        </td>
    </tr>
    <tr>
        <td>Sample response</td>
        <td>
~~~~~~~~~~~~~
{
    "resourceType": "Files",
    "currentFolder": {
        "path": "/sub1/",
        "url": "/ckfinder/userfiles/files/sub1/",
        "acl": 255
    },
    "type": "file",
    "path":"files\/sub1\/1.png",
    "timestamp":1425909932,
    "size":1336
}
~~~~~~~~~~~~~
        </td>
    </tr>
    <tr>
        <td>Notes</td>
        <td>
        The above response has also appended additional information about the resource type and current folder, which is the default
		behavior of CKFinder JSON responses. You can disable this by calling:
~~~
$workingFolder->omitResponseInfo();
~~~
        Another solution is to return any other type of [Response](https://symfony.com/doc/current/components/http_foundation/introduction.html#response) object directly from the `execute` method.

        </td>
    </tr>
</table>

For more detailed information about commands, please refer to the @ref commands section of the CKFinder PHP connector documentation.

@section howto_resource_type_folder Pointing Resource Type to an Existing Folder

The resource type folder can be defined with the `directory` configuration option (see @ref configuration_options_resourceTypes).
The defined directory is relative to the root of the backend.

Consider the following folder structure:

~~~
    rootDir
    └── dir1
        └── dir2
            └── dir3
~~~

where the `rootDir` is the root directory defined for the backend named `default`.

The resource type can be attached to the root folder simply by not providing the `directory` configuration option:

~~~
$config['resourceTypes'][] = array(
    'name' => 'Files',
    'backend' => 'default'
);
~~~

With above configuration you will see the following folder tree in CKFinder:

~~~
    Files
    └── dir1
        └── dir2
            └── dir3
~~~

Using the `directory` option you can point the resource type to any subfolder, as presented below:

~~~
$config['resourceTypes'][] = array(
    'name' => 'Files',
    'backend' => 'default',
    'directory' => 'dir1'
);
~~~

~~~
    Files
	└── dir2
		└── dir3
~~~

Or to point to a deeper subfolder:

~~~
$config['resourceTypes'][] = array(
    'name' => 'Files',
    'backend' => 'default',
    'directory' => 'dir1/dir2'
);
~~~

~~~
    Files
	└── dir3
~~~

@section howto_php_session_performance Avoiding Performance Issues Related to PHP Sessions

By default, the PHP session mechanism uses regular files to save the session data. When a request
is sent to a PHP script that starts the session with [session_start()](https://secure.php.net/manual/en/function.session-start.php),
it locks the session file. That means that any concurrent requests that use the same session will remain pending
until the processing required by the previous request is finished.

This may be particularly undesirable when one of the requests is time-consuming, and blocks a bunch of requests that are processed relatively fast. To avoid this issue, the PHP connector closes the write access to the session with 
[session_write_close()](https://secure.php.net/manual/en/function.session-write-close.php) as soon as possible,
so requests can be processed simultaneously (see @ref configuration_options_sessionWriteClose).

PHP sessions may also cause issues with caching of the content in the browser, which may slow down the application and degrade the user experience.

The cache headers added by the session depend on the [session_cache_limiter()](https://secure.php.net/manual/en/function.session-cache-limiter.php) configuration. The `session_cache_limiter()` function needs to be called before `session_start()`.

As CKFinder connector does not control the moment when the session starts, it needs to be configured in the main application.
The automatic sending of cache headers can be turned off by providing an empty string as a parameter of the `session_cache_limiter()` function, as presented below:

~~~
session_cache_limiter('');
~~~


@section howto_caching_layer Adding a Caching Layer in the Backend Adapter

Adding a caching layer to any backend adapter type is a fairly simple task. The CKFinder PHP connector uses
the [Flysystem](https://flysystem.thephpleague.com/) abstraction layer under the hood,
so there are [quite a few](https://flysystem.thephpleague.com/docs/advanced/caching/#persistent-caching)
ready-to-use caching adapters available that can be used to decorate the regular backend adapter.

Please have a look at the following [ticket on GitHub](https://github.com/ckfinder/ckfinder/issues/365) to read about
a sample implementation of Redis cache in the S3 backend.


@section howto_custom_storage Adding Support for Custom Storage

This example presents a plugin that adds support for storing files in a database.

For the purpose of this tutorial let us assume that the files will be stored in one database table, represented by the SQL schema
shown below:

**MySQL**
~~~
CREATE TABLE files (
  id int(11) NOT NULL AUTO_INCREMENT,
  path varchar(255) NOT NULL,
  type enum('file','dir') NOT NULL,
  contents longblob,
  size int(11) NOT NULL DEFAULT 0,
  mimetype varchar(127),
  timestamp int(11) NOT NULL DEFAULT 0,
  PRIMARY KEY (id),
  UNIQUE KEY path_unique (path)
);
~~~

**SQLite**
~~~
CREATE TABLE files (
    id INTEGER PRIMARY KEY,
    path TEXT NOT NULL UNIQUE,
    type TEXT NOT NULL,
    contents BLOB,
    size INTEGER NOT NULL DEFAULT 0,
    mimetype TEXT,
    timestamp INTEGER NOT NULL DEFAULT 0
);
~~~

## Implementing a Custom Flysystem Adapter {#howto_custom_storage_flysystem_adapter}

The CKFinder 3 PHP server-side connector uses [Flysystem](https://flysystem.thephpleague.com/docs/) as a file system abstraction layer.
Flysystem offers a very convenient way for communication with various file systems using a common API, and allows
to plug adapters that can be used to communicate with any kind of custom storage. To get familiar with the concept of Flysystem adapters, have a look at the ["Creating an adapter"](https://flysystem.thephpleague.com/docs/advanced/creating-an-adapter/) article in the official Flysystem documentation.

The first step in adding a custom storage in the CKFinder 3 PHP connector is creating an implementation
of [League\\Flysystem\\AdapterInterface](https://github.com/thephpleague/flysystem/blob/master/src/AdapterInterface.php).
This interface defines all the methods that are required to communicate with the given file system &mdash; for example writing, reading
or deleting a file.

Have a look at a custom implementation of [League\\Flysystem\\AdapterInterface](https://github.com/thephpleague/flysystem/blob/master/src/AdapterInterface.php)
required to save files in a database table with the assumed schema. The [PDOAdapter](https://github.com/ckfinder/ckfinder-plugin-database-adapter-php/blob/master/src/DatabaseAdapter/PDOAdapter.php)
class uses the [PDO](https://secure.php.net/manual/en/book.pdo.php) PHP extension, which defines an interface for accessing many database systems in PHP.
The constructor of the `PDOAdapter` class takes two parameters: a valid `PDO` object and a table name where files should be stored.
The instantiation of the `PDOAdapter` class is presented below.

**MySQL**
~~~
// https://secure.php.net/manual/en/ref.pdo-mysql.connection.php
$pdo = new PDO('mysql:host=hostname;dbname=database_name', 'username', 'password');
$adapter = new PDOAdapter($pdo, 'files');
~~~

**SQLite**
~~~
// https://secure.php.net/manual/en/ref.pdo-sqlite.connection.php
$pdo = new PDO('sqlite:/absolute/path/to/database.sqlite');
$adapter = new PDOAdapter($pdo, 'files');
~~~

## Registering a Custom Adapter with a Plugin {#howto_custom_storage_plugin}

With the implementation of [League\\Flysystem\\AdapterInterface](https://github.com/thephpleague/flysystem/blob/master/src/AdapterInterface.php) ready, it is now
time to tell the CKFinder connector to use it. The most convenient way is by creating a connector plugin (see @ref plugins_development) to make it possible to expose plugin options and configure them in the connector [configuration](@ref configuration). The example below presents a connector plugin that registers the `PDOAdapter` implemented in the previous step.

~~~
namespace CKSource\CKFinder\Plugin\DatabaseAdapter;

// This line may not be needed if the autoloader can load from the CKFinder plugins directory.
require_once __DIR__ . '/PDOAdapter.php';

use CKSource\CKFinder\CKFinder;
use CKSource\CKFinder\Plugin\PluginInterface;
use \PDO;

class DatabaseAdapter implements PluginInterface
{
    public function setContainer(CKFinder $app)
    {
        $backendFactory = $app->getBackendFactory();

        // Register a backend adapter named "database".
        $backendFactory->registerAdapter('database', function($backendConfig) use ($backendFactory) {
            // Create an instance of PDOAdapter using backend options defined in the CKFinder configuration.
            $pdo = new PDO($backendConfig['dsn'], $backendConfig['username'], $backendConfig['password']);
            $adapter = new PDOAdapter($pdo, $backendConfig['tableName']);

            // Create and return a CKFinder backend instance.
            return $backendFactory->createBackend($backendConfig, $adapter);
        });
    }

    /**
     * This plugin is a bit specific, as it only uses backend configuration options.
     * This method can be ignored, and simply return an empty array.
     */
    public function getDefaultConfig()
    {
        return [];
    }
}
~~~

See the [DatabaseAdapter plugin sample](https://github.com/ckfinder/ckfinder-plugin-database-adapter-php) for the complete source code implementing this functionality.

