diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..6f5331f
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "imageoptim/imageoptim": "^1.3"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..30f7383
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,66 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "bf14e9b7c85efaed15e3e84e7c063394",
+ "content-hash": "4bb4ba769cbf71a3e1875addaccbe5af",
+ "packages": [
+ {
+ "name": "imageoptim/imageoptim",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ImageOptim/php-imageoptim-api.git",
+ "reference": "b73eb5d6747fc181de86b2de50fb158dff463618"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ImageOptim/php-imageoptim-api/zipball/b73eb5d6747fc181de86b2de50fb158dff463618",
+ "reference": "b73eb5d6747fc181de86b2de50fb158dff463618",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.4 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "ImageOptim\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kornel",
+ "email": "kornel@imageoptim.com"
+ }
+ ],
+ "description": "ImageOptim API for PHP",
+ "homepage": "https://imageoptim.com/api",
+ "keywords": [
+ "image",
+ "optimize",
+ "performance",
+ "resize",
+ "scale"
+ ],
+ "time": "2017-01-09 23:58:20"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
diff --git a/kirby-imageoptim.php b/kirby-imageoptim.php
new file mode 100644
index 0000000..90204e6
--- /dev/null
+++ b/kirby-imageoptim.php
@@ -0,0 +1,94 @@
+url();
+
+ if($width == false) {
+ $width = $file->width();
+ }
+
+ if($height == false) {
+ $height = round($file->height() * $width / $file->width());
+ }
+
+ $imageoptimAPIKey = trim(c::get('plugin.imageoptim.apikey',''));
+
+ // If can do imageoptim...
+ if(!KirbyImageOptim::is_localhost() &&
+ c::get('plugin.imageoptim', false) &&
+ strlen($imageoptimAPIKey) > 0) {
+
+ $wxh = $width.'x'.$height;
+ $hash = sha1(
+ $file->name().'-'.
+ $wxh.'-'.
+ $dpr.'-'.
+ $quality.'-'.
+ $file->modified()).
+ '.'.$file->extension();
+
+ $filepath = str_replace(
+ $file->filename(),
+ $hash,
+ kirby()->roots()->thumbs().DS.$file->uri());
+
+ $urlOptim = str_replace(
+ $file->filename(),
+ $hash,
+ kirby()->urls()->thumbs().'/'.$file->uri());
+
+ if(!f::exists($filepath)) {
+ $api = new ImageOptim\API($imageoptimAPIKey);
+ try{
+ $imageData = $api->imageFromURL($file->url())
+ ->quality($quality)
+ ->dpr(intval($dpr))
+ ->resize($width, $height, $crop)
+ ->getBytes();
+
+ f::write($filepath, $imageData);
+ $url = $urlOptim;
+ } catch (Exception $ex) {
+ return $ex->getMessage();
+ }
+ } else {
+ $url = $urlOptim;
+ }
+
+ // ... use kirby thumb instead
+ } else {
+
+ if($file->orientation() == 'portrait') {
+ $nw = round($file->width() * $height / $file->height());
+ $url = $file->resize($nw, $height);
+ } else {
+ $url = $file->resize($width);
+ }
+ $url = str_replace([''],['',''], $url);
+ }
+
+ return $url;
+ }
+}
+
+$kirby->set('file::method', 'imageoptim',
+ function($file, $width = false, $height = false, $crop = 'fit', $dpr = 1) {
+ return KirbyImageOptim::imageoptim($file, $width, $height, $crop, $dpr);
+});
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..aad326b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "kirby-imageoptim",
+ "description": "Kirby CMS file method to optimize images using ImageOptim within your template code.",
+ "version": "1.0.0",
+ "type": "kirby-plugin",
+ "license": "MIT"
+}
\ No newline at end of file
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..34c4e82
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,93 @@
+# Kirby Imageoptim
+
+![GitHub release](https://img.shields.io/github/release/bnomei/kirby-imageoptim.svg?maxAge=1800) ![License](https://img.shields.io/github/license/mashape/apistatus.svg) ![Kirby Version](https://img.shields.io/badge/Kirby-2.3%2B-red.svg)
+
+Kirby CMS file method to optimize images using [ImageOptim PHP API](https://github.com/ImageOptim/php-imageoptim-api) within your template code. Optimized image is refreshed if file is changed or calling code requests different parameters. It is saved to the `/thumbs` folder (`kirby()->roots()->thumbs()`).
+
+Note: ImageOptim API will only be called on webserver. On localhost the kirby thumbs api will be used to avoid the timeconsuming [upload api call](https://github.com/ImageOptim/php-imageoptim-api#imagefrompathfilepath--local-source-image).
+
+## Requirements
+
+- [**Kirby**](https://getkirby.com/) 2.3+
+- [ImageOptim API key](https://imageoptim.com/api/register) (trial available). This plugin uses v1.3.1.
+- `allow_url_fopen` PHP setting must be enabled for the API to work. Check with `ini_get('allow_url_fopen')`. Please be aware of the potential security risks caused by allow_url_fopen!
+
+## Installation
+
+### [Kirby CLI](https://github.com/getkirby/cli)
+
+```
+kirby plugin:install bnomei/kirby-imageoptim
+```
+
+### Git Submodule
+
+```
+$ git submodule add https://github.com/bnomei/kirby-imageoptim.git site/plugins/kirby-imageoptim
+```
+
+### Copy and Paste
+
+1. [Download](https://github.com/bnomei/kirby-imageoptim/archive/master.zip) the contents of this repository as ZIP-file.
+2. Rename the extracted folder to `kirby-imageoptim` and copy it into the `site/plugins/` directory in your Kirby project.
+
+## Usage
+
+In your `site/config.php` activate the plugin and set the [ImageOptim API key](https://imageoptim.com/api/register).
+
+```
+c::set('plugin.imageoptim', true); // default is false
+c::set('plugin.imageoptim.apikey', 'YOUR_API_KEY_HERE');
+```
+
+The plugin adds a `$myFile->imageoptim()` function to [$file objects](https://getkirby.com/docs/cheatsheet#file).
+
+```
+file('image.jpg');
+
+ // get url (on your webserver) for optimized thumb
+ $url = $myFile->imageoptim();
+
+ // echo the url as image
+ // https://getkirby.com/docs/toolkit/api#brick
+ $img = brick('img')
+ ->attr('src', $url)
+ ->attr('alt', $myFile->filename());
+ echo $img;
+
+?>
+```
+
+Changing width, height and/or fitting is also supported. Modifying dpr and quality setting as well.
+
+```
+imageoptim(400);
+
+ // fit to 400px width and 300px height
+ $url = $myFile->imageoptim(400, 300);
+
+ // crop to 800x600px dimension
+ $url = $myFile->imageoptim(800, 600, 'crop');
+
+ // fit to 400px width and 300px height at 2x dpr
+ $url = $myFile->imageoptim(400, 300, 'fit', 2);
+
+ // fit to 400px width and 300px height at 2x dpr and 'high' quality
+ $url = $myFile->imageoptim(400, 300, 'fit', 2, 'high');
+
+?>
+```
+
+## Disclaimer
+
+This plugin is provided "as is" with no guarantee. Use it at your own risk and always test it yourself before using it in a production environment. If you find any issues, please [create a new issue](https://github.com/bnomei/kirby-imageoptim/issues/new).
+
+## License
+
+[MIT](https://opensource.org/licenses/MIT)
+
+It is discouraged to use this plugin in any project that promotes racism, sexism, homophobia, animal abuse, violence or any other form of hate speech.
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 0000000..805150d
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,7 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
+ if ('\\' == $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 0000000..1a28124
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) 2016 Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..7a91153
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/imageoptim/imageoptim/src'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..5efbe4c
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,52 @@
+= 50600 && !defined('HHVM_VERSION');
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInitdb60f64c7c86327cd38317daa842af05::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..80f08c7
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,31 @@
+
+ array (
+ 'ImageOptim\\' => 11,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'ImageOptim\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/imageoptim/imageoptim/src',
+ ),
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitdb60f64c7c86327cd38317daa842af05::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitdb60f64c7c86327cd38317daa842af05::$prefixDirsPsr4;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 0000000..7d60f93
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,51 @@
+[
+ {
+ "name": "imageoptim/imageoptim",
+ "version": "1.3.1",
+ "version_normalized": "1.3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ImageOptim/php-imageoptim-api.git",
+ "reference": "b73eb5d6747fc181de86b2de50fb158dff463618"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ImageOptim/php-imageoptim-api/zipball/b73eb5d6747fc181de86b2de50fb158dff463618",
+ "reference": "b73eb5d6747fc181de86b2de50fb158dff463618",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.4 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.3"
+ },
+ "time": "2017-01-09 23:58:20",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "ImageOptim\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kornel",
+ "email": "kornel@imageoptim.com"
+ }
+ ],
+ "description": "ImageOptim API for PHP",
+ "homepage": "https://imageoptim.com/api",
+ "keywords": [
+ "image",
+ "optimize",
+ "performance",
+ "resize",
+ "scale"
+ ]
+ }
+]
diff --git a/vendor/imageoptim/imageoptim/.gitignore b/vendor/imageoptim/imageoptim/.gitignore
new file mode 100644
index 0000000..d8a7996
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor/
diff --git a/vendor/imageoptim/imageoptim/ImageOptim.png b/vendor/imageoptim/imageoptim/ImageOptim.png
new file mode 100644
index 0000000..3b4555b
Binary files /dev/null and b/vendor/imageoptim/imageoptim/ImageOptim.png differ
diff --git a/vendor/imageoptim/imageoptim/README.md b/vendor/imageoptim/imageoptim/README.md
new file mode 100644
index 0000000..d28f5b8
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/README.md
@@ -0,0 +1,153 @@
+# ImageOptim API PHP client
+
+This library allows you to resize and optimize images using ImageOptim API.
+
+ImageOptim offers [advanced compression, high-DPI/responsive image mode, and color profile support](https://imageoptim.com/features.html) that are much better than PHP's built-in image resizing functions.
+
+## Installation
+
+The easiest is to use [PHP Composer](https://getcomposer.org/):
+
+```sh
+composer require imageoptim/imageoptim
+```
+
+If you don't use Composer, then `require` or autoload files from the `src` directory.
+
+## Usage
+
+First, [register to use the API](https://im2.io/register).
+
+```php
+imageFromURL('http://example.com/photo.jpg') // read this image
+ ->resize(160, 100, 'crop') // optional: resize to a thumbnail
+ ->dpr(2) // optional: double number of pixels for high-resolution "Retina" displays
+ ->getBytes(); // perform these operations and return the image data as binary string
+
+file_put_contents("images/photo_optimized.jpg", $imageData);
+```
+
+There's a longer example at the end of the readme.
+
+### Methods
+
+#### `API($username)` constructor
+
+ new ImageOptim\API("your api username goes here");
+
+Creates new instance of the API. You need to give it [your username](https://im2.io/api/username).
+
+#### `imageFromPath($filePath)` — local source image
+
+Creates a new request that will [upload](https://im2.io/api/upload) the image to the API, and then resize and optimize it. The upload method is necessary for optimizing files that are not on the web (e.g. `localhost`, files in `/tmp`).
+
+For images that have a public URLs (e.g. published on a website) it's faster to use the URL method instead:
+
+#### `imageFromURL($url)` — remote source image
+
+Creates a new request that will read the image from the given public URL, and then resize and optimize it.
+
+Please pass full absolute URL to images on your website.
+
+Ideally you should supply source image at very high quality (e.g. JPEG saved at 99%), so that ImageOptim can adjust quality itself. If source images you provide are already saved at low quality, ImageOptim will not be able to make them look better.
+
+#### `resize($width, $height = optional, $fit = optional)` — desired dimensions
+
+* `resize($width)` — sets maximum width for the image, so it'll be resized to this width. If the image is smaller than this, it won't be enlarged.
+
+* `resize($width, $height)` — same as above, but image will also have height same or smaller. Aspect ratio is always preserved.
+
+* `resize($width, $height, 'crop')` — resizes and crops image exactly to these dimensions.
+
+If you don't call `resize()`, then the original image size will be preserved.
+
+[See options reference](https://im2.io/api/post#options) for more resizing options.
+
+#### `dpr($x)` — pixel doubling for responsive images (HTML `srcset`)
+
+The default is `dpr(1)`, which means image is for regular displays, and `resize()` does the obvious thing you'd expect.
+
+If you set `dpr(2)` then pixel width and height of the image will be *doubled* to match density of "2x" displays. This is better than `resize($width*2)`, because it also adjusts sharpness and image quality to be optimal for high-DPI displays.
+
+[See options reference](https://im2.io/api/post#opt-2x) for explanation how DPR works.
+
+#### `quality($preset)` — if you need even smaller or extra sharp images
+
+Quality is set as a string, and can be `low`, `medium` or `high`. The default is `medium` and should be good enough for most cases.
+
+#### `getBytes()` — get the resized image
+
+Makes request to ImageOptim API and returns optimized image as a string. You should save that to your server's disk.
+
+ImageOptim performs optimizations that sometimes may take a few seconds, so instead of converting images on the fly on every request, you should convert them once and keep them.
+
+#### `apiURL()` — debug or use another HTTPS client
+
+Returns string with URL to `https://im2.io/…` that is equivalent of the options set. You can open this URL in your web browser to get more information about it. Or you can [make a `POST` request to it](https://im2.io/api/post#making-the-request) to download the image yourself, if you don't want to use the `getBytes()` method.
+
+### Error handling
+
+All methods throw on error. You can expect the following exception subclasses:
+
+* `ImageOptim\InvalidArgumentException` means arguments to functions are incorrect and you need to fix your code.
+* `ImageOptim\NetworkException` is thrown when there is problem comunicating with the API. You can retry the request.
+* `ImageOptim\NotFoundException` is thrown when URL given to `imageFromURL()` returned 404. Make sure paths and urlencoding are correct. [More](https://im2.io/api/post#response).
+* `ImageOptim\OriginServerException` is thrown when URL given to `imageFromURL()` returned 4xx or 5xx error. Make sure your server allows access to the file.
+
+If you're writing a script that processes a large number of images in one go, don't launch it from a web browser, as it will likely time out. It's best to launch such scripts via CLI (e.g. via SSH).
+
+### Help and info
+
+See [imageoptim.com/api](https://imageoptim.com/api) for documentation and contact info. I'm happy to help!
+
+### Example
+
+This is a script that optimizes an image. Such script usually would be ran when a new image is uploaded to the server. You don't need to run any PHP code to *serve* optimized images.
+
+The API operates on a single image at a time. When you want to generate multiple image sizes/thumbnails, repeat the whole procedure for each image at each size.
+
+```php
+imageFromURL('http://example.com/photo.jpg');
+
+// You set various settings on this object (or none to get the defaults).
+$imageParams->quality('low');
+$imageParams->resize(1024);
+
+// Next, to start the optimizations and get the optimized image, call:
+$imageData = $imageParams->getBytes();
+
+/*
+ the getBytes() call may take a while to run, so it's intended to be
+ called only once per image (e.g. only when a new image is uploaded
+ to your server). If you'd like to "lazily" optimize arbitrary images
+ on-the-fly when they're requested, there is a better API for that:
+ https://im2.io/api/get
+*/
+
+// Save the image data somewhere on the server, e.g.
+file_put_contents("images/photo_optimized.jpg", $imageData);
+
+// Note that this script only prepares a static image file
+// (in this example in images/photo_optimized.jpg),
+// and does not serve it to the browser. Once the optimized
+// image is saved to disk you should serve it normally
+// as you'd do with any regular image file.
+
+```
+
diff --git a/vendor/imageoptim/imageoptim/composer.json b/vendor/imageoptim/imageoptim/composer.json
new file mode 100644
index 0000000..00ca812
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "imageoptim/imageoptim",
+ "description": "ImageOptim API for PHP",
+ "minimum-stability": "stable",
+ "license": "BSD-2-Clause",
+ "authors": [
+ {
+ "name": "Kornel",
+ "email": "kornel@imageoptim.com"
+ }
+ ],
+ "homepage": "https://imageoptim.com/api",
+ "keywords": ["image","resize","optimize","scale","performance"],
+ "autoload": {
+ "psr-4" : {
+ "ImageOptim\\" : "src"
+ }
+ },
+ "require": {
+ "php" : "^5.4 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.3"
+ }
+}
diff --git a/vendor/imageoptim/imageoptim/examples/optimize_directory.php b/vendor/imageoptim/imageoptim/examples/optimize_directory.php
new file mode 100755
index 0000000..354c1eb
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/examples/optimize_directory.php
@@ -0,0 +1,164 @@
+#!/usr/bin/env php
+ /www/example.com/optimized/hello-640.png\n\n";
+ echo "If you have questions, ask support@imageoptim.com\n";
+}
+
+if (count($_SERVER['argv']) < 4) { // the arg 0 is the command name
+ usage();
+ exit(1);
+}
+
+$argn = 1;
+$apiUsername = $_SERVER['argv'][$argn++];
+if (!$apiUsername || ctype_digit($apiUsername) || file_exists($apiUsername)) {
+ echo "The first argument (". escapeshellarg($apiUsername) . ") must be an ImageOptim API username.\n";
+ echo "Get your username from https://imageoptim.com/api/register\n";
+ exit(1);
+}
+
+$width = null;
+if (count($_SERVER['argv']) > 4 && ctype_digit($_SERVER['argv'][$argn])) {
+ $width = $_SERVER['argv'][$argn++];
+}
+
+$sourceDir = $_SERVER['argv'][$argn++];
+if (!is_dir($sourceDir)) {
+ echo "ERROR: ", $sourceDir, " does not exist or is not a directory.\n\n";
+ usage();
+ exit(1);
+}
+
+$destDir = $_SERVER['argv'][$argn++];
+if (!is_dir($destDir)) {
+ if (is_dir(dirname($destDir))) {
+ if (!mkdir($destDir)) {
+ echo "ERROR: can't create ", $destDir, ". Please create this directory first.\n";
+ exit(1);
+ }
+ } else {
+ echo "ERROR: ", $destDir, " does not exist or is not a directory.\n\n";
+ usage();
+ exit(1);
+ }
+}
+
+// Clears symlinks from paths, makes them absolute and comparable
+$sourceDir = realpath($sourceDir);
+$destDir = realpath($destDir);
+
+try {
+ $api = new ImageOptim\API($apiUsername);
+
+ // This is a fancy way of getting a list of all files in a directory
+ $items = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS),
+ RecursiveIteratorIterator::SELF_FIRST,
+ RecursiveIteratorIterator::CATCH_GET_CHILD);
+
+ $nonImage = [];
+ $skipped = 0;
+ $done = 0;
+
+ foreach ($items as $item) {
+ if ($item->isDir()) continue;
+ $filename = $item->getFilename();
+ if (!preg_match('/\.(png|jpe?g|gif|svgz?|bmp|tiff?)/i', $filename)) {
+ $nonImage[] = $filename;
+ continue;
+ }
+
+ $sourcePath = $item->getPathname();
+ $destRelPath = substr($sourcePath, strlen($sourceDir));
+ $destPath = $destDir . $destRelPath;
+
+ // Append .min extension if source and destination are the same
+ if ($destPath === $sourcePath && false === strpos($destRelPath, '.min.')) {
+ $destRelPath = preg_replace('/\.[^.]+$/', '.min$0', $destRelPath);
+ $destPath = $destDir . $destRelPath;
+ }
+
+ echo substr($destRelPath,1),"... ";
+
+ if (file_exists($destPath) && filemtime($destPath) > filemtime($sourcePath)) {
+ echo " already exists (skipped)\n";
+ $skipped++;
+ continue;
+ }
+
+ // The process preserves directory structure, so it needs to create dirs
+ $destSubdir = dirname($destPath);
+ if (!is_dir($destSubdir)) {
+ if (!mkdir($destSubdir, 0777, true)) {
+ echo "error: unable to create", $destSubdir,"\n";
+ continue;
+ }
+ }
+
+ $apiRequest = $api->imageFromPath($sourcePath);
+ if ($width) {
+ // You could add more options here
+ $apiRequest->resize($width);
+ }
+ $data = $apiRequest->getBytes();
+ if (!file_put_contents($destPath, $data)) {
+ echo "ERROR: unable to save file $destPath\n";
+ break;
+ }
+
+ $inSize = filesize($sourcePath);
+ $outSize = strlen($data);
+ echo "ok (", ($inSize > $outSize ? "$inSize -> $outSize bytes" : "already optimized"), ")\n";
+ $done++;
+ }
+
+ if (count($nonImage)) {
+ echo "Skipped ", count($nonImage), " non-image file(s) ", implode(', ', array_slice($nonImage, 0, 50)), "\n";
+ $nonImage = [];
+ }
+
+ if ($skipped) {
+ echo "\nSkipped $skipped alredy-existing file(s) in $destDir";
+ }
+ echo "\nImageOptim API processed $done file(s)\n";
+
+} catch(\ImageOptim\AccessDeniedException $e) {
+ echo "ERROR\n\n";
+ echo "Please got to https://imageoptim.com/api/register\n";
+ echo "get your API username, and replace '$apiUsername' with\n";
+ echo "your new registered API username.\n\n";
+ echo $e;
+ exit(1);
+} catch(\Exception $e) {
+ echo "ERROR\n\n";
+ echo $e;
+ exit(1);
+}
diff --git a/vendor/imageoptim/imageoptim/phpunit.xml b/vendor/imageoptim/imageoptim/phpunit.xml
new file mode 100644
index 0000000..093e2d2
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/phpunit.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ test
+
+
+
+
diff --git a/vendor/imageoptim/imageoptim/src/API.php b/vendor/imageoptim/imageoptim/src/API.php
new file mode 100644
index 0000000..46e51ac
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/src/API.php
@@ -0,0 +1,22 @@
+username = $username;
+ }
+
+ function imageFromURL($url) {
+ return new URLRequest($this->username, $url);
+ }
+
+ function imageFromPath($file) {
+ return new FileRequest($this->username, $file);
+ }
+}
diff --git a/vendor/imageoptim/imageoptim/src/APIException.php b/vendor/imageoptim/imageoptim/src/APIException.php
new file mode 100644
index 0000000..493a68b
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/src/APIException.php
@@ -0,0 +1,7 @@
+path = $path;
+ }
+
+ function apiURL() {
+ return parent::apiURL();
+ }
+
+ function getBytes() {
+ $fileData = @file_get_contents($this->path);
+ if (!$fileData) {
+ throw new APIException("Unable to read {$this->path}");
+ }
+
+ $contentHash = md5($this->path);
+ $boundary = "XXX$contentHash";
+ $nameEscaped = addslashes(basename($this->path));
+
+ $url = $this->apiURL();
+ $content = "--$boundary\r\n" .
+ "Content-Disposition: form-data; name=\"file\"; filename=\"{$nameEscaped}\"\r\n" .
+ "Content-Type: application/octet-stream\r\n" .
+ "Content-Transfer-Encoding: binary\r\n" .
+ "\r\n$fileData\r\n--$boundary--";
+
+ return $this->getBytesWithOptions([
+ 'header' => "Content-Length: " . strlen($content) . "\r\n" .
+ "Content-MD5: $contentHash\r\n" .
+ "Content-Type: multipart/form-data, boundary=$boundary\r\n",
+ 'content' => $content,
+ ], $this->path);
+ }
+}
diff --git a/vendor/imageoptim/imageoptim/src/InvalidArgumentException.php b/vendor/imageoptim/imageoptim/src/InvalidArgumentException.php
new file mode 100644
index 0000000..c5d0ba1
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/src/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+username = $username;
+ }
+
+ public function resize($width, $height_or_fit = null, $fit = null) {
+ if (!is_numeric($width)) {
+ throw new InvalidArgumentException("Width is not a number: $width");
+ }
+
+ $width = intval($width);
+ if (null === $height_or_fit) {
+ $height = null;
+ } else if (is_numeric($height_or_fit)) {
+ $height = intval($height_or_fit);
+ } else if ($fit) {
+ throw new InvalidArgumentException("Height is not a number: $height_or_fit");
+ } else {
+ $fit = $height_or_fit;
+ $height = null;
+ }
+
+ if ($width < 1 || $width > 10000) {
+ throw new InvalidArgumentException("Width is out of allowed range: $width");
+ }
+ if ($height !== null && ($height < 1 || $height > 10000)) {
+ throw new InvalidArgumentException("Height is out of allowed range: $height");
+ }
+
+ $allowedFitOptions = ['fit', 'crop', 'scale-down', 'pad'];
+ if (null !== $fit && !in_array($fit, $allowedFitOptions)) {
+ throw new InvalidArgumentException("Fit is not one of ".implode(', ',$allowedFitOptions).". Got: $fit");
+ }
+
+ if (!$height && ('pad' === $fit || 'crop' === $fit)) {
+ throw new InvalidArgumentException("Height is required for '$fit' scaling mode\nPlease specify height or use 'fit' scaling mode to allow flexible height");
+ }
+
+ $this->width = $width;
+ $this->height = $height;
+ $this->fit = $fit;
+
+
+ return $this;
+ }
+
+ public function timeout($timeout) {
+ if (!is_numeric($timeout) || $timeout <= 0) {
+ throw new InvalidArgumentException("Timeout not a positive number: $timeout");
+ }
+ $this->timeout = $timeout;
+
+ return $this;
+ }
+
+ public function bgcolor($background_color) {
+ if ('transparent' === $background_color || false === $background_color || null === $background_color) {
+ $this->bgcolor = null;
+ } else if (is_string($background_color) && preg_match('/^#?([0-9a-f]+)$/i', $background_color, $m)) {
+ $this->bgcolor = $m[1];
+ } else {
+ throw new InvalidArgumentException("Background color must be a hex string (e.g. AABBCC). Got: $background_color");
+ }
+ return $this;
+ }
+
+ public function dpr($dpr) {
+ if (!preg_match('/^\d[.\d]*(x)?$/', $dpr, $m)) {
+ throw new InvalidArgumentException("DPR should be 1x, 2x or 3x. Got: $dpr");
+ }
+ $this->dpr = $dpr . (empty($m[1]) ? 'x' : '');
+
+ return $this;
+ }
+
+ public function quality($quality) {
+ $allowedQualityOptions = ['low', 'medium', 'high', 'lossless'];
+ if (!in_array($quality, $allowedQualityOptions)) {
+ throw new InvalidArgumentException("Quality is not one of ".implode(', ',$allowedQualityOptions).". Got: $quality");
+ }
+ $this->quality = $quality;
+
+ return $this;
+ }
+
+ function optimize() {
+ // always. This is here to make order of calls flexible
+ return $this;
+ }
+
+ protected function apiURL() {
+ $options = [];
+ if ($this->width) {
+ $size = $this->width;
+ if ($this->height) {
+ $size .= 'x' . $this->height;
+ }
+ $options[] = $size;
+ if ($this->fit) $options[] = $this->fit;
+ } else {
+ $options[] = 'full';
+ }
+ if ($this->dpr) $options[] = $this->dpr;
+ if ($this->quality) $options[] = 'quality=' . $this->quality;
+ if ($this->timeout) $options[] = 'timeout=' . $this->timeout;
+ if ($this->bgcolor) $options[] = 'bgcolor=' . $this->bgcolor;
+
+ return self::BASE_URL . '/' . rawurlencode($this->username) . '/' . implode(',', $options);
+ }
+
+ protected function getBytesWithOptions(array $options, $sourceURL) {
+ $url = $this->apiURL();
+ $options['timeout'] = max(30, $this->timeout);
+ $options['ignore_errors'] = true;
+ $options['method'] = 'POST';
+ $options['header'] .= "Accept: image/*,application/im2+json\r\n" .
+ "User-Agent: ImageOptim-php/1.1 PHP/" . phpversion();
+
+ $stream = @fopen($url, 'r', false, stream_context_create(['http'=>$options]));
+
+ if (!$stream) {
+ $err = error_get_last();
+ throw new NetworkException("Can't send HTTPS request to: $url\n" . ($err ? $err['message'] : ''));
+ }
+
+ $res = @stream_get_contents($stream);
+ if (!$res) {
+ $err = error_get_last();
+ fclose($stream);
+ throw new NetworkException("Error reading HTTPS response from: $url\n" . ($err ? $err['message'] : ''));
+ }
+
+ $meta = @stream_get_meta_data($stream);
+ if (!$meta) {
+ $err = error_get_last();
+ fclose($stream);
+ throw new NetworkException("Error reading HTTPS response from: $url\n" . ($err ? $err['message'] : ''));
+ }
+ fclose($stream);
+
+ if (!$meta || !isset($meta['wrapper_data'], $meta['wrapper_data'][0])) {
+ throw new NetworkException("Unable to read headers from HTTP request to: $url");
+ }
+ if (!empty($meta['timed_out'])) {
+ throw new NetworkException("Request timed out: $url", 504);
+ }
+
+ if (!preg_match('/HTTP\/[\d.]+ (\d+) (.*)/', $meta['wrapper_data'][0], $status)) {
+ throw new NetworkException("Unexpected response: ". $meta['wrapper_data'][0]);
+ }
+
+ $status = intval($status[1]);
+ $errorMessage = $status[2];
+
+ if ($res && preg_grep('/content-type:\s*application\/im2\+json/i', $meta['wrapper_data'])) {
+ $json = @json_decode($res);
+ if ($json) {
+ if (isset($json->status)) {
+ $status = $json->status;
+ }
+ if (isset($json->error)) {
+ $errorMessage = $json->error;
+ }
+ if (isset($json->code) && $json->code === 'IM2ACCOUNT') {
+ throw new AccessDeniedException($errorMessage, $status);
+ }
+ }
+ }
+
+ if ($status >= 500) {
+ throw new APIException($errorMessage, $status);
+ }
+ if ($status == 404) {
+ throw new NotFoundException("Could not find the image: {$sourceURL}", $status);
+ }
+ if ($status == 403) {
+ throw new OriginServerException("Origin server denied access to {$sourceURL}", $status);
+ }
+ if ($status >= 400) {
+ throw new InvalidArgumentException($errorMessage, $status);
+ }
+
+ return $res;
+ }
+}
diff --git a/vendor/imageoptim/imageoptim/src/URLRequest.php b/vendor/imageoptim/imageoptim/src/URLRequest.php
new file mode 100644
index 0000000..2b825ea
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/src/URLRequest.php
@@ -0,0 +1,26 @@
+url = $url;
+ }
+
+ function apiURL() {
+ return parent::apiURL() . '/' . rawurlencode($this->url);
+ }
+
+ function getBytes() {
+ return $this->getBytesWithOptions(['header' => ""], $this->url);
+ }
+}
diff --git a/vendor/imageoptim/imageoptim/test/BasicTest.php b/vendor/imageoptim/imageoptim/test/BasicTest.php
new file mode 100644
index 0000000..34b0960
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/test/BasicTest.php
@@ -0,0 +1,125 @@
+api = new ImageOptim\API("testtest");
+ }
+
+ /**
+ * @expectedException \ImageOptim\InvalidArgumentException
+ */
+ public function testRequiresUsername1() {
+ new ImageOptim\API([]);
+ }
+
+ /**
+ * @expectedException \ImageOptim\InvalidArgumentException
+ * @expectedExceptionMessage username
+ */
+ public function testRequiresUsername2() {
+ new ImageOptim\API(null);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage URL
+ */
+ public function testNeedsURL() {
+ $this->api->imageFromURL('local/path.png');
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage could not be found
+ */
+ public function testNeedsPath() {
+ $this->api->imageFromPath('http://nope/path.png');
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Width
+ */
+ public function testResizeWidth() {
+ $this->api->imageFromURL('http://example.com')->resize("bad");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Height
+ */
+ public function testResizeBadHeight() {
+ $this->api->imageFromURL('http://example.com')->resize(320, "bad", "crop");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Height
+ */
+ public function testResizeNegativeHeight() {
+ $this->api->imageFromURL('http://example.com')->resize(320, -1, "crop");
+ }
+
+ public function testResizeWithoutHeight() {
+ $this->api->imageFromURL('http://example.com')->resize(320, "fit");
+ }
+
+ public function testResizeWithHeight() {
+ $this->api->imageFromURL('http://example.com')->resize(320, 100, "crop");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Fit
+ */
+ public function testResizeInvalidKeyword() {
+ $this->api->imageFromURL('http://example.com')->resize(320, 100, "loose");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Height
+ */
+ public function testCropNeedsHeight() {
+ $this->api->imageFromURL('http://example.com')->resize(320, null, "crop");
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ * @expectedExceptionMessage Height
+ */
+ public function testPadNeedsHeight() {
+ $this->api->imageFromURL('http://example.com')->resize(320, null, "pad");
+ }
+
+ public function testEncodesURLIfNeeded() {
+ $example = 'http://example.com/%2F';
+ $this->assertContains(rawurlencode($example), $this->api->imageFromURL($example)->apiURL());
+ }
+
+ public function testPad() {
+ $apiurl = $this->api->imageFromURL('http://example.com')->resize(10,15,'pad')->bgcolor('#FFffFF')->apiURL();
+
+ $this->assertInternalType('string', $apiurl);
+ $this->assertContains('10x15', $apiurl);
+ $this->assertContains('pad', $apiurl);
+ $this->assertContains('bgcolor=FFffFF', $apiurl);
+ }
+
+ public function testChains() {
+ $c1 = $this->api->imageFromURL('http://example.com')->resize(1280)->optimize()->timeout(34)
+ ->quality('low')->resize(1280)->dpr('2x')->resize(1280, 300);
+
+ $c2 = $this->api->imageFromURL('http://example.com')->optimize()->resize(1280)->resize(1280)
+ ->dpr(2)->timeout(34)->resize(1280, 300)->quality('low');
+
+ $this->assertInternalType('string', $c1->apiURL());
+ $this->assertEquals($c1->apiURL(), $c2->apiURL());
+ $this->assertContains('quality=low', $c2->apiURL());
+ $this->assertContains('2x', $c2->apiURL());
+ $this->assertContains('1280x300', $c1->apiURL());
+ $this->assertContains('timeout=34', $c1->apiURL());
+ $this->assertContains('/http%3A%2F%2Fexample.com', $c1->apiURL());
+ }
+}
diff --git a/vendor/imageoptim/imageoptim/test/OnlineTest.php b/vendor/imageoptim/imageoptim/test/OnlineTest.php
new file mode 100644
index 0000000..a215185
--- /dev/null
+++ b/vendor/imageoptim/imageoptim/test/OnlineTest.php
@@ -0,0 +1,49 @@
+api = new ImageOptim\API("gnbkrbjhzb");
+ }
+
+ public function testFullMonty() {
+ $imageData = $this->api->imageFromURL('http://example.com/image.png')->resize(160,100,'crop')->dpr('2x')->getBytes();
+
+ $gdimg = imagecreatefromstring($imageData);
+ $this->assertEquals(160*2, imagesx($gdimg));
+ $this->assertEquals(100*2, imagesy($gdimg));
+ }
+
+ public function testUpload() {
+ $imageData = $this->api->imageFromPath(__dir__ . '/../ImageOptim.png')->resize(32)->getBytes();
+
+ $gdimg = imagecreatefromstring($imageData);
+ $this->assertEquals(32, imagesx($gdimg));
+ $this->assertEquals(32, imagesy($gdimg));
+ }
+
+ /**
+ * @expectedException ImageOptim\AccessDeniedException
+ * @expectedExceptionCode 403
+ */
+ public function testBadKey() {
+ $api = new ImageOptim\API("zzzzzzzz");
+ $api->imageFromURL('http://example.com/image.png')->dpr('2x')->getBytes();
+ }
+
+ /**
+ * @expectedException ImageOptim\OriginServerException
+ * @expectedExceptionCode 403
+ */
+ public function testGoodKeyUpstream403() {
+ $this->api->imageFromURL('https://im2.io/.htdeny')->dpr('2x')->getBytes();
+ }
+
+ /**
+ * @expectedException ImageOptim\NotFoundException
+ * @expectedExceptionCode 404
+ */
+ public function testUpstreamError() {
+ $this->api->imageFromURL('http://fail.example.com/nope')->getBytes();
+ }
+
+}