aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/config_default.inc.php69
-rw-r--r--include/emogrifier.class.php404
-rw-r--r--include/functions_mail.inc.php472
3 files changed, 646 insertions, 299 deletions
diff --git a/include/config_default.inc.php b/include/config_default.inc.php
index 9b97170a9..7e6b65cf3 100644
--- a/include/config_default.inc.php
+++ b/include/config_default.inc.php
@@ -229,42 +229,6 @@ $conf['users_page'] = 20;
// image level permissions available in the admin interface
$conf['available_permission_levels'] = array(0,1,2,4,8);
-// mail_options: only set it true if you have a send mail warning with
-// "options" parameter missing on mail() function execution.
-$conf['mail_options'] = false;
-
-// send_bcc_mail_webmaster: send bcc mail to webmaster. Set true for debug
-// or test.
-$conf['send_bcc_mail_webmaster'] = false;
-
-// default_email_format:
-// Define the default email format use to send email
-// Value could be text/plain or text/html
-$conf['default_email_format'] = 'text/html';
-
-// alternative_email_format:
-// Define the alternative email format use to send email
-// Value could be text/plain or text/html
-$conf['alternative_email_format'] = 'text/plain';
-
-// define the name of sender mail:
-// If value is empty, gallery title is used
-$conf['mail_sender_name'] = '';
-
-// smtp configuration
-// (work if fsockopen function is allowed for smtp port)
-// smtp_host: smtp server host
-// if null, regular mail function is used
-// format: hoststring[:port]
-// exemple: smtp.pwg.net:21
-// smtp_user/smtp_password: user & password for smtp identication
-$conf['smtp_host'] = '';
-$conf['smtp_user'] = '';
-$conf['smtp_password'] = '';
-
-// 'ssl' or 'tls'
-$conf['smtp_secure'] = null;
-
// check_upgrade_feed: check if there are database upgrade required. Set to
// true, a message will strongly encourage you to upgrade your database if
// needed.
@@ -307,6 +271,39 @@ $conf['ext_imagick_dir'] = '';
$conf['comments_page_nb_comments'] = 10;
// +-----------------------------------------------------------------------+
+// | email |
+// +-----------------------------------------------------------------------+
+
+// send_bcc_mail_webmaster: send bcc mail to webmaster. Set true for debug
+// or test.
+$conf['send_bcc_mail_webmaster'] = false;
+
+// define the name of sender mail: if value is empty, gallery title is used
+$conf['mail_sender_name'] = '';
+
+// define the email of sender mail: if valie is empty, webmaster email is used
+$conf['mail_sender_email'] = '';
+
+// set true to allow text/html emails
+$conf['allow_html_email'] = true;
+
+// 'clear' or 'dark'
+$conf['mail_theme'] = 'clear';
+
+// smtp configuration (work if fsockopen function is allowed for smtp port)
+// smtp_host: smtp server host
+// if null, regular mail function is used
+// format: hoststring[:port]
+// exemple: smtp.pwg.net:21
+// smtp_user/smtp_password: user & password for smtp identication
+$conf['smtp_host'] = '';
+$conf['smtp_user'] = '';
+$conf['smtp_password'] = '';
+
+// 'ssl' or 'tls'
+$conf['smtp_secure'] = null;
+
+// +-----------------------------------------------------------------------+
// | metadata |
// +-----------------------------------------------------------------------+
diff --git a/include/emogrifier.class.php b/include/emogrifier.class.php
new file mode 100644
index 000000000..ef2c49730
--- /dev/null
+++ b/include/emogrifier.class.php
@@ -0,0 +1,404 @@
+<?php
+/*
+UPDATES
+
+ 2008-08-10 Fixed CSS comment stripping regex to add PCRE_DOTALL (changed from '/\/\*.*\*\//U' to '/\/\*.*\*\//sU')
+ 2008-08-18 Added lines instructing DOMDocument to attempt to normalize HTML before processing
+ 2008-10-20 Fixed bug with bad variable name... Thanks Thomas!
+ 2008-03-02 Added licensing terms under the MIT License
+ Only remove unprocessable HTML tags if they exist in the array
+ 2009-06-03 Normalize existing CSS (style) attributes in the HTML before we process the CSS.
+ Made it so that the display:none stripper doesn't require a trailing semi-colon.
+ 2009-08-13 Added support for subset class values (e.g. "p.class1.class2").
+ Added better protection for bad css attributes.
+ Fixed support for HTML entities.
+ 2009-08-17 Fixed CSS selector processing so that selectors are processed by precedence/specificity, and not just in order.
+ 2009-10-29 Fixed so that selectors appearing later in the CSS will have precedence over identical selectors appearing earlier.
+ 2009-11-04 Explicitly declared static functions static to get rid of E_STRICT notices.
+ 2010-05-18 Fixed bug where full url filenames with protocols wouldn't get split improperly when we explode on ':'... Thanks Mark!
+ Added two new attribute selectors
+ 2010-06-16 Added static caching for less processing overhead in situations where multiple emogrification takes place
+ 2010-07-26 Fixed bug where '0' values were getting discarded because of php's empty() function... Thanks Scott!
+ 2010-09-03 Added checks to invisible node removal to ensure that we don't try to remove non-existent child nodes of parents that have already been deleted
+ 2011-04-08 Fixed errors in CSS->XPath conversion for adjacent sibling selectors and id/class combinations... Thanks Bob V.!
+ 2011-06-08 Fixed an error where CSS @media types weren't being parsed correctly... Thanks Will W.!
+ 2011-08-03 Fixed an error where an empty selector at the beginning of the CSS would cause a parse error on the next selector... Thanks Alexei T.!
+ 2011-10-13 Fully fixed a bug introduced in 2011-06-08 where selectors at the beginning of the CSS would be parsed incorrectly... Thanks Thomas A.!
+ 2011-10-26 Added an option to allow you to output emogrified code without extended characters being turned into HTML entities.
+ Moved static references to class attributes so they can be manipulated.
+ Added the ability to clear out the (formerly) static cache when CSS is reloaded.
+ 2011-12-22 Fixed a bug that was overwriting existing inline styles from the original HTML... Thanks Sagi L.!
+ 2012-01-31 Fixed a bug that was introduced with the 2011-12-22 revision... Thanks Sagi L. and M. BÄ…kowski!
+ Added extraction of <style> blocks within the HTML due to popular demand.
+ Added several new pseudo-selectors (first-child, last-child, nth-child, and nth-of-type).
+ 2012-02-07 Fixed some recent code introductions to use class constants rather than global constants.
+ Fixed some recent code introductions to make it cleaner to read.
+ 2012-05-01 Made removal of invisible nodes operate in a case-insensitive manner... Thanks Juha P.!
+ 2013-10-10 Add preserveStyleTag option
+*/
+
+define('CACHE_CSS', 0);
+define('CACHE_SELECTOR', 1);
+define('CACHE_XPATH', 2);
+
+class Emogrifier {
+
+ // for calculating nth-of-type and nth-child selectors
+ const INDEX = 0;
+ const MULTIPLIER = 1;
+
+ private $html = '';
+ private $css = '';
+ private $unprocessableHTMLTags = array('wbr');
+ private $caches = array();
+
+ // this attribute applies to the case where you want to preserve your original text encoding.
+ // by default, emogrifier translates your text into HTML entities for two reasons:
+ // 1. because of client incompatibilities, it is better practice to send out HTML entities rather than unicode over email
+ // 2. it translates any illegal XML characters that DOMDocument cannot work with
+ // if you would like to preserve your original encoding, set this attribute to true.
+ public $preserveEncoding = false;
+
+ // by default, emogrifier removes <style> tags, set preserveStyleTag to true to keep them
+ public $preserveStyleTag = false;
+
+ public function __construct($html = '', $css = '') {
+ $this->html = $html;
+ $this->css = $css;
+ $this->clearCache();
+ }
+
+ public function setHTML($html = '') { $this->html = $html; }
+ public function setCSS($css = '') {
+ $this->css = $css;
+ $this->clearCache(CACHE_CSS);
+ }
+
+ public function clearCache($key = null) {
+ if (!is_null($key)) {
+ if (isset($this->caches[$key])) $this->caches[$key] = array();
+ } else {
+ $this->caches = array(
+ CACHE_CSS => array(),
+ CACHE_SELECTOR => array(),
+ CACHE_XPATH => array(),
+ );
+ }
+ }
+
+ // there are some HTML tags that DOMDocument cannot process, and will throw an error if it encounters them.
+ // in particular, DOMDocument will complain if you try to use HTML5 tags in an XHTML document.
+ // these functions allow you to add/remove them if necessary.
+ // it only strips them from the code (does not remove actual nodes).
+ public function addUnprocessableHTMLTag($tag) { $this->unprocessableHTMLTags[] = $tag; }
+ public function removeUnprocessableHTMLTag($tag) {
+ if (($key = array_search($tag,$this->unprocessableHTMLTags)) !== false)
+ unset($this->unprocessableHTMLTags[$key]);
+ }
+
+ // applies the CSS you submit to the html you submit. places the css inline
+ public function emogrify() {
+ $body = $this->html;
+
+ // remove any unprocessable HTML tags (tags that DOMDocument cannot parse; this includes wbr and many new HTML5 tags)
+ if (count($this->unprocessableHTMLTags)) {
+ $unprocessableHTMLTags = implode('|',$this->unprocessableHTMLTags);
+ $body = preg_replace("/<\/?($unprocessableHTMLTags)[^>]*>/i",'',$body);
+ }
+
+ $encoding = mb_detect_encoding($body);
+ $body = mb_convert_encoding($body, 'HTML-ENTITIES', $encoding);
+
+ $xmldoc = new DOMDocument;
+ $xmldoc->encoding = $encoding;
+ $xmldoc->strictErrorChecking = false;
+ $xmldoc->formatOutput = true;
+ $xmldoc->loadHTML($body);
+ $xmldoc->normalizeDocument();
+
+ $xpath = new DOMXPath($xmldoc);
+
+ // before be begin processing the CSS file, parse the document and normalize all existing CSS attributes (changes 'DISPLAY: none' to 'display: none');
+ // we wouldn't have to do this if DOMXPath supported XPath 2.0.
+ // also store a reference of nodes with existing inline styles so we don't overwrite them
+ $vistedNodes = $vistedNodeRef = array();
+ $nodes = @$xpath->query('//*[@style]');
+ foreach ($nodes as $node) {
+ $normalizedOrigStyle = preg_replace('/[A-z\-]+(?=\:)/Se',"strtolower('\\0')", $node->getAttribute('style'));
+
+ // in order to not overwrite existing style attributes in the HTML, we have to save the original HTML styles
+ $nodeKey = md5($node->getNodePath());
+ if (!isset($vistedNodeRef[$nodeKey])) {
+ $vistedNodeRef[$nodeKey] = $this->cssStyleDefinitionToArray($normalizedOrigStyle);
+ $vistedNodes[$nodeKey] = $node;
+ }
+
+ $node->setAttribute('style', $normalizedOrigStyle);
+ }
+
+ // grab any existing style blocks from the html and append them to the existing CSS
+ // (these blocks should be appended so as to have precedence over conflicting styles in the existing CSS)
+ $css = $this->css;
+ $nodes = @$xpath->query('//style');
+ foreach ($nodes as $node) {
+ // append the css
+ $css .= "\n\n{$node->nodeValue}";
+ // remove the <style> node
+ if (!$this->preserveStyleTag) {
+ $node->parentNode->removeChild($node);
+ }
+ }
+
+ // filter the CSS
+ $search = array(
+ '/\/\*.*\*\//sU', // get rid of css comment code
+ '/^\s*@import\s[^;]+;/misU', // strip out any import directives
+ '/^\s*@media\s[^{]+{\s*}/misU', // strip any empty media enclosures
+ '/^\s*@media\s+((aural|braille|embossed|handheld|print|projection|speech|tty|tv)\s*,*\s*)+{.*}\s*}/misU', // strip out all media types that are not 'screen' or 'all' (these don't apply to email)
+ '/^\s*@media\s[^{]+{(.*})\s*}/misU', // get rid of remaining media type enclosures
+ );
+
+ $replace = array(
+ '',
+ '',
+ '',
+ '',
+ '\\1',
+ );
+
+ $css = preg_replace($search, $replace, $css);
+
+ $csskey = md5($css);
+ if (!isset($this->caches[CACHE_CSS][$csskey])) {
+
+ // process the CSS file for selectors and definitions
+ preg_match_all('/(^|[^{}])\s*([^{]+){([^}]*)}/mis', $css, $matches, PREG_SET_ORDER);
+
+ $all_selectors = array();
+ foreach ($matches as $key => $selectorString) {
+ // if there is a blank definition, skip
+ if (!strlen(trim($selectorString[3]))) continue;
+
+ // else split by commas and duplicate attributes so we can sort by selector precedence
+ $selectors = explode(',',$selectorString[2]);
+ foreach ($selectors as $selector) {
+
+ // don't process pseudo-elements and behavioral (dynamic) pseudo-classes; ONLY allow structural pseudo-classes
+ if (strpos($selector, ':') !== false && !preg_match('/:\S+\-(child|type)\(/i', $selector)) continue;
+
+ $all_selectors[] = array('selector' => trim($selector),
+ 'attributes' => trim($selectorString[3]),
+ 'line' => $key, // keep track of where it appears in the file, since order is important
+ );
+ }
+ }
+
+ // now sort the selectors by precedence
+ usort($all_selectors, array($this,'sortBySelectorPrecedence'));
+
+ $this->caches[CACHE_CSS][$csskey] = $all_selectors;
+ }
+
+ foreach ($this->caches[CACHE_CSS][$csskey] as $value) {
+
+ // query the body for the xpath selector
+ $nodes = $xpath->query($this->translateCSStoXpath(trim($value['selector'])));
+
+ foreach($nodes as $node) {
+ // if it has a style attribute, get it, process it, and append (overwrite) new stuff
+ if ($node->hasAttribute('style')) {
+ // break it up into an associative array
+ $oldStyleArr = $this->cssStyleDefinitionToArray($node->getAttribute('style'));
+ $newStyleArr = $this->cssStyleDefinitionToArray($value['attributes']);
+
+ // new styles overwrite the old styles (not technically accurate, but close enough)
+ $combinedArr = array_merge($oldStyleArr,$newStyleArr);
+ $style = '';
+ foreach ($combinedArr as $k => $v) $style .= (strtolower($k) . ':' . $v . ';');
+ } else {
+ // otherwise create a new style
+ $style = trim($value['attributes']);
+ }
+ $node->setAttribute('style', $style);
+ }
+ }
+
+ // now iterate through the nodes that contained inline styles in the original HTML
+ foreach ($vistedNodeRef as $nodeKey => $origStyleArr) {
+ $node = $vistedNodes[$nodeKey];
+ $currStyleArr = $this->cssStyleDefinitionToArray($node->getAttribute('style'));
+
+ $combinedArr = array_merge($currStyleArr, $origStyleArr);
+ $style = '';
+ foreach ($combinedArr as $k => $v) $style .= (strtolower($k) . ':' . $v . ';');
+
+ $node->setAttribute('style', $style);
+ }
+
+ // This removes styles from your email that contain display:none.
+ // We need to look for display:none, but we need to do a case-insensitive search. Since DOMDocument only supports XPath 1.0,
+ // lower-case() isn't available to us. We've thus far only set attributes to lowercase, not attribute values. Consequently, we need
+ // to translate() the letters that would be in 'NONE' ("NOE") to lowercase.
+ $nodes = $xpath->query('//*[contains(translate(translate(@style," ",""),"NOE","noe"),"display:none")]');
+ // The checks on parentNode and is_callable below ensure that if we've deleted the parent node,
+ // we don't try to call removeChild on a nonexistent child node
+ if ($nodes->length > 0)
+ foreach ($nodes as $node)
+ if ($node->parentNode && is_callable(array($node->parentNode,'removeChild')))
+ $node->parentNode->removeChild($node);
+
+ if ($this->preserveEncoding) {
+ return mb_convert_encoding($xmldoc->saveHTML(), $encoding, 'HTML-ENTITIES');
+ } else {
+ return $xmldoc->saveHTML();
+ }
+ }
+
+ private function sortBySelectorPrecedence($a, $b) {
+ $precedenceA = $this->getCSSSelectorPrecedence($a['selector']);
+ $precedenceB = $this->getCSSSelectorPrecedence($b['selector']);
+
+ // we want these sorted ascendingly so selectors with lesser precedence get processed first and
+ // selectors with greater precedence get sorted last
+ return ($precedenceA == $precedenceB) ? ($a['line'] < $b['line'] ? -1 : 1) : ($precedenceA < $precedenceB ? -1 : 1);
+ }
+
+ private function getCSSSelectorPrecedence($selector) {
+ $selectorkey = md5($selector);
+ if (!isset($this->caches[CACHE_SELECTOR][$selectorkey])) {
+ $precedence = 0;
+ $value = 100;
+ $search = array('\#','\.',''); // ids: worth 100, classes: worth 10, elements: worth 1
+
+ foreach ($search as $s) {
+ if (trim($selector == '')) break;
+ $num = 0;
+ $selector = preg_replace('/'.$s.'\w+/','',$selector,-1,$num);
+ $precedence += ($value * $num);
+ $value /= 10;
+ }
+ $this->caches[CACHE_SELECTOR][$selectorkey] = $precedence;
+ }
+
+ return $this->caches[CACHE_SELECTOR][$selectorkey];
+ }
+
+ // right now we support all CSS 1 selectors and most CSS2/3 selectors.
+ // http://plasmasturm.org/log/444/
+ private function translateCSStoXpath($css_selector) {
+
+ $css_selector = trim($css_selector);
+ $xpathkey = md5($css_selector);
+ if (!isset($this->caches[CACHE_XPATH][$xpathkey])) {
+ // returns an Xpath selector
+ $search = array(
+ '/\s+>\s+/', // Matches any element that is a child of parent.
+ '/\s+\+\s+/', // Matches any element that is an adjacent sibling.
+ '/\s+/', // Matches any element that is a descendant of an parent element element.
+ '/([^\/]+):first-child/i', // first-child pseudo-selector
+ '/([^\/]+):last-child/i', // last-child pseudo-selector
+ '/(\w)\[(\w+)\]/', // Matches element with attribute
+ '/(\w)\[(\w+)\=[\'"]?(\w+)[\'"]?\]/', // Matches element with EXACT attribute
+ '/(\w+)?\#([\w\-]+)/e', // Matches id attributes
+ '/(\w+|[\*\]])?((\.[\w\-]+)+)/e', // Matches class attributes
+
+ );
+ $replace = array(
+ '/',
+ '/following-sibling::*[1]/self::',
+ '//',
+ '*[1]/self::\\1',
+ '*[last()]/self::\\1',
+ '\\1[@\\2]',
+ '\\1[@\\2="\\3"]',
+ "(strlen('\\1') ? '\\1' : '*').'[@id=\"\\2\"]'",
+ "(strlen('\\1') ? '\\1' : '*').'[contains(concat(\" \",@class,\" \"),concat(\" \",\"'.implode('\",\" \"))][contains(concat(\" \",@class,\" \"),concat(\" \",\"',explode('.',substr('\\2',1))).'\",\" \"))]'",
+ );
+
+ $css_selector = '//'.preg_replace($search, $replace, $css_selector);
+
+ // advanced selectors are going to require a bit more advanced emogrification
+ // if we required PHP 5.3 we could do this with closures
+ $css_selector = preg_replace_callback('/([^\/]+):nth-child\(\s*(odd|even|[+\-]?\d|[+\-]?\d?n(\s*[+\-]\s*\d)?)\s*\)/i', array($this, 'translateNthChild'), $css_selector);
+ $css_selector = preg_replace_callback('/([^\/]+):nth-of-type\(\s*(odd|even|[+\-]?\d|[+\-]?\d?n(\s*[+\-]\s*\d)?)\s*\)/i', array($this, 'translateNthOfType'), $css_selector);
+
+ $this->caches[CACHE_SELECTOR][$xpathkey] = $css_selector;
+ }
+ return $this->caches[CACHE_SELECTOR][$xpathkey];
+ }
+
+ private function translateNthChild($match) {
+
+ $result = $this->parseNth($match);
+
+ if (isset($result[self::MULTIPLIER])) {
+ if ($result[self::MULTIPLIER] < 0) {
+ $result[self::MULTIPLIER] = abs($result[self::MULTIPLIER]);
+ return sprintf("*[(last() - position()) mod %u = %u]/self::%s", $result[self::MULTIPLIER], $result[self::INDEX], $match[1]);
+ } else {
+ return sprintf("*[position() mod %u = %u]/self::%s", $result[self::MULTIPLIER], $result[self::INDEX], $match[1]);
+ }
+ } else {
+ return sprintf("*[%u]/self::%s", $result[self::INDEX], $match[1]);
+ }
+ }
+
+ private function translateNthOfType($match) {
+
+ $result = $this->parseNth($match);
+
+ if (isset($result[self::MULTIPLIER])) {
+ if ($result[self::MULTIPLIER] < 0) {
+ $result[self::MULTIPLIER] = abs($result[self::MULTIPLIER]);
+ return sprintf("%s[(last() - position()) mod %u = %u]", $match[1], $result[self::MULTIPLIER], $result[self::INDEX]);
+ } else {
+ return sprintf("%s[position() mod %u = %u]", $match[1], $result[self::MULTIPLIER], $result[self::INDEX]);
+ }
+ } else {
+ return sprintf("%s[%u]", $match[1], $result[self::INDEX]);
+ }
+ }
+
+ private function parseNth($match) {
+
+ if (in_array(strtolower($match[2]), array('even','odd'))) {
+ $index = strtolower($match[2]) == 'even' ? 0 : 1;
+ return array(self::MULTIPLIER => 2, self::INDEX => $index);
+ // if there is a multiplier
+ } else if (stripos($match[2], 'n') === false) {
+ $index = intval(str_replace(' ', '', $match[2]));
+ return array(self::INDEX => $index);
+ } else {
+
+ if (isset($match[3])) {
+ $multiple_term = str_replace($match[3], '', $match[2]);
+ $index = intval(str_replace(' ', '', $match[3]));
+ } else {
+ $multiple_term = $match[2];
+ $index = 0;
+ }
+
+ $multiplier = str_ireplace('n', '', $multiple_term);
+
+ if (!strlen($multiplier)) $multiplier = 1;
+ elseif ($multiplier == 0) return array(self::INDEX => $index);
+ else $multiplier = intval($multiplier);
+
+ while ($index < 0) $index += abs($multiplier);
+
+ return array(self::MULTIPLIER => $multiplier, self::INDEX => $index);
+ }
+ }
+
+ private function cssStyleDefinitionToArray($style) {
+ $definitions = explode(';',$style);
+ $retArr = array();
+ foreach ($definitions as $def) {
+ if (empty($def) || strpos($def, ':') === false) continue;
+ list($key,$value) = explode(':',$def,2);
+ if (empty($key) || strlen(trim($value)) === 0) continue;
+ $retArr[trim($key)] = trim($value);
+ }
+ return $retArr;
+ }
+} \ No newline at end of file
diff --git a/include/functions_mail.inc.php b/include/functions_mail.inc.php
index b67f46d3c..57ac3ef86 100644
--- a/include/functions_mail.inc.php
+++ b/include/functions_mail.inc.php
@@ -21,13 +21,8 @@
// | USA. |
// +-----------------------------------------------------------------------+
-// +-----------------------------------------------------------------------+
-// | functions |
-// +-----------------------------------------------------------------------+
-
-/*
- * Returns the name of the mail sender :
- *
+/**
+ * Returns the name of the mail sender
* @return string
*/
function get_mail_sender_name()
@@ -37,20 +32,29 @@ function get_mail_sender_name()
return (empty($conf['mail_sender_name']) ? $conf['gallery_title'] : $conf['mail_sender_name']);
}
-/*
+/**
+ * Returns the email of the mail sender
+ * @since 2.6
+ * @return string
+ */
+function get_mail_sender_email()
+{
+ global $conf;
+
+ return (empty($conf['mail_sender_email']) ? get_webmaster_mail_address() : $conf['mail_sender_email']);
+}
+
+/**
* Returns an array of mail configuration parameters :
- *
- * - mail_options
* - send_bcc_mail_webmaster
- * - default_email_format
- * - alternative_email_format
+ * - allow_html_email
* - use_smtp
* - smtp_host
* - smtp_user
* - smtp_password
- * - boundary_key
+ * - smtp_secure
* - email_webmaster
- * - formated_email_webmaster
+ * - name_webmaster
*
* @return array
*/
@@ -59,35 +63,28 @@ function get_mail_configuration()
global $conf;
$conf_mail = array(
- 'mail_options' => $conf['mail_options'],
'send_bcc_mail_webmaster' => $conf['send_bcc_mail_webmaster'],
- 'default_email_format' => $conf['default_email_format'],
- 'alternative_email_format' => $conf['alternative_email_format'],
+ 'allow_html_email' => $conf['allow_html_email'],
+ 'mail_theme' => $conf['mail_theme'],
'use_smtp' => !empty($conf['smtp_host']),
'smtp_host' => $conf['smtp_host'],
'smtp_user' => $conf['smtp_user'],
'smtp_password' => $conf['smtp_password'],
'smtp_secure' => $conf['smtp_secure'],
+ 'email_webmaster' => get_mail_sender_email(),
+ 'name_webmaster' => get_mail_sender_name(),
);
- // we have webmaster id among user list, what's his email address ?
- $conf_mail['email_webmaster'] = get_webmaster_mail_address();
-
- // name of the webmaster is the title of the gallery
- $conf_mail['formated_email_webmaster'] = format_email(get_mail_sender_name(), $conf_mail['email_webmaster']);
-
return $conf_mail;
}
/**
* Returns an email address with an associated real name
- *
* @param string name
* @param string email
*/
function format_email($name, $email)
{
- // Spring cleaning
$cvt_email = trim(preg_replace('#[\n\r]+#s', '', $email));
$cvt_name = trim(preg_replace('#[\n\r]+#s', '', $name));
@@ -107,14 +104,45 @@ function format_email($name, $email)
}
/**
+ * Returns the mail and the name from a formatted address
+ * @since 2.6
+ * @param string|array $input
+ * @return array
+ */
+function unformat_email($input)
+{
+ if (is_array($input))
+ {
+ return $input;
+ }
+
+ if (preg_match('/(.*)<(.*)>.*/', $input, $matches))
+ {
+ return array(
+ 'email' => trim($matches[2]),
+ 'name' => trim($matches[1]),
+ );
+ }
+ else
+ {
+ return array(
+ 'email' => trim($input),
+ 'name' => '',
+ );
+ }
+}
+
+
+/**
* Returns an email address list with minimal email string
- *
- * @param string with email list (email separated by comma)
+ * @param string $email_list - comma separated
+ * @return string
*/
function get_strict_email_list($email_list)
{
$result = array();
$list = explode(',', $email_list);
+
foreach ($list as $email)
{
if (strpos($email, '<') !== false)
@@ -130,37 +158,19 @@ function get_strict_email_list($email_list)
/**
* Return an new mail template
- *
- * @param string email_format: mail format, text/html or text/plain
- * @param string theme: theme to use [default get_default_theme()]
+ * @param string $email_format - text/html or text/plain
+ * @return Template
*/
-function & get_mail_template($email_format, $theme='')
+function &get_mail_template($email_format)
{
- if (empty($theme))
- {
- $theme = get_default_theme();
- }
-
- $mail_template = new Template(PHPWG_ROOT_PATH.'themes', $theme, 'template/mail/'.$email_format);
-
- return $mail_template;
+ $template = new Template(PHPWG_ROOT_PATH.'themes', 'default', 'template/mail/'.$email_format);
+ return $template;
}
/**
- * Return string email format (text/html or text/plain)
- *
- * @param string format
- */
-function get_str_email_format($is_html)
-{
- return ($is_html ? 'text/html' : 'text/plain');
-}
-
-/*
- * Switch language to param language
+ * Switch language to specified language
* All entries are push on language stack
- *
- * @param string language
+ * @param string $language
*/
function switch_lang_to($language)
{
@@ -197,6 +207,7 @@ function switch_lang_to($language)
// Translations are in admin file too
load_language('admin.lang', '', array('language'=>$language) );
+ // Reload all plugins files (see load_language declaration)
if (!empty($language_files))
{
foreach ($language_files as $dirname => $files)
@@ -219,10 +230,8 @@ function switch_lang_to($language)
}
}
-/*
+/**
* Switch back language pushed with switch_lang_to function
- *
- * @param: none
*/
function switch_lang_back()
{
@@ -456,12 +465,11 @@ WHERE
return $return;
}
-/*
+/**
* sends an email, using Piwigo specific informations
*
- * @param:
- * - to: receiver(s) of the mail (list separated by comma).
- * - args: function params of mail function:
+ * @param string|string[] $to
+ * @param array $args
* o from: sender [default value webmaster email]
* o Cc: array of carbon copy receivers of the mail. [default value empty]
* o Bcc: array of blind carbon copy receivers of the mail. [default value empty]
@@ -469,9 +477,11 @@ WHERE
* o content: content of mail [default value '']
* o content_format: format of mail content [default value 'text/plain']
* o email_format: global mail format [default value $conf_mail['default_email_format']]
- * o theme: template to use [default get_default_theme()]
+ * o theme: theme to use [default value $conf_mail['mail_theme']]
+ * o mail_title: main title of the mail [default value $conf['gallery_title']]
+ * o mail_subtitle: subtitle of the mail [default value subject]
*
- * @return boolean (Ok or not)
+ * @return boolean
*/
function pwg_mail($to, $args = array())
{
@@ -491,11 +501,13 @@ function pwg_mail($to, $args = array())
$mail = new PHPMailer;
- foreach (explode(',', get_strict_email_list($to)) as $recipient)
+ $recipients = !is_array($to) ? explode(',', $to) : $to;
+ foreach ($recipients as $recipient)
{
- $mail->addAddress($recipient);
+ $recipient = unformat_email($recipient);
+ $mail->addAddress($recipient['email'], $recipient['name']);
}
-
+
$mail->WordWrap = 76;
$mail->CharSet = 'UTF-8';
@@ -504,86 +516,97 @@ function pwg_mail($to, $args = array())
if (empty($args['from']))
{
- $mail->From = get_webmaster_mail_address();
- $mail->FromName = get_mail_sender_name();
-
- $mail->addReplyTo(get_webmaster_mail_address(), get_mail_sender_name());
+ $from = array(
+ 'email' => $conf_mail['email_webmaster'],
+ 'name' => $conf_mail['name_webmaster'],
+ );
}
else
{
- $mail->From = $args['from'];
- $mail->addReplyTo($args['from']);
+ $from = unformat_email($args['from']);
}
+ $mail->setFrom($from['email'], $from['name']);
+ $mail->addReplyTo($from['email'], $from['name']);
// Subject
if (empty($args['subject']))
{
$args['subject'] = 'Piwigo';
}
-
$args['subject'] = trim(preg_replace('#[\n\r]+#s', '', $args['subject']));
-
$mail->Subject = $args['subject'];
// Cc
if (!empty($args['Cc']))
{
- $mail->addCC($args['Cc']);
+ foreach ($args['Cc'] as $cc)
+ {
+ $cc = unformat_email($cc);
+ $mail->addCC($cc['email'], $cc['name']);
+ }
}
// Bcc
if ($conf_mail['send_bcc_mail_webmaster'])
{
- $args['Bcc'][] = get_webmaster_mail_address();;
+ $args['Bcc'][] = get_webmaster_mail_address();
}
-
if (!empty($args['Bcc']))
{
foreach ($args['Bcc'] as $bcc)
{
- $mail->addBCC($bcc);
+ $bcc = unformat_email($bcc);
+ $mail->addBCC($bcc['email'], $bcc['name']);
}
}
- // content
- if (empty($args['email_format']))
+ // theme
+ if (empty($args['theme']) or !in_array($args['theme'], array('clear','dark')))
{
- $args['email_format'] = $conf_mail['default_email_format'];
+ $args['theme'] = $conf_mail['mail_theme'];
}
+ // content
if (!isset($args['content']))
{
$args['content'] = '';
}
-
- if (empty($args['content_format']))
+ if (!isset($args['mail_title']))
{
- $args['content_format'] = 'text/plain';
+ $args['mail_title'] = $conf['gallery_title'];
+ }
+ if (!isset($args['mail_subtitle']))
+ {
+ $args['mail_subtitle'] = $args['subject'];
}
- if (empty($args['theme']))
+ // content type
+ if (empty($args['content_format']))
{
- $args['theme'] = get_default_theme();
+ $args['content_format'] = 'text/plain';
}
- $content_type_list[] = $args['email_format'];
- if (!empty($conf_mail['alternative_email_format']))
+ $content_type_list = array();
+ if ($conf_mail['allow_html_email'] and @$args['email_format'] != 'text/plain')
{
- $content_type_list[] = $conf_mail['alternative_email_format'];
+ $content_type_list[] = 'text/html';
}
+ $content_type_list[] = 'text/plain';
$contents = array();
-
- foreach (array_unique($content_type_list) as $content_type)
+ foreach ($content_type_list as $content_type)
{
- // key compose of indexes witch allow ti cache mail data
- $cache_key = $content_type.'-'.$lang_info['code'].'-'.$args['theme'];
+ // key compose of indexes witch allow to cache mail data
+ $cache_key = $content_type.'-'.$lang_info['code'];
+ $cache_key.= '-'.crc32(@$args['mail_title'] . @$args['mail_subtitle']);
if (!isset($conf_mail[$cache_key]))
{
+ // instanciate a new Template
if (!isset($conf_mail[$cache_key]['theme']))
{
- $conf_mail[$cache_key]['theme'] = get_mail_template($content_type, $args['theme']);
+ $conf_mail[$cache_key]['theme'] = get_mail_template($content_type);
+ trigger_action('before_parse_mail_template', $cache_key, $content_type);
}
$conf_mail[$cache_key]['theme']->set_filename('mail_header', 'header.tpl');
@@ -594,9 +617,11 @@ function pwg_mail($to, $args = array())
'GALLERY_URL' => get_gallery_home_url(),
'GALLERY_TITLE' => isset($page['gallery_title']) ? $page['gallery_title'] : $conf['gallery_title'],
'VERSION' => $conf['show_version'] ? PHPWG_VERSION : '',
- 'PHPWG_URL' => PHPWG_URL,
- 'TITLE_MAIL' => urlencode(l10n('A comment on your site')),
- 'MAIL' => get_webmaster_mail_address()
+ 'PHPWG_URL' => defined('PHPWG_URL') ? PHPWG_URL : '',
+ 'CONTENT_ENCODING' => get_pwg_charset(),
+ 'CONTACT_MAIL' => $conf_mail['email_webmaster'],
+ 'MAIL_TITLE' => $args['mail_title'],
+ 'MAIL_SUBTITLE' => $args['mail_subtitle'],
)
);
@@ -608,18 +633,14 @@ function pwg_mail($to, $args = array())
$conf_mail[$cache_key]['theme']->assign_var_from_handle('GLOBAL_MAIL_CSS', 'css');
}
- $file = PHPWG_ROOT_PATH.'themes/'.$args['theme'].'/mail-css.tpl';
- if (is_file($file))
+ if ($conf_mail[$cache_key]['theme']->smarty->template_exists('mail-css-'. $args['theme'] .'.tpl'))
{
- $conf_mail[$cache_key]['theme']->set_filename('css', realpath($file));
+ $conf_mail[$cache_key]['theme']->set_filename('css', 'mail-css-'. $args['theme'] .'.tpl');
$conf_mail[$cache_key]['theme']->assign_var_from_handle('MAIL_CSS', 'css');
}
}
- // what are displayed on the header of each mail ?
$conf_mail[$cache_key]['header'] = $conf_mail[$cache_key]['theme']->parse('mail_header', true);
-
- // what are displayed on the footer of each mail ?
$conf_mail[$cache_key]['footer'] = $conf_mail[$cache_key]['theme']->parse('mail_footer', true);
}
@@ -627,16 +648,21 @@ function pwg_mail($to, $args = array())
$contents[$content_type] = $conf_mail[$cache_key]['header'];
// Content
- if (($args['content_format'] == 'text/plain') and ($content_type == 'text/html'))
+ if ($args['content_format'] == 'text/plain' and $content_type == 'text/html')
{
- $contents[$content_type].= '<p>'.
- nl2br(
- preg_replace("/(http:\/\/)([^\s,]*)/i",
- "<a href='$1$2' class='thumblnk'>$1$2</a>",
- htmlspecialchars($args['content']))).
- '</p>';
+ // convert plain text to html
+ $contents[$content_type].=
+ '<p>'.
+ nl2br(
+ preg_replace(
+ '/(https?:\/\/([-\w\.]+[-\w])+(:\d+)?(\/([\w\/_\.\#-]*(\?\S+)?[^\.\s])?)?)/i',
+ '<a href="$1">$1</a>',
+ htmlspecialchars($args['content'])
+ )
+ ).
+ '</p>';
}
- else if (($args['content_format'] == 'text/html') and ($content_type == 'text/plain'))
+ else if ($args['content_format'] == 'text/html' and $content_type == 'text/plain')
{
// convert html text to plain text
$contents[$content_type].= strip_tags($args['content']);
@@ -653,10 +679,11 @@ function pwg_mail($to, $args = array())
// Undo Compute root_path in order have complete path
unset_make_full_url();
+ // Send content to PHPMailer
if (isset($contents['text/html']))
{
- $mail->isHTML(true); // Set email format to HTML
- $mail->Body = $contents['text/html'];
+ $mail->isHTML(true);
+ $mail->Body = move_css_to_body($contents['text/html']);
if (isset($contents['text/plain']))
{
@@ -703,93 +730,34 @@ function pwg_mail($to, $args = array())
}
}
- $ret = $mail->send();
- if(!$ret)
+ $ret = true;
+ $pre_result = trigger_event('before_send_mail', true, $to, $args, $mail);
+
+ if ($pre_result)
{
- trigger_error( 'Mailer Error: ' . $mail->ErrorInfo, E_USER_WARNING);
+ $ret = $mail->send();
+ if (!$ret and is_admin())
+ {
+ trigger_error('Mailer Error: ' . $mail->ErrorInfo, E_USER_WARNING);
+ }
}
+
return $ret;
}
-/* DEPRECATED
- * pwg sendmail
- *
- * @param:
- * - result of other sendmail
- * - to: Receiver or receiver(s) of the mail.
- * - subject [default value 'Piwigo']
- * - content: content of mail
- * - headers: headers of mail
- *
- * @return boolean (Ok or not)
+/**
+ * @deprecated 2.6
*/
-function pwg_send_mail($result, $to, $subject, $contents, $headers)
+function pwg_send_mail($result, $to, $subject, $content, $headers)
{
+ trigger_error('pwg_send_mail function is deprecated', E_USER_NOTICE);
+
if (!$result)
{
- include_once(PHPWG_ROOT_PATH.'include/class.phpmailer.php');
-
- global $conf_mail;
-
- if ($conf_mail['use_smtp'])
- {
- include_once( PHPWG_ROOT_PATH.'include/class_smtp_mail.inc.php' );
- $smtp_mail = new smtp_mail(
- $conf_mail['smtp_host'], $conf_mail['smtp_user'], $conf_mail['smtp_password'],
- $conf_mail['email_webmaster']);
- return $smtp_mail->mail($to, $subject, $content, $headers);
- }
- else
- {
- $mail = new PHPMailer;
-
- $mail->From = 'plg@pigolabs.com';
- $mail->FromName = 'Pierrick en local';
- foreach (explode(',', $to) as $recipient)
- {
- $mail->addAddress($recipient); // Add a recipient
- }
- // $mail->addReplyTo('plg@piwigo.org', 'Pierrick de Piwigo.org');
- // $mail->addCC('cc@example.com');
- // $mail->addBCC('bcc@example.com');
-
- $mail->WordWrap = 76; // Set word wrap to 50 characters
-
- if (isset($contents['text/html']))
- {
- $mail->isHTML(true); // Set email format to HTML
- $mail->Body = $contents['text/html'];
-
- if (isset($contents['text/plain']))
- {
- $mail->AltBody = $contents['text/plain'];
- }
- }
- else
- {
- $mail->isHTML(false);
- $mail->Body = $contents['text/plain'];
- }
-
- $mail->CharSet = 'UTF-8';
- $mail->Subject = $subject;
-
- if(!$mail->send()) {
- echo 'Message could not be sent.';
- echo 'Mailer Error: ' . $mail->ErrorInfo;
- exit;
- }
-
- // if ($conf_mail['mail_options'])
- // {
- // $options = '-f '.$conf_mail['email_webmaster'];
- // return mail($to, $subject, $content, $headers, $options);
- // }
- // else
- // {
- // return mail($to, $subject, $content, $headers);
- // }
- }
+ return pwg_mail($to, array(
+ 'content' => $content,
+ 'subject' => $subject,
+ ));
}
else
{
@@ -797,95 +765,73 @@ function pwg_send_mail($result, $to, $subject, $contents, $headers)
}
}
+/**
+ * @deprecated 2.6
+ */
function move_ccs_rules_to_body($content)
{
- return $content;
- // We search all css rules in style tags
- preg_match('#<style>(.*?)</style>#s', $content, $matches);
-
- if (!empty($matches[1]))
- {
- preg_match_all('#([^\n]*?)\{(.*?)\}#s', $matches[1], $matches);
-
- $selectors = array();
- $unknow_selectors = '';
-
- foreach ($matches[1] as $key => $value)
- {
- $selects = explode(',', $value);
- $style = trim($matches[2][$key], ' ;');
+ trigger_error('move_ccs_rules_to_body function is deprecated, use move_css_to_body', E_USER_NOTICE);
+
+ return move_css_to_body($content);
+}
- foreach($selects as $select)
- {
- $select = trim($select);
- $selectors[$select][] = $style;
- }
- }
+/**
+ * Moves CSS rules contained in the <style> tag to inline CSS
+ * (for compatibility with Gmail and such clients)
+ * @since 2.6
+ * @param string $content
+ * @return string
+ */
+function move_css_to_body($content)
+{
+ include_once(PHPWG_ROOT_PATH.'include/emogrifier.class.php');
- foreach ($selectors as $selector => $style)
- {
- if (!preg_match('/^(#|\.|)([A-Za-z0-9_-]*)$/', $selector, $matches))
- {
- $unknow_selectors .= $selector.' {'.implode(";\n", $style).";}\n";
- }
- else switch ($matches[1])
- {
- case '#':
- $content = preg_replace('|id="'.$matches[2].'"|', 'id="'.$matches[2].'" style="'.implode(";\n", $style).";\"\n", $content);
- break;
- case '.':
- $content = preg_replace('|class="'.$matches[2].'"|', 'class="'.$matches[2].'" style="'.implode(";\n", $style).";\"\n", $content);
- break;
- default:
- $content = preg_replace('#<'.$matches[2].'( |>)#', '<'.$matches[2].' style="'.implode(";\n", $style).";\"\n$1", $content);
- break;
- }
- }
+ $e = new Emogrifier($content);
+ $e->preserveStyleTag = true;
+ return $e->emogrify();
+}
- // Keep unknow tags in page head
- if (!empty($unknow_selectors))
+/**
+ * Saves a copy of the mail if _data/tmp
+ * @param boolean $result
+ * @param string $to
+ * @param array $args
+ * @param PHPMailer $mail
+ * @return boolean $result
+ */
+function pwg_send_mail_test($result, $to, $args, $mail)
+{
+ global $conf, $user, $lang_info;
+
+ $dir = PHPWG_ROOT_PATH.$conf['data_location'].'tmp';
+ if (mkgetdir($dir, MKGETDIR_DEFAULT&~MKGETDIR_DIE_ON_ERROR))
+ {
+ $filename = $dir.'/mail.'.stripslashes($user['username']).'.'.$lang_info['code'].'.'.$args['theme'].'-'.date('YmdHis');
+ if ($args['content_format'] == 'text/plain')
{
- $content = preg_replace('#<style>.*?</style>#s', "<style type=\"text/css\">\n$unknow_selectors</style>", $content);
+ $filename .= '.txt';
}
else
{
- $content = preg_replace('#<style>.*?</style>#s', '', $content);
+ $filename .= '.html';
}
+
+ $file = fopen($filename, 'w+');
+ fwrite($file, implode(', ', $to) ."\n");
+ fwrite($file, $mail->Subject ."\n");
+ fwrite($file, $mail->createHeader() ."\n");
+ fwrite($file, $mail->createBody());
+ fclose($file);
}
- return $content;
+
+ return $result;
}
-/*Testing block*/
-function pwg_send_mail_test($result, $to, $subject, $content, $headers, $args)
+if ($conf['debug_mail'])
{
- global $conf, $user, $lang_info;
- $dir = PHPWG_ROOT_PATH.$conf['data_location'].'tmp';
- if ( mkgetdir( $dir, MKGETDIR_DEFAULT&~MKGETDIR_DIE_ON_ERROR) )
- {
- $filename = $dir.'/mail.'.stripslashes($user['username']).'.'.$lang_info['code'].'.'.$args['theme'].'-'.date('YmdHis');
- if ($args['content_format'] == 'text/plain')
- {
- $filename .= '.txt';
- }
- else
- {
- $filename .= '.html';
- }
- $file = fopen($filename, 'w+');
- fwrite($file, $to ."\n");
- fwrite($file, $subject ."\n");
- fwrite($file, $headers);
- fwrite($file, $content);
- fclose($file);
- }
- return $result;
+ add_event_handler('before_send_mail', 'pwg_send_mail_test', EVENT_HANDLER_PRIORITY_NEUTRAL+10, 4);
}
-if ($conf['debug_mail'])
- add_event_handler('send_mail', 'pwg_send_mail_test', EVENT_HANDLER_PRIORITY_NEUTRAL+10, 6);
-
-add_event_handler('send_mail', 'pwg_send_mail', EVENT_HANDLER_PRIORITY_NEUTRAL, 5);
-add_event_handler('send_mail_content', 'move_ccs_rules_to_body');
trigger_action('functions_mail_included');
-?>
+?> \ No newline at end of file