Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00%
1 / 1
100.00%
13 / 13
CRAP
100.00%
109 / 109
Magentron_EmailImages_Helper_Data
100.00%
1 / 1
100.00%
13 / 13
42
100.00%
109 / 109
 isEnabled()
100.00%
1 / 1
1
100.00%
1 / 1
 getCacheTime()
100.00%
1 / 1
2
100.00%
4 / 4
 getRegularExpression()
100.00%
1 / 1
2
100.00%
4 / 4
 getRegularExpressionIndex()
100.00%
1 / 1
2
100.00%
4 / 4
 addImages( Zend_Mail $mail, $context = null )
100.00%
1 / 1
4
100.00%
9 / 9
 cleanCache()
100.00%
1 / 1
1
100.00%
3 / 3
 _getImageUrlsFromMail( Zend_Mail $mail, $context = null )
100.00%
1 / 1
7
100.00%
20 / 20
 _getImageUrlsFromBodyHtml( $bodyHtml )
100.00%
1 / 1
4
100.00%
15 / 15
 _getContextDataFromCache( $context_cache_id )
100.00%
1 / 1
2
100.00%
2 / 2
 _getBodyHtml( Zend_Mail $mail )
100.00%
1 / 1
5
100.00%
11 / 11
 _saveContextDataToCache( $context_cache_id, $isHtml, $urls )
100.00%
1 / 1
1
100.00%
5 / 5
 _attachImageUrls( Zend_Mail $mail, array $urls )
100.00%
1 / 1
5
100.00%
13 / 13
 _retrieveImageData( $url )
100.00%
1 / 1
6
100.00%
18 / 18
<?php
/**
* Magentron EmailImages Extension
*
* @category Magentron
* @package Magentron_EmailImages
* @author Jeroen Derks
* @copyright Copyright (c) 2011 Jeroen Derks http://www.magentron.com
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
class Magentron_EmailImages_Helper_Data extends Mage_Core_Helper_Abstract
{
/** Configuration path for extension enablement.
* @var string
* @see isEnabled()
*/
const XML_EMAIL_IMAGES_ENABLE = 'system/emailimages/enable';
/** Configuration path for maximum cache lifetime.
* @var string
* @see getCacheTime()
*/
const XML_EMAIL_IMAGES_CACHE_TIME = 'system/emailimages/cache_time';
/** Configuration path for regular expression used to find image URLs in HTML.
* @var string
* @see getRegularExpression()
*/
const XML_EMAIL_IMAGES_REGEXP = 'system/emailimages/regexp';
/** Configuration path for index into matches of regular expression used to find image URLs in HTML.
* @var string
* @see XML_EMAIL_IMAGES_REGEXP,
* getRegularExpressionIndex()
*/
const XML_EMAIL_IMAGES_REGEXP_INDEX = 'system/emailimages/regexp_index';
/** Default maximum lifetime used for cache.
* @var integer
* @see getCacheTime()
*/
const DEFAULT_CACHE_TIME = 86400;
/** Default regular expression to extract image URLs from the email HTML body.
* @var string
* @see getRegularExpression()
*/
const DEFAULT_REGEXP = '/((<[iI][mM][gG] [^>]*[sS][rR][cC]|[Bb][Aa][Cc][Kk][Gg][Rr][Oo][Uu][Nn][Dd])="|image:url\(\'?)([^\'"\)]*)(["\'\)])/';
/** Default index in the regular expression matches to extract the image URLs from the email HTML body.
* @var integer
* @see getRegularExpressionIndex()
*/
const DEFAULT_REGEXP_INDEX = 3;
/** Cache tag to use.
* @var string
*/
const CACHE_TAG = 'MAGENTRON_EMAILIMAGES';
/** Cache type to use.
* @var string
*/
const CACHE_TYPE = 'emailimages';
/**
* Is the EmailImages extension enabled to actually attach images?
*
* @return boolean
*
* @see XML_EMAIL_IMAGES_ENABLE
*/
public function isEnabled()
{
return (boolean) Mage::getStoreConfig(self::XML_EMAIL_IMAGES_ENABLE);
}
/**
* Retrieve the maximum lifetime for caching in seconds.
*
* @return integer
*
* @see XML_EMAIL_IMAGES_CACHE_TIME, DEFAULT_CACHE_TIME
*/
public function getCacheTime()
{
$config = Mage::getStoreConfig(self::XML_EMAIL_IMAGES_CACHE_TIME);
if ( !is_numeric($config) )
$config = self::DEFAULT_CACHE_TIME;
return (integer) $config;
}
/**
* Retrieve the regular expression to extract the image URLs from the email HTML body.
*
* @return string
*
* @see XML_EMAIL_IMAGES_REGEXP, DEFAULT_REGEXP
*/
public function getRegularExpression()
{
$config = Mage::getStoreConfig(self::XML_EMAIL_IMAGES_REGEXP);
if ( '' == $config )
$config = self::DEFAULT_REGEXP;
return (string) $config;
}
/**
* Retrieve the index in the regular expression matches to extract the image URLs from the email HTML body.
*
* @return integer
*
* @see XML_EMAIL_IMAGES_REGEXP_INDEX, DEFAULT_REGEXP_INDEX
*/
public function getRegularExpressionIndex()
{
$config = Mage::getStoreConfig(self::XML_EMAIL_IMAGES_REGEXP_INDEX);
if ( !is_numeric($config) )
$config = self::DEFAULT_REGEXP_INDEX;
return (integer) $config;
}
/**
* Attach images to mail object.
*
* @param Zend_Mail $mail Zend_Mail instance to attach images to.
* @param string $context [optional] Set to unique identifier for template, so that body needs to be parsed only once per template (NB: case-insensitive).
* @return void Fails silently if unable to attach image, warning message sent to log.
*
* @see isEnabled(), _getImageUrlsFromMail(), _attachImageUrls()
*/
public function addImages( Zend_Mail $mail, $context = null )
{
// check whether the administrator has enabled the module
if ( !$this->isEnabled() )
{
Mage::log('EmailImages - extension disabled');
return;
}
try
{
$urls = $this->_getImageUrlsFromMail($mail, $context);
if ( $urls )
$this->_attachImageUrls($mail, $urls);
}
catch ( Exception $e )
{
// ignore exception, but do log it
Mage::log('EmailImages - ERROR: exception caught: ' . $e, Zend_Log::ERR);
}
}
/**
* Remove cached image and context data from the cache.
*
* @return Magentron_EmailImages_Helper_Data Provides fluent interface
*
* @see CACHE_TAG,
* Mage_Core_Model_Cache::flush()
*/
public function cleanCache()
{
/** @var $cache Mage_Core_Model_Cache */
$cache = Mage::getSingleton('core/cache');
$cache->flush(self::CACHE_TAG);
return $this;
}
/**
* Retrieve image URLs from email content
*
* @param Zend_Mail $mail Zend_Mail instance to attach images to.
* @param string $context [optional] Set to unique identifier for template, so that body needs to be parsed only once per template (NB: case-insensitive).
* @return array Array of image URLs.
*
* @see CACHE_TYPE,
* _getContextDataFromCache(), _getBodyHtml(), _getImageUrlsFromBodyHtml(), _saveContextDataToCache(),
* Mage_Core_Model_Cache::canUse()
*/
protected function _getImageUrlsFromMail( Zend_Mail $mail, $context = null )
{
// check cache for context
/** @var $cache Mage_Core_Model_Cache */
$cache = Mage::getSingleton('core/cache');
$context_data = null;
$use_cache = null !== $context && $cache->canUse(self::CACHE_TYPE);
Mage::log(__CLASS__ . '::' . __FUNCTION__ . '(): use_cache = ' . var_export($use_cache, 1));
if ( $use_cache )
{
$context_cache_id = self::CACHE_TYPE . '-urls-' . (is_string($context) ? $context : md5(serialize($context)));
$context_data = $this->_getContextDataFromCache($context_cache_id);
}
if ( !$context_data )
{
$bodyHtml = $this->_getBodyHtml($mail);
$urls = $this->_getImageUrlsFromBodyHtml($bodyHtml);
// save URLs to cache, if context defined
if ( $use_cache )
{
$isHtml = (boolean) $bodyHtml;
$this->_saveContextDataToCache($context_cache_id, $isHtml, $urls);
}
}
else
{
$urls = $context_data['is_html'] ? $context_data['urls'] : array();
Mage::log('EmailImages - loaded URLs from cache (cache ID: ' . $context_cache_id . ')');
}
return $urls;
}
/**
* Retrieve image URL from email body HTML
*
* @param string $bodyHtml Email body HTML to use.
* @return array Array of image URLs.
*
* @see getRegularExpression(), getRegularExpressionIndex()
*/
protected function _getImageUrlsFromBodyHtml( $bodyHtml )
{
$urls = array();
Mage::log('EmailImages - parsing HTML body');
if ( $bodyHtml )
{
// find image URLs in email HTML body
$regexp = $this->getRegularExpression();
if ( preg_match_all($regexp, $bodyHtml, $matches) )
{
$index = $this->getRegularExpressionIndex();
$urls = $matches[$index];
$urls = array_unique($urls);
}
if ( 0 == count($urls) ) // no URLs in HTML body?
Mage::log('EmailImages - no images found in email HTML body', Zend_Log::WARN);
}
else // otherwise no HTML body?
{
Mage::log('EmailImages - no HTML body for email', Zend_Log::WARN);
}
return $urls;
}
/**
* Retrieve cached context data.
*
* @param string $context_cache_id Cached context data cache ID.
* @return array Array containing keys 'isHtml' to indicate a HTML body, 'urls' for image URLs from that HTML body.
*
* @see Mage_Core_Model_Cache::load()
*/
protected function _getContextDataFromCache( $context_cache_id )
{
/** @var $cache Mage_Core_Model_Cache */
$cache = Mage::getSingleton('core/cache');
$context_data = $cache->load($context_cache_id);
if ( $context_data )
$context_data = unserialize($context_data);
Mage::log(__CLASS__ . '::' . __FUNCTION__ . '(): context_data=' . print_r($context_data, 1));
return $context_data;
}
/**
* Retrieve HTML body from mail object.
*
* @param Zend_Mail $mail Mail object instance to use.
* @return string|false HTML body from the mail object instance, if any;
* false, otherwise.
*
* @see Zend_Mail::getBodyHtml(), Zend_Mime_Part::getContent()
*/
protected function _getBodyHtml( Zend_Mail $mail )
{
$bodyHtmlObject = $mail->getBodyHtml();
if ( $bodyHtmlObject instanceof Zend_Mime_Part )
{
$bodyHtml = $bodyHtmlObject->getContent();
if ( $bodyHtmlObject->encoding == Zend_Mime::ENCODING_QUOTEDPRINTABLE )
$bodyHtml = quoted_printable_decode($bodyHtml);
}
else
{
$bodyHtml = $bodyHtmlObject;
}
if ( !is_string($bodyHtml) )
{
Mage::log(__CLASS__ . '::' . __FUNCTION__ . '(): unsupported bodyHtml = ' . substr(var_export($bodyHtml, 1), 0, 128) . '...');
$bodyHtml = false;
}
Mage::log(__CLASS__ . '::' . __FUNCTION__ . '(): bodyHtml = ' . ($bodyHtml ? preg_replace('/[\s\t\r\n\k]+/', ' ', substr($bodyHtml, 0, 128)) : '<empty>'));
return $bodyHtml;
}
/**
* Save context data to cache.
*
* @param string $context_cache_id Cache ID to use.
* @param boolean $isHtml Set to true if email body is HTML.
* @param array $urls Array of image URLs from HTML body.
* @return void
*
* @see CACHE_TAG,
* getCacheTime(),
* Mage_Core_Model_Cache::save()
*/
protected function _saveContextDataToCache( $context_cache_id, $isHtml, $urls )
{
/** @var $cache Mage_Core_Model_Cache */
$cache = Mage::getSingleton('core/cache');
$cache_time = $this->getCacheTime();
$context_data = array(
'urls' => $urls,
'is_html' => $isHtml,
);
$context_data = serialize($context_data);
$cache->save($context_data, $context_cache_id, array( self::CACHE_TAG ), $cache_time);
Mage::log('EmailImages - saved context URLs to cache');
}
/**
* Attach image URLs to the email.
*
* @param Zend_Mail $mail Zend_Mail instance to attach images to.
* @param array $urls Array of image URLs to attach.
* @return void
*
* @see _retrieveImageData(),
* Zend_Mime::MULTIPART_RELATED,
* Zend_Mail::createAttachment(), Zend_Mail::setType(),
* Zend_Mime_Part
*/
protected function _attachImageUrls( Zend_Mail $mail, array $urls )
{
foreach ( $urls as $index => $url )
{
if ( $url )
{
// retrieved image data
$data = $this->_retrieveImageData($url);
if ( $data )
{
// attach image data
$mp = $mail->createAttachment($data['image']
, $data['size']['mime']
, Zend_Mime::DISPOSITION_INLINE
, Zend_Mime::ENCODING_BASE64, basename($url)
);
$mp->id = md5($url);
$mp->location = $url;
}
else
{
Mage::log('EmailImages - unable to retrieve image from URL ' . $url, Zend_Log::WARN);
// remove images that failed to load
UnSet($urls[$index]);
}
}
}
// set Content-Type to multipart/related to properly display the images inline, if any
if ( 0 < count($urls) )
$mail->setType(Zend_Mime::MULTIPART_RELATED);
}
/**
* Retrieve image data from URL.
* NB: uses file_get_contents().
*
* @TODO should we try to use curl if available? should that be configurable by admin?
*
* @param string $url URL to retrieve image data from.
* @return array|false Array with keys 'image' for image binary, 'size' for image size, if successfully retrieved image from URL;
* false, otherwise.
*
* @see CACHE_TAG, CACHE_TYPE,
* getCacheTime(),
* Mage_Core_Model_Cache::canUse(), Mage_Core_Model_Cache::load(), Mage_Core_Model_Cache::save(),
* file_get_contents()
*/
protected function _retrieveImageData( $url )
{
// retrieve image from cache or URL
/** @var $cache Mage_Core_Model_Cache */
$cache = Mage::getSingleton('core/cache');
$use_cache = $cache->canUse(self::CACHE_TYPE);
$data = false;
if ( $use_cache )
{
$cache_id = self::CACHE_TYPE . $url;
$data = $cache->load(self::CACHE_TYPE . $url);
if ( $data )
$data = unserialize($data);
}
if ( !$data )
{
// retrieve image data from URL
Mage::log('EmailImages - loading image from URL ' . $url);
$data = file_get_contents($url);
if ( $data )
{
$data = array(
'image' => $data,
'size' => getimagesize($url),
);
}
// save retrieved image data to cache even if retrieving the image failed, if allowed
if ( $use_cache )
{
$serialized_data = serialize($data);
$cache_time = $this->getCacheTime();
$cache->save($serialized_data, $cache_id, array( self::CACHE_TAG ), $cache_time);
Mage::log('EmailImages - saved image to cache');
}
}
else
{
Mage::log('EmailImages - loaded image from cache');
}
return $data;
}
}