diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3e98f81
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+# Numerous always-ignore extensions
+*.diff
+*.err
+*.orig
+*.log
+*.rej
+*.swo
+*.swp
+*.vi
+*~
+*.sass-cache
+
+# OS or Editor folders
+.DS_Store
+Thumbs.db
+.cache
+.project
+.settings
+.tmproj
+*.esproj
+nbproject
+
+# Dreamweaver added files
+_notes
+dwsync.xml
+
+# Komodo
+*.komodoproject
+.komodotools
+
+# Folders to ignore
+.hg
+.svn
+.CVS
+intermediate
+publish
+.idea
+
+# misc
+node_modules
+*.sublime-workspace
diff --git a/Blick.module b/Blick.module
new file mode 100644
index 0000000..a5bd459
--- /dev/null
+++ b/Blick.module
@@ -0,0 +1,224 @@
+
+ * @copyright Copyright (c) 2015, Christian Raunitschka
+ *
+ * @version 0.1.0
+ *
+ * @filesource
+ *
+ * @see https://github.com/owzim/pw-blick
+ * @see http://raunitschka.de
+ * @see http://www.processwire.com
+ */
+
+use owzim\Blick\AssetFactory;
+
+class Blick extends Wire implements Module
+{
+
+ public static $configTypeKeys = array('css', 'js', 'img');
+ public static $configSubKeys = array(
+ 'Path', 'Url', 'Markup', 'Default', 'Extension',
+ 'Variations', 'VariationSubDir',
+ 'Versioning', 'VersioningFormat',
+ 'Min', 'MinFormat',
+ );
+
+ /**
+ * $conf
+ *
+ * @var null
+ */
+ protected $conf = null;
+
+ /**
+ * getModuleInfo
+ *
+ * @return array
+ */
+ public static function getModuleInfo()
+ {
+ return array(
+ 'title' => 'Blick',
+ 'summary' => 'A small helper module for including JS, CSS and image files',
+ 'version' => '0.1.0',
+ 'icon' => 'eye',
+ 'requires' => array('PHP>=5.4','ProcessWire>=2.5.5'),
+ 'singular' => true,
+ 'autoload' => function() {
+ $config = wire('config');
+ $autoload =
+ isset($config->blick) &&
+ isset($config->blick['autoloadAs']) &&
+ is_string($config->blick['autoloadAs']) &&
+ $config->blick['autoloadAs'] !== '';
+ return $autoload;
+ },
+ );
+ }
+
+ /**
+ * getDefaultConfig
+ *
+ * @return array
+ */
+ public static function getDefaultConfig()
+ {
+ $config = wire('config');
+ $paths = $config->paths;
+ $urls = $config->urls;
+
+ return array(
+ 'jsPath' => $paths->templates . 'scripts',
+ 'jsUrl' => $urls->templates . 'scripts',
+ 'jsMarkup' => '',
+ 'jsDefault' => 'markup',
+ 'jsVersioning' => false,
+ 'jsVersioningFormat' => '?v={version}',
+ 'jsMin' => false,
+ 'jsMinFormat' => "{file}.min.{ext}",
+
+ 'cssPath' => $paths->templates . 'styles',
+ 'cssUrl' => $urls->templates . 'styles',
+ 'cssMarkup' => '',
+ 'cssDefault' => 'markup',
+ 'cssVersioning' => false,
+ 'cssVersioningFormat' => '?v={version}',
+ 'cssMin' => false,
+ 'cssMinFormat' => "{file}.min.{ext}",
+
+ 'imgPath' => $paths->templates . 'images',
+ 'imgUrl' => $urls->templates . 'images',
+ 'imgMarkup' => '
',
+ 'imgDefault' => 'markup',
+ 'imgVariations' => array(),
+ 'imgVariationSubDir' => 'variations',
+ 'imgVersioning' => false,
+ 'imgVersioningFormat' => '',
+ 'imgMin' => false,
+ 'imgMinFormat' => '',
+
+ 'appendNewLine' => true,
+ );
+ }
+
+ /**
+ * init
+ *
+ */
+ public function init()
+ {
+ require_once(__DIR__ . '/owzim/Blick/Autoloader.php');
+ spl_autoload_register('\owzim\Blick\Autoloader::autoload');
+
+ $this->conf = self::getConfig();
+
+ if (isset($this->conf->autoloadAs)) {
+ $this->wire($this->conf->autoloadAs, $this);
+ }
+ }
+
+ /**
+ * getConfig
+ *
+ * @param boolean $forceParse
+ * @return object
+ */
+ public static function getConfig($forceParse = false)
+ {
+ static $parsed = null;
+ if (!is_null($parsed) && !$forceParse) return $parsed;
+ $wireConfig = wire('config');
+ return $parsed = self::combineConfig(
+ self::getDefaultConfig(),
+ isset($wireConfig->blick) ? $wireConfig->blick : array(),
+ self::$configTypeKeys,
+ self::$configSubKeys
+ );
+ }
+
+ /**
+ * Combine default and custom config into one,
+ * create sub objects by given `$typeKeys` and extract `$subKeys` into them
+ * see example: http://3v4l.org/IoofQ
+ *
+ * @param array $default
+ * @param array $custom
+ * @param array $typeKeys
+ * @param array $subKeys
+ * @return object
+ */
+ public static function combineConfig($default, $custom, $typeKeys, $subKeys)
+ {
+ $config = array_merge($default, $custom);
+ foreach ($typeKeys as $typeKey) {
+ $sub = $config[$typeKey] = new \stdClass();
+ foreach ($subKeys as $subKey) {
+ $fullKey = "{$typeKey}{$subKey}";
+ if (array_key_exists($fullKey, $config)) {
+ $sub->{lcfirst($subKey)} = $config[$fullKey];
+ unset($config[$fullKey]);
+ }
+ }
+ }
+ return (object) $config;
+ }
+
+ /**
+ * asset
+ *
+ * @param string $name
+ * @param string $type
+ * @param array $args
+ * @return ozwim\Blick\Asset|ozwim\Blick\Image
+ *
+ * @see js
+ * @see css
+ * @see img
+ */
+ public function asset($name, $type, $args)
+ {
+ return AssetFactory::get($name, $type, $this->conf, $args);
+ }
+
+ /**
+ * js
+ *
+ * @param string $name
+ * @return ozwim\Blick\Asset
+ */
+ public function js($name)
+ {
+ return $this->asset($name, 'js', array_slice(func_get_args(), 1));
+ }
+
+ /**
+ * css
+ *
+ * @param string $name
+ * @return ozwim\Blick\Asset
+ */
+ public function css($name)
+ {
+ return $this->asset($name, 'js', array_slice(func_get_args(), 1));
+ }
+
+ /**
+ * img
+ *
+ * @param string $name
+ * @return ozwim\Blick\Image
+ */
+ public function img($name)
+ {
+ return $this->asset($name, 'img', array_slice(func_get_args(), 1));
+ }
+}
diff --git a/owzim/blick/Asset.php b/owzim/blick/Asset.php
new file mode 100644
index 0000000..84f8279
--- /dev/null
+++ b/owzim/blick/Asset.php
@@ -0,0 +1,323 @@
+
+ * @copyright Copyright (c) 2015, Christian Raunitschka
+ *
+ * @version 0.1.0
+ *
+ * @filesource
+ *
+ * @property string $default
+ * @property string $markup
+ * @property string $version
+ * @property string $path
+ * @property string $url
+ */
+
+namespace owzim\Blick;
+
+class Asset extends \WireData
+{
+
+ /**
+ * Regex for determining if a given file name is a remote url
+ *
+ * @see http://regexr.com/3a5d0
+ */
+ const REMOTE_REGEX = '/^(https?\:)?\/\//';
+
+ /**
+ * Regex for determining if a given file name is an absolute url
+ */
+ const ABSOLUTE_REGEX = '/^\/.*/';
+
+ /**
+ * @var string $_fullName
+ */
+ protected $_fullName = null;
+
+ /**
+ * @var string $type
+ */
+ protected $type = null;
+
+ /**
+ * @var object $conf
+ */
+ protected $conf = null;
+
+ /**
+ * @var array $args
+ */
+ protected $args = null;
+
+ /**
+ * @var string $pathPrefix
+ */
+ protected $pathPrefix = '';
+
+ /**
+ * @var string $urlPrefix
+ */
+ protected $urlPrefix = '';
+
+ /**
+ * @var string $filePrefix
+ */
+ protected $filePrefix = '';
+
+ /**
+ * Constructor
+ *
+ */
+ public function __construct($fullName, $type, $conf, $args = array())
+ {
+ $this->type = $type;
+ $this->conf = $conf;
+ $this->args = $args;
+ $this->fullName = $fullName;
+ $this->default = $conf->default;
+ }
+
+ /**
+ * Return a value depending on what's set to `$this->default`, which is used as
+ * the key
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return isset($this->{$this->default})
+ ? $this->{$this->default}
+ : $this->markup;
+ }
+
+ /**
+ * __get
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ $methodName = 'get' . ucfirst($name);
+ if ($name && method_exists($this, $methodName)) {
+ return $this->$methodName();
+ } else {
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * __isset
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ $methodName = 'get' . ucfirst($name);
+ if (method_exists($this, $methodName)) {
+ return true;
+ } else {
+ return parent::__isset($name);
+ }
+ }
+
+ /**
+ * __set
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function __set($name, $value) {
+ $methodName = 'set' . ucfirst($name);
+ if (method_exists($this, $methodName)) {
+ return $this->$methodName($value);
+ } else {
+ return parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * setFullName
+ *
+ * @param string $fullName Examples:
+ * '/some/abs/url/main.js'
+ * '/some/abs/url/main'
+ * 'rel/url/main.js'
+ * 'rel/url/main'
+ * 'main.js'
+ * 'main'
+ * @see __set
+ * @return string
+ */
+ public function setFullName($fullName)
+ {
+ $this->_fullName = $fullName;
+
+ $this->isRemote = preg_match(self::REMOTE_REGEX, $fullName) ? true : false;
+ $this->isAbsolute = preg_match(self::ABSOLUTE_REGEX, $fullName) ? true : false;
+
+ $pathPrefix = rtrim($this->conf->path, '/');
+ $urlPrefix = rtrim($this->conf->url, '/');
+
+ // either '.' or 'http://url'
+ $filePrefix = pathinfo($fullName, PATHINFO_DIRNAME);
+
+ // either '' or 'http://url'
+ $filePrefix = $filePrefix === '.' ? '' : $filePrefix;
+
+ if ($this->isAbsolute) {
+ $filePrefix = ltrim($filePrefix, '/');
+ $pathPrefix = $this->config->paths->root . $filePrefix;
+ $urlPrefix = $this->config->urls->root . $filePrefix;
+ $filePrefix = '';
+ }
+
+ $this->filename = pathinfo($fullName, PATHINFO_BASENAME);
+ $this->pathPrefix = $pathPrefix;
+ $this->urlPrefix = $urlPrefix;
+ $this->filePrefix = $filePrefix;
+ }
+
+ /**
+ * getPath
+ *
+ * @see __get
+ * @return string
+ */
+ protected function getPath()
+ {
+ if (!$this->isRemote) {
+ $filePrefix = $this->filePrefix ? "{$this->filePrefix}/" : '';
+ $variationSubDir = $this->variationSubDir ? "{$this->variationSubDir}/" : '';
+ return "{$this->pathPrefix}/{$filePrefix}{$variationSubDir}{$this->filename}";
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * setFilename
+ *
+ * @param string $filename
+ * @see __set
+ * @return string
+ */
+ protected function setFilename($filename)
+ {
+ // either 'js' or ''
+ $ext = pathinfo($filename, PATHINFO_EXTENSION);
+
+ // 'js'
+ $ext = $ext ? "$ext" : $this->type;
+
+ // 'index'
+ $name = pathinfo($filename, PATHINFO_FILENAME);
+
+ $this->ext = $ext;
+ $this->name = $name;
+ }
+
+ /**
+ * getDir
+ *
+ * @see __get
+ * @return string
+ */
+ protected function getDir()
+ {
+ $filePrefix = $this->filePrefix ? "/{$this->filePrefix}" : '';
+ $variationSubDir = $this->variationSubDir ? "/{$this->variationSubDir}" : '';
+ return "{$this->pathPrefix}{$filePrefix}{$variationSubDir}";
+ }
+
+ /**
+ * getUrl
+ *
+ * @see __get
+ * @return string
+ */
+ protected function getUrl()
+ {
+ if (!$this->isRemote) {
+ $filePrefix = $this->filePrefix ? "{$this->filePrefix}/" : '';
+ $variationSubDir = $this->variationSubDir ? "{$this->variationSubDir}/" : '';
+ return "{$this->urlPrefix}/{$filePrefix}{$variationSubDir}{$this->filename}{$this->param}";
+ } else {
+ return $this->_fullName;
+ }
+
+ }
+
+ /**
+ * getFilename
+ *
+ * @see __get
+ * @return string
+ */
+ protected function getFilename()
+ {
+
+ if ($this->conf->min) {
+ $formatted = utils\str::format($this->conf->minFormat, array(
+ 'file' => $this->name,
+ 'ext' => $this->ext,
+ ));
+ $file = "{$this->dir}/$formatted";
+
+ if (file_exists($file)) {
+ return "$formatted";
+ }
+ }
+
+ return "{$this->name}.{$this->ext}";
+ }
+
+ /**
+ * getParam
+ *
+ * @see __get
+ * @return string
+ */
+ protected function getParam()
+ {
+ if (!$this->conf->versioning) return '';
+ return utils\str::format($this->conf->versioningFormat, array(
+ 'version' => $this->version
+ ));
+ }
+
+ /**
+ * getMarkup
+ *
+ * @see __get
+ * @return string
+ */
+ protected function getMarkup()
+ {
+ $markup = utils\str::format($this->conf->markup, array(
+ 'url' => $this->url,
+ 'path' => $this->path,
+ 'param' => $this->param,
+ 'version' => $this->version
+ ));
+ $markup = utils\str::format($markup, $this->args);
+ return "{$markup}{$this->nl}";
+ }
+
+ /**
+ * Get the modfied unix timestamp of `$filePath`
+ *
+ * @param string $filePath
+ * @return int
+ */
+ protected function getVersion()
+ {
+ return file_exists($this->path) ? filemtime($this->path) : '';
+ }
+}
diff --git a/owzim/blick/AssetFactory.php b/owzim/blick/AssetFactory.php
new file mode 100644
index 0000000..48acc86
--- /dev/null
+++ b/owzim/blick/AssetFactory.php
@@ -0,0 +1,61 @@
+
+ * @copyright Copyright (c) 2015, Christian Raunitschka
+ *
+ * @version 0.1.0
+ *
+ * @filesource
+ */
+
+namespace owzim\Blick;
+
+class AssetFactory
+{
+
+ /**
+ *
+ */
+ const TYPE_IMG = 'img';
+
+ /**
+ *
+ */
+ const TYPE_JS = 'js';
+
+ /**
+ *
+ */
+ const TYPE_CSS = 'css';
+
+ /**
+ * http://regexr.com/3a5d0
+ *
+ */
+ const REMOTE_REGEX = '/^(https?\:)?\/\//';
+
+ /**
+ * Get a new Asset instance with properties depending on `$config`
+ *
+ * @param string $fullName
+ * @param string $type
+ * @param object $config
+ * @param array $args
+ * @return Asset
+ */
+ public static function get($fullName, $type, $config, $args = array(), $forceNew = false)
+ {
+ static $cache = null;
+ if (is_null($cache)) $cache = array();
+ $id = "{$fullName}-{$type}-" . implode('-', $args);
+ if (!$forceNew && array_key_exists($id, $cache)) return $cache[$id];
+
+ $class = __NAMESPACE__ . ($type === self::TYPE_IMG ? '\Image' : '\Asset');
+ $asset = new $class($fullName, $type, $config->$type, $args);
+
+ return $cache[$id] = $asset;
+ }
+}
diff --git a/owzim/blick/Autoloader.php b/owzim/blick/Autoloader.php
new file mode 100644
index 0000000..e73308e
--- /dev/null
+++ b/owzim/blick/Autoloader.php
@@ -0,0 +1,81 @@
+
+ * @author Christian (owzim) Raunitschka
+ * @version 1.0.5
+ * @copyright Copyright (c) 2013, neuwaerts GmbH
+ * @copyright Copyright (c) 2015, Christian Raunitschka
+ * @filesource
+ *
+ * modified by Christian Raunitschka to use 'owzim\Blick' namespace
+ */
+
+namespace owzim\Blick;
+
+/**
+ * Class Autoloader
+ *
+ * Generic autoloader (PSR-0 style)
+ *
+ * Can be registered as follows:
+ *
+ *
+ * require_once('/path/to/Blick/owzim/Blick/Autoloader.php');
+ * spl_autoload_register('owzim\Blick\Autoloader::autoload');
+ *
+ *
+ * @see http://www.php-fig.org/psr/0/
+ */
+class Autoloader
+{
+
+ static $basePath = null;
+
+ /**
+ * @field boolean Flag signaling, whether the autoload callback is already registered
+ */
+ protected static $isRegistered = false;
+
+ /**
+ * Registers the autoload callback
+ *
+ * Does nothing, if already registered.
+ */
+ public static function register()
+ {
+ if (self::$isRegistered) return;
+
+ spl_autoload_register(array(__CLASS__, 'autoload'));
+ self::$isRegistered = true;
+ }
+
+ /**
+ * Autoloader callback
+ *
+ * @param $className
+ */
+ public static function autoload($className)
+ {
+ if (is_null(self::$basePath)) self::$basePath =
+ dirname(dirname(__FILE__)) .
+ DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
+
+ $basePath = self::$basePath;
+
+ $className = ltrim($className, '\\');
+ $fileName = $basePath;
+ if ($lastNsPos = strrpos($className, '\\')) {
+ $namespace = substr($className, 0, $lastNsPos);
+ $className = substr($className, $lastNsPos + 1);
+ $fileName .= str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
+ }
+ $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
+
+ if (!is_file($fileName)) return;
+
+ require_once($fileName);
+ }
+}
diff --git a/owzim/blick/Image.php b/owzim/blick/Image.php
new file mode 100644
index 0000000..a95c6b2
--- /dev/null
+++ b/owzim/blick/Image.php
@@ -0,0 +1,274 @@
+
+ * @copyright Copyright (c) 2015, Christian Raunitschka
+ *
+ * @version 0.1.0
+ *
+ * @filesource
+ *
+ * @property string $default
+ * @property string $markup
+ * @property string $version
+ * @property string $path
+ * @property string $url
+ */
+
+namespace owzim\Blick;
+
+class Image extends Asset
+{
+ /**
+ * borrowed from \PageImage::size and modified
+ *
+ * @param int $width
+ * @param int $height
+ * @param array $options
+ * @return Image
+ */
+ function size($width, $height, $options = null)
+ {
+
+ if($this->ext == 'svg') return $this;
+
+ if(!is_array($options)) {
+ if(is_string($options)) {
+ // optionally allow a string to be specified with crop direction, for shorter syntax
+ if(strpos($options, ',') !== false) $options = explode(',', $options); // 30,40
+ $options = array('cropping' => $options);
+ } else if(is_int($options)) {
+ // optionally allow an integer to be specified with quality, for shorter syntax
+ $options = array('quality' => $options);
+ } else if(is_bool($options)) {
+ // optionally allow a boolean to be specified with upscaling toggle on/off
+ $options = array('upscaling' => $options);
+ } else {
+ // unknown options type
+ $options = array();
+ }
+ }
+
+ $defaultOptions = array(
+ 'upscaling' => true,
+ 'cropping' => true,
+ 'quality' => 90,
+ 'hidpiQuality' => 40,
+ 'suffix' => array(), // can be array of suffixes or string of 1 suffix
+ 'forceNew' => false, // force it to create new image even if already exists
+ 'hidpi' => false,
+ 'cleanFilename' => false, // clean filename of historial resize information
+ );
+
+ $this->error = '';
+ $configOptions = $this->config->imageSizerOptions;
+ if(!is_array($configOptions)) $configOptions = array();
+ $options = array_merge($defaultOptions, $configOptions, $options);
+
+ $width = (int) $width;
+ $height = (int) $height;
+
+ if(strpos($options['cropping'], 'x') === 0 && preg_match('/^x(\d+)[yx](\d+)/', $options['cropping'], $matches)) {
+ $options['cropping'] = true;
+ $options['cropExtra'] = array((int) $matches[1], (int) $matches[2], $width, $height);
+ $crop = '';
+ } else {
+ $crop = \ImageSizer::croppingValueStr($options['cropping']);
+ }
+
+ $suffixStr = '';
+ if(!empty($options['suffix'])) {
+ $suffix = is_array($options['suffix']) ? $options['suffix'] : array($options['suffix']);
+ sort($suffix);
+ foreach($suffix as $key => $s) {
+ $s = strtolower($this->wire('sanitizer')->fieldName($s));
+ if(empty($s)) unset($suffix[$key]);
+ else $suffix[$key] = $s;
+ }
+ if(count($suffix)) $suffixStr = '-' . implode('-', $suffix);
+ }
+
+ if($options['hidpi']) {
+ $suffixStr .= '-hidpi';
+ if($options['hidpiQuality']) $options['quality'] = $options['hidpiQuality'];
+ }
+
+ //$basename = $this->pagefiles->cleanBasename($this->basename(), false, false, false);
+ // cleanBasename($basename, $originalize = false, $allowDots = true, $translate = false)
+ $basename = $this->name; // i.e. myfile
+ if($options['cleanFilename'] && strpos($basename, '.') !== false) {
+ $basename = substr($basename, 0, strpos($basename, '.'));
+ }
+ $basename .= '.' . $width . 'x' . $height . $crop . $suffixStr . "." . $this->ext; // i.e. myfile.100x100.jpg or myfile.100x100nw-suffix1-suffix2.jpg
+
+
+ if ($this->conf->variationSubDir) {
+ $variationSubDir = "{$this->conf->variationSubDir}/";
+ $subPath = "{$this->dir}/{$this->conf->variationSubDir}";
+ } else {
+ $variationSubDir = '';
+ $subPath = $this->dir;
+ }
+
+
+ if ($variationSubDir && !file_exists($subPath)) wireMkdir($subPath, true);
+ wireChmod($subPath, true);
+
+ $filenameFinal = "$subPath/{$basename}";
+
+
+ $tmpDir = "{$this->dir}/tmp_" . str_replace('.', '_', $basename);
+ if (!file_exists($tmpDir)) wireMkdir($tmpDir, true);
+ wireChmod($tmpDir, true);
+
+
+
+ $filenameUnvalidated = "$tmpDir/$basename";
+
+ $exists = file_exists($filenameFinal);
+
+ if(!$exists || $options['forceNew']) {
+ if($exists && $options['forceNew']) @unlink($filenameFinal);
+ if(file_exists($filenameUnvalidated)) @unlink($filenameUnvalidated);
+
+ if(@copy($this->path, $filenameUnvalidated)) {
+
+ try {
+ $sizer = new \ImageSizer($filenameUnvalidated);
+ $sizer->setOptions($options);
+ if($sizer->resize($width, $height) && @rename($filenameUnvalidated, $filenameFinal)) {
+ wireChmod($filenameFinal);
+ } else {
+ $this->error = "ImageSizer::resize($width, $height) failed for $filenameUnvalidated";
+ }
+ } catch(\Exception $e) {
+
+ $this->error = $e->getMessage();
+ }
+ } else {
+ $this->error = "Unable to copy {$this->path} => $filenameUnvalidated";
+ }
+ }
+
+ $image = clone $this;
+
+ // if desired, user can check for property of $image->error to see if an error occurred.
+ // if an error occurred, that error property will be populated with details
+ if($this->error) {
+
+ // error condition: unlink copied file
+ if(is_file($filenameFinal)) @unlink($filenameFinal);
+ if(is_file($filenameUnvalidated)) @unlink($filenameUnvalidated);
+
+
+ // write an invalid image so it's clear something failed
+ // todo: maybe return a 1-pixel blank image instead?
+ $data = "This is intentionally invalid image data.\n$this->error";
+ if(file_put_contents($filenameFinal, $data) !== false) wireChmod($filenameFinal);
+
+ // we also tell PW about it for logging and/or admin purposes
+ $this->error($this->error);
+ }
+
+ if(is_dir($tmpDir)) wireRmdir($tmpDir, true);
+
+
+
+ $image->filename = pathinfo($filenameFinal, PATHINFO_BASENAME);
+ $image->variationSubDir = rtrim($variationSubDir, '/');
+
+ return $image;
+ }
+
+ /**
+ * Get a variation by name, which can be configured in `$config->blick->imgVariations`
+ *
+ * Variations example
+ *
+ * ```
+ * array(
+ * 'header' => array(
+ * 'width' => 960,
+ * 'height' => 360,
+ * 'options' => array(
+ * 'suffix' => 'header',
+ * ),
+ * ),
+ * 'person' => array(
+ * 'width' => 200,
+ * 'height' => 200,
+ * 'options' => array(
+ * 'suffix' => 'person',
+ * ),
+ * ),
+ * )
+ * ```
+ *
+ * @param string $name
+ * @return Image
+ */
+ public function getVariation($name)
+ {
+ if (isset($this->conf->variations[$name])) {
+ $variation = $this->conf->variations[$name];
+ $width = isset($variation['width']) ? $variation['width'] : null;
+ $height = isset($variation['height']) ? $variation['height'] : null;
+ $options = isset($variation['options']) ? $variation['options'] : array();
+
+ if (!is_null($width) && !is_null($height)) {
+ return $this->size($width, $height, $options);
+ } else if (!is_null($width)) {
+ return $this->width($width, $options);
+ } else if (!is_null($height)) {
+ return $this->height($height, $options);
+ } else {
+ return $this;
+ }
+ }
+ return $this;
+ }
+
+ public function crop($x, $y, $width, $height, $options = array())
+ {
+
+ $x = (int) $x;
+ $y = (int) $y;
+ $width = (int) $width;
+ $height = (int) $height;
+
+ if(empty($options['suffix'])) {
+ $options['suffix'] = array();
+ } else if(!is_array($options['suffix'])) {
+ $options['suffix'] = array($options['suffix']);
+ }
+
+ $options['suffix'][] = "cropx{$x}y{$y}";
+ $options['cropExtra'] = array($x, $y, $width, $height);
+ $options['cleanFilename'] = true;
+
+ return $this->size($width, $height, $options);
+ }
+
+ public function width($n = 0, $options = array())
+ {
+ return $this->size($n, 0, $options);
+ }
+
+ /**
+ * Multipurpose: return the height of the Pageimage OR return an image sized with a given height (and proportional width)
+ *
+ * If given a height, it'll return a new Pageimage object sized to that height.
+ * If not given a height, it'll return the height of this Pageimage
+ *
+ * @param int $n Optional height
+ * @param array|string|int|bool $options Optional options (see size function)
+ * @return int|Pageimage
+ *
+ */
+ public function height($n = 0, $options = array())
+ {
+ return $this->size(0, $n, $options);
+ }
+}
diff --git a/owzim/blick/utils/str.php b/owzim/blick/utils/str.php
new file mode 100644
index 0000000..32f3839
--- /dev/null
+++ b/owzim/blick/utils/str.php
@@ -0,0 +1,40 @@
+ 'Jane', 'compliment' => 'stunning']
+ * );
+ * // returns 'Hello Jane, you look stunning!'
+ * ```
+ *
+ * @param string $string
+ * @param array $values
+ * @return string The formatted string
+ */
+ public static function format($string, array $values)
+ {
+ foreach ($values as $key => $value) {
+ $string = str_replace('{' . $key. '}', $value, $string);
+ }
+ return $string;
+ }
+}