예제 #1
0
/**
 * Render markup for an image tag in smarty
 *
 * The {img} smarty function is the recommended way to load images in templates from asset or public directories.
 * In addition to automatically resolving URLs, it can also handle server-side resizing and a few other nifty features.
 *
 * #### Image Types & Animations
 *
 * As of Core 3.3.0, image types are preserved, so if a .jpg is requested, an image/jpeg is returned.
 * This changed from the previous behaviour where all images were converted to a PNG.
 *
 * Supported image types are `.jp[e]g`, `.png`, and `.gif`.
 *
 * If an animated gif is resized, the server will attempt to preserve the animation on the resized image.
 * This is done via imagemagick, (so that library needs to be present on the server in order for this trick to work).
 *
 * #### SEO Data
 *
 * As of Core 3.2.0, alt tags are automatically added to every image that does not have the alt attribute explicitly set.
 * This alt name is pulled from the filename of the image, with automatic capitalization and '_' => (space) converting.
 *
 * #### Smarty Parameters
 *
 *  * assign
 *    * string
 *    * Set to a string to assign that variable name instead of returning the output.
 *  * dimensions
 *    * Provide both width and height in pixels, along with special instructions
 *    * Structure is "widthxheight" with no spaces between the "x" and the two integers.
 *    * Special modes available are:
 *    * Carat "`^`" at the end of the string fits the smallest dimension instead of the largest.
 *    * Exclamation mark "`!`" at the end forces size regardless of aspect ratio.
 *    * Greater than "`>`" at the end will only increase image sizes.
 *    * Less than "`<`" at the end will only decrease image sizes.
 *  * file
 *    * \Core\Filestore\File
 *    * File object passed in to display
 *    * Either "file" or "src" is required.
 *  * height
 *    * int
 *    * Maximum image height (in pixels).
 *    * If both width and height are provided, the image will be constrained to both without any distortion.
 *  * inline
 *    * int "1" or "0", (default "0")
 *    * New in Core 4.2.0
 *    * Request that the resized image be encoded as base64 and inserted inline in the markdown instead of returned as the URL.
 *  * placeholder
 *    * string
 *    * placeholder image if the requested image is blank or not found.  Useful for optional fields that should still display something.
 *    * Current values: "building", "generic", "person", "person-tall", "person-wide", "photo"
 *  * src
 *    * string
 *    * Source filename to display.  This can start with "assets" for an asset, or "public" for a public file.
 *    * Either "file" or "src" is required.
 *  * width
 *    * int
 *    * Maximum image width (in pixels).
 *    * If both width and height are provided, the image will be constrained to both without any distortion.
 *
 * Any other parameter is transparently sent to the resulting `<img/>` tag.
 *
 *
 * #### Example Usage
 *
 * <pre>
 * {img src="public/gallery/photo123.png" width="123" height="123" placeholder="photo" alt="My photo 123"}
 * </pre>
 *
 * @param array  $params  Associative (and/or indexed) array of smarty parameters passed in from the template
 * @param Smarty $smarty  Parent Smarty template object
 *
 * @return string
 * @throws SmartyException
 */
function smarty_function_img($params, $smarty){

	// Key/value array of attributes for the resulting HTML.
	$attributes = array();

	if(isset($params['file'])){
		$f = $params['file'];
		if(!$f instanceof \Core\Filestore\File){
			throw new SmartyException('{img} tag expects a \Core\Filestore\File object for the "file" parameter.');
		}
		unset($params['file']);
	}
	elseif(isset($params['src'])){
		$f = \Core\Filestore\Factory::File($params['src']);
		unset($params['src']);
	}
	else{
		$f = null;
	}

	// Some optional parameters, (and their defaults)
	$assign = $width = $height = $dimensions = $inline = false;
	$placeholder = $previewfile = null;

	if(isset($params['assign'])){
		$assign = $params['assign'];
		unset($params['assign']);
	}

	if(isset($params['width'])){
		$width = $params['width'];
		unset($params['width']);
	}

	if(isset($params['height'])){
		$height = $params['height'];
		unset($params['height']);
	}

	if(isset($params['dimensions'])){
		$dimensions = $params['dimensions'];
		$width = preg_replace('#[^0-9]?([0-9]*)x.*#', '$1', $dimensions);
		$height = preg_replace('#.*x([0-9]*)[^0-9]?#', '$1', $dimensions);
		unset($params['dimensions']);
	}

	if(isset($params['placeholder'])){
		$placeholder = $params['placeholder'];
		unset($params['placeholder']);
	}

	if(isset($params['inline'])){
		$inline = ($params['inline'] == '1');
		unset($params['inline']);
	}


	if($dimensions){
		// Passing in dimensions raw will allow the user more control over the size of the images.
		$d = $dimensions;
	}
	else{
		// If one is provided but not the other, just make them the same.
		if($width && !$height) $height = $width;
		if($height && !$width) $width = $height;

		$d = ($width && $height) ? $width . 'x' . $height : false;
	}

	// If the file doesn't exist and a placeholder was provided, use the appropriate placeholder image!
	if(!($f && $f->exists() && $f->isImage()) && $placeholder){
		// Try that!
		$f = \Core\Filestore\Factory::File('assets/images/placeholders/' . $placeholder . '.png');
	}

	if(!$f){
		throw new SmartyException('{img} tag requires either "src", "file", or a "placeholder" parameter.');
	}

	// Do the rest of the attributes that the user sent in (if there are any)
	foreach($params as $k => $v){
		$attributes[$k] = $v;
	}

	if($f instanceof Core\Filestore\Backends\FileRemote){
		// Erm... Give the original URL with the dimension requests.
		$attributes['src'] = $f->getURL();
		if($width) $attributes['width'] = $width;
		if($height) $attributes['height'] = $height;
	}
	else{
		// Try to lookup the preview file.
		// if it exists, then YAY... I can return that direct resource.
		// otherwise, I should check and see if the file is larger than a set filesize.
		// if it is, then I want to return a link to a controller to render that file instead of rendering the file from within the {img} tag.
		//
		// This is useful because any logic contained within this block will halt page execution!
		// To improve the perception of performance, that can be offloaded to the browser requesting the <img/> contents.
		$previewfile = $d ? $f->getQuickPreviewFile($d) : $f;

		if(!$previewfile){
			$attributes['src'] = '#';
			$attributes['title'] = 'No preview files available!';
		}
		elseif($inline && $f->getFilesize() < 1048576*4){
			// Overwrite the src attribute with the base64 contents.
			// This can only happen after the preview file exists!

			if(!$previewfile->exists()){
				// Since quick ran, ensure that it's actually resized!
				$previewfile = $f->getPreviewFile($d);
			}
			$attributes['src'] = 'data:' . $previewfile->getMimetype() . ';base64,' . base64_encode($previewfile->getContents());
		}
		elseif(!$previewfile->exists()){
			// Ok, it doesn't exist... return a link to the controller to render this file.
			$attributes['src'] = \Core\resolve_link('/file/preview') . '?f=' . $f->getFilenameHash() . '&d=' . $d;
		}
		else{
			$attributes['src'] = $previewfile->getURL();
		}
	}

	// All images need alt data!
	if(!isset($attributes['alt'])){
		$attributes['alt'] = $f->getTitle();
	}

	// Merge them back together in one string.
	$html = '<img';
	foreach($attributes as $k => $v) $html .= " $k=\"$v\"";
	$html .= '/>';

	// If the extended metadata was requested... look that up too!
	if(isset($params['includemeta']) && $params['includemeta']){

		$metahelper  = new \Core\Filestore\FileMetaHelper($f);
		$metacontent = $metahelper->getAsHTML();
		if($metacontent){
			$html = '<div class="image-metadata-wrapper">' . $html . $metacontent . '</div>';
		}
	}

	return $assign ? $smarty->assign($assign, $html) : $html;
}
예제 #2
0
/**
 * Utility to Core-ify a given HTML string.
 *
 * Will use a tokenizer to scan for <img /> tags and <a /> tags.
 *
 * @param $html
 * @return string
 */
function parse_html($html){
	// Counter for the current position of the tokenizer.
	$x = 0;
	// Set to the position of the current image
	$imagestart = null;

	// @todo a rel=nofollow tags for external/untrusted links
	//       This can make use of an external utility to allow the admin to set which links are allowed.

	// @todo a tags that are absolutely resolved or have a core prefix such as core:///about-us or what not.

	while($x < strlen($html)){
		// Replace images with the optimized version.
		if(substr($html, $x, 4) == '<img'){
			$imagestart = $x;
			$x+= 3;
			continue;
		}

		$fullimagetag = null;

		if($imagestart !== null && $html{$x} == '>'){
			// This will equal the full image HTML tag, ie: <img src="blah"/>...
			$fullimagetag = substr($html, $imagestart, $x-$imagestart+1);
		}
		elseif($imagestart !== null && substr($html, $x, 2) == '/>'){
			// This will equal the full image HTML tag, ie: <img src="blah"/>...
			$fullimagetag = substr($html, $imagestart, $x-$imagestart+2);
		}

		if($imagestart !== null && $fullimagetag){
			// Convert it to a DOM element so I can process it.
			$simple = new \SimpleXMLElement($fullimagetag);
			$attributes = array();
			foreach($simple->attributes() as $k => $v){
				$attributes[$k] = (string)$v;
			}

			$file = \Core\Filestore\Factory::File($attributes['src']);

			// All images need alt tags.
			if(!isset($attributes['alt']) || $attributes['alt'] == ''){
				// Since this is usually only used to render content and the images contained therein,
				// and tinymce auto-adds a blank alt="" attribute,
				// I can safely add the alt based on the file's rendered title.
				$attributes['alt'] = $file->getTitle();
			}

			if(isset($attributes['width']) || isset($attributes['height'])){

				if(isset($attributes['width']) && isset($attributes['height'])){
					$dimension = $attributes['width'] . 'x' . $attributes['height'] . '!';
					unset($attributes['width'], $attributes['height']);
				}
				elseif(isset($attributes['width'])){
					$dimension = $attributes['width'];
					unset($attributes['width']);
				}
				else{
					$dimension = $attributes['height'];
					unset($attributes['height']);
				}

				$attributes['src'] = $file->getPreviewURL($dimension);
			}

			// And rebuild.
			$img = '<img';
			foreach($attributes as $k => $v){
				$img .= ' ' . $k . '="' . str_replace('"', '&quot;', $v) . '"';
			}
			$img .= '/>';

			$metahelper  = new \Core\Filestore\FileMetaHelper($file);
			$metacontent = $metahelper->getAsHTML();
			if($metacontent){
				$img = '<div class="image-metadata-wrapper">' . $img . $metacontent . '</div>';
			}

			// Figure out the offset for X.  I'll need to modify this after I merge it in.
			$x += strlen($img) - strlen($fullimagetag);
			// Split this string back in.
			$html = substr_replace($html, $img, $imagestart, strlen($fullimagetag));

			// Reset...
			$imagestart = null;
		}
		$x++;
	}
	//var_dump($html);

	return $html;
}