diff --git a/composer.json b/composer.json index a2de6d7f7..38c30bc87 100644 --- a/composer.json +++ b/composer.json @@ -18,19 +18,20 @@ }, "require": { "php": ">=5.4.0", - "ext-imagick": ">=2.0", + "ext-imagick": ">=3.0.1", "symfony/http-foundation": "2.*" }, "require-dev": { "mikey179/vfsStream": "1.*", - "doctrine/dbal": "2.*", "phpunit/phpunit": "3.*", "behat/behat": "2.*", - "guzzle/guzzle": "3.*" + "guzzle/guzzle": "3.*", + "doctrine/dbal": "2.*" }, "suggest": { - "ext-mongo": ">=1.3.0", - "ext-memcached": ">=2.0.0" + "ext-mongo": "Enables usage of MongoDB and GridFS as database and store. Recommended version: >=1.4.0", + "ext-memcached": "Enables usage of the Memcached cache adapter for custom event listeners. Recommended version: >=2.0.0", + "doctrine/dbal": "Enables usage of using RDMS for storing data (and optionally images). Recommended version: >=2.3" }, "autoload": { "psr-0": { diff --git a/composer.lock b/composer.lock index d4159d47b..1b386186b 100644 --- a/composer.lock +++ b/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "0ce22fafe56e9e7e36f77b550707ef90", + "hash": "ff4d84ad4435a8e93e190ec35f3eda95", "packages": [ { "name": "symfony/http-foundation", @@ -237,9 +237,9 @@ "email": "kontakt@beberlei.de" }, { - "name": "Johannes M. Schmitt", + "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", + "homepage": "http://jmsyst.com", "role": "Developer of wrapped JMSSerializerBundle" } ], @@ -319,16 +319,16 @@ }, { "name": "guzzle/guzzle", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "v3.7.0" + "reference": "v3.7.1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/v3.7.0", - "reference": "v3.7.0", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/v3.7.1", + "reference": "v3.7.1", "shasum": "" }, "require": { @@ -407,7 +407,7 @@ "rest", "web service" ], - "time": "2013-06-11 00:24:07" + "time": "2013-07-05 20:17:54" }, { "name": "mikey179/vfsStream", @@ -441,16 +441,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "1.2.11", + "version": "1.2.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "1.2.11" + "reference": "1.2.12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.11", - "reference": "1.2.11", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.12", + "reference": "1.2.12", "shasum": "" }, "require": { @@ -460,13 +460,18 @@ "phpunit/php-token-stream": ">=1.1.3@stable" }, "require-dev": { - "phpunit/phpunit": "3.7.*" + "phpunit/phpunit": "3.7.*@dev" }, "suggest": { "ext-dom": "*", "ext-xdebug": ">=2.0.5" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, "autoload": { "classmap": [ "PHP/" @@ -493,7 +498,7 @@ "testing", "xunit" ], - "time": "2013-05-23 18:23:24" + "time": "2013-07-06 06:26:16" }, { "name": "phpunit/php-file-iterator", @@ -675,16 +680,16 @@ }, { "name": "phpunit/phpunit", - "version": "3.7.21", + "version": "3.7.22", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3.7.21" + "reference": "3.7.22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.21", - "reference": "3.7.21", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.22", + "reference": "3.7.22", "shasum": "" }, "require": { @@ -693,12 +698,12 @@ "ext-reflection": "*", "ext-spl": "*", "php": ">=5.3.3", - "phpunit/php-code-coverage": ">=1.2.1,<1.3.0", + "phpunit/php-code-coverage": "~1.2.1", "phpunit/php-file-iterator": ">=1.3.1", "phpunit/php-text-template": ">=1.1.1", - "phpunit/php-timer": ">=1.0.2,<1.1.0", - "phpunit/phpunit-mock-objects": ">=1.2.0,<1.3.0", - "symfony/yaml": ">=2.0,<3.0" + "phpunit/php-timer": "~1.0.2", + "phpunit/phpunit-mock-objects": "~1.2.0", + "symfony/yaml": "~2.0" }, "require-dev": { "pear-pear/pear": "1.9.4" @@ -745,7 +750,7 @@ "testing", "xunit" ], - "time": "2013-05-23 18:54:29" + "time": "2013-07-06 06:29:15" }, { "name": "phpunit/phpunit-mock-objects", @@ -1213,7 +1218,7 @@ ], "platform": { "php": ">=5.4.0", - "ext-imagick": ">=2.0" + "ext-imagick": ">=3.0.1" }, "platform-dev": [ diff --git a/config/config.default.php b/config/config.default.php index 8d7072884..a293ff2b6 100644 --- a/config/config.default.php +++ b/config/config.default.php @@ -153,18 +153,18 @@ * @var array */ 'eventListeners' => array( - 'auth' => function() { - return new EventListener\Authenticate(); - }, 'accessToken' => function() { return new EventListener\AccessToken(); }, + 'auth' => function() { + return new EventListener\Authenticate(); + }, 'statsAccess' => function() { return new EventListener\StatsAccess(array( - 'whitelist' => array('127.0.0.1'), + 'whitelist' => array('127.0.0.1', '::1'), 'blacklist' => array(), )); - } + }, ), /** @@ -241,25 +241,30 @@ }, ), + /** - * Custom routes for Imbo + * Custom resources for Imbo * + * @link http://docs.imbo-project.org * @var array */ - 'routes' => array(), + 'resources' => array(), /** - * Custom resources for Imbo + * Custom routes for Imbo * + * @link http://docs.imbo-project.org * @var array */ - 'resources' => array(), + 'routes' => array(), ); -if (file_exists(__DIR__ . '/../../../../config/config.php')) { +if (is_dir(__DIR__ . '/../../../../config')) { // Someone has installed Imbo via a custom composer.json, so the custom config is outside of - // the vendor dir - $config = array_replace_recursive($config, require __DIR__ . '/../../../../config/config.php'); + // the vendor dir. Loop through all available php files in the config dir + foreach (glob(__DIR__ . '/../../../../config/*.php') as $file) { + $config = array_replace_recursive($config, require $file); + } } else if (file_exists(__DIR__ . '/config.php')) { $config = array_replace_recursive($config, require __DIR__ . '/config.php'); } diff --git a/config/config.testing.php b/config/config.testing.php index 30884fd3f..51b96b6e2 100644 --- a/config/config.testing.php +++ b/config/config.testing.php @@ -13,7 +13,6 @@ use Imbo\Image\Transformation, Imbo\Cache, Imbo\Resource\ResourceInterface, - Imbo\EventListener\ListenerInterface, Imbo\EventListener\ListenerDefinition, Imbo\EventManager\EventInterface, Imbo\Model\ArrayModel, @@ -24,7 +23,7 @@ // Require composer autoloader require __DIR__ . '/../vendor/autoload.php'; -class CustomResource implements ResourceInterface, ListenerInterface { +class CustomResource implements ResourceInterface { public function getAllowedMethods() { return array('GET'); } @@ -45,7 +44,7 @@ public function get(EventInterface $event) { } } -class CustomResource2 implements ResourceInterface, ListenerInterface { +class CustomResource2 implements ResourceInterface { public function getAllowedMethods() { return array('GET', 'PUT'); } diff --git a/config/imbo.apache.conf.dist b/config/imbo.apache.conf.dist index d204f8f9c..1eae2a782 100644 --- a/config/imbo.apache.conf.dist +++ b/config/imbo.apache.conf.dist @@ -6,14 +6,14 @@ # ServerAlias imbo1 imbo2 imbo3 # Document root where the index.php file is located - DocumentRoot /path/to/imbo/public + DocumentRoot /path/to/install/vendor/imbo/imbo/public # Logging # CustomLog /var/log/apache2/imbo.access_log combined # ErrorLog /var/log/apache2/imbo.error_log # Rewrite rules that rewrite all requests to the index.php script - + RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteRule .* index.php diff --git a/config/imbo.nginx.conf.dist b/config/imbo.nginx.conf.dist index 3c97ad023..e51ac7397 100644 --- a/config/imbo.nginx.conf.dist +++ b/config/imbo.nginx.conf.dist @@ -9,7 +9,7 @@ server { # server_name imbo imbo1 imbo2 imbo3; # Path to the public directory where index.php is located - root /path/to/imbo/public; + root /path/to/install/vendor/imbo/imbo/public; index index.php; # Logs @@ -21,7 +21,7 @@ server { location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME /path/to/imbo/public/index.php; + fastcgi_param SCRIPT_FILENAME /path/to/install/vendor/imbo/imbo/public/index.php; include fastcgi_params; } } diff --git a/docs/advanced/cache_adapters.rst b/docs/advanced/cache_adapters.rst deleted file mode 100644 index a4bdd61df..000000000 --- a/docs/advanced/cache_adapters.rst +++ /dev/null @@ -1,71 +0,0 @@ -Cache adapters -============== - -If you want to leverage caching in a custom event listener, Imbo ships with some different solutions: - -.. _apc-cache: - -APC ---- - -This adapter uses the `APC`_ extension for caching. If your Imbo installation consists of a single httpd this is a good choice. The adapter has the following parameters: - -``$namespace`` (optional) - A namespace for your cached items. For instance: "imbo" - -Example -~~~~~~~ - -.. code-block:: php - :linenos: - - set('key', 'value'); - - echo $adapter->get('key'); // outputs "value" - -.. _memcached-cache: - -Memcached ---------- - -This adapter uses `Memcached`_ for caching. If you have multiple httpd instances running Imbo this adapter lets you share the cache between all instances automatically by letting the adapter connect to the same Memcached daemon. The adapter has the following parameters: - -``$memcached`` - An instance of the pecl/memcached class. - -``$namespace`` (optional) - A namespace for your cached items. For instance: "imbo". - -Example -~~~~~~~ - -.. code-block:: php - :linenos: - - addServer('hostname', 11211); - - $adapter = new Imbo\Cache\Memcached($memcached, 'imbo'); - $adapter->set('key', 'value'); - - echo $adapter->get('key'); // outputs "value" - -Custom adapter --------------- - -If you want to use some other cache mechanism an interface exists (``Imbo\Cache\CacheInterface``) for you to implement: - -.. literalinclude:: ../../library/Imbo/Cache/CacheInterface.php - :language: php - :linenos: - -If you choose to implement this interface you can also use your custom cache adapter for all the event listeners Imbo ships with that leverages a cache. - -If you implement an adapter that you think should be a part of Imbo feel free to send a pull request to the `project over at GitHub`_. - -.. _project over at GitHub: https://github.com/imbo/imbo -.. _APC: http://pecl.php.net/apc -.. _Memcached: http://pecl.php.net/memcached diff --git a/docs/advanced/custom_database_drivers.rst b/docs/advanced/custom_database_drivers.rst deleted file mode 100644 index 96525488b..000000000 --- a/docs/advanced/custom_database_drivers.rst +++ /dev/null @@ -1,12 +0,0 @@ -Custom database drivers -======================= - -If you wish to implement your own database driver you are free to do so. The only requirement is that you implement the ``Imbo\Database\DatabaseInterface`` interface that comes with Imbo. Below is the complete interface with comments: - -.. literalinclude:: ../../library/Imbo/Database/DatabaseInterface.php - :language: php - :linenos: - -Have a look at the existing implementations of this interface for more details. If you implement a driver that you think should be a part of Imbo feel free to send a pull request to the `project over at GitHub`_. - -.. _project over at GitHub: https://github.com/imbo/imbo diff --git a/docs/advanced/custom_event_listeners.rst b/docs/advanced/custom_event_listeners.rst deleted file mode 100644 index dfd539a47..000000000 --- a/docs/advanced/custom_event_listeners.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. _custom-event-listeners: - -Custom event listeners -====================== - -If you wish to implement your own event listeners you are free to do so. The only requirement is that you implement the ``Imbo\EventListener\ListenerInterface`` interface that comes with Imbo. Below is the complete interface with comments: - -.. literalinclude:: ../../library/Imbo/EventListener/ListenerInterface.php - :language: php - :linenos: - -Have a look at the existing implementations of this interface for more details. If you implement a listener that you think should be a part of Imbo feel free to send a pull request to the `project over at GitHub`_. - -.. _project over at GitHub: https://github.com/imbo/imbo diff --git a/docs/advanced/custom_storage_drivers.rst b/docs/advanced/custom_storage_drivers.rst deleted file mode 100644 index 41a80443d..000000000 --- a/docs/advanced/custom_storage_drivers.rst +++ /dev/null @@ -1,12 +0,0 @@ -Custom storage drivers -====================== - -If you wish to implement your own storage driver you are free to do so. The only requirement is that you implement the ``Imbo\Storage\StorageInterface`` interface that comes with Imbo. Below is the complete interface with comments: - -.. literalinclude:: ../../library/Imbo/Storage/StorageInterface.php - :language: php - :linenos: - -Have a look at the existing implementations of this interface for more details. If you implement a driver that you think should be a part of Imbo feel free to send a pull request to the `project over at GitHub`_. - -.. _project over at GitHub: https://github.com/imbo/imbo diff --git a/docs/conf.py b/docs/conf.py index bf3bff705..ca56be8f1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -173,7 +173,7 @@ # -- Options for LaTeX output -------------------------------------------------- -latex_elements = { +# latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', @@ -182,14 +182,14 @@ # Additional stuff for the LaTeX preamble. #'preamble': '', -} +# } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'Imbo.tex', u'Imbo Documentation', - u'Christer Edvartsen', 'manual'), -] +# latex_documents = [ +# ('index', 'Imbo.tex', u'Imbo Documentation', +# u'Christer Edvartsen', 'manual'), +# ] # The name of an image file (relative to this directory) to place at the top of # the title page. @@ -216,10 +216,10 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'imbo', u'Imbo Documentation', - [u'Christer Edvartsen'], 1) -] +# man_pages = [ +# ('index', 'imbo', u'Imbo Documentation', +# [u'Christer Edvartsen'], 1) +# ] # If true, show URL addresses after external links. #man_show_urls = False @@ -230,11 +230,11 @@ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) -texinfo_documents = [ - ('index', 'Imbo', u'Imbo Documentation', - u'Christer Edvartsen', 'Imbo', 'One line description of project.', - 'Miscellaneous'), -] +# texinfo_documents = [ +# ('index', 'Imbo', u'Imbo Documentation', +# u'Christer Edvartsen', 'Imbo', 'One line description of project.', +# 'Miscellaneous'), +# ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] diff --git a/docs/develop/custom_adapters.rst b/docs/develop/custom_adapters.rst new file mode 100644 index 000000000..5a5dbbe52 --- /dev/null +++ b/docs/develop/custom_adapters.rst @@ -0,0 +1,8 @@ +Implement your own database and/or storage adapter +================================================== + +If the adapters shipped with Imbo does not fit your needs you can implement your own set of database and/or storage adapters and have Imbo use them pretty easily. A set of interfaces exists for you to implement, and then all that's left to do is to enable the adapters in your configuration file. See the :ref:`Database confguration ` and :ref:`Storage configuration ` sections for more information on how to enable different adapters in the configuration. + +Custom database adapters must implement the ``Imbo\Database\DatabaseInterface`` interface, and custom storage adapters must implement the ``Imbo\Storage\StorageInterface`` interface. + +If you implement an adapter that you think should be a part of Imbo feel free to send a pull request on `GitHub `_. diff --git a/docs/develop/event_listeners.rst b/docs/develop/event_listeners.rst new file mode 100644 index 000000000..a53c1fbd6 --- /dev/null +++ b/docs/develop/event_listeners.rst @@ -0,0 +1,605 @@ +Working with events and event listeners +======================================= + +Imbo uses an event dispatcher to trigger certain events from inside the application that you can subscribe to by using event listeners. In this chapter you can find information regarding the events that are triggered, and how to be able to write your own event listeners for Imbo. There is also a section on the event listeners shipped with Imbo that you can configure to fit your needs. + +Events +------ + +When implementing an event listener you need to know about the events that Imbo triggers. The most important events are combinations of the accessed resource along with the HTTP method used. Imbo currently provides these resources: + +* :ref:`stats ` +* :ref:`status ` +* :ref:`user ` +* :ref:`images ` +* :ref:`image ` +* :ref:`shorturl ` +* :ref:`metadata ` + +Examples of events that are triggered: + +* ``image.get`` +* ``image.put`` +* ``image.delete`` +* ``metadata.get`` +* ``status.head`` +* ``stats.get`` + +As you can see from the above examples the events are built up by the resource name and the HTTP method, lowercased and separated by ``.``. + +Some other notable events: + +* ``storage.image.insert`` +* ``storage.image.load`` +* ``storage.image.delete`` +* ``db.image.insert`` +* ``db.image.load`` +* ``db.image.delete`` +* ``db.metadata.update`` +* ``db.metadata.load`` +* ``db.metadata.delete`` +* ``route`` +* ``response.send`` + +.. _custom-event-listeners: + +Writing an event listener +------------------------- + +When writing an event listener for Imbo you can choose one of the following approaches: + +1) Implement the ``Imbo\EventListener\ListenerInterface`` interface that comes with Imbo +2) Implement a callable piece of code, for instance a class with an ``__invoke`` method +3) Use an anonymous function + +Below you will find examples on the approaches mentioned above. + +.. note:: + Information regarding how to **attach** the event listeners to Imbo is available in the :ref:`event listener configuration ` section. + +Implement the ``Imbo\EventListener\ListenerInterface`` interface +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Below is the complete interface with comments: + +.. literalinclude:: ../../library/Imbo/EventListener/ListenerInterface.php + :language: php + :linenos: + +The only method you need to implement is called ``getDefinition`` and that method should return an array of ``Imbo\EventListener\ListenerDefinition`` instances. Each listener definition contains an event name, a callback, a priority and an optional list of public keys, that you can set if you want your listener to only trigger for some users. + +Below is an example of how the :ref:`authenticate-event-listener` event listener implements the ``getDefinition`` method: + +.. code-block:: php + + ` that it can work with. The fact that the above code only uses a single callback for all events is an implementation detail. You can use different callbacks for all events if you want to. + +Use a class with an ``__invoke`` method ++++++++++++++++++++++++++++++++++++++++ + +You can also keep the listener definition code out of the event listener entirely, and specify that piece of information in the Imbo configuration instead. An invokable class could for instance look like this: + +.. code-block:: php + + array( + 'customListener' => array( + 'callback' => function(Imbo\EventManager\EventInterface $event) { + // Custom code + }, + 'events' => array('image.get'), + ), + ), + + // ... + ); + +The ``$event`` object passed to the function is the same as in the previous two examples. This approach should mostly be used for testing purposes and quick hacks. More information regarding this approach is available in the :ref:`event listener configuration ` section. + + +.. _the-event-object: + +The event object +---------------- + +The object passed to the event listeners is an instance of the ``Imbo\EventManager\EventInterface`` interface. This interface has some methods that event listeners can use: + +``getName()`` + Get the name of the current event. For instance ``image.delete``. + +``getRequest()`` + Get the current request object (an instance of ``Imbo\Http\Request\Request``) + +``getResponse()`` + Get the current response object (an instance of ``Imbo\Http\Response\Response``) + +``getDatabase()`` + Get the current database adapter (an instance of ``Imbo\Database\DatabaseInterface``) + +``getStorage()`` + Get the current storage adapter (an instance of ``Imbo\Storage\StorageInterface``) + +``getManager()`` + Get the current event manager (an instance of ``Imbo\EventManager\EventManager``) + +``getConfig()`` + Get the complete Imbo configuration. This should be used with caution as it includes all authentication information regarding the Imbo users. + +``stopPropagation($flag)`` + If you want your event listener to force Imbo to skip all following listeners for the same event, call this method with ``true``. + +``propagationIsStopped()`` + This method is used by Imbo to check if a listener wants the propagation to stop. Your listener will most likely never need to use this method. + +With these methods you have access to most parts of Imbo that is worth working with. Be careful when using the database and storage adapters as these grant you access to all data stored in Imbo, with both read and write permissions. + +Event listeners shipped with Imbo +--------------------------------- + +Imbo ships with a collection of event listeners for you to use. Some of them are enabled in the default configuration file. + +.. contents:: + :local: + :depth: 1 + +.. _access-token-event-listener: + +Access token +++++++++++++ + +This event listener enforces the usage of access tokens on all read requests against user-specific resources. You can read more about how the actual access tokens work in the :ref:`access-tokens` part of the :doc:`../usage/api` chapter. + +To enforce the access token check this event listener subscribes to the following events: + +* ``user.get`` +* ``user.head`` +* ``images.get`` +* ``images.head`` +* ``image.get`` +* ``image.head`` +* ``metadata.get`` +* ``metadata.head`` + +This event listener has a single parameter that can be used to whitelist and/or blacklist certain image transformations, used when the current request is against an image resource. The parameter is an array with a single key: ``transformations``. This is another array with two keys: ``whitelist`` and ``blacklist``. These two values are arrays where you specify which transformation(s) to whitelist or blacklist. The names of the transformations are the same as the ones used in the request. See :ref:`image-transformations` for a complete list of the supported transformations. + +Use ``whitelist`` if you want the listener to skip the access token check for certain transformations, and ``blacklist`` if you want it to only check certain transformations: + +.. code-block:: php + + array( + 'whitelist' => array( + 'border', + ) + )) + +means that the access token will **not** be enforced for the :ref:`border ` transformation. + +.. code-block:: php + + array( + 'blacklist' => array( + 'border', + ) + )) + +means that the access token will be enforced **only** for the :ref:`border ` transformation. + +If both ``whitelist`` and ``blacklist`` are specified all transformations will require an access token unless it's included in ``whitelist``. + +This event listener is included in the default configuration file without specifying any transformation filters: + +.. code-block:: php + + array( + 'accessToken' => function() { + return new Imbo\EventListener\AccessToken(); + }, + ), + + // ... + ); + +Disable this event listener with care. Installations with no access token check is open for `DDoS `_ attacks. + +.. _authenticate-event-listener: + +Authenticate +++++++++++++ + +This event listener enforces the usage of signatures on all write requests against user-specific resources. You can read more about how the actual signature check works in the :ref:`signing-write-requests` section in the :doc:`../usage/api` chapter. + +To enforce the signature check for all write requests this event listener subscribes to the following events: + +* ``image.put`` +* ``image.post`` +* ``image.delete`` +* ``metadata.put`` +* ``metadata.post`` +* ``metadata.delete`` + +This event listener does not support any parameters and is enabled per default like this: + +.. code-block:: php + + array( + 'authenticate' => function() { + return new Imbo\EventListener\Authenticate(); + }, + ), + + // ... + ); + +Disable this event listener with care. User agents can delete all your images and metadata if this listener is disabled. + +.. _auto-rotate-image-event-listener: + +Auto rotate image ++++++++++++++++++ + +This event listener will auto rotate new images based on metadata embedded in the image itself (`EXIF `_). + +The listener does not support any parameters and can be enabled like this: + +.. code-block:: php + + array( + 'autoRotate' => function() { + return new Imbo\EventListener\AutoRotateImage(); + }, + ), + + // ... + ); + +If you enable this listener all new images added to Imbo will be auto rotated based on the EXIF data. This might also cause the image identifier sent in the response to be different from the one used in the URI when storing the image. This can happen with all event listeners which can possibly modify the image before storing it. + +CORS (Cross-Origin Resource Sharing) +++++++++++++++++++++++++++++++++++++ + +This event listener can be used to allow clients such as web browsers to use Imbo when the client is located on a different origin/domain than the Imbo server is. This is implemented by sending a set of CORS-headers on specific requests, if the origin of the request matches a configured domain. + +The event listener can be configured on a per-resource and per-method basis, and will therefore listen to any related events. If enabled without any specific configuration, the listener will allow and respond to the **GET**, **HEAD** and **OPTIONS** methods on all resources. Note however that no origins are allowed by default and that a client will still need to provide a valid access token, unless the :ref:`Access token listener ` is disabled. + +Here is an example on how to enable the CORS listener: + +.. code-block:: php + + array( + 'cors' => function() { + return new Imbo\EventListener\Cors(array( + 'allowedOrigins' => array('http://some.origin'), + 'allowedMethods' => array( + 'image' => array('GET', 'HEAD', 'PUT'), + 'images' => array('GET', 'HEAD'), + ), + 'maxAge' => 3600, + )); + }, + ), + + // ... + ); + +``allowedOrigins`` + is an array of allowed origins. Specifying ``*`` as a value in the array will allow any origin. + +``allowedMethods`` + is an associative array where the keys represent the resource (``shorturl``, ``status``, ``stats``, ``user``, ``images``, ``image`` and ``metadata``) and the values are arrays of HTTP methods you wish to open up. + +``maxAge`` + specifies how long the response of an OPTIONS-request can be cached for, in seconds. Defaults to 3600 (one hour). + +Exif metadata ++++++++++++++ + +This event listener can be used to fetch the EXIF-tags from uploaded images and adding them as metadata. Enabling this event listener will not populate metadata for images already added to Imbo. + +The event listener subscribes to the following events: + +* ``image.put`` +* ``db.image.insert`` + +and has the following parameters: + +``$allowedTags`` + The tags you want to be populated as metadata, if present. Optional - by default all tags are added. + +and is enabled like this: + +.. code-block:: php + + array( + 'exifMetadata' => function() { + return new Imbo\EventListener\ExifMetadata(array( + 'exif:Make', + 'exif:Model', + )); + }, + ), + + // ... + ); + +which would allow only ``exif:Make`` and ``exif:Model`` as metadata tags. Not passing an array to the constructor will allow all tags. + +Image transformation cache +++++++++++++++++++++++++++ + +This event listener enables caching of image transformations. Read more about image transformations in the :ref:`image-transformations` section. + +To achieve this the listener subscribes to the following events: + +* ``image.get`` (both before and after the main application logic) +* ``image.delete`` + +The event listener has one parameter: + +``$path`` + Root path where the cached images will be stored. + +and is enabled like this: + +.. code-block:: php + + array( + 'imageTransformationCache' => function() { + return new Imbo\EventListener\ImageTransformationCache('/path/to/cache'); + }, + ), + + // ... + ); + +.. note:: + This event listener uses a similar algorithm when generating file names as the :ref:`filesystem-storage-adapter` storage adapter. + +.. warning:: + It can be wise to purge old files from the cache from time to time. If you have a large amount of images and present many different variations of these the cache will use up quite a lot of storage. + + An example on how to accomplish this: + + .. code-block:: bash + + $ find /path/to/cache -ctime +7 -type f -delete + + The above command will delete all files in ``/path/to/cache`` older than 7 days and can be used with for instance `crontab `_. + +.. _max-image-size-event-listener: + +Max image size +++++++++++++++ + +This event listener can be used to enforce a maximum size (height and width, not byte size) of **new** images. Enabling this event listener will not change images already added to Imbo. + +The event listener subscribes to the following event: + +* ``image.put`` + +and has the following parameters: + +``$width`` + The max width in pixels of new images. If a new image exceeds this limit it will be downsized. + +``$height`` + The max height in pixels of new images. If a new image exceeds this limit it will be downsized. + +and is enabled like this: + +.. code-block:: php + + array( + 'maxImageSize' => function() { + return new Imbo\EventListener\MaxImageSize(1024, 768); + }, + ), + + // ... + ); + +which would effectively downsize all images exceeding a ``width`` of ``1024`` or a ``height`` of ``768``. The aspect ratio will be kept. + +Metadata cache +++++++++++++++ + +This event listener enables caching of metadata fetched from the backend so other requests won't need to go all the way to the backend to fetch it. To achieve this the listener subscribes to the following events: + +* ``db.metadata.load`` +* ``db.metadata.delete`` +* ``db.metadata.update`` + +and has the following parameters: + +``Imbo\Cache\CacheInterface $cache`` + An instance of a cache adapter. Imbo ships with :ref:`apc-cache` and :ref:`memcached-cache` adapters, and both can be used for this event listener. If you want to use another form of caching you can simply implement the ``Imbo\Cache\CacheInterface`` interface and pass an instance of the custom adapter to the constructor of the event listener. See :ref:`custom-cache-adapter` for more information regarding this. Here is an example that uses the APC adapter for caching: + +.. code-block:: php + + array( + 'metadataCache' => function() { + return new Imbo\EventListener\MetadataCache(new Imbo\Cache\APC('imbo')); + }, + ), + + // ... + ); + + +.. _stats-access-event-listener: + +Stats access +++++++++++++ + +This event listener controls the access to the :ref:`stats resource ` by using simple white-/blacklists containing IPv4 and/or IPv6 addresses. `CIDR-notations `_ are also supported. + +This listener is enabled per default, and only allows ``127.0.0.1`` and ``::1`` to access the statistics: + +.. code-block:: php + + array( + 'statsAccess' => function() { + return new Imbo\EventListener\StatsAccess(array( + 'whitelist' => array('127.0.0.1', '::1'), + 'blacklist' => array(), + )); + }, + ), + + // ... + ); + +If the whitelist is populated, only the listed IP addresses/subnets will gain access. If the blacklist is populated only the listed IP addresses/subnets will be denied access. If both lists are populated the IP address of the client must be present in the whitelist to gain access. If an IP address is present in both lists, it will not gain access. + +Cache adapters +-------------- + +If you want to leverage caching in a custom event listener, Imbo ships with some different solutions: + +.. _apc-cache: + +APC ++++ + +This adapter uses the `APCu `_ extension for caching. If your Imbo installation consists of a single httpd this is a good choice. The adapter has the following parameters: + +``$namespace`` (optional) + A namespace for your cached items. For instance: "imbo" + +Example: + +.. code-block:: php + + set('key', 'value'); + + echo $adapter->get('key'); // outputs "value" + echo apc_fetch('imbo:key'); // outputs "value" + +.. _memcached-cache: + +Memcached ++++++++++ + +This adapter uses `Memcached `_ for caching. If you have multiple httpd instances running Imbo this adapter lets you share the cache between all instances automatically by letting the adapter connect to the same Memcached daemon. The adapter has the following parameters: + +``$memcached`` + An instance of the pecl/memcached class. + +``$namespace`` (optional) + A namespace for your cached items. For instance: "imbo". + +Example: + +.. code-block:: php + + addServer('hostname', 11211); + + $adapter = new Imbo\Cache\Memcached($memcached, 'imbo'); + $adapter->set('key', 'value'); + + echo $adapter->get('key'); // outputs "value" + echo $memcached->get('imbo:key'); // outputs "value" + +.. _custom-cache-adapter: + +Implement a custom cache adapter +++++++++++++++++++++++++++++++++ + +If you want to use some other cache mechanism an interface exists (``Imbo\Cache\CacheInterface``) for you to implement: + +.. literalinclude:: ../../library/Imbo/Cache/CacheInterface.php + :language: php + :linenos: + +If you choose to implement this interface you can also use your custom cache adapter for all the event listeners Imbo ships with that leverages a cache. + +If you implement an adapter that you think should be a part of Imbo feel free to send a pull request on `GitHub `_. diff --git a/docs/develop/image_transformations.rst b/docs/develop/image_transformations.rst new file mode 100644 index 000000000..296d9ef81 --- /dev/null +++ b/docs/develop/image_transformations.rst @@ -0,0 +1,93 @@ +.. _custom-image-transformations: + +Implement your own image transformations +======================================== + +Imbo also supports custom image transformations. All you need to do is to implement the ``Imbo\Image\Transformation\TransformationInterface`` interface, or specify a callable piece of code in the :ref:`image transformation configuration `. Below is an implementation of the border transformation as a callable piece of code: + +.. code-block:: php + + array( + 'border' => function (array $params) { + return function (Imbo\Model\Image $image) use ($params) { + $color = !empty($params['color']) ? $params['color'] : '#000'; + $width = !empty($params['width']) ? $params['width'] : 1; + $height = !empty($params['height']) ? $params['height'] : 1; + + try { + $imagick = new Imagick(); + $imagick->readImageBlob($image->getBlob()); + $imagick->borderImage($color, $width, $height); + + $size = $imagick->getImageGeometry(); + + $image->setBlob($imagick->getImageBlob()) + ->setWidth($size['width']) + ->setHeight($size['height']); + } catch (ImagickException $e) { + throw new Imbo\Image\Transformation\TransformationException($e->getMessage(), 400, $e); + } + }; + }, + ), + + // ... + ); + +It's not recommended to use this method for big complicated transformations. It's better to implement the interface mentioned above, and refer to your class in the configuration array instead: + +.. code-block:: php + + array( + 'border' => function (array $params) { + return new My\Custom\BorderTransformation($params); + }, + ), + + // ... + ); + +where ``My\Custom\BorderTransformation`` implements ``Imbo\Image\Transformation\TransformationInterface``. + +Image transformation presets/collections +---------------------------------------- + +If you want to combine some of the existing image transformations you can use the ``Imbo\Image\Transformation\Collection`` transformation for this purpose. The constructor takes an array of other transformations: + +.. code-block:: php + + array( + 'thumb' => function ($params) { + return new Transformation\Collection(array( + new Transformation\Desaturate(), + new Transformation\Thumbnail(array( + 'width' => 60, + 'height' => 60, + )), + new Transformation\Border(array( + 'width' => 2, + 'height' => 2, + 'mode' => 'inline', + )), + )); + }, + ), + + // ... + ); + +When images are requested with the ``t[]=thumb`` query parameter they will first be desaturated, then made into a 60 x 60 pixel thumbnail and last they will get a 2 pixel border painted inside of the thumbnail, maintaining the 60 x 60 pixel size. diff --git a/docs/examples/generateSignatureForDelete.php b/docs/examples/generateSignatureForDelete.php index 644a69e16..4e11a0846 100644 --- a/docs/examples/generateSignatureForDelete.php +++ b/docs/examples/generateSignatureForDelete.php @@ -16,8 +16,7 @@ // Generate the token $signature = hash_hmac('sha256', $data, $privateKey); -// Request using request headers - +// Create the context for the request $context = stream_context_create(array( 'http' => array( 'method' => $method, @@ -29,17 +28,3 @@ ), )); file_get_contents($url, false, $context); - -// or, request using query parameters - -$context = stream_context_create(array( - 'http' => array( - 'method' => $method, - ), -)); -$url = sprintf('%s?signature=%s×tamp=%s', - $url, - rawurlencode($signature), - rawurlencode($timestamp)); - -file_get_contents($url, false, $context); diff --git a/docs/index.rst b/docs/index.rst index 356f071a9..9d160fb53 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,33 +1,32 @@ Imbo - Image box ================ -Imbo is an image "server" that can be used to add/get/delete images using a RESTful HTTP API. There is also support for adding meta data to the images stored in Imbo. The main idea behind Imbo is to have a place to store high quality original images and to use the API to fetch variations of the images. Imbo will resize, rotate and crop (amongst other transformations) images on the fly so you won't have to store all the different variations. See :ref:`image-transformations` for a complete list of the supported transformations. +Imbo is an image "server" that can be used to add/get/delete images using a RESTful HTTP API. There is also support for adding meta data to the images stored in Imbo. The main idea behind Imbo is to have a place to store high quality original images and to use the API to fetch variations of the images. Imbo will resize, rotate and crop (amongst other transformations) images on the fly so you won't have to store all the different variations. -Imbo is an open source project written in `PHP`_ and is `available on GitHub`_. If you find any issues or missing features please add an issue in the `issue tracker`_. +Imbo is an open source project written in `PHP `_ and is `available on GitHub `_. If you find any issues or missing features please add an issue in the `issue tracker `_. If you want to know more feel free to join the #imbo channel on the `Freenode IRC network `_ (chat.freenode.net) as well. -Feel free to join the #imbo channel on the `Freenode IRC network`_ (chat.freenode.net). +Installation guide +------------------ +.. toctree:: + :maxdepth: 2 -.. _PHP: http://php.net/ -.. _available on GitHub: http://github.com/imbo/imbo -.. _issue tracker: https://github.com/imbo/imbo/issues -.. _Freenode IRC network: http://freenode.net + installation/requirements + installation/installation + installation/configuration -Documentation -------------- +End user guide +-------------- .. toctree:: - :maxdepth: 3 + :maxdepth: 3 - usage/requirements - usage/installation - usage/configuration - usage/api + usage/api + usage/image-transformations -Extending Imbo --------------- +Extending/customizing Imbo +-------------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 2 - advanced/cache_adapters - advanced/custom_database_drivers - advanced/custom_storage_drivers - advanced/custom_event_listeners + develop/event_listeners + develop/custom_adapters + develop/image_transformations diff --git a/docs/installation/configuration.rst b/docs/installation/configuration.rst new file mode 100644 index 000000000..3d15472a8 --- /dev/null +++ b/docs/installation/configuration.rst @@ -0,0 +1,695 @@ +.. _configuration: + +Configuration +============= + +Imbo ships with a default configuration file that it will load. You will have to create one or more configuration files of your own that will automatically be loaded and merged with the default configuration by Imbo. The location of these files depends on the :ref:`installation method ` you choose. You should never have to edit the default configuration file provided by Imbo. + +The configuration file(s) you need to create should simply return arrays with configuration data. All available configuration options is covered in this chapter. + +.. contents:: + :local: + :depth: 1 + +Imbo users - ``auth`` +--------------------- + +Every user that wants to store images in Imbo needs a public and private key pair. These keys are stored in the ``auth`` part of your configuration file: + +.. code-block:: php + + array( + 'username' => '95f02d701b8dc19ee7d3710c477fd5f4633cec32087f562264e4975659029af7', + 'otheruser' => 'b312ff29d5da23dcd230b61ff4db1e2515c862b9fb0bb59e7dd54ce1e4e94a53', + ), + + // ... + ); + +The public keys can consist of the following characters: + +* a-z (only lowercase is allowed) +* 0-9 +* _ and - + +and must be at least 3 characters long. + +For the private keys you can for instance use a `SHA-256 `_ hash of a random value. The private key is used by clients to sign requests, and if you accidentally give away your private key users can use it to delete all your images. Make sure not to generate a private key that is easy to guess (like for instance the MD5 or SHA-256 hash of the public key). Imbo does not require the private key to be in a specific format, so you can also use regular passwords if you want. The key itself will never be a part of the payload sent to the server. + +Imbo ships with a small command line tool that can be used to generate private keys for you using the `openssl_random_pseudo_bytes `_ function. The script is located in the ``scripts`` directory of the Imbo installation and does not require any arguments: + +.. code-block:: bash + + $ php scripts/generatePrivateKey.php + 3b98dde5f67989a878b8b268d82f81f0858d4f1954597cc713ae161cdffcc84a + +The private key can be changed whenever you want as long as you remember to change it in both the server configuration and in the client you use. The public key can not be changed easily as database and storage adapter use it when storing images and metadata. + +.. _database-configuration: + +Database configuration - ``database`` +------------------------------------- + +The database adapter you decide to use is responsible for storing metadata and basic image information, like width and height for example, along with the generated short URLs. Imbo ships with some different implementations that you can use. Remember that you will not be able to switch the adapter whenever you want and expect all data to be automatically transferred. Choosing a database adapter should be a long term commitment unless you have migration scripts available. + +In the default configuration file the :ref:`default-database-adapter` database adapter is used. You can choose to override this in your configuration file by specifying a different adapter. You can either specify an instance of a database adapter directly, or specify an anonymous function that will return an instance of a database adapter when executed. Which database adapter to use is specified in the ``database`` key in the configuration array: + +.. code-block:: php + + function() { + return new Imbo\Database\MongoDB(array( + 'databaseName' => 'imbo', + )); + }, + + // or + + 'database' => new Imbo\Database\MongoDB(array( + 'databaseName' => 'imbo', + )), + + // ... + ); + +.. _doctrine-database-adapter: + +Doctrine +++++++++ + +This adapter uses the `Doctrine Database Abstraction Layer `_. The options you pass to the constructor of this adapter is passed to the underlying classes, so have a look at the Doctrine DBAL documentation over at `doctrine-project.org `_. When using this adapter you need to create the required tables in the DBMS first, as specified in the :ref:`installation` chapter. + +Examples +^^^^^^^^ + +Here are some examples on how to use the Doctrine adapter in the configuration file: + +1) Use a `PDO `_ instance to connect to a SQLite database: + +.. code-block:: php + + function() { + return new Imbo\Database\Doctrine(array( + 'pdo' => new PDO('sqlite:/path/to/database'), + )); + }, + + // ... + ); + +2) Connect to a MySQL database using PDO: + +.. code-block:: php + + function() { + return new Imbo\Database\Doctrine(array( + 'dbname' => 'database', + 'user' => 'username', + 'password' => 'password', + 'host' => 'hostname', + 'driver' => 'pdo_mysql', + )); + }, + + // ... + ); + +.. _mongodb-database-adapter: +.. _default-database-adapter: + +MongoDB ++++++++ + +This adapter uses PHP's `mongo extension `_ to store data in `MongoDB `_. The following parameters are supported: + +``databaseName`` + Name of the database to use. Defaults to ``imbo``. + +``server`` + The server string to use when connecting. Defaults to ``mongodb://localhost:27017``. + +``options`` + Options passed to the underlying adapter. Defaults to ``array('connect' => true, 'timeout' => 1000)``. See the `manual for the Mongo constructor `_ for available options. + +Examples +^^^^^^^^ + +1) Connect to a local MongoDB instance using the default ``databaseName``: + +.. code-block:: php + + function() { + return new Imbo\Database\MongoDB(); + }, + + // ... + ); + +2) Connect to a `replica set `_: + +.. code-block:: php + + function() { + return new Imbo\Database\MongoDB(array( + 'server' => 'mongodb://server1,server2,server3', + 'options' => array( + 'replicaSet' => 'nameOfReplicaSet', + ), + )); + }, + + // ... + ); + +Custom database adapter ++++++++++++++++++++++++ + +If you need to create your own database adapter you need to create a class that implements the ``Imbo\Database\DatabaseInterface`` interface, and then specify that adapter in the configuration: + +.. code-block:: php + + function() { + return new My\Custom\Adapter(array( + 'some' => 'option', + )); + }, + + // ... + ); + +More about how to achieve this in the :doc:`../develop/custom_adapters` chapter. + +.. _storage-configuration: + +Storage configuration - ``storage`` +----------------------------------- + +Storage adapters are responsible for storing the original images you put into Imbo. As with the database adapter it is not possible to simply switch the adapter without having migration scripts available to move the stored images. Choose an adapter with care. + +In the default configuration file the :ref:`default-storage-adapter` storage adapter is used. You can choose to override this in your configuration file by specifying a different adapter. You can either specify an instance of a storage adapter directly, or specify an anonymous function that will return an instance of a storage adapter when executed. Which storage adapter to use is specified in the ``storage`` key in the configuration array: + +.. code-block:: php + + function() { + return new Imbo\Storage\Filesystem(array( + 'dataDir' => '/path/to/images', + )); + }, + + // or + + 'storage' => new Imbo\Storage\Filesystem(array( + 'dataDir' => '/path/to/images', + )), + + // ... + ); + +Doctrine +++++++++ + +This adapter uses the `Doctrine Database Abstraction Layer `_. The options you pass to the constructor of this adapter is passed to the underlying classes, so have a look at the Doctrine DBAL documentation over at `doctrine-project.org `_. When using this adapter you need to create the required tables in the DBMS first, as specified in the :ref:`installation` chapter. + +Examples +^^^^^^^^ + +Here are some examples on how to use the Doctrine adapter in the configuration file: + +1) Use a PDO instance to connect to a SQLite database: + +.. code-block:: php + + function() { + return new Imbo\Storage\Doctrine(array( + 'pdo' => new PDO('sqlite:/path/to/database'), + )); + }, + + // ... + ); + +2) Connect to a MySQL database using PDO: + +.. code-block:: php + + function() { + return new Imbo\Storage\Doctrine(array( + 'dbname' => 'database', + 'user' => 'username', + 'password' => 'password', + 'host' => 'hostname', + 'driver' => 'pdo_mysql', + )); + }, + + // ... + ); + +.. _filesystem-storage-adapter: + +Filesystem +++++++++++ + +This adapter simply stores all images on the file system. It only has a single parameter, and that is the base directory of where you want your images stored: + +``dataDir`` + The base path where the images are stored. + +This adapter is configured to create subdirectories inside of ``dataDir`` based on the public key of the user and the checksum of the images added to Imbo. The algorithm that generates the path simply takes the three first characters of the public key and creates directories for each of them, then the full public key, then a directory of each of the first characters in the image identifier, and lastly it stores the image in a file with a filename equal to the image identifier itself. + +Examples +^^^^^^^^ + +Default configuration: + +.. code-block:: php + + function() { + new Imbo\Storage\Filesystem(array( + 'dataDir' => '/path/to/images', + )); + }, + + // ... + ); + +.. _gridfs-storage-adapter: +.. _default-storage-adapter: + +GridFS +++++++ + +The GridFS adapter is used to store the images in MongoDB using the `GridFS specification `_. This adapter has the following parameters: + +``databaseName`` + The name of the database to store the images in. Defaults to ``imbo_storage``. + +``server`` + The server string to use when connecting to MongoDB. Defaults to ``mongodb://localhost:27017`` + +``options`` + Options passed to the underlying adapter. Defaults to ``array('connect' => true, 'timeout' => 1000)``. See the `manual for the Mongo constructor `_ for available options. + +Examples +^^^^^^^^ + +1) Connect to a local MongoDB instance using the default ``databaseName``: + +.. code-block:: php + + function() { + return new Imbo\Storage\GridFS(); + }, + + // ... + ); + +2) Connect to a replica set: + +.. code-block:: php + + function() { + return new Imbo\Storage\GridFS(array( + 'server' => 'mongodb://server1,server2,server3', + 'options' => array( + 'replicaSet' => 'nameOfReplicaSet', + ), + )); + }, + + // ... + ); + +Custom storage adapter +++++++++++++++++++++++ + +If you need to create your own storage adapter you need to create a class that implements the ``Imbo\Storage\StorageInterface`` interface, and then specify that adapter in the configuration: + +.. code-block:: php + + function() { + return new My\Custom\Adapter(array( + 'some' => 'option', + )); + }, + + // ... + ); + +More about how to achieve this in the :doc:`../develop/custom_adapters` chapter. + +.. _configuration-event-listeners: + +Event listeners - ``eventListeners`` +------------------------------------ + +Imbo also supports event listeners that you can use to hook into Imbo at different phases without having to edit Imbo itself. An event listener is simply a piece of code that will be executed when a certain event is triggered from Imbo. Event listeners are added to the ``eventListeners`` part of the configuration array as associative arrays. The keys are short names used to identify the listeners, and are not really used for anything in the Imbo application, but exists so you can override/disable event listeners specified in the default configuration. If you want to disable some of the default event listeners simply specify the same key in your configuration file and set the value to ``null`` or ``false``. + +Event listeners can be added in the following ways: + +1) Use an instance of a class implementing the ``Imbo\EventListener\ListenerInterface`` interface: + +.. code-block:: php + + array( + 'accessToken' => new Imbo\EventListener\AccessToken(), + ), + + // ... + ); + +2) An anonymous function returning an instance of a class implementing the ``Imbo\EventListener\ListenerInterface`` interface: + +.. code-block:: php + + array( + 'accessToken' => function() { + return new Imbo\EventListener\AccessToken(); + }, + ), + + // ... + ); + +3) Use an instance of a class implementing the ``Imbo\EventListener\ListenerInterface`` interface together with a public key filter: + +.. code-block:: php + + array( + 'maxImageSize' => array( + 'listener' => new Imbo\EventListener\MaxImageSize(1024, 768), + 'publicKeys' => array( + 'include' => array('user'), + // 'exclude' => array('someotheruser'), + ), + ), + ), + + // ... + ); + +where ``listener`` is an instance of a class implementing the ``Imbo\EventListener\ListenerInterface`` interface, and ``publicKeys`` is an array that you can use if you want your listener to only be triggered for some users (public keys). The value of this is an array with one of two keys: ``include`` or ``exclude`` where ``include`` is an array you want your listener to trigger for, and ``exclude`` is an array of users you don't want your listener to trigger for. ``publicKeys`` is optional, and per default the listener will trigger for all users. + +4) Use an anonymous function directly: + +.. code-block:: php + + array( + 'customListener' => array( + 'callback' => function(Imbo\EventManager\EventInterface $event) { + // Custom code + }, + 'events' => array('image.get'), + 'priority' => 1, + 'publicKeys' => array( + 'include' => array('user'), + // 'exclude' => array('someotheruser'), + ), + ), + ), + + // ... + ); + +where ``callback`` is the code you want executed, and ``events`` is an array of the events you want it triggered for. ``priority`` is the priority of the listener and defaults to 1. The higher the number, the earlier in the chain your listener will be triggered. This number can also be negative. Imbo's internal event listeners uses numbers between 1 and 100. ``publicKeys`` uses the same format as described above. This way of attaching event listeners should mostly be used for quick and temporary solutions. + +All event listeners will end up receiving an event object (which implements ``Imbo\EventManager\EventInterface``), that is described in detail in the :ref:`the-event-object` section. + +Listeners added by default +++++++++++++++++++++++++++ + +The default configuration file includes some event listeners by default: + +* :ref:`access-token-event-listener` +* :ref:`authenticate-event-listener` +* :ref:`stats-access-event-listener` + +Read more about these listeners in the :doc:`../develop/event_listeners` chapter. If you want to disable any of these you could do so in your configuration file in the following way: + +.. code-block:: php + + array( + 'accessToken' => null, + 'auth' => null, + 'statsAccess' => null, + ), + + // ... + ); + +Keep in mind that these listeners are added by default for a reason, so make sure you know what it means to remove them before you do so. + +.. _image-transformations-config: + +Image transformations - ``imageTransformations`` +------------------------------------------------ + +Imbo supports a set of image transformations out of the box using the `Imagick PHP extension `_. All supported image transformations are included in the configuration, and you can easily add your own custom transformations or create presets using a combination of existing transformations. + +Transformations are triggered using the ``t`` query parameter together with the image resource (read more about the image resource and the included transformations and their parameters in the :ref:`image-resource` section). This query parameter is used as an array so that multiple transformations can be applied. The transformations are applied in the order they are specified in the URL. + +All transformations are registered in the configuration array under the ``imageTransformations`` key: + +.. code-block:: php + + array( + 'border' => function (array $params) { + return new Imbo\Image\Transformation\Border($params); + }, + 'canvas' => function (array $params) { + return new Imbo\Image\Transformation\Canvas($params); + }, + // ... + ), + + // ... + ); + +where the keys are the names of the transformations as specified in the URL, and the values are closures which all receive a single argument. This argument is an array that matches the parameters for the transformation as specified in the URL. If you use the following query parameter: + +``t[]=border:width=1,height=2,color=f00`` + +the ``$params`` array given to the closure will look like this: + +.. code-block:: php + + '1', + 'height' => '1', + 'color' => 'f00' + ) + + +The return value of the closure must either be an instance of the ``Imbo\Image\Transformation\TransformationInterface`` interface, or code that is callable (for instance another anonymous function, or a class that implements an ``__invoke`` method). If the return value is a callable piece of code it will receive a single parameter which is an instance of ``Imbo\Model\Image``, which is the image you want your transformation to modify. + +Presets ++++++++ + +Imbo supports transformation presets by using the ``Imbo\Image\Transformation\Collection`` transformation. The constructor of this transformation takes an array containing other transformations. + +.. code-block:: php + + array( + 'graythumb' => function ($params) { + return new Imbo\Image\Transformation\Collection(array( + new Imbo\Image\Transformation\Desaturate(), + new Imbo\Image\Transformation\Thumbnail($params), + )); + }, + ), + + // ... + ); + +which can be triggered using the following query parameter: + +``t[]=graythumb`` + +If you want to implement your own set of image transformation you can see how in the :doc:`../develop/image_transformations` chapter. + +Custom resources and routes - ``resources`` and ``routes`` +---------------------------------------------------------- + +.. warning:: Custom resources and routes is an experimental and advanced way of extending Imbo, and requires extensive knowledge of how Imbo works internally. + +If you need to create a custom route you can attach a route and a custom resource class using the configuration. Two keys exists for this purpose: ``resources`` and ``routes``: + +.. code-block:: php + + array( + 'users' => new ImboUsers(); + + // or + + 'users' => function() { + return new ImboUsers(); + }, + + // or + + 'users' => 'ImboUsers', + ), + + 'routes' => array( + 'users' => '#^/users(\.(?json|xml))?$#', + ), + + // ... + ); + +In the above example we are creating a route for Imbo using a regular expression, called ``users``. The route itself will match the following three requests: + +* ``/users`` +* ``/users.json`` +* ``/users.xml`` + +When a request is made against any of these endpoints Imbo will try to access a resource that is specified with the same key (``users``). The value specified for this entry in the ``resources`` array can be: + +1) a string representing the name of the resource class +2) an instance of a resource class +3) an anonymous function that, when executed, returns an instance of a resource class + +The resource class specified in 2. and returned by 3. must implement the ``Imbo\Resource\ResourceInterface`` interface to be able to response to a request. + +Below is an example implementation of the ``ImboUsers`` resource used in the above configuration: + +.. code-block:: php + + setList('users', 'user', array_keys($event->getConfig()['auth'])); + $event->getResponse()->setModel($model); + } + } + +This resource informs Imbo that it supports ``HTTP GET``, and specifies a callback for the ``users.get`` event. The name of the event is the name specified for the resource in the configuration above, along with the HTTP method, separated with a dot. + +In the ``get()`` method we are simply creating a list model for Imbo's response formatter, and we are supplying the keys from the ``auth`` part of your configuration file as data. When formatted as JSON the response looks like this: + +.. code-block:: json + + { + "users": [ + "someuser", + "someotheruser" + ] + } + +and the XML representation looks like this: + +.. code-block:: xml + + + + + someuser + someotheruser + + + +Feel free to experiment with this feature. If you end up creating a resource that you think should be a part of Imbo, send a `pull request on GitHub `_. diff --git a/docs/installation/installation.rst b/docs/installation/installation.rst new file mode 100644 index 000000000..2d523a14e --- /dev/null +++ b/docs/installation/installation.rst @@ -0,0 +1,260 @@ +.. _installation: + +Installation +============ + +To install Imbo on the server you can choose between two different methods, :ref:`Composer ` (recommended) or :ref:`git clone `. + +.. _using-composer: + +Using composer +-------------- + +The recommended way of installing Imbo is by creating a ``composer.json`` file for your installation, and then install Imbo and optional 3rd party plug-ins via `Composer `_. You will need the following directory structure for this method to work:: + + /path/to/install/composer.json + /path/to/install/config/ + +where the ``composer.json`` file can contain: + +.. code-block:: json + + { + "name": "yourname/imbo", + "require": { + "imbo/imbo": "dev-master" + } + } + +and the ``config/`` directory contains one or more configuration files that will be merged with the :ref:`default configuration `. + +If you want to install 3rd party plug-ins and/or for instance the Doctrine DBAL library simply add these to the ``require`` object in your ``composer.json``: + +.. code-block:: json + + { + "name": "yourname/imbo", + "require": { + "imbo/imbo": "dev-master", + "rexxars/imbo-hipsta": "dev-master", + "doctrine/dbal": "2.*" + } + } + +If some of the 3rd party plug-ins provide configuration files, you can link to these in the ``config/`` directory to have Imbo automatically load them: + +.. code-block:: bash + + cd /path/to/install/config + ln -s ../vendor/rexxars/imbo-hipsta/config/config.php 01-imbo-hipsta.php + +To be able to control the order that Imbo will use when loading the configuration files you should prefix them with a number, like ``01`` in the example above. Lower numbers will be loaded first, meaning that configuration files with higher numbers will override settings set in configuration files with a lower number. + +Regarding the Imbo version you are about to install you can use ``dev-master`` for the latest released version, or you can use a specific version if you want to. Head over to `Packagist `_ to see the available versions. + +When you have created the ``composer.json`` file you can install Imbo with Composer: + +.. code-block:: bash + + curl -s https://getcomposer.org/installer | php + php composer.phar install -o --no-dev + +After composer has finished installing Imbo and optional dependencies the Imbo installation will reside in ``/path/to/install/vendor/imbo/imbo``. The correct web server document root in this case would be ``/path/to/install/vendor/imbo/imbo/public``. + +If you later want to update Imbo you can bump the version number you have specified in ``composer.json`` and run: + +.. code-block:: bash + + php composer.phar update -o --no-dev + +.. _git-clone: + +Using git clone +--------------- + +You can also install Imbo directly via git, and then use Composer to install the dependencies: + +.. code-block:: bash + + mkdir /path/to/install; cd /path/to/install + git clone https://github.com/imbo/imbo.git + cd imbo + curl -s https://getcomposer.org/installer | php + php composer.phar install -o --no-dev + +In this case the correct web server document root would be ``/path/to/install/imbo/public``. Remember to checkout the correct branch after cloning the repository to get the version you want, for instance ``git checkout master``. If you use this method of installation you will have to modify Imbo's ``composer.json`` to install 3rd party libraries. You will also have to place your own configuration file in the same directory as the default Imbo configuration file, which in the above example would be the ``/path/to/install/imbo/config`` directory. + +Web server configuration +------------------------ + +After installing Imbo by using one of the methods mentioned above you will have to configure the web server you want to use. Imbo ships with sample configuration files for `Apache `_ and `Nginx `_ that can be used with a few minor adjustments. Both configuration files assume the httpd runs on port 80. If you use `Varnish `_ or some other HTTP accelerator simply change the port number to the port that your httpd listens to. + +Apache +~~~~~~ + +You will need to enable `mod_rewrite `_ if you want to use Imbo with Apache. Below is an example on how to configure Apache for Imbo: + +.. literalinclude:: ../../config/imbo.apache.conf.dist + :language: console + +You will need to update ``ServerName`` to match the host name you will use for Imbo. If you want to use several host names you can update the ``ServerAlias`` line as well. You must also update ``DocumentRoot`` and ``Directory`` to point to the ``public`` directory in the Imbo installation. If you want to enable logging update the ``CustomLog`` and ``ErrorLog`` lines. ``RewriteCond`` and ``RewriteRule`` should be left alone. + +Nginx +~~~~~ + +Below is an example on how to configure Nginx for Imbo. This example uses PHP via `FastCGI `_: + +.. literalinclude:: ../../config/imbo.nginx.conf.dist + :language: console + +You will need to update ``server_name`` to match the host name you will use for Imbo. If you want to use several host names simply put several host names on that line. ``root`` must point to the ``public`` directory in the Imbo installation. If you want to enable logging update the ``error_log`` and ``access_log`` lines. You must also update the ``fastcgi_param SCRIPT_FILENAME`` line to point to the ``public/index.php`` file in the Imbo installation. + +Varnish +~~~~~~~ + +Imbo strives to follow the `HTTP Protocol `_, and can because of this easily leverage `Varnish `_. + +The only required configuration you need in your `VCL `_ is a default backend: + +.. code-block:: console + + backend default { + .host = "127.0.0.1"; + .port = "81"; + } + +where ``.host`` and ``.port`` is where Varnish can reach your web server. + +If you use the same host name (or a sub-domain) for your Imbo installation as other services, that in turn uses `Cookies `_, you might want the VCL to ignore these Cookies for the requests made against your Imbo installation (unless you have implemented event listeners for Imbo that uses Cookies). To achieve this you can put the following snippet into your VCL file: + +.. code-block:: console + + sub vcl_recv { + if (req.http.host == "imbo.example.com") { + unset req.http.Cookie; + } + } + +or, if you have Imbo installed in some path: + +.. code-block:: console + + sub vcl_recv { + if (req.http.host ~ "^(www.)?example.com$" && req.url ~ "^/imbo/") { + unset req.http.Cookie; + } + } + +if your Imbo installation is available on ``[www.]example.com/imbo``. + +Database setup +-------------- + +If you choose to use a RDMS to store data in, you will need to manually create a database, a user and the tables Imbo stores information in. Below you will find schemas for different RDMSs. You will find information regarding how to authenticate against the RDMS of you choice in the :ref:`configuration` topic. + +MySQL +~~~~~ + +.. code-block:: sql + + CREATE TABLE IF NOT EXISTS `imageinfo` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `publicKey` varchar(255) COLLATE utf8_danish_ci NOT NULL, + `imageIdentifier` char(32) COLLATE utf8_danish_ci NOT NULL, + `size` int(10) unsigned NOT NULL, + `extension` varchar(5) COLLATE utf8_danish_ci NOT NULL, + `mime` varchar(20) COLLATE utf8_danish_ci NOT NULL, + `added` int(10) unsigned NOT NULL, + `updated` int(10) unsigned NOT NULL, + `width` int(10) unsigned NOT NULL, + `height` int(10) unsigned NOT NULL, + `checksum` char(32) COLLATE utf8_danish_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `image` (`publicKey`,`imageIdentifier`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci AUTO_INCREMENT=1 ; + + CREATE TABLE IF NOT EXISTS `metadata` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `imageId` int(10) unsigned NOT NULL, + `tagName` varchar(255) COLLATE utf8_danish_ci NOT NULL, + `tagValue` varchar(255) COLLATE utf8_danish_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `imageId` (`imageId`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci AUTO_INCREMENT=1 ; + + CREATE TABLE IF NOT EXISTS `shorturl` ( + `shortUrlId` char(7) COLLATE utf8_danish_ci NOT NULL, + `publicKey` varchar(255) COLLATE utf8_danish_ci NOT NULL, + `imageIdentifier` char(32) COLLATE utf8_danish_ci NOT NULL, + `extension` char(3) COLLATE utf8_danish_ci DEFAULT NULL, + `query` text COLLATE utf8_danish_ci NOT NULL, + PRIMARY KEY (`shortUrlId`), + KEY `params` (`publicKey`,`imageIdentifier`,`extension`,`query`(255)) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci; + + +The following table is only needed if you plan on storing the actual images themselves in MySQL: + +.. code-block:: sql + + CREATE TABLE IF NOT EXISTS `storage_images` ( + `publicKey` varchar(255) COLLATE utf8_danish_ci NOT NULL, + `imageIdentifier` char(32) COLLATE utf8_danish_ci NOT NULL, + `data` blob NOT NULL, + `updated` int(10) unsigned NOT NULL, + PRIMARY KEY (`publicKey`,`imageIdentifier`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci; + +SQLite +~~~~~~ + +.. code-block:: sql + + CREATE TABLE IF NOT EXISTS imageinfo ( + id INTEGER PRIMARY KEY NOT NULL, + publicKey TEXT NOT NULL, + imageIdentifier TEXT NOT NULL, + size INTEGER NOT NULL, + extension TEXT NOT NULL, + mime TEXT NOT NULL, + added INTEGER NOT NULL, + updated INTEGER NOT NULL, + width INTEGER NOT NULL, + height INTEGER NOT NULL, + checksum TEXT NOT NULL, + UNIQUE (publicKey,imageIdentifier) + ); + + CREATE TABLE IF NOT EXISTS metadata ( + id INTEGER PRIMARY KEY NOT NULL, + imageId KEY INTEGER NOT NULL, + tagName TEXT NOT NULL, + tagValue TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS shorturl ( + shortUrlId TEXT PRIMARY KEY NOT NULL, + publicKey TEXT NOT NULL, + imageIdentifier TEXT NOT NULL, + extension TEXT, + query TEXT NOT NULL + ); + + CREATE INDEX shorturlparams ON shorturl ( + publicKey, + imageIdentifier, + extension, + query + ); + +The following table is only needed if you plan on storing the actual images themselves in SQLite: + +.. code-block:: sql + + CREATE TABLE IF NOT EXISTS storage_images ( + publicKey TEXT NOT NULL, + imageIdentifier TEXT NOT NULL, + data BLOB NOT NULL, + updated INTEGER NOT NULL, + PRIMARY KEY (publicKey,imageIdentifier) + ); diff --git a/docs/installation/requirements.rst b/docs/installation/requirements.rst new file mode 100644 index 000000000..1852b9589 --- /dev/null +++ b/docs/installation/requirements.rst @@ -0,0 +1,6 @@ +Requirements +============ + +Imbo requires a web server running `PHP >= 5.4 `_ and the `Imagick `_ extension for PHP. + +You will also need a backend for storing image information, like for instance `MongoDB `_ or `MySQL `_. If you want to use MongoDB as a database and/or `GridFS `_ for storage, you will need to install the `Mongo `_ PECL extension, and if you want to use a RDMS like MySQL, you will need to install the `Doctrine Database Abstraction Layer `_. diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 8b1a58b4c..ca2c76647 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,3 +1,5 @@ +schemas +cURL Exif Imbo imbo @@ -27,3 +29,4 @@ pecl memcached Freenode freenode +IPv diff --git a/docs/usage/api.rst b/docs/usage/api.rst index 6118511ea..1811e020b 100644 --- a/docs/usage/api.rst +++ b/docs/usage/api.rst @@ -1,62 +1,10 @@ -RESTful API -=========== +Imbo's API +========== -Imbo uses a `RESTful`_ API to manage the stored images and metadata. Each image is identified by a public key (the "username") and an MD5 checksum of the file itself. The public key and the image identifier will be referred to as ```` and ```` respectively for the remainder of this document. For all `cURL`_ examples ``imbo`` will be used as a host name. The examples will also omit access tokens and authentication signatures. +In this chapter you will learn more about how Imbo's API works, and how you as a user are able to read from and write to Imbo. Most examples used in this chapter will use `cURL `_, so while playing around with the API it's encouraged to have cURL easily available. For the sake of simplicity the access tokens and signature information is not used in the examples. See the :ref:`access-tokens` and :ref:`signing-write-requests` sections for more information regarding this. -.. _cURL: http://curl.haxx.se/ -.. _RESTful: http://en.wikipedia.org/wiki/REST - -Content types -------------- - -Currently Imbo responds with images (jpg, gif and png), `JSON`_ and `XML`_, but only accepts images (jpg, gif and png) and JSON as input. - -Imbo will do content negotiation using the `Accept`_ header found in the request, unless you specify a file extension, in which case Imbo will deliver the type requested without looking at the Accept header. - -The default `Content-Type`_ for non-image responses is JSON, and for most examples in this document you will see the ``.json`` extension being used. Change that to ``.xml`` to get XML data. You can also skip the extension and force a specific Content-Type using the Accept header: - -.. code-block:: bash - - $ curl http://imbo/status.json - -and - -.. code-block:: bash - - $ curl -H "Accept: application/json" http://imbo/status - -will end up with the same content-type. Use ``application/xml`` for XML. - -If you use JSON you can wrap the content in a function (`JSONP`_) by using one of the following query parameters: - -* ``callback`` -* ``jsonp`` -* ``json`` - -.. code-block:: bash - - $ curl http://imbo/status.json?callback=func - -will result in: - -.. code-block:: javascript - - func( - { - "date": "Mon, 05 Nov 2012 19:18:40 GMT", - "database": true, - "storage": true - } - ) - -.. _Accept: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html -.. _Content-Type: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html -.. _JSON: http://en.wikipedia.org/wiki/JSON -.. _JSONP: http://en.wikipedia.org/wiki/JSONP -.. _XML: http://en.wikipedia.org/wiki/XML - -Resources ---------- +Resources/endpoints +------------------- In this section you will find information on the different resources Imbo's RESTful API expose, along with their capabilities: @@ -66,13 +14,14 @@ In this section you will find information on the different resources Imbo's REST .. _stats-resource: -Stats resource -++++++++++++++ +Stats resource - ``/stats`` ++++++++++++++++++++++++++++ + Imbo provides an endpoint for fetching simple statistics about the data stored in Imbo. .. code-block:: bash - $ curl http://imbo/stats.json + curl http://imbo/stats.json results in: @@ -97,18 +46,19 @@ results in: "custom": {} } +if the client making the request is allowed access. + Access control ~~~~~~~~~~~~~~ -The access control for the stats endpoint is controlled by an :ref:`event listener `, which is enabled per default, and only allows connections from ``127.0.0.1``. +The access control for the stats endpoint is controlled by an event listener, which is enabled per default, and only allows connections from ``127.0.0.1``. Read more about how to configure this event listener in the :ref:`Stats access event listener ` section. Custom statistics ~~~~~~~~~~~~~~~~~ -The stats resource lets users attach custom data via event listeners by using the model as a regular associative array. The following example attaches a simple event listener in the configuration file that populates some custom data in the statistics model: +The stats resource enables users to attach custom statistics via event listeners by using the data model as a regular associative array. The following example attaches a simple event listener in the configuration file that populates some custom data in the statistics model: .. code-block:: php - :linenos: 'bar', ); + $model['someList'] = array(1, 2, 3); } ), ), @@ -157,7 +108,8 @@ When requesting the stats endpoint, the output will look like this: "someValue": 123, "someOtherValue": { "foo": "bar" - } + }, + "someList": [1, 2, 3] } } @@ -165,14 +117,14 @@ Use cases for this might be to simply store data in some backend in various even .. _status-resource: -Status resource -+++++++++++++++ +Status resource - ``/status`` ++++++++++++++++++++++++++++++ Imbo includes a simple status resource that can be used with for instance monitoring software. .. code-block:: bash - $ curl http://imbo/status.json + curl http://imbo/status.json results in: @@ -184,28 +136,29 @@ results in: "storage": true } -where ``timestamp`` is the current timestamp on the server, and ``database`` and ``storage`` are boolean values informing of the status of the current database and storage drivers respectively. If both are ``true`` the HTTP status code is ``200 OK``, and if one or both are ``false`` the status code is ``500``. When the status code is ``500`` the status message will inform you whether it's the database or the storage driver (or both) that is having issues. +where ``timestamp`` is the current timestamp on the server, and ``database`` and ``storage`` are boolean values informing of the status of the current database and storage adapters respectively. If both are ``true`` the HTTP status code is ``200 OK``, and if one or both are ``false`` the status code is ``503``. When the status code is ``503`` the status message will inform you whether it's the database or the storage adapter (or both) that is having issues. As soon as the status code does not equal ``200`` Imbo will no longer work as expected. + +The reason for adapter failures depends on what kind of adapter you are using. The :ref:`file system storage adapter ` will for instance return a failure if it can no longer write to the storage directory. The :ref:`MongoDB ` and :ref:`Doctrine ` database adapters will fail if they can no longer connect to the server they are configured to talk to. **Typical response codes:** * 200 OK -* 500 Internal Server Error - -.. _user-resource: +* 503 Database error +* 503 Storage error +* 503 Storage and database error -User resource -+++++++++++++ +.. note:: The status resource is not cache-able. -The user resource represents a single user on the current Imbo installation. +.. _user-resource: -GET /users/ -~~~~~~~~~~~~~~~~~ +User resource - ``/users/`` ++++++++++++++++++++++++++++++++++ -Fetch information about a specific user. The output contains basic user information: +The user resource represents a single user on the current Imbo installation. The output contains basic user information: .. code-block:: bash - $ curl http://imbo/users/.json + curl http://imbo/users/.json results in: @@ -217,44 +170,39 @@ results in: "lastModified": "Wed, 18 Apr 2012 15:12:52 GMT" } -where ``publicKey`` is the public key of the user, ``numImages`` is the number of images the user has stored in Imbo and ``lastModified`` is when the user last uploaded an image or updated metadata of an image. +where ``publicKey`` is the public key of the user (the same used in the request against this endpoint), ``numImages`` is the number of images the user has stored in Imbo and ``lastModified`` is when the user last uploaded or deleted an image, or when the user last updated metadata of an image. **Typical response codes:** * 200 OK * 304 Not modified -* 404 Not found +* 404 Public key not found .. _images-resource: -Images resource -+++++++++++++++ - -The images resource represents a collection of images owned by a specific user. - -GET /users//images -~~~~~~~~~~~~~~~~~~~~~~~~ +Images resource - ``/users//images`` +++++++++++++++++++++++++++++++++++++++++++ -Get information about the images stored in Imbo for a specific user. Supported query parameters are: +The images resource represents a collection of images owned by a specific user. Supported query parameters are: ``page`` The page number. Defaults to ``1``. ``limit`` - Number of images pr. page. Defaults to ``20``. + Number of images per page. Defaults to ``20``. ``metadata`` Whether or not to include metadata in the output. Defaults to ``0``, set to ``1`` to enable. ``from`` - Fetch images starting from this Unix timestamp. + Fetch images starting from this `Unix timestamp `_. ``to`` Fetch images up until this timestamp. .. code-block:: bash - $ curl "http://imbo/users//images.json?limit=1&metadata=1" + curl "http://imbo/users//images.json?limit=1&metadata=1" results in: @@ -278,7 +226,7 @@ results in: } ] -where ``added`` is a formatted date of when the image was added to Imbo, ``extension`` is the original image extension, ``height`` is the height of the image in pixels, ``imageIdentifier`` is the image identifier (MD5 checksum of the file itself), ``metadata`` is a JSON object containing metadata attached to the image, ``mime`` is the mime type of the image, ``publicKey`` is the public key of the user who owns the image, ``size`` is the size of the image in bytes, ``updated`` is a formatted date of when the image was last updated (read: when metadata attached to the image was last updated, as the image itself never changes), and ``width`` is the width of the image in pixels. +where ``added`` is a formatted date of when the image was added to Imbo, ``extension`` is the original image extension, ``height`` is the height of the image in pixels, ``imageIdentifier`` is the image identifier (`MD5 checksum `_ of the file itself), ``metadata`` is a JSON object containing metadata attached to the image, ``mime`` is the mime type of the image, ``publicKey`` is the public key of the user who owns the image, ``size`` is the size of the image in bytes, ``updated`` is a formatted date of when the image was last updated (read: when metadata attached to the image was last updated, as the image itself never changes), and ``width`` is the width of the image in pixels. The ``metadata`` field is only available if you used the ``metadata`` query parameter described above. @@ -292,359 +240,159 @@ Images in the array are ordered on the ``added`` field in a descending fashion. .. _image-resource: -Image resource -++++++++++++++ +Image resource - ``/users//images/`` ++++++++++++++++++++++++++++++++++++++++++++++++++ -The image resource represents specific images owned by a user. +The image resource represents specific images owned by a user. This resource is used to add, retrieve and remove images. It's also responsible for transforming the images based on the transformation parameters in the query. -GET /users//images/ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add an image +~~~~~~~~~~~~ -Fetch the image identified by ```` owned by ````. Without any query parameters this will return the original image. +To be able to display images stored in Imbo you will first need to add one or more images. This is done by requesting this endpoint with an image attached to the request body, and changing the HTTP METHOD to ``PUT``. The body of the response for such a request contains a JSON object containing the image identifier of the added image: .. code-block:: bash - $ curl http://imbo/users//images/ + curl -XPUT http://imbo/users//images/ --data-binary @ results in: -.. code-block:: bash +.. code-block:: javascript - + { + "imageIdentifier": "", + "width": , + "height": , + "extension": "" + } + +The ```` part of the URI is the `MD5 checksum `_ of the file itself. The ```` in the response can be used to fetch the added image and apply transformations to it. The output from this method is important as the ```` in the response might not be the same as the one used in the URI when adding the image (which might occur if for instance event listeners transform the image in some way before Imbo stores it, like the :ref:`auto-rotate-image-event-listener` and :ref:`max-image-size-event-listener` event listeners). The response body also contains the width, height and extension of the image that was just added. **Typical response codes:** * 200 OK -* 304 Not modified +* 201 Created * 400 Bad Request -* 404 Not found - -Image transformations -~~~~~~~~~~~~~~~~~~~~~ - -Below you can find information on the transformations shipped with Imbo along with their parameters. - -.. _border-transformation: - -border -###### - -This transformation will apply a border around the image. - -**Parameters:** - -``color`` - Color of the border in hexadecimal. Defaults to ``000000`` (You can also specify short values like ``f00`` (``ff0000``)). - -``width`` - Width of the border in pixels on the left and right sides of the image. Defaults to ``1``. - -``height`` - Height of the border in pixels on the top and bottom sides of the image. Defaults to ``1``. - -``mode`` - Mode of the border. Can be ``inline`` or ``outbound``. Defaults to ``outbound``. Outbound places the border outside of the image, increasing the dimensions of the image. ``inline`` paints the border inside of the image, retaining the original width and height of the image. - -**Examples:** - -* ``t[]=border`` -* ``t[]=border:mode=inline`` -* ``t[]=border:color=000`` -* ``t[]=border:color=f00,width=2,height=2`` -canvas -###### +Fetch images +~~~~~~~~~~~~ -This transformation can be used to change the canvas of the original image. +Fetching images added to Imbo is done by requesting the image identifiers (checksum) of the images. -**Parameters:** - -``width`` - Width of the surrounding canvas in pixels. If omitted the width of ```` will be used. - -``height`` - Height of the surrounding canvas in pixels. If omitted the height of ```` will be used. - -``mode`` - The placement mode of the original image. ``free``, ``center``, ``center-x`` and ``center-y`` are available values. Defaults to ``free``. - -``x`` - X coordinate of the placement of the upper left corner of the existing image. Only used for modes: ``free`` and ``center-y``. - -``y`` - Y coordinate of the placement of the upper left corner of the existing image. Only used for modes: ``free`` and ``center-x``. - -``bg`` - Background color of the canvas. Defaults to ``ffffff`` (also supports short values like ``f00`` (``ff0000``)). - -**Examples:** - -* ``t[]=canvas:width=200,mode=center`` -* ``t[]=canvas:width=200,height=200,x=10,y=10,bg=000`` -* ``t[]=canvas:width=200,height=200,x=10,mode=center-y`` -* ``t[]=canvas:width=200,height=200,y=10,mode=center-x`` - -compress -######## - -This transformation compresses images on the fly resulting in a smaller payload. - -**Parameters:** - -``quality`` - Quality of the resulting image. 100 is maximum quality (lowest compression rate). - -**Examples:** - -* ``t[]=compress:quality=40`` - -.. warning:: - This transformation currently only works as expected for ``image/jpeg`` images. - -convert -####### - -This transformation can be used to change the image type. It is not applied like the other transformations, but is triggered when specifying a custom extension to the ````. Currently Imbo can convert to: - -* ``jpg`` -* ``png`` -* ``gif`` - -**Examples:** - -* ``curl http://imbo/users//images/.gif`` -* ``curl http://imbo/users//images/.jpg`` -* ``curl http://imbo/users//images/.png`` - -It is not possible to explicitly trigger this transformation via the ``t[]`` query parameter. - -crop -#### - -This transformation is used to crop the image. - -**Parameters:** - -``x`` - The X coordinate of the cropped region's top left corner. - -``y`` - The Y coordinate of the cropped region's top left corner. - -``width`` - The width of the crop in pixels. - -``height`` - The height of the crop in pixels. - -**Examples:** - -* ``t[]=crop:x=10,y=25,width=250,height=150`` - -desaturate -########## - -This transformation desaturates the image (in practice, gray scales it). - -**Examples:** - -* ``t[]=desaturate`` - -flipHorizontally -################ - -This transformation flips the image horizontally. - -**Examples:** - -* ``t[]=flipHorizontally`` - -flipVertically -############## - -This transformation flips the image vertically. - -**Examples:** - -* ``t[]=flipVertically`` - -maxSize -####### - -This transformation will resize the image using the original aspect ratio. Two parameters are supported and at least one of them must be supplied to apply the transformation. - -Note the difference from the :ref:`resize` transformation: given both ``width`` and ``height``, the resulting image will not be the same width and height as specified unless the aspect ratio is the same. - -**Parameters:** - -``width`` - The max width of the resulting image in pixels. If not specified the width will be calculated using the same aspect ratio as the original image. - -``height`` - The max height of the resulting image in pixels. If not specified the height will be calculated using the same aspect ratio as the original image. - -**Examples:** - -* ``t[]=maxSize:width=100`` -* ``t[]=maxSize:height=100`` -* ``t[]=maxSize:width=100,height=50`` - -.. _resize: - -resize -###### - -This transformation will resize the image. Two parameters are supported and at least one of them must be supplied to apply the transformation. - -**Parameters:** - -``width`` - The width of the resulting image in pixels. If not specified the width will be calculated using the same aspect ratio as the original image. - -``height`` - The height of the resulting image in pixels. If not specified the height will be calculated using the same aspect ratio as the original image. - -**Examples:** - -* ``t[]=resize:width=100`` -* ``t[]=resize:height=100`` -* ``t[]=resize:width=100,height=50`` - -rotate -###### - -This transformation will rotate the image clock-wise. - -**Parameters:** - -``angle`` - The number of degrees to rotate the image (clock-wise). - -``bg`` - Background color in hexadecimal. Defaults to ``000000`` (also supports short values like ``f00`` (``ff0000``)). - -**Examples:** - -* ``t[]=rotate:angle=90`` -* ``t[]=rotate:angle=45,bg=fff`` - -sepia -##### - -This transformation will apply a sepia color tone transformation to the image. - -**Parameters:** - -``threshold`` - Threshold ranges from 0 to QuantumRange and is a measure of the extent of the sepia toning. Defaults to ``80`` +.. code-block:: bash -**Examples:** + curl http://imbo/users//images/ -* ``t[]=sepia`` -* ``t[]=sepia:threshold=70`` +results in: -thumbnail -######### +.. code-block:: bash -This transformation creates a thumbnail of ````. + -**Parameters:** +When fetching images Imbo also sends a set of custom HTTP response headers related to the image:: -``width`` - Width of the thumbnail in pixels. Defaults to ``50``. + X-Imbo-Originalextension: png + X-Imbo-Originalmimetype: image/png + X-Imbo-Originalfilesize: 45826 + X-Imbo-Originalheight: 390 + X-Imbo-Originalwidth: 380 + X-Imbo-ShortUrl: http://imbo/s/w7CiqDM -``height`` - Height of the thumbnail in pixels. Defaults to ``50``. +These are all related to the image that was just requested. -``fit`` - Fit style. Possible values are: ``inset`` or ``outbound``. Default to ``outbound``. +How to use this resource to generate image transformations is described in the :doc:`../usage/image-transformations` chapter. -**Examples:** +**Typical response codes:** -* ``t[]=thumbnail`` -* ``t[]=thumbnail:width=20,height=20,fit=inset`` +* 200 OK +* 304 Not modified +* 400 Bad Request +* 404 Image not found -transpose -######### +Delete images +~~~~~~~~~~~~~ -This transformation transposes the image. +Deleting images from Imbo is accomplished by requesting the image URIs using ``HTTP DELETE``. All metadata attached to the image will be removed as well. -**Examples:** +.. code-block:: bash -* ``t[]=transpose`` + curl -XDELETE http://imbo/users//images/ -transverse -########## +results in: -This transformation transverses the image. +.. code-block:: javascript -**Examples:** + { + "imageIdentifier": "" + } -* ``t[]=transverse`` +where ```` is the image identifier of the image that was just deleted (the same as the one used in the URI). -watermark -######### +**Typical response codes:** -This transformation can be used to apply a watermark on top of the original image. +* 200 OK +* 400 Bad Request +* 404 Image not found -**Parameters:** +.. _shorturl-resource: -``img`` - Image identifier of the image to apply as watermark. Can be set to a default value in configuration by using ````. +ShortURL resource - ``/s/`` ++++++++++++++++++++++++++++++++ -``width`` - Width of the watermark image in pixels. If omitted the width of ```` will be used. +Images in Imbo have short URLs associated with them, which are generated on request when you access an image (with or without image transformations) for the first time. These URLs do not take any query parameters and can be used in place for original image URLs. To fetch these URLs you can request an image using HTTP HEAD, then look for the `X-Imbo-ShortUrl` header in the response:: -``height`` - Height of the watermark image in pixels. If omitted the height of ```` will be used. + curl -Ig "http://imbo/users//images/?t[]=thumbnail&t[]=desaturate&t[]=border&accessToken=f3fa1d9f0649cfad61e840a6e09b156e851858799364d1d8ee61b386e10b0c05"|grep Imbo -``position`` - The placement of the watermark image. ``top-left``, ``top-right``, ``bottom-left``, ``bottom-right`` and ``center`` are available values. Defaults to ``top-left``. +results in (some headers omitted): -``x`` - Number of pixels in the X-axis the watermark image should be offset from the original position (defined by the ``position`` parameter). Supports negative numbers. Defaults to ``0`` +.. code-block:: none + :emphasize-lines: 6 -``y`` - Number of pixels in the Y-axis the watermark image should be offset from the original position (defined by the ``position`` parameter). Supports negative numbers. Defaults to ``0`` + X-Imbo-OriginalMimeType: image/gif + X-Imbo-OriginalWidth: 771 + X-Imbo-OriginalHeight: 771 + X-Imbo-OriginalFileSize: 152066 + X-Imbo-OriginalExtension: gif + X-Imbo-ShortUrl: http://imbo/s/3VEFrpB + X-Imbo-ImageIdentifier: 4492acb937a1f056ae43509bc7f85d21 -**Examples:** +The value of the ``X-Imbo-ShortUrl`` can be used to request the image with the applied transformations, and does not require an access token query parameter. -* ``t[]=watermark:img=f5f7851c40e2b76a01af9482f67bbf3f`` -* ``t[]=watermark:img=f5f7851c40e2b76a01af9482f67bbf3f,width=200,x=5`` -* ``t[]=watermark:img=f5f7851c40e2b76a01af9482f67bbf3f,height=50,x=-5,y=-5,position=bottom-right`` +The format of the random ID part of the short URL can be matched with the following `regular expression `_:: -If you want to set the default watermark image you will have to do so in the configuration: + |^[a-zA-Z0-9]{7}$| -.. code-block:: php +There are some caveats regarding the short URLs: - array( - 'watermark' => function (array $params) { - $transformation = new Imbo\Image\Transformation\Watermark($params); - $transformation->setDefaultImage('some image identifier'); +.. note:: In Imbo only images have short URLs - return $transformation; - }, - ), +.. _metadata-resource: - // ... - ); +Metadata resource - ``/users//images//meta`` ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -When you have specified a default watermark image you are not required to use the ``img`` option for the transformation, but if you do so it will override the default one. +Imbo can also be used to attach metadata to the stored images. The metadata is based on a simple ``key => value`` model, for instance: -PUT /users//images/ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* ``category: Music`` +* ``band: Koldbrann`` +* ``genre: Black metal`` +* ``country: Norway`` -Store a new image on the server. +Adding/replacing metadata +~~~~~~~~~~~~~~~~~~~~~~~~~ -The body of the response contains a JSON object containing the image identifier of the resulting image: +To add (or replace all existing metadata) on an image a client should make a request against this resource using ``HTTP PUT`` with the metadata attached in the request body as a JSON object. .. code-block:: bash - $ curl -XPUT http://imbo/users//images/ --data-binary @ + curl -XPUT http://imbo/users//images//meta.json -d '{ + "beer":"Dark Horizon First Edition", + "brewery":"Nøgne Ø", + "style":"Imperial Stout" + }' results in: @@ -654,22 +402,25 @@ results in: "imageIdentifier": "" } -where ```` can be used to fetch the added image and apply transformations to it. The output from this method is important as the ```` in the response might not be the same as ```` in the URI in the above example (which might occur if for instance event listeners transform the image in some way before Imbo stores it). +where ```` is the image that just got updated. **Typical response codes:** * 200 OK -* 201 Created * 400 Bad Request +* 404 Image not found -DELETE /users//images/ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Partially updating metadata +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Delete the image identified by ```` owned by ```` along with all metadata attached to the image. +Partial updates to metadata attached to an image is done by making a request with ``HTTP POST`` and attaching metadata to the request body as a JSON object. If the object contains keys that already exists in the metadata on the server the old values will be replaced by the ones found in the request body. New keys will be added to the metadata. .. code-block:: bash - $ curl -XDELETE http://imbo/users//images/ + curl -XPOST http://imbo/users//images//meta.json -d '{ + "ABV":"16%", + "score":"100/100" + }' results in: @@ -679,43 +430,30 @@ results in: "imageIdentifier": "" } -where ```` is the image identifier of the image that was just deleted (the same as the one used in the URI). +where ```` is the image that just got updated. **Typical response codes:** * 200 OK -* 404 Not found - -.. _metadata-resource: - -Metadata resource -+++++++++++++++++ - -Imbo can also be used to attach metadata to the stored images. The metadata is based on a simple ``key => value`` model, for instance: - -* ``category: Music`` -* ``band: Koldbrann`` -* ``genre: Black metal`` -* ``country: Norway`` - -Metadata is handled via the ``meta`` resource in the URI, which is a sub-resource of ````. +* 400 Bad Request +* 404 Image not found -GET /users//images//meta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Fetch metadata +~~~~~~~~~~~~~~ -Get all metadata attached to ```` owned by ````. The output from Imbo is an empty list if the image has no metadata attached, or a JSON object with keys and values if metadata exists: +Requests using ``HTTP GET`` on this resource returns all metadata attached to an image. .. code-block:: bash - $ curl http://imbo/users//images//meta.json + curl http://imbo/users//images//meta.json results in: .. code-block:: javascript - [] + {} -when there is not metadata, or for example +when there is no metadata stored, or for example .. code-block:: javascript @@ -732,135 +470,120 @@ if the image has metadata attached to it. * 200 OK * 304 Not modified -* 404 Not found +* 404 Image not found -PUT /users//images//meta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Remove metadata +~~~~~~~~~~~~~~~ -Replace all existing metadata attached to ```` owned by ```` with the metadata contained in a JSON object in the request body. The response body contains a JSON object with the image identifier: +To remove metadata attached to an image a request using ``HTTP DELETE`` can be made. .. code-block:: bash - $ curl -XPUT http://imbo/users//images//meta.json -d '{ - "beer":"Dark Horizon First Edition", - "brewery":"Nøgne Ø", - "style":"Imperial Stout" - }' + curl -XDELETE http://imbo/users//images//meta.json results in: .. code-block:: javascript { - "imageIdentifier": "" + "imageIdentifier":"" } -where ```` is the image that just got updated. +where ```` is the image identifier of the image that just got all its metadata deleted. **Typical response codes:** * 200 OK * 400 Bad Request -* 404 Not found - -POST /users//images//meta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Edit existing metadata and/or add new keys/values to ```` owned by ```` with the metadata contained in a JSON object in the request body. The response body contains a JSON object with the image identifier: +* 404 Image not found -.. code-block:: bash +.. _access-tokens: - $ curl -XPOST http://imbo/users//images//meta.json -d '{ - "ABV":"16%", - "score":"100/100" - }' +Access tokens +------------- -results in: +Access tokens are enforced by an event listener that is enabled in the default configuration file. The access tokens are used to prevent `DoS `_ attacks so think twice before you disable the event listener. -.. code-block:: javascript +An access token, when enforced by the event listener, must be supplied in the URI using the ``accessToken`` query parameter and without it all ``GET`` and ``HEAD`` requests will result in a ``400 Bad Request`` response. The value of the ``accessToken`` parameter is a `Hash-based Message Authentication Code `_ (HMAC). The code is a `SHA-256 `_ hash of the URI itself using the private key of the user as the secret key. It is very important that the URI is not URL encoded when generating the hash. Below is an example on how to generate a valid access token for a specific image using PHP: - { - "imageIdentifier": "" - } +.. literalinclude:: ../examples/generateAccessToken.php + :language: php + :linenos: -where ```` is the image that just got updated. +If the event listener enforcing the access token check is removed, Imbo will ignore the ``accessToken`` query parameter completely. If you wish to implement your own form of access token you can do this by implementing an event listener of your own (see :ref:`custom-event-listeners` for more information). -**Typical response codes:** +.. _signing-write-requests: -* 200 OK -* 400 Bad Request -* 404 Not found +Signing write requests +---------------------- -DELETE /users//images//meta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +To be able to write to Imbo the user agent will have to specify two request headers: ``X-Imbo-Authenticate-Signature`` and ``X-Imbo-Authenticate-Timestamp``. -Delete all existing metadata attached to ```` owner by ````. The response body contains a JSON object with the image identifier: +``X-Imbo-Authenticate-Signature`` is, like the access token, an HMAC (also using SHA-256 and the private key of the user). -.. code-block:: bash +The data for the hash is generated using the following elements: - $ curl -XDELETE http://imbo/users//images//meta.json +* HTTP method (``PUT``, ``POST`` or ``DELETE``) +* The URI +* Public key of the user +* GMT timestamp (``YYYY-MM-DDTHH:MM:SSZ``, for instance: ``2011-02-01T14:33:03Z``) -results in: +These elements are concatenated in the above order with ``|`` as a delimiter character, and a hash is generated using the private key of the user. The following snippet shows how this can be accomplished in PHP when deleting an image: -.. code-block:: javascript +.. literalinclude:: ../examples/generateSignatureForDelete.php + :language: php + :linenos: - { - "imageIdentifier":"" - } +Imbo requires that ``X-Imbo-Authenticate-Timestamp`` is within ± 120 seconds of the current time on the server. -where ```` is the image identifier of the image that just got all its metadata deleted. +As with the access token the signature check is enforced by an event listener that can also be disabled. If you disable this event listener you effectively open up for writing from anybody, which you probably don't want to do. -**Typical response codes:** +If you want to implement your own authentication paradigm you can do this by creating a custom event listener. -* 200 OK -* 400 Bad Request -* 404 Not found +Supported content types +----------------------- -Authentication --------------- +Imbo currently responds with images (jpg, gif and png), `JSON `_ and `XML `_, but only accepts images (jpg, gif and png) and JSON as input. -Imbo uses two types of authentication mechanisms out of the box. It requires access tokens for all ``GET`` and ``HEAD`` requests made against all resources (with the exception of the status resource), and a valid request signature for all ``PUT``, ``POST`` and ``DELETE`` requests made against all resources that support these methods. Both mechanisms are enforced by event listeners that is enabled in the default configuration file. +Imbo will do content negotiation using the `Accept `_ header found in the request, unless you specify a file extension, in which case Imbo will deliver the type requested without looking at the Accept header. -.. _access-tokens: +The default Content-Type for non-image responses is JSON. Examples on this document uses the ``.json`` extension. Change it to ``.xml`` to get the XML representation instead. You can also skip the extension and force a specific Content-Type using the Accept header: -Access tokens -+++++++++++++ +.. code-block:: bash -Access tokens for all read requests are enforced by an event listener that is enabled per default. The access tokens are used to prevent `DoS`_ attacks so think twice (or maybe even some more) before you remove the listener. More about how to remove the listener in :ref:`configuration-event-listeners`. + curl http://imbo/status.json -The access token, when enforced, must be supplied in the URI using the ``accessToken`` query parameter and without it all ``GET`` and ``HEAD`` requests will result in a ``400 Bad Request`` response. The value of the ``accessToken`` parameter is a `Hash-based Message Authentication Code`_ (HMAC). The code is a hash of the URI itself (hashed with the `SHA-256`_ algorithm) using the private key of the user as the secret key. Below is an example on how to generate a valid access token for a specific image using PHP: +and -.. _SHA-256: http://en.wikipedia.org/wiki/SHA-2 -.. _DoS: http://en.wikipedia.org/wiki/Denial-of-service_attack -.. _Hash-based Message Authentication Code: http://en.wikipedia.org/wiki/HMAC +.. code-block:: bash -.. literalinclude:: ../examples/generateAccessToken.php - :language: php - :linenos: + curl -H "Accept: application/json" http://imbo/status -If you request a resource from Imbo without a valid access token it will respond with a ``400 Bad Request``. If the event listener enforcing the access token check is removed, Imbo will ignore the ``accessToken`` query parameter completely. If you wish to implement your own form of access token you can do this by implementing an event listener of your own (see :doc:`/advanced/custom_event_listeners` for more information). +will end up with the same content-type. Use ``application/xml`` for XML. -.. _signing-write-requests: +If you use JSON you can wrap the content in a function (`JSONP `_) by using one of the following query parameters: -Signing write requests -++++++++++++++++++++++ +* ``callback`` +* ``jsonp`` +* ``json`` -Imbo uses a similar method when authenticating write operations. To be able to write to Imbo the user agent will have to specify two request headers: ``X-Imbo-Authenticate-Signature`` and ``X-Imbo-Authenticate-Timestamp``, or two query parameters: ``signature`` and ``timestamp``. ``X-Imbo-Authenticate-Signature``/``signature`` is, like the access token, an HMAC (also using SHA-256 and the private key of the user), and is generated using the following elements: +.. code-block:: bash -* HTTP method (``PUT``, ``POST`` or ``DELETE``) -* The URI -* Public key of the user -* GMT timestamp (``YYYY-MM-DDTHH:MM:SSZ``, for instance: ``2011-02-01T14:33:03Z``) + curl http://imbo/status.json?callback=func -These elements are concatenated in the above order with ``|`` as a delimiter character, and a hash is generated using the private key of the user. The following snippet shows how this can be accomplished in PHP when deleting an image: +will result in: -.. literalinclude:: ../examples/generateSignatureForDelete.php - :language: php - :linenos: +.. code-block:: javascript -Imbo requires that ``X-Imbo-Authenticate-Timestamp``/``timestamp`` is within ± 120 seconds of the current time on the server. Both the signature and the timestamp must be URL-encoded when used as query parameters. + func( + { + "date": "Mon, 05 Nov 2012 19:18:40 GMT", + "database": true, + "storage": true + } + ) -As with the access token the signature check is enforced by an event listener that can also be disabled. If you want to implement your own authentication paradigm you can do this by creating a custom event listener. +For images the default mime-type is the original mime-type of the image. If you add an ``image/gif`` and fetches that image with ``Accept: */*`` or ``Accept: image/*`` the mime type of the image returned will be ``image/gif``. To choose a different mime type either change the Accept header, or use ``.jpg`` or ``.png`` (for ``image/jpeg`` and ``image/png`` respectively). Errors ------ @@ -869,7 +592,7 @@ When an error occurs Imbo will respond with a fitting HTTP response code along w .. code-block:: bash - $ curl "http://imbo/users//images/.jpg?t\[\]=foobar" + curl "http://imbo/users//images/.jpg?t\[\]=foobar" results in: @@ -893,7 +616,7 @@ If the user agent specifies a nonexistent username the following occurs: .. code-block:: bash - $ curl http://imbo/users/.json + curl http://imbo/users/.json results in: @@ -902,10 +625,8 @@ results in: { "error": { "code": 404, - "message": "Unknown public key", + "message": "Public key not found", "date": "Mon, 13 Aug 2012 17:22:37 GMT", "imboErrorCode": 100 } } - -if ```` does not exist. diff --git a/docs/usage/configuration.rst b/docs/usage/configuration.rst deleted file mode 100644 index 11d3b4c23..000000000 --- a/docs/usage/configuration.rst +++ /dev/null @@ -1,1339 +0,0 @@ -.. _configuration: - -Configuration -============= - -Imbo ships with a default configuration file named ``config/config.default.php`` that Imbo will load. You can specify your own configuration file, ``config/config.php``, that Imbo will merge with the default. You should never update ``config/config.default.php``. - -User key pairs --------------- - -Every user that wants to store images in Imbo needs a public and private key pair. These keys are stored in the ``auth`` part of the configuration file: - -.. code-block:: php - :linenos: - - array( - 'username' => '95f02d701b8dc19ee7d3710c477fd5f4633cec32087f562264e4975659029af7', - 'otheruser' => 'b312ff29d5da23dcd230b61ff4db1e2515c862b9fb0bb59e7dd54ce1e4e94a53', - ), - - // ... - ); - -The public keys can consist of the following characters: - -* a-z (only lowercase is allowed) -* 0-9 -* _ and - - -and must be at least 3 characters long. - -For the private keys you can for instance use a `SHA-256`_ hash of a random value. The private key is used by clients to sign requests, and if you accidentally give away your private key users can use it to delete all your images. Make sure not to generate a private key that is easy to guess (like for instance the MD5 or SHA-256 hash of the public key). Imbo does not require the private key to be in a specific format, so you can also use regular passwords if you want. - -Imbo ships with a small command line tool that can be used to generate private keys for you using the `openssl_random_pseudo_bytes`_ function. The script is located in the `scripts` directory and does not require any arguments: - -.. code-block:: bash - - $ php scripts/generatePrivateKey.php - 3b98dde5f67989a878b8b268d82f81f0858d4f1954597cc713ae161cdffcc84a - -.. _SHA-256: http://en.wikipedia.org/wiki/SHA-2 -.. _openssl_random_pseudo_bytes: http://php.net/openssl_random_pseudo_bytes - -The private key can be changed whenever you want as long as you remember to change it in both the server configuration and in the client you use. The public key can not be changed easily as database and storage drivers use it when storing images and metadata. - -Database configuration ----------------------- - -The database driver you decide to use is responsible for storing metadata and basic image information, like width and height for example, along with the generated short URLs. Imbo ships with some different implementations that you can use. Remember that you will not be able to switch the driver whenever you want and expect all data to be automatically transferred. Choosing a database driver should be a long term commitment unless you have migration scripts available. - -In the default configuration file the :ref:`default-database-driver` storage driver is used, and it is returned via a Closure. You can choose to override this in your ``config.php`` file by specifying a closure that returns a different value, or you can specify an implementation of the ``Imbo\Database\DatabaseInterface`` interface directly. Which database driver to use is specified in the ``database`` key in the configuration array: - -.. code-block:: php - :linenos: - - function() { - return new Database\MongoDB(array( - 'databaseName' => 'imbo', - )); - }, - - // or - - 'database' => new Database\MongoDB(array( - 'databaseName' => 'imbo', - )), - - // ... - ); - -Available database drivers -++++++++++++++++++++++++++ - -The following database drivers are shipped with Imbo: - -.. contents:: - :local: - :depth: 1 - -.. _doctrine-database-driver: - -Doctrine -^^^^^^^^ - -This driver uses the `Doctrine Database Abstraction Layer`_. The options you pass to the constructor of this driver is passed to the underlying classes, so have a look at the Doctrine-DBAL documentation over at `doctrine-project.org`_. - -.. _Doctrine Database Abstraction Layer: http://www.doctrine-project.org/projects/dbal.html -.. _doctrine-project.org: http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html - -Database schema -~~~~~~~~~~~~~~~ - -When using this driver you need to create a couple of tables in the `DBMS`_ you choose to use. Below you will find statements to create the necessary tables for `SQLite`_ and `MySQL`_. - -.. _DBMS: http://en.wikipedia.org/wiki/Relational_database_management_system -.. _SQLite: http://www.sqlite.org/ -.. _MySQL: http://www.mysql.com/ - -SQLite -'''''' - -.. code-block:: sql - :linenos: - - CREATE TABLE IF NOT EXISTS imageinfo ( - id INTEGER PRIMARY KEY NOT NULL, - publicKey TEXT NOT NULL, - imageIdentifier TEXT NOT NULL, - size INTEGER NOT NULL, - extension TEXT NOT NULL, - mime TEXT NOT NULL, - added INTEGER NOT NULL, - updated INTEGER NOT NULL, - width INTEGER NOT NULL, - height INTEGER NOT NULL, - checksum TEXT NOT NULL, - UNIQUE (publicKey,imageIdentifier) - ); - - CREATE TABLE IF NOT EXISTS metadata ( - id INTEGER PRIMARY KEY NOT NULL, - imageId KEY INTEGER NOT NULL, - tagName TEXT NOT NULL, - tagValue TEXT NOT NULL - ); - - CREATE TABLE IF NOT EXISTS shorturl ( - shortUrlId TEXT PRIMARY KEY NOT NULL, - publicKey TEXT NOT NULL, - imageIdentifier TEXT NOT NULL, - extension TEXT, - query TEXT NOT NULL - ); - - CREATE INDEX shorturlparams ON shorturl ( - publicKey, - imageIdentifier, - extension, - query - ); - -MySQL -''''' - -.. code-block:: sql - :linenos: - - CREATE TABLE IF NOT EXISTS `imageinfo` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `publicKey` varchar(255) COLLATE utf8_danish_ci NOT NULL, - `imageIdentifier` char(32) COLLATE utf8_danish_ci NOT NULL, - `size` int(10) unsigned NOT NULL, - `extension` varchar(5) COLLATE utf8_danish_ci NOT NULL, - `mime` varchar(20) COLLATE utf8_danish_ci NOT NULL, - `added` int(10) unsigned NOT NULL, - `updated` int(10) unsigned NOT NULL, - `width` int(10) unsigned NOT NULL, - `height` int(10) unsigned NOT NULL, - `checksum` char(32) COLLATE utf8_danish_ci NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `image` (`publicKey`,`imageIdentifier`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci AUTO_INCREMENT=1 ; - - CREATE TABLE IF NOT EXISTS `metadata` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `imageId` int(10) unsigned NOT NULL, - `tagName` varchar(255) COLLATE utf8_danish_ci NOT NULL, - `tagValue` varchar(255) COLLATE utf8_danish_ci NOT NULL, - PRIMARY KEY (`id`), - KEY `imageId` (`imageId`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci AUTO_INCREMENT=1 ; - - CREATE TABLE `shorturl` ( - `shortUrlId` char(7) COLLATE utf8_danish_ci NOT NULL, - `publicKey` varchar(255) COLLATE utf8_danish_ci NOT NULL, - `imageIdentifier` char(32) COLLATE utf8_danish_ci NOT NULL, - `extension` char(3) COLLATE utf8_danish_ci DEFAULT NULL, - `query` text COLLATE utf8_danish_ci NOT NULL, - PRIMARY KEY (`shortUrlId`), - KEY `params` (`publicKey`,`imageIdentifier`,`extension`,`query`(255)) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci; - -.. note:: Imbo will not create these tables automatically. - -Examples -~~~~~~~~ - -Here are some examples on how to use the Doctrine driver in the configuration file: - -1) Use a `PDO`_ instance to connect to a SQLite database: - -.. _PDO: http://php.net/pdo - -.. code-block:: php - :linenos: - - function() { - return new Database\Doctrine(array( - 'pdo' => new \PDO('sqlite:/path/to/database'), - )); - }, - - // ... - ); - -2) Connect to a MySQL database using PDO: - -.. _PDO: http://php.net/pdo - -.. code-block:: php - :linenos: - - function() { - return new Database\Doctrine(array( - 'dbname' => 'database', - 'user' => 'username', - 'password' => 'password', - 'host' => 'hostname', - 'driver' => 'pdo_mysql', - )); - }, - - // ... - ); - -.. _mongodb-database-driver: -.. _default-database-driver: - -MongoDB -^^^^^^^ - -This driver uses PHP's `mongo extension`_ to store data in `MongoDB`_. The following parameters are supported: - -.. _mongo extension: http://pecl.php.net/package/mongo -.. _MongoDB: http://www.mongodb.org/ - -``databaseName`` - Name of the database to use. Defaults to ``imbo``. - -``server`` - The server string to use when connecting. Defaults to ``mongodb://localhost:27017``. - -``options`` - Options passed to the underlying driver. Defaults to ``array('connect' => true, 'timeout' => 1000)``. See the `manual for the Mongo constructor`_ at `php.net `_ for available options. - -.. _manual for the Mongo constructor: http://php.net/manual/en/mongo.construct.php - -Examples -~~~~~~~~ - -1) Connect to a local MongoDB instance using the default ``databaseName``: - -.. code-block:: php - :linenos: - - function() { - return new Database\MongoDB(); - }, - - // ... - ); - -2) Connect to a `replica set`_: - -.. _replica set: http://www.mongodb.org/display/DOCS/Replica+Sets - -.. code-block:: php - :linenos: - - function() { - return new Database\MongoDB(array( - 'server' => 'mongodb://server1,server2,server3', - 'options' => array( - 'replicaSet' => 'nameOfReplicaSet', - ), - )); - }, - - // ... - ); - -Storage configuration ---------------------- - -Storage drivers are responsible for storing the original images you put into imbo. Like with the database driver it is not possible to simply switch a driver without having migration scripts available to move the stored images. Choose a driver with care. - -In the default configuration file the :ref:`default-storage-driver` storage driver is used, and it is returned via a Closure. You can choose to override this in your ``config.php`` file by specifying a closure that returns a different value, or you can specify an implementation of the ``Imbo\Storage\StorageInterface`` interface directly. Which storage driver to use is specified in the ``storage`` key in the configuration array: - -.. code-block:: php - :linenos: - - new function() { - return new Storage\Filesystem(array( - 'dataDir' => '/path/to/images', - )); - }, - - // ... - ); - -Available storage drivers -+++++++++++++++++++++++++ - -The following storage drivers are shipped with Imbo: - -.. contents:: - :local: - :depth: 1 - -.. _doctrine-storage-driver: - -Doctrine -^^^^^^^^ - -This driver uses the `Doctrine Database Abstraction Layer`_. The options you pass to the constructor of this driver is passed to the underlying classes, so have a look at the Doctrine-DBAL documentation over at `doctrine-project.org`_. - -.. _Doctrine Database Abstraction Layer: http://www.doctrine-project.org/projects/dbal.html -.. _doctrine-project.org: http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html - -Database schema -~~~~~~~~~~~~~~~ - -When using this driver you need to create a table in the `DBMS`_ you choose to use. Below you will find a statement to create this table in `SQLite`_ and `MySQL`_. - -SQLite -'''''' - -.. code-block:: sql - :linenos: - - CREATE TABLE storage_images ( - publicKey TEXT NOT NULL, - imageIdentifier TEXT NOT NULL, - data BLOB NOT NULL, - updated INTEGER NOT NULL, - PRIMARY KEY (publicKey,imageIdentifier) - ) - -MySQL -''''' - -.. code-block:: sql - :linenos: - - CREATE TABLE IF NOT EXISTS `storage_images` ( - `publicKey` varchar(255) COLLATE utf8_danish_ci NOT NULL, - `imageIdentifier` char(32) COLLATE utf8_danish_ci NOT NULL, - `data` blob NOT NULL, - `updated` int(10) unsigned NOT NULL, - PRIMARY KEY (`publicKey`,`imageIdentifier`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci; - -.. note:: Imbo will not create the table automatically. - -Examples -~~~~~~~~ - -Here are some examples on how to use the Doctrine driver in the configuration file: - -1) Use a `PDO`_ instance to connect to a SQLite database: - -.. _PDO: http://php.net/pdo - -.. code-block:: php - - function() { - return new Storage\Doctrine(array( - 'pdo' => new \PDO('sqlite:/path/to/database'), - )); - }, - - // ... - ); - -2) Connect to a MySQL database using PDO: - -.. _PDO: http://php.net/pdo - -.. code-block:: php - - function() { - return new Storage\Doctrine(array( - 'dbname' => 'database', - 'user' => 'username', - 'password' => 'password', - 'host' => 'hostname', - 'driver' => 'pdo_mysql', - )); - }, - - // ... - ); - -.. _filesystem-storage-driver: - -Filesystem -^^^^^^^^^^ - -This driver simply stores all images on the file system. This driver only has one parameter, and that is the directory where you want your images stored: - -``dataDir`` - The base path where the images are stored. - -This driver is configured to create subdirectories inside of ``dataDir`` based on the public key of the user and the checksum of the images added to Imbo. If you have configured this driver with ``/path/to/images`` as ``dataDir`` and issue the following command: - -.. code-block:: bash - - $ curl -XPUT http://imbo/users/username/images/bbd9ae7bbfcefb0cc9a52f03f89dd3f9 --data-binary @someImage.jpg - -the image will be stored in: - -``/path/to/images/u/s/e/username/b/b/d/bbd9ae7bbfcefb0cc9a52f03f89dd3f9`` - -The algorithm that generates the path simply takes the three first characters of ```` and creates directories for each of them, then the full public key, then a directory of each of the first characters in ```` and lastly it stores the image in a file with a filename equal to ````. - -Read more about the API in the :doc:`api` topic. - -Examples -~~~~~~~~ - -Default configuration: - -.. code-block:: php - - function() { - new Storage\Filesystem(array( - 'dataDir' => '/path/to/images', - )); - }, - - // ... - ); - -.. _gridfs-storage-driver: -.. _default-storage-driver: - -GridFS -^^^^^^ - -The GridFS driver is used to store the images in MongoDB using the `GridFS specification`_. This driver has the following parameters: - -.. _GridFS specification: http://www.mongodb.org/display/DOCS/GridFS - -``databaseName`` - The name of the database to store the images in. Defaults to ``imbo_storage``. - -``server`` - The server string to use when connecting to MongoDB. Defaults to ``mongodb://localhost:27017`` - -``options`` - Options passed to the underlying driver. Defaults to ``array('connect' => true, 'timeout' => 1000)``. See the `manual for the Mongo constructor`_ at `php.net `_ for available options. - -Examples -~~~~~~~~ - -1) Connect to a local MongoDB instance using the default ``databaseName``: - -.. code-block:: php - :linenos: - - function() { - return new Storage\GridFS(); - }, - - // ... - ); - -2) Connect to a `replica set`_: - -.. code-block:: php - :linenos: - - function() { - return new Storage\GridFS(array( - 'server' => 'mongodb://server1,server2,server3', - 'options' => array( - 'replicaSet' => 'nameOfReplicaSet', - ), - )); - }, - - // ... - ); - -.. _configuration-event-listeners: - -Event listeners ---------------- - -Imbo also supports event listeners that you can use to hook into Imbo at different phases without having to edit Imbo itself. An event listener is simply a piece of code that will be executed when a certain event is triggered from Imbo. Event listeners are added to the ``eventListeners`` part of the configuration array as associative arrays. The keys are short names used to identify the listeners, and are not really used for anything in the Imbo application, but exists so you can override/disable event listeners specified in ``config.default.php``. If you want to disable the default event listeners simply specify the same key in the ``config.php`` file and set the value to ``null`` or ``false``. - -Event listeners can be added in the following ways: - -1) Use an instance of a class implementing the ``Imbo\EventListener\ListenerInterface`` interface: - -.. code-block:: php - :linenos: - - array( - 'accessToken' => new EventListener\AccessToken(), - ), - - // ... - ); - -2) A closure returning an instance of the ``Imbo\EventListener\ListenerInterface`` interface - -.. code-block:: php - :linenos: - - array( - 'accessToken' => function() { - return new EventListener\AccessToken(); - }, - ), - - // ... - ); - -3) Use an instance of a class implementing the ``Imbo\EventListener\ListenerInterface`` interface together with a public key filter: - -.. code-block:: php - :linenos: - - array( - 'maxImageSize' => array( - 'listener' => new EventListener\MaxImageSize(1024, 768), - 'publicKeys' => array( - 'include' => array('user'), - // 'exclude' => array('someotheruser'), - ), - ), - ), - - // ... - ); - -where ``listener`` is an instance of the ``Imbo\EventListener\ListenerInterface`` interface, and ``publicKeys`` is an array that you can use if you want your listener to only be triggered for some users (public keys). The value of this is an array with one of two keys: ``include`` or ``exclude`` where ``include`` is an array you want your listener to trigger for, and ``exclude`` is an array of users you don't want your listener to trigger for. ``publicKeys`` is optional, and per default the listener will trigger for all users. - -4) Use a `closure`_: - -.. _closure: http://php.net/manual/en/functions.anonymous.php - -.. code-block:: php - :linenos: - - array( - 'customListener' => array( - 'callback' => function(EventManager\EventInterface $event) { - // Custom code - }, - 'events' => array('image.get'), - 'priority' => 1, - 'publicKeys' => array( - 'include' => array('user'), - // 'exclude' => array('someotheruser'), - ), - ), - ), - - // ... - ); - -where ``callback`` is the code you want executed, and ``events`` is an array of the events you want it triggered for. ``priority`` is the priority of the listener and defaults to 1. The higher the number, the earlier in the chain your listener will be triggered. This number can also be negative. Imbo's internal event listeners uses numbers between 1 and 100. ``publicKeys`` uses the same format as described above. - -Events -++++++ - -When configuring an event listener you need to know about the events that Imbo triggers. The most important events are combinations of the accessed resource along with the HTTP method used. Imbo currently provides five resources: - -* :ref:`stats ` -* :ref:`status ` -* :ref:`user ` -* :ref:`images ` -* :ref:`image ` -* :ref:`metadata ` - -Examples of events that is triggered: - -* ``image.get`` -* ``image.put`` -* ``image.delete`` - -As you can see from the above examples the events are built up by the resource name and the HTTP method, separated by ``.``. - -Some other notable events: - -* ``storage.image.insert`` -* ``storage.image.load`` -* ``storage.image.delete`` -* ``db.image.insert`` -* ``db.image.load`` -* ``db.image.delete`` -* ``db.metadata.update`` -* ``db.metadata.load`` -* ``db.metadata.delete`` -* ``route`` -* ``response.send`` - -Below you will see the different event listeners that Imbo ships with and the events they subscribe to. - -Event listeners -+++++++++++++++ - -Imbo ships with a collection of event listeners for you to use. Some of them are enabled in the default configuration file. - -.. contents:: - :local: - :depth: 1 - -.. _access-token-event-listener: - -Access token -^^^^^^^^^^^^ - -This event listener enforces the usage of access tokens on all read requests against user-specific resources. You can read more about how the actual access tokens works in the :ref:`access-tokens` topic in the :doc:`api` section. - -To enforce the access token check for all read requests this event listener subscribes to the following events: - -* ``user.get`` -* ``images.get`` -* ``image.get`` -* ``metadata.get`` -* ``user.head`` -* ``images.head`` -* ``image.head`` -* ``metadata.head`` - -This event listener has a single parameter that can be used to whitelist and/or blacklist certain image transformations, used when the current request is against an image resource. The parameter is an array with a single key: ``transformations``. This is another array with two keys: ``whitelist`` and ``blacklist``. These two values are arrays where you specify which transformation(s) to whitelist or blacklist. The names of the transformations are the same as the ones used in the request. See :ref:`image-transformations` for a complete list of the supported transformations. - -Use ``whitelist`` if you want the listener to skip the access token check for certain transformations, and ``blacklist`` if you want it to only check certain transformations: - -.. code-block:: php - - array('transformations' => array( - 'whitelist' => array( - 'border', - ) - )) - -means that the access token will **not** be enforced for the :ref:`border-transformation` transformation. - -.. code-block:: php - - array('transformations' => array( - 'blacklist' => array( - 'border', - ) - )) - -means that the access token will be enforced **only** for the :ref:`border-transformation` transformation. - -If both ``whitelist`` and ``blacklist`` are specified all transformations will require an access token unless it's included in ``whitelist``. - -This event listener is included in the default configuration file without specifying any filters (which means that the access token will be enforced for all requests): - -.. code-block:: php - :linenos: - - array( - 'accessToken' => function() { - return new EventListener\AccessToken(); - }, - ), - - // ... - ); - -Disable this event listener with care. Clients can easily `DDoS`_ your installation if you let them specify image transformations without limitations. - -.. _DDoS: http://en.wikipedia.org/wiki/DDoS - -Authenticate -^^^^^^^^^^^^ - -This event listener enforces the usage of signatures on all write requests against user-specific resources. You can read more about how the actual signature check works in the :ref:`signing-write-requests` topic in the :doc:`api` section. - -To enforce the signature check for all write requests this event listener subscribes to the following events: - -* ``image.put`` -* ``image.post`` -* ``image.delete`` -* ``metadata.put`` -* ``metadata.post`` -* ``metadata.delete`` - -This event listener does not support any parameters and is enabled per default like this: - -.. code-block:: php - :linenos: - - array( - 'authenticate' => function() { - return new EventListener\Authenticate(); - }, - ), - - // ... - ); - -Disable this event listener with care. Clients can delete all your images and metadata when this listener is not enabled. - -Auto rotate image -^^^^^^^^^^^^^^^^^ - -This event listener will auto rotate new images based on metadata embedded in the image itself (`EXIF`_). - -.. _EXIF: http://en.wikipedia.org/wiki/Exchangeable_image_file_format - -The listener does not support any parameters and can be enabled like this: - -.. code-block:: php - :linenos: - - array( - 'autoRotate' => function() { - return new EventListener\AutoRotateImage(); - }, - ), - - // ... - ); - -If you enable this listener all new images added to Imbo will be auto rotated based on the EXIF data. - -CORS (Cross-Origin Resource Sharing) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This event listener can be used to allow clients such as web browsers to use Imbo when the client is located on a different origin/domain than the Imbo server is. This is implemented by sending a set of CORS-headers on specific requests, if the origin of the request matches a configured domain. - -The event listener can be configured on a per-resource and per-method basis, and will therefore listen to any related events. If enabled without any specific configuration, the listener will allow and respond to the **GET**, **HEAD** and **OPTIONS** methods on all resources. Note however that no origins are allowed by default and that a client will still need to provide a valid access token, unless the :ref:`access-token-event-listener` listener is disabled. - -To enable the listener, use the following: - -.. code-block:: php - :linenos: - - array( - 'cors' => function() { - return new EventListener\Cors(array( - 'allowedOrigins' => array('http://some.origin'), - 'allowedMethods' => array( - 'image' => array('GET', 'HEAD', 'PUT'), - 'images' => array('GET', 'HEAD'), - ), - 'maxAge' => 3600, - )); - }, - ), - - // ... - ); - -``allowedOrigins`` is an array of allowed origins. Specifying ``*`` as a value in the array will allow any origin. - -``allowedMethods`` is an associative array where the keys represent the resource (``image``, ``images``, ``metadata``, ``status`` and ``user``). The value is an array of HTTP methods you wish to open up. - -``maxAge`` specifies how long the response of an OPTIONS-request can be cached for, in seconds. Defaults to 3600 (one hour). - -Exif metadata -^^^^^^^^^^^^^ - -This event listener can be used to fetch the EXIF-tags from uploaded images and adding them as metadata. Enabling this event listener will not populate metadata for images already added to Imbo. - -The event listener subscribes to the following events: - -* ``image.put`` -* ``db.image.insert`` - -and has the following parameters: - -``$allowedTags`` - The tags you want to be populated as metadata, if present. Optional - by default all tags are added. - -and is enabled like this: - -.. code-block:: php - :linenos: - - array( - 'exifMetadata' => function() { - return new EventListener\ExifMetadata(array( - 'exif:Make', - 'exif:Model', - )); - }, - ), - - // ... - ); - -which would allow only ``exif:Make`` and ``exif:Model`` as metadata tags. Not passing an array to the constructor will allow all tags. - -Image transformation cache -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This event listener enables caching of image transformations. Read more about image transformations in the :ref:`image-transformations` topic in the :doc:`api` section. - -To achieve this the listener subscribes to the following events: - -* ``image.get`` (both before and after the main application logic) -* ``image.delete`` - -The event listener has one parameter: - -``$path`` - Root path where the cached images will be stored. - -and is enabled like this: - -.. code-block:: php - :linenos: - - array( - 'imageTransformationCache' => function() { - return new EventListener\ImageTransformationCache('/path/to/cache'); - }, - ), - - // ... - ); - -.. note:: - This event listener uses a similar algorithm when generating file names as the :ref:`filesystem-storage-driver` storage driver. - -.. warning:: - It can be wise to purge old files from the cache from time to time. If you have a large amount of images and present many different variations of these the cache will use up quite a lot of storage. - - An example on how to accomplish this: - - .. code-block:: bash - - $ find /path/to/cache -ctime +7 -type f -delete - - The above command will delete all files in /path/to/cache older than 7 days and can be used with for instance `crontab`_. - -.. _crontab: http://en.wikipedia.org/wiki/Cron - -Max image size -^^^^^^^^^^^^^^ - -This event listener can be used to enforce a maximum size (height and width, not byte size) of **new** images. Enabling this event listener will not change images already added to Imbo. - -The event listener subscribes to the following event: - -* ``image.put`` - -and has the following parameters: - -``$width`` - The max width in pixels of new images. If a new image exceeds this limit it will be downsized. - -``$height`` - The max height in pixels of new images. If a new image exceeds this limit it will be downsized. - -and is enabled like this: - -.. code-block:: php - :linenos: - - array( - 'maxImageSize' => function() { - return new EventListener\MaxImageSize(1024, 768); - }, - ), - - // ... - ); - -which would effectively downsize all images exceeding a ``width`` of ``1024`` or a ``height`` of ``768``. The aspect ratio will be kept. - -Metadata cache -^^^^^^^^^^^^^^ - -This event listener enables caching of metadata fetched from the backend so other requests won't need to go all the way to the backend to fetch metadata. To achieve this the listener subscribes to the following events: - -* ``db.metadata.load`` -* ``db.metadata.delete`` -* ``db.metadata.update`` - -and has the following parameters: - -``Imbo\Cache\CacheInterface $cache`` - An instance of a cache adapter. Imbo ships with :ref:`apc-cache` and :ref:`memcached-cache` adapters, and both can be used for this event listener. If you want to use another form of caching you can simply implement the ``Imbo\Cache\CacheInterface`` interface and pass an instance of the custom adapter to the constructor of the event listener. Here is an example that uses the APC adapter for caching: - -.. code-block:: php - :linenos: - - array( - 'metadataCache' => function() { - return new EventListener\MetadataCache(new Cache\APC('imbo')); - }, - ), - - // ... - ); - -.. _stats-access: - -Stats access -^^^^^^^^^^^^ - -This event listener controls the access to the :ref:`stats endpoint ` by using simple white-/blacklists containing IP addresses. - -This listener is enabled per default, and only allows ``127.0.0.1`` to access the statistics: - - -.. code-block:: php - - array( - 'statsAccess' => function() { - return new EventListener\StatsAccess(array( - 'whitelist' => array('127.0.0.1'), - 'blacklist' => array(), - )); - }, - ), - - // ... - ); - -If the whitelist is populated, only the listed IP addresses will gain access. If the blacklist is populated only the listed IP addresses will be denied access. If both lists are populated the IP address of the client must be present in the whitelist to gain access. If an IP address is present in both lists, it will not gain access. - -The event object -++++++++++++++++ - -The object passed to the event listeners (and closures) is an instance of the ``Imbo\EventManager\EventInterface`` interface. This interface has some methods that event listeners can use: - -``getName()`` - Get the name of the current event. For instance ``image.delete``. - -``getRequest()`` - Get the current request object (an instance of ``Imbo\Http\Request\Request``) - -``getResponse()`` - Get the current response object (an instance of ``Imbo\Http\Response\Response``) - -``getDatabase()`` - Get the current database adapter (an instance of ``Imbo\Database\DatabaseInterface``) - -``getStorage()`` - Get the current storage adapter (an instance of ``Imbo\Storage\StorageInterface``) - -``getManager()`` - Get the current event manager (an instance of ``Imbo\EventManager\EventManager``) - -.. _image-transformations: - -Image transformations ---------------------- - -Imbo supports a set of image transformations out of the box using the `Imagick PHP extension `_. All supported image transformations are included in the configuration, and you can easily add your own custom transformations or create presets using a combination of existing transformations. - -Transformations are triggered using the ``t[]`` query parameter together with the image resource (read more about the image resource and the included transformations and their parameters in the :ref:`image-resource` section). This parameter should be used as an array so that multiple transformations can be made. The transformations are applied in the order they are specified in the URL. - -All transformations are registered in the configuration array under the ``imageTransformations`` key: - -.. code-block:: php - :linenos: - - array( - 'border' => function (array $params) { - return new Image\Transformation\Border($params); - }, - 'canvas' => function (array $params) { - return new Image\Transformation\Canvas($params); - }, - // ... - ), - - // ... - ); - -where the keys are the names of the transformations as specified in the URL, and the values are closures which all receive a single argument. This argument is an array that matches the parameters for the transformation as specified in the URL. If you use the following query parameter: - -``t[]=border:width=1,height=2,color=f00`` - -the ``$params`` array given to the closure will look like this: - -.. code-block:: php - - '1', - 'height' => '1', - 'color' => 'f00' - ) - - -The return value of the closure must either be an instance of the ``Imbo\Image\Transformation\TransformationInterface`` interface, or code that is callable (for instance another closure, or a class that includes an ``__invoke`` method). If the return value is a callable piece of code it will receive a single parameter which is an instance of ``Imbo\Model\Image``, which is the image you want your transformation to modify. See some examples in the :ref:`custom-transformations` section below. - -Presets -+++++++ - -Imbo supports the notion of transformation presets by using the ``Imbo\Image\Transformation\Collection`` transformation. The constructor of this transformation takes an array containing other transformations. - -.. code-block:: php - :linenos: - - array( - 'graythumb' => function ($params) { - return new Image\Transformation\Collection(array( - new Image\Transformation\Desaturate(), - new Image\Transformation\Thumbnail($params), - )); - }, - ), - - // ... - ); - -which can be triggered using the following query parameter: - -``t[]=graythumb`` - -.. _custom-transformations: - -Custom transformations -++++++++++++++++++++++ - -You can also implement your own transformations by implementing the ``Imbo\Image\Transformation\TransformationInterface`` interface, or by specifying a callable piece of code. An implementation of the border transformation as a callable piece of code could for instance look like this: - -.. code-block:: php - :linenos: - - array( - 'border' => function (array $params) { - return function (Model\Image $image) use ($params) { - $color = !empty($params['color']) ? $params['color'] : '#000'; - $width = !empty($params['width']) ? $params['width'] : 1; - $height = !empty($params['height']) ? $params['height'] : 1; - - try { - $imagick = new \Imagick(); - $imagick->readImageBlob($image->getBlob()); - $imagick->borderImage($color, $width, $height); - - $size = $imagick->getImageGeometry(); - - $image->setBlob($imagick->getImageBlob()) - ->setWidth($size['width']) - ->setHeight($size['height']); - } catch (\ImagickException $e) { - throw new Image\Transformation\TransformationException($e->getMessage(), 400, $e); - } - }; - }, - ), - - // ... - ); - -It's not recommended to use this method for big complicated transformations. It's better to implement the interface mentioned above, and refer to your class in the configuration array instead: - -.. code-block:: php - :linenos: - - array( - 'border' => function (array $params) { - return new My\Custom\BorderTransformation($params); - }, - ), - - // ... - ); - -where ``My\Custom\BorderTransformation`` implements ``Imbo\Image\Transformation\TransformationInterface``. - -Custom resources and routes ---------------------------- - -.. warning:: Custom resources and routes is an experimental and advanced way of extending Imbo, and requires extensive knowledge of how Imbo works internally. - -If you need to create a custom route you can attach a route and a custom resource class using the configuration. Two keys exists for this purpose: ``routes`` and ``resources``: - -.. code-block:: php - :linenos: - - array( - 'users' => '#^/users(\.(?json|xml))?$#', - ), - - 'resources' => array( - 'users' => function() { - return new Users(); - }, - - // or - - 'users' => __NAMESPACE__ . '\Users', - ), - - // ... - ); - -In the above example we are creating a route for Imbo using a regular expression, called ``users``. The route itself will match the following three requests: - -* ``/users`` -* ``/users.json`` -* ``/users.xml`` - -When a request is made against any of these endpoints Imbo will try to access a resource that is specified with the same key (``users``). The value specified for this entry in the ``resources`` array must either be a string representing the name of the resource class or a closure that, when executed, returns an instance of the resource class. This resource class must implement at least two interfaces to be able to respond to a request: ``Imbo\Resource\ResourceInterface`` and ``Imbo\EventListener\ListenerInterface``. - -Below is an example implementation of the ``Imbo\Users`` resource: - -.. code-block:: php - :linenos: - - setList('users', 'user', array_keys($event->getConfig()['auth'])); - $event->getResponse()->setModel($model); - } - } - -This resource informs Imbo that it supports ``HTTP GET``, and specifies a callback for the ``users.get`` event. The name of the event is the name specified for the resource in the configuration above, along with the HTTP method, separated with a dot. - -In the ``get()`` method we are simply creating a list model for Imbo's response formatter, and we are supplying the keys from the ``auth`` part of the configuration as data. When formatted as JSON the response looks like this: - -.. code-block:: json - - { - "users": [ - "someuser", - "someotheruser" - ] - } - -and the XML representation looks like this: - -.. code-block:: xml - - - - - someuser - someotheruser - - diff --git a/docs/usage/image-transformations.rst b/docs/usage/image-transformations.rst new file mode 100644 index 000000000..a0b90148e --- /dev/null +++ b/docs/usage/image-transformations.rst @@ -0,0 +1,317 @@ +.. _image-transformations: + +Transforming images on the fly +============================== + +What you as an end-user of an Imbo installation will be doing most of the time, is working with images. This is what Imbo was originally made for, and this chapter includes details about all the different image transformations Imbo supports. + +.. _border-transformation: + +Add an image border - ``t[]=border`` +------------------------------------ + +This transformation will apply a border around the image. + +**Parameters:** + +``color`` + Color of the border in hexadecimal. Defaults to ``000000`` (You can also specify short values like ``f00`` (``ff0000``)). + +``width`` + Width of the border in pixels on the left and right sides of the image. Defaults to ``1``. + +``height`` + Height of the border in pixels on the top and bottom sides of the image. Defaults to ``1``. + +``mode`` + Mode of the border. Can be ``inline`` or ``outbound``. Defaults to ``outbound``. Outbound places the border outside of the image, increasing the dimensions of the image. ``inline`` paints the border inside of the image, retaining the original width and height of the image. + +**Examples:** + +* ``t[]=border`` +* ``t[]=border:mode=inline`` +* ``t[]=border:color=000`` +* ``t[]=border:color=f00,width=2,height=2`` + +Expand the image canvas - ``t[]=canvas`` +---------------------------------------- + +This transformation can be used to change the canvas of the original image. + +**Parameters:** + +``width`` + Width of the surrounding canvas in pixels. If omitted the width of ```` will be used. + +``height`` + Height of the surrounding canvas in pixels. If omitted the height of ```` will be used. + +``mode`` + The placement mode of the original image. ``free``, ``center``, ``center-x`` and ``center-y`` are available values. Defaults to ``free``. + +``x`` + X coordinate of the placement of the upper left corner of the existing image. Only used for modes: ``free`` and ``center-y``. + +``y`` + Y coordinate of the placement of the upper left corner of the existing image. Only used for modes: ``free`` and ``center-x``. + +``bg`` + Background color of the canvas. Defaults to ``ffffff`` (also supports short values like ``f00`` (``ff0000``)). + +**Examples:** + +* ``t[]=canvas:width=200,mode=center`` +* ``t[]=canvas:width=200,height=200,x=10,y=10,bg=000`` +* ``t[]=canvas:width=200,height=200,x=10,mode=center-y`` +* ``t[]=canvas:width=200,height=200,y=10,mode=center-x`` + +Compress the image - ``t[]=compress`` +------------------------------------- + +This transformation compresses images on the fly resulting in a smaller payload. + +**Parameters:** + +``quality`` + Quality of the resulting image. 100 is maximum quality (lowest compression rate). + +**Examples:** + +* ``t[]=compress:quality=40`` + +.. warning:: + This transformation currently only works as expected for ``image/jpeg`` images. + +Convert the image type - ``.jpg/.gif/.png`` +------------------------------------------- + +This transformation can be used to change the image type. It is not applied like the other transformations, but is triggered when specifying a custom extension to the ````. Currently Imbo can convert to: + +* ``jpg`` +* ``png`` +* ``gif`` + +**Examples:** + +* ``curl http://imbo/users//images/.gif`` +* ``curl http://imbo/users//images/.jpg`` +* ``curl http://imbo/users//images/.png`` + +It is not possible to explicitly trigger this transformation via the ``t[]`` query parameter. + +Crop the image - ``t[]=crop`` +----------------------------- + +This transformation is used to crop the image. + +**Parameters:** + +``x`` + The X coordinate of the cropped region's top left corner. + +``y`` + The Y coordinate of the cropped region's top left corner. + +``width`` + The width of the crop in pixels. + +``height`` + The height of the crop in pixels. + +**Examples:** + +* ``t[]=crop:x=10,y=25,width=250,height=150`` + +Make a gray scaled image - ``t[]=desaturate`` +--------------------------------------------- + +This transformation desaturates the image (in practice, gray scales it). + +**Examples:** + +* ``t[]=desaturate`` + +Make a mirror image - ``t[]=flipHorizontally`` +---------------------------------------------- + +This transformation flips the image horizontally. + +**Examples:** + +* ``t[]=flipHorizontally`` + +Flip the image upside down - ``t[]=flipVertically`` +--------------------------------------------------- + +This transformation flips the image vertically. + +**Examples:** + +* ``t[]=flipVertically`` + +Enforce a max size of an image - ``t[]=maxSize`` +------------------------------------------------ + +This transformation will resize the image using the original aspect ratio. Two parameters are supported and at least one of them must be supplied to apply the transformation. + +Note the difference from the :ref:`resize` transformation: given both ``width`` and ``height``, the resulting image will not be the same width and height as specified unless the aspect ratio is the same. + +**Parameters:** + +``width`` + The max width of the resulting image in pixels. If not specified the width will be calculated using the same aspect ratio as the original image. + +``height`` + The max height of the resulting image in pixels. If not specified the height will be calculated using the same aspect ratio as the original image. + +**Examples:** + +* ``t[]=maxSize:width=100`` +* ``t[]=maxSize:height=100`` +* ``t[]=maxSize:width=100,height=50`` + +.. _resize: + +Resize the image - ``t[]=resize`` +--------------------------------- + +This transformation will resize the image. Two parameters are supported and at least one of them must be supplied to apply the transformation. + +**Parameters:** + +``width`` + The width of the resulting image in pixels. If not specified the width will be calculated using the same aspect ratio as the original image. + +``height`` + The height of the resulting image in pixels. If not specified the height will be calculated using the same aspect ratio as the original image. + +**Examples:** + +* ``t[]=resize:width=100`` +* ``t[]=resize:height=100`` +* ``t[]=resize:width=100,height=50`` + +Rotate the image - ``t[]=rotate`` +--------------------------------- + +This transformation will rotate the image clock-wise. + +**Parameters:** + +``angle`` + The number of degrees to rotate the image (clock-wise). + +``bg`` + Background color in hexadecimal. Defaults to ``000000`` (also supports short values like ``f00`` (``ff0000``)). + +**Examples:** + +* ``t[]=rotate:angle=90`` +* ``t[]=rotate:angle=45,bg=fff`` + +Apply a sepia color tone - ``t[]=sepia`` +---------------------------------------- + +This transformation will apply a sepia color tone transformation to the image. + +**Parameters:** + +``threshold`` + Threshold ranges from 0 to QuantumRange and is a measure of the extent of the sepia toning. Defaults to ``80`` + +**Examples:** + +* ``t[]=sepia`` +* ``t[]=sepia:threshold=70`` + +Create a thumbnail of the image - ``t[]=thumbnail`` +--------------------------------------------------- + +This transformation creates a thumbnail of ````. + +**Parameters:** + +``width`` + Width of the thumbnail in pixels. Defaults to ``50``. + +``height`` + Height of the thumbnail in pixels. Defaults to ``50``. + +``fit`` + Fit style. Possible values are: ``inset`` or ``outbound``. Default to ``outbound``. + +**Examples:** + +* ``t[]=thumbnail`` +* ``t[]=thumbnail:width=20,height=20,fit=inset`` + +Create a vertical mirror image - ``t[]=transpose`` +-------------------------------------------------- + +This transformation transposes the image. + +**Examples:** + +* ``t[]=transpose`` + +Create a horizontal mirror image - ``t[]=transverse`` +----------------------------------------------------- + +This transformation transverses the image. + +**Examples:** + +* ``t[]=transverse`` + +Add a watermark to the image - ``t[]=watermark`` +------------------------------------------------ + +This transformation can be used to apply a watermark on top of the original image. + +**Parameters:** + +``img`` + Image identifier of the image to apply as watermark. Can be set to a default value in configuration by using ````. + +``width`` + Width of the watermark image in pixels. If omitted the width of ```` will be used. + +``height`` + Height of the watermark image in pixels. If omitted the height of ```` will be used. + +``position`` + The placement of the watermark image. ``top-left``, ``top-right``, ``bottom-left``, ``bottom-right`` and ``center`` are available values. Defaults to ``top-left``. + +``x`` + Number of pixels in the X-axis the watermark image should be offset from the original position (defined by the ``position`` parameter). Supports negative numbers. Defaults to ``0`` + +``y`` + Number of pixels in the Y-axis the watermark image should be offset from the original position (defined by the ``position`` parameter). Supports negative numbers. Defaults to ``0`` + +**Examples:** + +* ``t[]=watermark:img=f5f7851c40e2b76a01af9482f67bbf3f`` +* ``t[]=watermark:img=f5f7851c40e2b76a01af9482f67bbf3f,width=200,x=5`` +* ``t[]=watermark:img=f5f7851c40e2b76a01af9482f67bbf3f,height=50,x=-5,y=-5,position=bottom-right`` + +If you want to set the default watermark image you will have to do so in the configuration: + +.. code-block:: php + + array( + 'watermark' => function (array $params) { + $transformation = new Imbo\Image\Transformation\Watermark($params); + $transformation->setDefaultImage('some image identifier'); + + return $transformation; + }, + ), + + // ... + ); + +When you have specified a default watermark image you are not required to use the ``img`` option for the transformation, but if you do so it will override the default one. diff --git a/docs/usage/installation.rst b/docs/usage/installation.rst deleted file mode 100644 index e10b093bb..000000000 --- a/docs/usage/installation.rst +++ /dev/null @@ -1,127 +0,0 @@ -Installation -============ - -.. _clone the repository: http://github.com/imbo/imbo -.. _Composer: http://getcomposer.org/ - -Using composer --------------- - -The recommended way of installing Imbo is by creating a ``composer.json`` file for your installation, and then install Imbo and optional dependencies to 3rd party plug-ins and/or image transformations via `Composer`_. You will need the following directory structure for this to work:: - - /path/to/install/composer.json - /path/to/install/config/config.php - -where the ``composer.json`` file can contain: - -.. code-block:: json - - { - "name": "yourname/imbo", - "require": { - "imbo/imbo": "dev-master" - } - } - -and the ``config/config.php`` file is your :ref:`Imbo configuration `. - -Next you need to install Imbo by using composer: - -.. code-block:: bash - - cd /path/to/install - curl -s https://getcomposer.org/installer | php - php composer.phar install - -The correct document root for Imbo would in the above case be ``/path/to/install/vendor/imbo/imbo/public``. - -Using git ---------- - -You can also install Imbo directly via git, and then use `Composer`_ to install the dependencies: - -.. code-block:: bash - - mkdir /path/to/install; cd /path/to/install - git clone git@github.com:imbo/imbo.git - cd imbo - curl -s https://getcomposer.org/installer | php - php composer.phar install - -Web server configuration ------------------------- - -Imbo ships with sample configuration files for `Apache`_ and `Nginx`_ that can be used with a few minor adjustments. Both configuration files assumes you run your httpd on port 80. If you use `Varnish`_ or some other HTTP accelerator, simply change the port number to the port that your httpd listens to. - -Apache -~~~~~~ - -You will need to enable `mod_rewrite`_ if you want to use Imbo with Apache. - -.. literalinclude:: ../../config/imbo.apache.conf.dist - :language: console - :linenos: - -You will need to update ``ServerName`` to match the host name you will use for Imbo. If you want to use several host names you can update the ``ServerAlias`` line as well. You must also update ``DocumentRoot`` and ``Directory`` to point to the ``public`` directory in the Imbo installation. If you want to enable logging update the ``CustomLog`` and ``ErrorLog`` lines. - -Nginx -~~~~~ - -The sample Nginx configuration uses PHP via `FastCGI`_. - -.. literalinclude:: ../../config/imbo.nginx.conf.dist - :language: console - :linenos: - -You will need to update ``server_name`` to match the host name you will use for Imbo. If you want to use several host names simply put several host names on that line. ``root`` must point to the ``public`` directory in the Imbo installation. If you want to enable logging update the ``error_log`` and ``access_log`` lines. You must also update the ``fastcgi_param SCRIPT_FILENAME`` line to point to the ``public/index.php`` file in the Imbo installation. - -.. _Apache: http://httpd.apache.org/ -.. _mod_rewrite: http://httpd.apache.org/docs/current/mod/mod_rewrite.html -.. _Nginx: http://nginx.org/ -.. _Varnish: https://www.varnish-cache.org/ -.. _FastCGI: http://www.fastcgi.com/ - -Varnish -------- - -Imbo strives to follow the `HTTP Protocol`_, and can because of this easily leverage `Varnish`_. - -.. _HTTP Protocol: http://www.ietf.org/rfc/rfc2616.txt -.. _Varnish: https://www.varnish-cache.org/ - -The only required configuration you need in your `VCL`_ is a default backend: - -.. _VCL: https://www.varnish-cache.org/docs/3.0/reference/vcl.html - -.. code-block:: console - - backend default { - .host = "127.0.0.1"; - .port = "81"; - } - -where ``.host`` and ``.port`` is where Varnish can reach your web server. - -If you use the same host name (or a sub-domain) for your Imbo installation as other services, that in turn uses `Cookies`_, you might want the VCL to ignore these Cookies for the requests made against your Imbo installation (unless you have implemented event listeners for Imbo that uses Cookies). To achieve this you can put the following snippet into your VCL file: - -.. _Cookies: http://en.wikipedia.org/wiki/HTTP_cookie - -.. code-block:: console - - sub vcl_recv { - if (req.http.host == "imbo.example.com") { - unset req.http.Cookie; - } - } - -or, if you have Imbo installed in some path: - -.. code-block:: console - - sub vcl_recv { - if (req.http.host ~ "^(www.)?example.com$" && req.url ~ "^/imbo/") { - unset req.http.Cookie; - } - } - -if you have Imbo installed in ``example.com/imbo``. diff --git a/docs/usage/requirements.rst b/docs/usage/requirements.rst deleted file mode 100644 index c0aaca1c8..000000000 --- a/docs/usage/requirements.rst +++ /dev/null @@ -1,17 +0,0 @@ -Requirements -============ - -Imbo requires a web server running `PHP >= 5.4`_ and the `Imagick`_ extension for PHP. - -You will also need a backend for storing image information, like for instance `MongoDB`_ or `MySQL`_. - -Optional requirements are `Doctrine Database Abstraction Layer`_ (for some storage and database drivers), and `Memcached`_ and/or `APC`_ for caching. - -.. _PHP >= 5.4: http://php.net/ -.. _Imagick: http://pecl.php.net/package/imagick -.. _MongoDB: http://www.mongodb.org/ -.. _Mongo: http://pecl.php.net/package/mongo -.. _Doctrine Database Abstraction Layer: http://www.doctrine-project.org/projects/dbal.html -.. _Memcached: http://pecl.php.net/package/memcached -.. _APC: http://pecl.php.net/package/apc -.. _MySQL: http://www.mysql.com diff --git a/features/content-negotiation.feature b/features/content-negotiation.feature index 9687fade2..da3ff12df 100644 --- a/features/content-negotiation.feature +++ b/features/content-negotiation.feature @@ -43,7 +43,7 @@ Feature: Imbo supports content negotiation Scenario: If the server responds with an error, and the client included a valid extension, that type should be returned Given the "Accept" request header is "application/xml" When I request "/users/foobar.json" - Then I should get a response with "404 Unknown public key" + Then I should get a response with "404 Public key not found" And the "Content-Type" response header is "application/json" Scenario Outline: Imbo uses the Accept header when encountering errors to choose the error format diff --git a/features/user.feature b/features/user.feature index 097148ced..9eb198f6a 100644 --- a/features/user.feature +++ b/features/user.feature @@ -21,8 +21,8 @@ Feature: Imbo provides a user endpoint Scenario: Request user that does not exist Given I use "foo" and "bar" for public and private keys When I request "/users/foo.json" - Then I should get a response with "404 Unknown public key" - And the Imbo error message is "Unknown public key" and the error code is "100" + Then I should get a response with "404 Public key not found" + And the Imbo error message is "Public key not found" and the error code is "100" Scenario Outline: The user endpoint only supports HTTP GET and HEAD Given I use "publickey" and "privatekey" for public and private keys diff --git a/library/Imbo/Application.php b/library/Imbo/Application.php index f39ce296e..18c8b59ea 100644 --- a/library/Imbo/Application.php +++ b/library/Imbo/Application.php @@ -81,7 +81,7 @@ public function run() { // See if the public key exists if ($publicKey) { if (!isset($authConfig[$publicKey])) { - $e = new RuntimeException('Unknown public key', 404); + $e = new RuntimeException('Public key not found', 404); $e->setImboErrorCode(Exception::AUTH_UNKNOWN_PUBLIC_KEY); throw $e; diff --git a/library/Imbo/Cache/CacheInterface.php b/library/Imbo/Cache/CacheInterface.php index 68e2f2e51..38505af51 100644 --- a/library/Imbo/Cache/CacheInterface.php +++ b/library/Imbo/Cache/CacheInterface.php @@ -11,9 +11,9 @@ namespace Imbo\Cache; /** - * Cache driver interface + * Cache adapter interface * - * This is an interface for different database drivers. + * An interface for cache adapters. * * @author Christer Edvartsen * @package Cache diff --git a/library/Imbo/Database/DatabaseInterface.php b/library/Imbo/Database/DatabaseInterface.php index 8d3977735..9d25ec29d 100644 --- a/library/Imbo/Database/DatabaseInterface.php +++ b/library/Imbo/Database/DatabaseInterface.php @@ -16,9 +16,9 @@ DateTime; /** - * Database driver interface + * Database adapter interface * - * This is an interface for different database drivers. + * This is an interface for storage adapters in Imbo. * * @author Christer Edvartsen * @package Database diff --git a/library/Imbo/EventListener/AccessToken.php b/library/Imbo/EventListener/AccessToken.php index 3298d21e7..dce824d08 100644 --- a/library/Imbo/EventListener/AccessToken.php +++ b/library/Imbo/EventListener/AccessToken.php @@ -87,7 +87,7 @@ public function getDefinition() { $definition = array(); - foreach($events as $eventName) { + foreach ($events as $eventName) { $definition[] = new ListenerDefinition($eventName, $callback, $priority); } diff --git a/library/Imbo/Http/Response/Formatter/XML.php b/library/Imbo/Http/Response/Formatter/XML.php index e9cf53f02..6a27a5800 100644 --- a/library/Imbo/Http/Response/Formatter/XML.php +++ b/library/Imbo/Http/Response/Formatter/XML.php @@ -220,16 +220,26 @@ public function formatStats(Model\Stats $model) { private function formatArray(array $data) { $xml = ''; - foreach ($data as $key => $value) { - $xml .= '<' . $key . '>'; + if (isset($data[0])) { + $xml .= ''; - if (is_array($value)) { - $xml .= $this->formatArray($value); - } else { - $xml .= $value; + foreach ($data as $value) { + $xml .= '' . $value . ''; } - $xml .= ''; + $xml .= ''; + } else { + foreach ($data as $key => $value) { + $xml .= '<' . $key . '>'; + + if (is_array($value)) { + $xml .= $this->formatArray($value); + } else { + $xml .= $value; + } + + $xml .= ''; + } } return $xml; diff --git a/library/Imbo/Resource/Image.php b/library/Imbo/Resource/Image.php index 3b8a92ab2..eeb5c4995 100644 --- a/library/Imbo/Resource/Image.php +++ b/library/Imbo/Resource/Image.php @@ -10,8 +10,7 @@ namespace Imbo\Resource; -use Imbo\EventListener\ListenerInterface, - Imbo\Exception\ResourceException, +use Imbo\Exception\ResourceException, Imbo\EventManager\EventInterface, Imbo\EventListener\ListenerDefinition, Imbo\Model; @@ -22,7 +21,7 @@ * @author Christer Edvartsen * @package Resources */ -class Image implements ResourceInterface, ListenerInterface { +class Image implements ResourceInterface { /** * {@inheritdoc} */ diff --git a/library/Imbo/Resource/Images.php b/library/Imbo/Resource/Images.php index 91b52e470..ef2d5ae0c 100644 --- a/library/Imbo/Resource/Images.php +++ b/library/Imbo/Resource/Images.php @@ -11,8 +11,7 @@ namespace Imbo\Resource; use Imbo\EventManager\EventInterface, - Imbo\EventListener\ListenerDefinition, - Imbo\EventListener\ListenerInterface; + Imbo\EventListener\ListenerDefinition; /** * Images resource @@ -30,7 +29,7 @@ * @author Christer Edvartsen * @package Resources */ -class Images implements ResourceInterface, ListenerInterface { +class Images implements ResourceInterface { /** * {@inheritdoc} */ diff --git a/library/Imbo/Resource/Metadata.php b/library/Imbo/Resource/Metadata.php index 779e423be..7243c6299 100644 --- a/library/Imbo/Resource/Metadata.php +++ b/library/Imbo/Resource/Metadata.php @@ -12,7 +12,6 @@ use Imbo\EventManager\EventInterface, Imbo\EventListener\ListenerDefinition, - Imbo\EventListener\ListenerInterface, Imbo\Exception\InvalidArgumentException, Imbo\Model; @@ -22,7 +21,7 @@ * @author Christer Edvartsen * @package Resources */ -class Metadata implements ResourceInterface, ListenerInterface { +class Metadata implements ResourceInterface { /** * {@inheritdoc} */ diff --git a/library/Imbo/Resource/ResourceInterface.php b/library/Imbo/Resource/ResourceInterface.php index 0b8ab8554..32ecb4d91 100644 --- a/library/Imbo/Resource/ResourceInterface.php +++ b/library/Imbo/Resource/ResourceInterface.php @@ -10,8 +10,7 @@ namespace Imbo\Resource; -use Imbo\EventManager\EventInterface, - Imbo\EventListener\ListenerInterface; +use Imbo\EventListener\ListenerInterface; /** * Resource interface @@ -22,7 +21,7 @@ * @author Christer Edvartsen * @package Resources */ -interface ResourceInterface { +interface ResourceInterface extends ListenerInterface { /**#@+ * Resource types * diff --git a/library/Imbo/Resource/ShortUrl.php b/library/Imbo/Resource/ShortUrl.php index dc2645730..7cc570a9e 100644 --- a/library/Imbo/Resource/ShortUrl.php +++ b/library/Imbo/Resource/ShortUrl.php @@ -12,7 +12,6 @@ use Imbo\EventManager\EventInterface, Imbo\EventListener\ListenerDefinition, - Imbo\EventListener\ListenerInterface, Imbo\Exception\ResourceException, Symfony\Component\HttpFoundation\ParameterBag; @@ -23,7 +22,7 @@ * @author Christer Edvartsen * @package Resources */ -class ShortUrl implements ResourceInterface, ListenerInterface { +class ShortUrl implements ResourceInterface { /** * {@inheritdoc} */ diff --git a/library/Imbo/Resource/Stats.php b/library/Imbo/Resource/Stats.php index 3a170229d..76eb6bab1 100644 --- a/library/Imbo/Resource/Stats.php +++ b/library/Imbo/Resource/Stats.php @@ -12,7 +12,6 @@ use Imbo\EventManager\EventInterface, Imbo\EventListener\ListenerDefinition, - Imbo\EventListener\ListenerInterface, Imbo\Model, DateTime, DateTimeZone; @@ -26,7 +25,7 @@ * @author Christer Edvartsen * @package Resources */ -class Stats implements ResourceInterface, ListenerInterface { +class Stats implements ResourceInterface { /** * {@inheritdoc} */ diff --git a/library/Imbo/Resource/Status.php b/library/Imbo/Resource/Status.php index c48781727..afd4d0fb7 100644 --- a/library/Imbo/Resource/Status.php +++ b/library/Imbo/Resource/Status.php @@ -12,7 +12,6 @@ use Imbo\EventManager\EventInterface, Imbo\EventListener\ListenerDefinition, - Imbo\EventListener\ListenerInterface, Imbo\Model, DateTime, DateTimeZone; @@ -26,7 +25,7 @@ * @author Christer Edvartsen * @package Resources */ -class Status implements ResourceInterface, ListenerInterface { +class Status implements ResourceInterface { /** * {@inheritdoc} */ diff --git a/library/Imbo/Resource/User.php b/library/Imbo/Resource/User.php index 4edc4e28a..c07093a7a 100644 --- a/library/Imbo/Resource/User.php +++ b/library/Imbo/Resource/User.php @@ -11,8 +11,7 @@ namespace Imbo\Resource; use Imbo\EventManager\EventInterface, - Imbo\EventListener\ListenerDefinition, - Imbo\EventListener\ListenerInterface; + Imbo\EventListener\ListenerDefinition; /** * User resource @@ -20,7 +19,7 @@ * @author Christer Edvartsen * @package Resources */ -class User implements ResourceInterface, ListenerInterface { +class User implements ResourceInterface { /** * {@inheritdoc} */ diff --git a/library/Imbo/Storage/StorageInterface.php b/library/Imbo/Storage/StorageInterface.php index 5f5bbac79..da361ee60 100644 --- a/library/Imbo/Storage/StorageInterface.php +++ b/library/Imbo/Storage/StorageInterface.php @@ -14,9 +14,9 @@ Imbo\Exception\StorageException; /** - * Storage driver interface + * Storage adapter interface * - * This is an interface for different storage drivers for Imbo. + * This is an interface for storage adapters in Imbo. * * @author Christer Edvartsen * @package Storage @@ -26,7 +26,7 @@ interface StorageInterface { * Store an image * * This method will receive the binary data of the image and store it somewhere suited for the - * actual storage driver. If an error occurs the driver should throw an + * actual storage adaper. If an error occurs the adapter should throw an * Imbo\Exception\StorageException exception. * * If the image already exists, simply overwrite it. diff --git a/setup/doctrine.mysql.sql b/setup/doctrine.mysql.sql index be083db6d..63973ae7f 100644 --- a/setup/doctrine.mysql.sql +++ b/setup/doctrine.mysql.sql @@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS `metadata` ( KEY `imageId` (`imageId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci AUTO_INCREMENT=1 ; -CREATE TABLE `shorturl` ( +CREATE TABLE IF NOT EXISTS `shorturl` ( `shortUrlId` char(7) COLLATE utf8_danish_ci NOT NULL, `publicKey` varchar(255) COLLATE utf8_danish_ci NOT NULL, `imageIdentifier` char(32) COLLATE utf8_danish_ci NOT NULL, diff --git a/tests/Imbo/UnitTest/Http/Response/Formatter/JSONTest.php b/tests/Imbo/UnitTest/Http/Response/Formatter/JSONTest.php index f3dc06bd5..4c8542068 100644 --- a/tests/Imbo/UnitTest/Http/Response/Formatter/JSONTest.php +++ b/tests/Imbo/UnitTest/Http/Response/Formatter/JSONTest.php @@ -60,7 +60,7 @@ public function testCanFormatAnErrorModel() { $model = $this->getMock('Imbo\Model\Error'); $model->expects($this->once())->method('getHttpCode')->will($this->returnValue(404)); - $model->expects($this->once())->method('getErrorMessage')->will($this->returnValue('Unknown public key')); + $model->expects($this->once())->method('getErrorMessage')->will($this->returnValue('Public key not found')); $model->expects($this->once())->method('getDate')->will($this->returnValue($date)); $model->expects($this->once())->method('getImboErrorCode')->will($this->returnValue(100)); $model->expects($this->once())->method('getImageIdentifier')->will($this->returnValue('identifier')); @@ -71,7 +71,7 @@ public function testCanFormatAnErrorModel() { $data = json_decode($json, true); $this->assertSame($formattedDate, $data['error']['date']); - $this->assertSame('Unknown public key', $data['error']['message']); + $this->assertSame('Public key not found', $data['error']['message']); $this->assertSame(404, $data['error']['code']); $this->assertSame(100, $data['error']['imboErrorCode']); $this->assertSame('identifier', $data['imageIdentifier']); diff --git a/tests/Imbo/UnitTest/Http/Response/Formatter/XMLTest.php b/tests/Imbo/UnitTest/Http/Response/Formatter/XMLTest.php index 249e49725..58b97a9fd 100644 --- a/tests/Imbo/UnitTest/Http/Response/Formatter/XMLTest.php +++ b/tests/Imbo/UnitTest/Http/Response/Formatter/XMLTest.php @@ -59,7 +59,7 @@ public function testCanFormatAnErrorModel() { $model = $this->getMock('Imbo\Model\Error'); $model->expects($this->once())->method('getHttpCode')->will($this->returnValue(404)); - $model->expects($this->once())->method('getErrorMessage')->will($this->returnValue('Unknown public key')); + $model->expects($this->once())->method('getErrorMessage')->will($this->returnValue('Public key not found')); $model->expects($this->once())->method('getDate')->will($this->returnValue($date)); $model->expects($this->once())->method('getImboErrorCode')->will($this->returnValue(100)); $model->expects($this->once())->method('getImageIdentifier')->will($this->returnValue('identifier')); @@ -69,7 +69,7 @@ public function testCanFormatAnErrorModel() { $xml = $this->formatter->format($model); $this->assertTag(array('tag' => 'code', 'content' => '404', 'parent' => array('tag' => 'error')), $xml, 'Missing HTTP status code', false); - $this->assertTag(array('tag' => 'message', 'content' => 'Unknown public key', 'parent' => array('tag' => 'error')), $xml, 'Missing error message', false); + $this->assertTag(array('tag' => 'message', 'content' => 'Public key not found', 'parent' => array('tag' => 'error')), $xml, 'Missing error message', false); $this->assertTag(array('tag' => 'date', 'content' => $formattedDate, 'parent' => array('tag' => 'error')), $xml, 'Missing date', false); $this->assertTag(array('tag' => 'imboErrorCode', 'content' => '100', 'parent' => array('tag' => 'error')), $xml, 'Missing imbo error code', false); $this->assertTag(array('tag' => 'imageIdentifier', 'content' => 'identifier', 'parent' => array('tag' => 'imbo')), $xml, 'Missing image identifier', false); @@ -345,6 +345,23 @@ public function testCanFormatAnArrayModel() { } } + /** + * @covers Imbo\Http\Response\Formatter\Formatter::format + * @covers Imbo\Http\Response\Formatter\XML::formatArrayModel + * @covers Imbo\Http\Response\Formatter\XML::formatArray + */ + public function testCanFormatArrayModelWithLists() { + $data = array( + 'key' => array(1, 2, 3), + ); + $model = $this->getMock('Imbo\Model\ArrayModel'); + $model->expects($this->once())->method('getData')->will($this->returnValue($data)); + + $xml = $this->formatter->format($model); + + $this->assertContains('123', $xml); + } + /** * @covers Imbo\Http\Response\Formatter\Formatter::format * @covers Imbo\Http\Response\Formatter\XML::formatArrayModel