/** * 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; }
/** * 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('"', '"', $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; }