Index: /trunk/include/config_default.inc.php
===================================================================
--- /trunk/include/config_default.inc.php	(revision 25343)
+++ /trunk/include/config_default.inc.php	(revision 25344)
@@ -230,7 +230,48 @@
 $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;
+// 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.
+//
+// This configuration parameter is set to true in BSF branch and to false
+// elsewhere.
+$conf['check_upgrade_feed'] = true;
+
+// rate_items: available rates for a picture
+$conf['rate_items'] = array(0,1,2,3,4,5);
+
+// Define default method to use ('http' or 'html' in order to do redirect)
+$conf['default_redirect_method'] = 'http';
+
+// Define using double password type in admin's users management panel
+$conf['double_password_type_in_admin'] = false;
+
+// Define if logins must be case sentitive or not at users registration. ie :
+// If set true, the login "user" will equal "User" or "USER" or "user",
+// etc. ... And it will be impossible to use such login variation to create a
+// new user account.
+$conf['insensitive_case_logon'] = false;
+
+// how should we check for unicity when adding a photo. Can be 'md5sum' or
+// 'filename'
+$conf['uniqueness_mode'] = 'md5sum';
+
+// Library used for image resizing. Value could be 'auto', 'imagick',
+// 'ext_imagick' or 'gd'. If value is 'auto', library will be choosen in this
+// order. If choosen library is not available, another one will be picked up.
+$conf['graphics_library'] = 'auto';
+
+// If library used is external installation of ImageMagick ('ext_imagick'),
+// you can define imagemagick directory.
+$conf['ext_imagick_dir'] = '';
+
+// how many user comments to display by default on comments.php. Use 'all'
+// to display all user comments without pagination. Default available values
+// are array(5,10,20,50,'all') but you can set any other numeric value.
+$conf['comments_page_nb_comments'] = 10;
+
+// +-----------------------------------------------------------------------+
+// |                                 email                                 |
+// +-----------------------------------------------------------------------+
 
 // send_bcc_mail_webmaster: send bcc mail to webmaster. Set true for debug
@@ -238,20 +279,17 @@
 $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
+// 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)
+// 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
@@ -265,45 +303,4 @@
 // '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.
-//
-// This configuration parameter is set to true in BSF branch and to false
-// elsewhere.
-$conf['check_upgrade_feed'] = true;
-
-// rate_items: available rates for a picture
-$conf['rate_items'] = array(0,1,2,3,4,5);
-
-// Define default method to use ('http' or 'html' in order to do redirect)
-$conf['default_redirect_method'] = 'http';
-
-// Define using double password type in admin's users management panel
-$conf['double_password_type_in_admin'] = false;
-
-// Define if logins must be case sentitive or not at users registration. ie :
-// If set true, the login "user" will equal "User" or "USER" or "user",
-// etc. ... And it will be impossible to use such login variation to create a
-// new user account.
-$conf['insensitive_case_logon'] = false;
-
-// how should we check for unicity when adding a photo. Can be 'md5sum' or
-// 'filename'
-$conf['uniqueness_mode'] = 'md5sum';
-
-// Library used for image resizing. Value could be 'auto', 'imagick',
-// 'ext_imagick' or 'gd'. If value is 'auto', library will be choosen in this
-// order. If choosen library is not available, another one will be picked up.
-$conf['graphics_library'] = 'auto';
-
-// If library used is external installation of ImageMagick ('ext_imagick'),
-// you can define imagemagick directory.
-$conf['ext_imagick_dir'] = '';
-
-// how many user comments to display by default on comments.php. Use 'all'
-// to display all user comments without pagination. Default available values
-// are array(5,10,20,50,'all') but you can set any other numeric value.
-$conf['comments_page_nb_comments'] = 10;
 
 // +-----------------------------------------------------------------------+
Index: /trunk/include/emogrifier.class.php
===================================================================
--- /trunk/include/emogrifier.class.php	(revision 25344)
+++ /trunk/include/emogrifier.class.php	(revision 25344)
@@ -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;
+    }
+}
Index: /trunk/include/functions_mail.inc.php
===================================================================
--- /trunk/include/functions_mail.inc.php	(revision 25343)
+++ /trunk/include/functions_mail.inc.php	(revision 25344)
@@ -22,11 +22,6 @@
 // +-----------------------------------------------------------------------+
 
-// +-----------------------------------------------------------------------+
-// |                               functions                               |
-// +-----------------------------------------------------------------------+
-
-/*
- * Returns the name of the mail sender :
- *
+/**
+ * Returns the name of the mail sender
  * @return string
  */
@@ -38,18 +33,27 @@
 }
 
-/*
+/**
+ * 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
@@ -60,8 +64,7 @@
 
   $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'],
@@ -69,12 +72,8 @@
     '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;
 }
@@ -82,5 +81,4 @@
 /**
  * Returns an email address with an associated real name
- *
  * @param string name
  * @param string email
@@ -88,5 +86,4 @@
 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));
@@ -108,7 +105,37 @@
 
 /**
+ * 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)
@@ -116,4 +143,5 @@
   $result = array();
   $list = explode(',', $email_list);
+
   foreach ($list as $email)
   {
@@ -131,35 +159,17 @@
 /**
  * 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()]
- */
-function & get_mail_template($email_format, $theme='')
-{
-  if (empty($theme))
-  {
-    $theme = get_default_theme();
-  }
-
-  $mail_template = new Template(PHPWG_ROOT_PATH.'themes', $theme, 'template/mail/'.$email_format);
-
-  return $mail_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
+ * @param string $email_format - text/html or text/plain
+ * @return Template
+ */
+function &get_mail_template($email_format)
+{
+  $template = new Template(PHPWG_ROOT_PATH.'themes', 'default', 'template/mail/'.$email_format);
+  return $template;
+}
+
+/**
+ * Switch language to specified language
  * All entries are push on language stack
- *
- * @param string language
+ * @param string $language
  */
 function switch_lang_to($language)
@@ -198,4 +208,5 @@
     load_language('admin.lang', '', array('language'=>$language) );
     
+    // Reload all plugins files (see load_language declaration)
     if (!empty($language_files))
     {
@@ -220,8 +231,6 @@
 }
 
-/*
+/**
  * Switch back language pushed with switch_lang_to function
- *
- * @param: none
  */
 function switch_lang_back()
@@ -457,10 +466,9 @@
 }
 
-/*
+/**
  * 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]
@@ -470,7 +478,9 @@
  *       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())
@@ -492,9 +502,11 @@
   $mail = new PHPMailer;
 
-  foreach (explode(',', get_strict_email_list($to)) as $recipient)
-  {
-    $mail->addAddress($recipient);
-  }
-  
+  $recipients = !is_array($to) ? explode(',', $to) : $to;
+  foreach ($recipients as $recipient)
+  {
+    $recipient = unformat_email($recipient);
+    $mail->addAddress($recipient['email'], $recipient['name']);
+  }
+
   $mail->WordWrap = 76;
   $mail->CharSet = 'UTF-8';
@@ -505,14 +517,15 @@
   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
@@ -521,7 +534,5 @@
     $args['subject'] = 'Piwigo';
   }
-  
   $args['subject'] = trim(preg_replace('#[\n\r]+#s', '', $args['subject']));
-
   $mail->Subject = $args['subject'];
 
@@ -529,5 +540,9 @@
   if (!empty($args['Cc']))
   {
-    $mail->addCC($args['Cc']);
+    foreach ($args['Cc'] as $cc)
+    {
+      $cc = unformat_email($cc);
+      $mail->addCC($cc['email'], $cc['name']);
+    }
   }
 
@@ -535,26 +550,36 @@
   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']);
+    }
+  }
+
+  // theme
+  if (empty($args['theme']) or !in_array($args['theme'], array('clear','dark')))
+  {
+    $args['theme'] = $conf_mail['mail_theme'];
   }
 
   // content
-  if (empty($args['email_format']))
-  {
-    $args['email_format'] = $conf_mail['default_email_format'];
-  }
-
   if (!isset($args['content']))
   {
     $args['content'] = '';
   }
-
+  if (!isset($args['mail_title']))
+  {
+    $args['mail_title'] = $conf['gallery_title'];
+  }
+  if (!isset($args['mail_subtitle']))
+  {
+    $args['mail_subtitle'] = $args['subject'];
+  }
+
+  // content type
   if (empty($args['content_format']))
   {
@@ -562,27 +587,25 @@
   }
 
-  if (empty($args['theme']))
-  {
-    $args['theme'] = get_default_theme();
-  }
-
-  $content_type_list[] = $args['email_format'];
-  if (!empty($conf_mail['alternative_email_format']))
-  {
-    $content_type_list[] = $conf_mail['alternative_email_format'];
-  }
+  $content_type_list = array();
+  if ($conf_mail['allow_html_email'] and @$args['email_format'] != 'text/plain')
+  {
+    $content_type_list[] = 'text/html';
+  }
+  $content_type_list[] = 'text/plain';
 
   $contents = array();
-
-  foreach (array_unique($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'];
+  foreach ($content_type_list as $content_type)
+  {
+    // 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);
       }
 
@@ -595,7 +618,9 @@
           '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'],
           )
         );
@@ -609,16 +634,12 @@
         }
 
-        $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);
     }
@@ -628,14 +649,19 @@
 
     // Content
-    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>';
-    }
-    else if (($args['content_format'] == 'text/html') and ($content_type == 'text/plain'))
+    if ($args['content_format'] == 'text/plain' and $content_type == 'text/html')
+    {
+      // 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')
     {
       // convert html text to plain text
@@ -654,8 +680,9 @@
   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']))
@@ -704,187 +731,106 @@
   }
 
-  $ret = $mail->send();
-  if(!$ret)
-  {
-    trigger_error( 'Mailer Error: ' . $mail->ErrorInfo, E_USER_WARNING);
-  }
+  $ret = true;
+  $pre_result = trigger_event('before_send_mail', true, $to, $args, $mail);
+
+  if ($pre_result)
+  {
+    $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)
- */
-function pwg_send_mail($result, $to, $subject, $contents, $headers)
-{
+/**
+ * @deprecated 2.6
+ */
+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');
+    return pwg_mail($to, array(
+        'content' => $content,
+        'subject' => $subject,
+      ));
+  }
+  else
+  {
+    return $result;
+  }
+}
+
+/**
+ * @deprecated 2.6
+ */
+function move_ccs_rules_to_body($content)
+{
+  trigger_error('move_ccs_rules_to_body function is deprecated, use move_css_to_body', E_USER_NOTICE);
+  
+  return move_css_to_body($content);
+}
+
+/**
+ * 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');
+
+  $e = new Emogrifier($content);
+  $e->preserveStyleTag = true;
+  return $e->emogrify();
+}
+
+/**
+ * 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')
+    {
+      $filename .= '.txt';
+    }
+    else
+    {
+      $filename .= '.html';
+    }
     
-    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);
-      // }
-    }
-  }
-  else
-  {
-    return $result;
-  }
-}
-
-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], ' ;');
-
-      foreach($selects as $select)
-      {
-        $select = trim($select);
-        $selectors[$select][] = $style;
-      }
-    }
-
-    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;
-      }
-    }
-
-    // Keep unknow tags in page head
-    if (!empty($unknow_selectors))
-    {
-      $content = preg_replace('#<style>.*?</style>#s', "<style type=\"text/css\">\n$unknow_selectors</style>", $content);
-    }
-    else
-    {
-      $content = preg_replace('#<style>.*?</style>#s', '', $content);
-    }
-  }
-  return $content;
-}
-
-/*Testing block*/
-function pwg_send_mail_test($result, $to, $subject, $content, $headers, $args)
-{
-    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;
-}
+    $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 $result;
+}
+
 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');
+{
+  add_event_handler('before_send_mail', 'pwg_send_mail_test', EVENT_HANDLER_PRIORITY_NEUTRAL+10, 4);
+}
+
 trigger_action('functions_mail_included');
 
Index: /trunk/themes/default/template/mail/text/html/footer.tpl
===================================================================
--- /trunk/themes/default/template/mail/text/html/footer.tpl	(revision 25343)
+++ /trunk/themes/default/template/mail/text/html/footer.tpl	(revision 25344)
@@ -1,22 +1,23 @@
-</div> <!-- content -->
-<div id="copyright">
-<hr>
-  {'Sent by'|@translate}
-  <a href="{$GALLERY_URL}">{$GALLERY_TITLE}</a>
+            {* <!-- end $CONTENT --> *}
+          </td></tr>
 
- {* Please, do not remove this copyright. If you really want to,
-      contact us on http://piwigo.org to find a solution on how
-      to show the origin of the script...*}
-  - {'Powered by'|@translate}
-  <a href="http://piwigo.org" class="Piwigo">
-  <span class="Piwigo">Piwigo</span></a>
-  {$VERSION}
+          <tr><td id="footer">
+            {* <!-- begin FOOTER --> *}
+{* <!-- Please, do not remove this copyright. If you really want to,
+contact us on http://piwigo.org to find a solution on how
+to show the origin of the script... --> *}
+      
+            {'Sent by'|translate} <a href="{$GALLERY_URL}">{$GALLERY_TITLE}</a>
+            - {'Powered by'|translate} <a href="{$PHPWG_URL}" class="Piwigo">Piwigo</a>
+            {if not empty($VERSION)}{$VERSION}{/if}
+            
+            - {'Contact'|translate}
+            <a href="mailto:{$CONTACT_MAIL}?subject={'A comment on your site'|translate|escape:url}">{'Webmaster'|@translate}</a>
+            {* <!-- end FOOTER --> *}
+          </td></tr>
+        </table>
 
-  - {'Contact'|@translate}
-  <a href="mailto:{$MAIL}?subject={$TITLE_MAIL}">{'Webmaster'|@translate}</a>
-
-</div> <!-- copyright -->
-</div> <!-- the_page -->
-
-</body>
+      </td></tr>
+    </table>
+  </body>
 </html>
Index: /trunk/themes/default/template/mail/text/html/global-mail-css.tpl
===================================================================
--- /trunk/themes/default/template/mail/text/html/global-mail-css.tpl	(revision 25343)
+++ /trunk/themes/default/template/mail/text/html/global-mail-css.tpl	(revision 25344)
@@ -1,11 +1,103 @@
-/* Global mail css */
+{strip}
+/* page */
+body {
+  font-family:"Verdana", "Helvetica", "Optima", sans-serif;
+  font-size:12px;
+  margin:0px;
+  padding:0px;
+}
 
-body {ldelim} font-family: Univers, Helvetica, Optima; font-size:12px; margin:0px; padding:0px; }
-#the_page {ldelim} margin:0px; padding:0px; text-align:left;}
-#content {ldelim} margin:0px; padding:30px 0px 0px 30px; width:732px;}
-hr {ldelim} width:632px; margin-left:0;}
-#copyright {ldelim} font-size:10px; margin:0px; padding:48px 0px 32px 62px;}
-.PWG {ldelim} font-family:verdana, sans-serif !important; font-size:0.9em; font-weight:normal; letter-spacing:0px;}
-h2 {ldelim} padding:7px 15px; width:617px; font-weight:bold;}
-img {ldelim} margin:16px; border:16px solid #aaa; -moz-border-radius: 4px; border-radius: 4px 4px; }
-img:hover {ldelim} padding: 15px; border: 1px solid yellow; -moz-border-radius: 4px; border-radius: 4px 4px; }
+html, body, #bodyTable {
+  width:100%;
+  height:100%;
+}
+
+/* main block */
+#header {
+  padding:20px;
+}
+#header #title {
+  font-size:26px;
+  font-weight:bold;
+}
+#header #subtitle {
+  font-size:20px;
+}
+#content {
+  padding:0 20px 20px 20px;
+}
+#footer {
+  font-size:10px;
+  padding:5px;
+}
+#topSpacer {
+  margin-bottom:20px;
+}
+
+/* images */
+img {
+  border:none;
+}
+
+h1, h2, h3, h4, h5 {
+  margin:0.5em 0 0.25em 0;
+}
+h1 { font-size:26px; }
+h2 { font-size:20px; }
+h3 { font-size:16px; }
+h4 { font-size:12px; }
+h5 { font-size:10px; }
+
+/* paragraphs */
+p {
+  margin:0.25em 0 0.5em 0;
+}
+blockquote {
+  margin-left:10px;
+  padding-left:10px;
+  font-family:"Times",serif;
+  font-size:14px;
+}
+
+/* list */
+ul, ol {
+  margin:0.5em 0 0.25em 30px;
+  padding:0;
+}
+dl {
+  margin:0.5em 0 0.25em 10px;
+}
+dt {
+  margin:0.25em 0 0 0;
+  font-weight:bold;
+}
+dd {
+  margin:0 0 0.25em 10px;
+}
+
+/* tables */
+#content table {
+  width:100%;
+  border-spacing:0;
+  border-collapse:collapse;
+}
+#content table td {
+  padding:2px 3px;
+}
+#content table th {
+  font-weight:bold;
+  text-align:center;
+  padding:4px 0;
+}
+#content table tfoot td {
+  font-size:0.8em;
+}
+
+/* line */
+hr {
+  border-width:0 0 1px 0;
+  border-color:#ddd;
+  border-style:solid;
+  margin:1em;
+}
+{/strip}
Index: /trunk/themes/default/template/mail/text/html/header.tpl
===================================================================
--- /trunk/themes/default/template/mail/text/html/header.tpl	(revision 25343)
+++ /trunk/themes/default/template/mail/text/html/header.tpl	(revision 25344)
@@ -1,13 +1,29 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html lang="{$lang_info.code}" dir="{$lang_info.direction}">
-<head>
-<title>Piwigo Mail</title>
-<meta http-equiv="Content-Type" content="text/html; charset={$CONTENT_ENCODING}">
-<style><!-- /* Mini style for mails */
-{if isset($GLOBAL_MAIL_CSS)}{$GLOBAL_MAIL_CSS}{/if}
-{if isset($MAIL_CSS)}{$MAIL_CSS}{/if}
---></style>
-</head>
-<body>
-<div id="the_page">
-<div id="content" class="content">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset={$CONTENT_ENCODING}"/>
+    <title>Piwigo Mail</title>
+
+    <style type="text/css">
+    {if isset($GLOBAL_MAIL_CSS)}{$GLOBAL_MAIL_CSS}{/if}
+    {if isset($MAIL_CSS)}{$MAIL_CSS}{/if}
+    </style>
+  </head>
+
+  <body>
+    <table id="bodyTable" cellspacing="0" cellpadding="10" border="0">
+      <tr><td align="center" valign="top">
+
+        <table id="contentTable" cellspacing="0" cellpadding="0" border="0">
+          <tr><td id="header">
+            {* <!-- begin HEADER --> *}
+            <div id="title">{$MAIL_TITLE}</div>
+            {if not empty($MAIL_SUBTITLE)}<div id="subtitle">{$MAIL_SUBTITLE}</div>{/if}
+            {* <!-- end HEADER --> *}
+          </td></tr>
+
+          <tr><td id="content">
+{* <!-- use an invisible div with a bottom margin to force the browser 
+to merge the margin with the element bellow (typically h1/h2/...) --> *}
+            <div id="topSpacer"></div>
+            {* <!-- begin CONTENT --> *}
Index: /trunk/themes/default/template/mail/text/html/mail-css-clear.tpl
===================================================================
--- /trunk/themes/default/template/mail/text/html/mail-css-clear.tpl	(revision 25344)
+++ /trunk/themes/default/template/mail/text/html/mail-css-clear.tpl	(revision 25344)
@@ -0,0 +1,73 @@
+{strip}
+/* page */
+body {
+  color:#111;
+}
+
+html, body, #bodyTable {
+  background:#fff;
+}
+#contentTable {
+  width:600px;
+}
+
+/* main block */
+#header {
+  background:#eee;
+  background:radial-gradient(ellipse at center, #fefefe, #e5e5e5);
+  border-top:4px solid #f70;
+  text-align:center;
+  text-shadow:1px 1px 0px #fff;
+}
+#header #subtitle {
+  color:#e06900;
+}
+#content {
+  background:#fefefe;
+  border-width:1px;
+  border-style:solid;
+  border-color:#ddd #eee;
+  box-shadow:inset 0 0 20px #eee;
+}
+#footer {
+  background:#eee;
+  border-bottom:2px solid #f70;
+}
+
+/* links */
+a {
+  color:#f70;
+  text-decoration:none;
+}
+a:hover {
+  text-decoration:underline;
+}
+
+/* images */
+img.photo {
+  border:10px solid #ddd;
+}
+img.photo:hover {
+  border-color:#eee;
+}
+
+/* paragraphs */
+blockquote {
+  border-left:2px solid #aaa;
+  border-radius:2px;
+}
+
+/* tables */
+#content table td {
+  border-bottom:1px solid #efefef;
+}
+#content table th {
+  background:#aaa;
+  color:#fff;
+  border-right:1px solid #ddd;
+}
+#content table tfoot td {
+  background:#eee;
+  border-right:1px solid #fff;
+}
+{/strip}
Index: /trunk/themes/default/template/mail/text/html/mail-css-dark.tpl
===================================================================
--- /trunk/themes/default/template/mail/text/html/mail-css-dark.tpl	(revision 25344)
+++ /trunk/themes/default/template/mail/text/html/mail-css-dark.tpl	(revision 25344)
@@ -0,0 +1,87 @@
+{strip}
+/* page */
+body {
+  color:#fff;
+}
+
+html, body, #bodyTable {
+  background:#111;
+}
+#contentTable {
+  width:600px;
+}
+
+/* main block */
+#header {
+  background:#444;
+  background:radial-gradient(ellipse at center, #555, #333);
+  border:1px solid #000;
+  border-top:4px solid #f70;
+  text-align:center;
+  text-shadow:1px 1px 0px #000;
+}
+#header #title {
+  color:#eee;
+}
+#header #subtitle {
+  color:#e06900;
+}
+#content {
+  background:#111;
+  border-width:1px;
+  border-style:solid;
+  border-color:#666 #000;
+  box-shadow:inset 0 0 20px #333;
+}
+#footer {
+  background:#333;
+  border:1px solid #000;
+  border-bottom:2px solid #f70;
+}
+
+/* links */
+a {
+  color:#f70;
+  text-decoration:none;
+}
+a:hover {
+  text-decoration:underline;
+}
+
+/* images */
+img.photo {
+  border:10px solid #666;
+}
+img.photo:hover {
+  border-color:#999;
+}
+
+h1, h2, h3, h4, h5 {
+  color:#bbb;
+}
+
+/* paragraphs */
+blockquote {
+  border-left:2px solid #aaa;
+  border-radius:2px;
+}
+
+/* tables */
+#content table td {
+  border-bottom:1px solid #999;
+}
+#content table th {
+  background:#666;
+  border-right:1px solid #aaa;
+}
+#content table tfoot td {
+  background:#444;
+  color:#aaa;
+  border-right:1px solid #aaa;
+}
+
+/* line */
+hr {
+  border-color:#555;
+}
+{/strip}
Index: /trunk/themes/default/template/mail/text/plain/footer.tpl
===================================================================
--- /trunk/themes/default/template/mail/text/plain/footer.tpl	(revision 25343)
+++ /trunk/themes/default/template/mail/text/plain/footer.tpl	(revision 25344)
@@ -1,5 +1,7 @@
+{literal}
 
--- 
+{/literal}
+----
 {'Sent by'|@translate} "{$GALLERY_TITLE}" {$GALLERY_URL}
-{'Contact'|@translate} {$MAIL}
-{'Powered by'|@translate} Piwigo {$VERSION}
+{'Powered by'|@translate} "Piwigo{if not empty($VERSION)} {$VERSION}{/if}" {$PHPWG_URL}
+{'Contact'|@translate}: {$CONTACT_MAIL}
Index: /trunk/themes/default/template/mail/text/plain/header.tpl
===================================================================
--- /trunk/themes/default/template/mail/text/plain/header.tpl	(revision 25343)
+++ /trunk/themes/default/template/mail/text/plain/header.tpl	(revision 25344)
@@ -1,3 +1,6 @@
+{$MAIL_TITLE}
+{if not empty($MAIL_SUBTITLE)}{$MAIL_SUBTITLE}
+{/if}
+----
 {literal}
-
-{/literal}{*note that there must be an empty line to separate the mime headers*}
+{/literal}
