pepebe
7/26/2013 - 7:48 AM

Experimental fork of imageSlim snippet Added imgSrc paramter and changed 'src' to $imgSrc. Warning: I doesn't work!

Experimental fork of imageSlim snippet Added imgSrc paramter and changed 'src' to $imgSrc. Warning: I doesn't work!

<?php
/**
 * imageSlim
 * Copyright 2013 Jason Grant
 *
 * imageSlim is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * imageSlim is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * imageSlim; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 *
 *
 * @package imageslim
 * @author Jason Grant
 * @version 1.0.2-pl
 */

/**
 * Documentation, bug reports, etc.
 * https://github.com/oo12/imageSlim
 *
 * Variables
 * ---------
 *
 * @var modX $modx
 * @var input $input
 * @var options $options
 *
 *
 * Properties
 * ----------
 *
 * @property float scale
 * @property float conventThreshold
 * @property integer maxWidth
 * @property integer maxHeight
 * @property string phpthumbof
 * @property boolean fixAspect
 * @property boolean remoteImages
 * @property integer remoteTimeout
 * @property integer q
 * @property boolean debug
 * @property string imgSrc defaults to src, but may be any attribute.
 *
 * See the default properties for a description of each.
 *
 * @package imageslim
 **/

if (empty($input)) { return; }  // if we've got nothing to do, it's quittin' time

if (isset($options)) {  // if we're being called as an output filter, set variables for any options
  parse_str($options);
}

// process our properties
$scale = !empty($scale) ? (float) $scale : 1;
$convertThreshold = isset($convertThreshold) && $convertThreshold !== '' ? (float) $convertThreshold * 1024 : FALSE;
$maxWidth = isset($maxWidth) && $maxWidth !== '' ? (int) $maxWidth: 999999;
$maxHeight = isset($maxHeight) && $maxHeight !== '' ? (int) $maxHeight: 999999;
$phpthumbof = isset($phpthumbof) ? $phpthumbof : '';
$fixAspect = isset($fixAspect) ? (bool) $fixAspect : TRUE;
$remoteImages = isset($remoteImages) ? (bool) $remoteImages && function_exists('curl_init') : FALSE;
$remoteTimeout = isset($remoteTimeout) ? (int) $remoteTimeout : 5;
$q = empty($q) ? '' : (int) $q;
$debug = isset($debug) ? (bool) $debug : FALSE;

$imgSrc = isset($imgSrc) ? $imgSrc : "src";

$debug &&   $debugstr = "i m a g e S l i m  [1.0.1pl]\nscale:$scale  convertThreshold:" . ($convertThreshold ? $convertThreshold / 1024 . 'KB' : 'none') . "\nmaxWidth:$maxWidth  maxHeight:$maxHeight  q:$q\nfixAspect:$fixAspect  phpthumbof:$phpthumbof\nRemote images:$remoteImages  Timeout:$remoteTimeout  cURL:" . (!function_exists('curl_init') ? 'not ':'') . "installed\n";

$cachePath = MODX_ASSETS_PATH . 'components/imageslim/cache/';
$remoteDomains = FALSE;
$dom = new DOMDocument;
@$dom->loadHTML('<?xml encoding="UTF-8">' . $input);  // load this mother up

$emptynode = $dom->createTextNode('');
foreach (array('iframe', 'video', 'audio') as $tag) {  // prevent certain tags from getting turned into self-closing tags by domDocument
	foreach ($dom->getElementsByTagName($tag) as $node) {
		$node->appendChild($emptynode);
	}
}

foreach ($dom->getElementsByTagName('img') as $node) {  // for all our images
	$src = $node->getAttribute($imgSrc);
	$file = $size = FALSE;
	if ( preg_match('/^(?:https?:)?\/\/(.+?)\/(.+)/i', $src, $matches) ) {  // if we've got a remote image to work with
		$allowedDomain = TRUE;
		$file = $src;
		if ( $remoteImages && $modx->config['phpthumb_nohotlink_enabled'] ) {  // if nohotlink is enabled, make sure it's an allowed domain
			$allowedDomain = FALSE;  // domains will only be allowed if they match one in phpthumb_nohotlink_valid_domains
			if ($remoteDomains === FALSE) {  // first time through get a list of allowed domains
				$remoteDomains = explode(',', $modx->config['phpthumb_nohotlink_valid_domains']);
				$remoteDomainsCount = count($remoteDomains);
				$debug &&   $debugstr .= "\nphpthumb_nohotlink: Enabled  valid_domains:{$modx->config['phpthumb_nohotlink_valid_domains']}\n";
			}
			for ($i=0; $i < $remoteDomainsCount && !$allowedDomain; ++$i) {
				$allowedDomain = preg_match("/{$remoteDomains[$i]}$/i", $matches[1]);
			}
		}
		if (!$allowedDomain) {
			$debug &&   $debugstr .= "\nsrc:$src\nDomain:{$matches[1]}\n*** Remote image not allowed. Skipping ***\n";
			continue;
		}
		$debug &&   $debugstr .= "\nsrc:$src\nDomain:{$matches[1]}  Allowed:$allowedDomain\n";
		$file = $cachePath . preg_replace("/[^\w\d\-_\.]/", '-', "{$matches[1]}-{$matches[2]}");
		if (!file_exists($file)) {  // if it's not in our cache, go get it
			$debug &&   $debugstr .= "Retrieving $src\nTarget filename: $file\n";
			$fh = fopen($file, 'wb');
			if (!$fh) {
				$debug &&   $debugstr .= "*** Error ***  Can't write to cache directory $cachePath\n";
				continue;
			}
			$curlFail = FALSE;
			$ch = curl_init($src);
			curl_setopt_array($ch, array(
				CURLOPT_TIMEOUT	=> $remoteTimeout,
				CURLOPT_FILE => $fh,
				CURLOPT_FAILONERROR => TRUE
			));
			curl_exec($ch);
			if (curl_errno($ch)) {
				$debug &&   $debugstr .= 'cURL error: ' . curl_error($ch) . " *** Skipping ***\n";
				$curlFail = TRUE;
			}
			curl_close($ch);
			fclose($fh);
			if ($curlFail) {  // if we didn't get it, skip and don't cache
				unlink($file);
				continue;
			}
		}
		elseif ($debug) { $debugstr .= "Retrieved from cache: $file\n";}
	}
	else {
		$file = MODX_BASE_PATH . rawurldecode(ltrim($src, '/'));  // Fix spaces and other encoded characters in the URL
		$debug &&   $debugstr .= "\nsrc:$file\n";
	}

	$size = @getimagesize($file);  // get the actual size and file type of the image.
	if ($size === FALSE) {  // weed out missing images and formats like svg
		$debug && $debugstr .= "*** Can't get image size. Skipping ***\n";
		continue;
	}

	$type = strtolower( substr($size['mime'], strpos( $size['mime'], '/')+1) );  // extract the image type
	$ar = $size[0] / $size[1];  // calculate our intrinsic aspect ratio

	$w = $wCss = 0;  // initialize some stuff we'll need
	$h = $hCss = 0;
	$updateStyles = $adjustDisplaySize = FALSE;
	parse_str($phpthumbof, $opts);  // add in any user-specified phpthumb parameters

	$styleAttr = $node->getAttribute('style');  // check for width and height in an inline style first
	if ($styleAttr) {
		$styles = array();
		preg_match_all('/([\w-]+)\s*:\s*([^;]+)\s*;?/', $styleAttr, $matches, PREG_SET_ORDER);
		foreach ($matches as $match) { $styles[$match[1]] = $match[2]; }  // bust everything out into an array
		if ( isset($styles['width']) && stripos($styles['width'], 'px') ) {  // if we have a width in pixels
			preg_match('/\d+/', $styles['width'], $matches);
			$wCss = $matches[0];  // get just the value
		}
		if ( isset($styles['height']) && stripos($styles['height'], 'px') ) {  // same deal for height
			preg_match('/\d+/', $styles['height'], $matches);
			$hCss = $matches[0];
		}
	}
	$w = $wCss ? $wCss : $node->getAttribute('width');  // if we don't have a CSS width, get it from the width attribute
	$h = $hCss ? $hCss : $node->getAttribute('height');
	$debug &&   $debugstr .= "w:$w  h:$h  realw:{$size[0]}  realh:{$size[1]}  type:$type\n";

	$aspectNeedsFix = FALSE;  // let's see if we need to fix a stretched image
	if ($fixAspect && $w && $h) {
		$new_ar = $w / $h;
		if ( abs($new_ar - $ar) > 0.01 ) {  // allow a little discrepancy, but nothing crazy
			$aspectNeedsFix = TRUE;
			$ar = $new_ar;
			$maxScale = min($scale, $size[0] / $w, $size[1] / $h);
			if ($maxScale >= 1) {  // if we've got enough resolution to correct it, let's go ahead and set that up
				$opts['w'] = round($w * $maxScale);
				$opts['h'] = round($h * $maxScale);
				$debug &&   $debugstr .= "++ Fixing aspect ratio. w:{$opts['w']}  h:{$opts['h']}  scale:$maxScale  zc:1\n";
			}
			elseif ($debug)  { $debugstr .= "!! Image stretched.  scale:$maxScale\n"; }  // otherwise we might be able to if the image gets sized down below
		}
	}

	$heightPlay = 0;  // used to prevent height resizing on a 1px rounding difference
	if ( $w && ($w > $maxWidth || $size[0] > $w * $scale)  ||  $size[0] > $maxWidth * $scale ) {
		$wMax = $scale * ($w ? ( $w < $maxWidth ? $w : $maxWidth ) : $maxWidth);
		$wMax = $wMax > $size[0] ? $size[0] : $wMax;
		$opts['w'] = $wMax;
		$newH = $size[1] < $wMax/$ar ? $size[1] : $wMax / $ar;
		if ($aspectNeedsFix) {
			$opts['w'] = $wMax < $size[1]*$ar ? $wMax : $size[1] * $ar;  // reduce scale if we need to to fix a stretched image
			$opts['h'] = $newH;
		}
		$size[1] = $newH;
		$heightPlay = 1;
		$debug &&   $debugstr .= "++(W) realw:{$opts['w']}  realh:{$size[1]}\n";
		if ($maxWidth && $w > $maxWidth) {  // if we need to change the display sizing
			$w = $maxWidth;
			$h = round($maxWidth / $ar);
			$adjustDisplaySize = TRUE;  // we'll set the size in a bit..
			$debug && $debugstr .= "++   w:$w  h:$h\n";
		}
	}

	if ( $h && ($h > $maxHeight || $size[1] - $heightPlay > $h * $scale)  ||  $size[1] - $heightPlay > $maxHeight * $scale ) {
		$hMax = $scale * ($h ? ( $h < $maxHeight ? $h : $maxHeight ) : $maxHeight);
		$hMax = $hMax > $size[1] ? $size[1] : $hMax;
		$opts['h'] = $hMax;
		if ($aspectNeedsFix)  {
			$opts['h'] = $hMax < $size[0]/$ar ? $hMax : $size[0] / $ar;  // reduce scale if we need to to fix a stretched image
			$opts['w'] = $opts['h'] * $ar;
		}
		else  { unset($opts['w']); }  // forget about width, since height is our limiting dimension
		$debug &&   $debugstr .= '++(H) ' . (isset($opts['w']) ? $opts['w'] : '') . " realh:{$opts['h']}\n";
		if ($maxHeight && $h > $maxHeight) {
			$h = $maxHeight;
			$w = round($maxHeight * $ar);
			$adjustDisplaySize = TRUE;
			$debug &&   $debugstr .= "++   w:$w  h:$h\n";
		}
	}
	if (isset($opts['w']))  { $opts['w'] = round($opts['w']); }  // round these to integers
	if (isset($opts['h']))  { $opts['h'] = round($opts['h']); }

	if ($adjustDisplaySize) {  // ok, update our display size if we need do
		if ($wCss) {  // if the width was in an inline style (and in px), use that
			$styles['width'] = $w . 'px';
			$updateStyles = TRUE;
		}
		else { $node->setAttribute('width', $w); }

		if ($hCss) {  // same for height
			$styles['height'] = $h . 'px';
			$updateStyles = TRUE;
		}
		else { $node->setAttribute('height', $h); }
	}

	if ($convertThreshold !== FALSE && $type !== 'jpeg') {
		$fsize = filesize($file);
		if ($fsize > $convertThreshold) {  // if we've got a non-jpeg that's too big, convert it to jpeg
			$opts['f'] = 'jpeg';
			$debug &&   $debugstr .= "File size:$fsize  Threshold exceeded; converting to jpeg.\n";
		}
	}

	if (!empty($opts)) {  // have we anything to do for this lovely image?
		if ($aspectNeedsFix)  { $opts['zc'] = 1; }
		if ( !isset($opts['f']) ) {  // if output file type isn't user specified...
			$opts['f'] = ($type === 'jpeg' ? 'jpeg' : 'png');  // if it's a gif or bmp let's just make it a png, shall we?
		}
		if ($q && $opts['f'] === 'jpeg')  { $opts['q'] = $q; }  // add user-specified jpeg quality if it's relevant
		if ($opts['f'] === 'jpeg') { $opts['f'] = 'jpg'; }  // workaround for phpThumbOf issue #53
		$image = array();
		$image['input'] = $file;
		$option_str = '';
		foreach ($opts as $k => $v)  {  // turn our phpthumb options array back into a string
			if (is_array($v)) {  // handle any array options like fltr[]
				foreach($v as $param) { $option_str .= $k . "[]=$param&"; }
			}
			else { $option_str .= "$k=$v&"; }
		}
		$image['options'] = rtrim($option_str, '&');
		$debug &&   $debugstr .= "phpthumbof options: {$image['options']}\n";
		$node->setAttribute( $imgSrc , $modx->runSnippet('phpthumbof', $image) );  // do the business and set the src
		if ($updateStyles) {
			$style = '';
			foreach($styles as $k => $v) { $style .= "$k:$v;"; }  // turn $styles array into an inline style string
			$node->setAttribute('style', $style);
		}
	}
}

$output = str_replace('&#13;', '', substr($dom->saveXML($dom->documentElement), 12, -14) );  // strip off the <body> tags and CR characters that DOM adds (?)
$debug &&   $output = "<!--\n$debugstr-->\n$output";
return $output;