source: spip-zone/_plugins_/facteur/trunk/facteur_fonctions.php @ 65612

Last change on this file since 65612 was 62827, checked in by cedric@…, 7 years ago

Le reglage configurable "Transformer les styles contenus entre <head> </head> en des styles en ligne" n'a pas de sens en tant que filtre systematique applique a tous les mails :

  • il casse les mails faits mains sur mesure (comme le wrapper html des emails texte du plugin) car incapable de gerer les @media
  • il peut etre utile sur certains mails html simples
  • il est a utiliser au cas par cas, et c'est le webmestre qui code son mail html qui peut le savoir, pas l'admin qui configure le site et utilise simplement

En consequence, le reglage disparait de la configuration et n'est plus pris en compte
Par compatibilite la methode ConvertirStylesEnligne? de la classe Facteur reste disponible, mais tout son code est deporte dans le filtre facteur_convertir_styles_inline
que l'on appellera au cas par cas dans les squelettes HTML par un appel #FILTRE{facteur_convertir_styles_inline}

Ainsi on preserve son usage quand necessaire, sans plus risque de casser les emails HTML bien fichus
On incremente la version en 2.2.0 pour marquer la difference fonctionnelle

  • Property svn:executable set to *
File size: 9.4 KB
Line 
1<?php
2/*
3 * Plugin Facteur 2
4 * (c) 2009-2011 Collectif SPIP
5 * Distribue sous licence GPL
6 *
7 */
8
9if (!defined("_ECRIRE_INC_VERSION")) return;
10
11        /*
12
13        Written by Eric Dols - edols@auditavenue.com
14
15        You may freely use or modify this, provided
16        you leave credits to the original coder.
17        Feedback about (un)successfull uses, bugs and improvements done
18        are much appreciated, but don't expect actual support.
19
20        PURPOSE OF THIS FUNCTION
21                It is designed to process html emails relying
22                on a css stylesheet placed in the <head> for layout in
23                order to enhance compatibility with email clients,
24                including webmail services.
25                Provided you use minimal css, you can keep styling separate
26                from the content in your email template, and let this function
27                "inject" those styles inline in your email html tags on-the-fly,
28                just before sending.
29                Technically, it grabs the style declarations found in the
30                <head> section and inserts each declaration inline,
31                inside the corresponding html tags in the email message.
32
33                Supports both HTML and XHTML markup seamlessly. Thus
34                tolerant to email message writers using non-xhtml tag,
35                even when template is xhtml compliant (e.g. they would
36                add <img ...> instead of a xhtml compliant <img ... />).
37
38        NEW 10 dec. 2003:
39                - code revised, including a few regexp bugs fixed.
40                - multiple class for a tag are now allowed <p class="firstclass secondclass">
41                - all unsupported css styles are now moved to the body section (not just a:hover etc...)
42
43        USE
44                Add this function to a function library include, like "inline.inc"
45                and include it near the beginning of your php page:
46                require ("inline.inc");
47
48                load the html source of message into a variable
49                like $html_source and process it using:
50                $html_source = sheet2inline($html_source)
51
52
53        STYLE DEFINITIONS SUPPORTED
54                TAG { ... }
55                TAG1, TAG2, ... { ... }
56                TAG.class { ... }
57                .class { ...)
58                TAG:pseudo { ... }
59
60
61                CSS definitions may be freely formatted (spaces, tabs, linefeeds...),
62                they are converted to oneliners before inserting them inline in the html tags.
63
64                .class definitions are processed AFTER tag definitions,
65                thus appended inline after any existing tag styling to
66                preserve the normal css priority behavior.
67
68                Existing style="..." attributes in tags are NOT stripped. However they MUST
69                be with double quotes. If not, an addtional style="..." attribute will be added
70
71
72        KNOWN LIMITATIONS
73                - style info should be placed in <head> section. I believe
74                        it shouldnt be too hard to modify to point to an external
75                        stylesheet instead.
76                - no support (yet?):
77                        * chains like P UL LI { .... } or P UL LI.class { .... }
78                        * #divname p { ... } and <tag id="...">
79                        * a:hover, a:visited {...} multiple class:pseudo
80                        They require a significantly more complicated processing likely
81                        based on stylesheet and document trees parsing.
82                        Many email clients don't handle more than what is supported
83                        by this script anyway.
84                - pseudo-classes like a:hover {...} can't be inserted inline
85                        in the html tags: they are moved to a <style> declaration in
86                        the <body> instead. This is a limitation from html, not this script.
87                - It is still up to you to check if target email clients render
88                        your css styled templates correctly, especially webmail services
89                        like Hotmail, in which the email becomes a sub-part of an html page,
90                        with styles already in place.
91        */
92function facteur_convertir_styles_inline($body){
93        // variables to be accessed in the callback sub-function too
94        global $styledefinition, $styletag, $styleclass;
95
96        // Let's first load the stylesheet information in a $styles array using a regexp
97        preg_match_all ( "/^[ \t]*([.]?)([\w, #]+)([.:])?(\S*)\s+{([^}]+)}/mi", $body , $styles);
98        /*
99                $styles[1] = . or ''  => .class or tag (empty)
100                $styles[2] = name of class or tag(s)
101                $styles[3] = : . or '' => followed by pseudo-element, class separator or nothing (empty)
102                $styles[4] = name of pseudo-element after a tag, if any
103                $styles[5] = the style definition itself, i.e. what's between the { }
104        */
105
106        // Now loop through the styles found and act accordingly;
107
108        // process TAG {...} & TAG1, TAG2,... {...} definitions only first by order of appearance
109        foreach ($styles[1] as $i => $type) {
110                if ($type=="" && $styles[3][$i]=="") {
111                        $styledefinition = trim($styles[5][$i]);
112                        $styletag = preg_replace("/ *, */", "|", trim($styles[2][$i])); //echo $styletag."<br />";
113                        $styleclass = "";
114                        // process TAG {...} and TAG1, TAG2 {...} but not TAG1 TAG2 {...} or #divname styles
115                        if (!preg_match("/ /", $styletag) && !preg_match("/#/", $styletag)) {
116                                $pattern = "!<(".$styletag.")([^>]*(?= /)|[^>]*)( /)?>!mi";
117                                $body = preg_replace_callback ($pattern, 'facteur_addstyle' , $body);
118                                $styles[6][$i]=1; // mark as injected inline
119                        }
120                }
121        }
122
123        // append additional .CLASS {...} and TAG.CLASS {...} styling by order of appearance
124        // important to do so after TAG {...} definitions, so that class attributes override TAG styles when needed
125        foreach ($styles[1] as $i => $type) {
126                if ($type!="." && $styles[3][$i]=="." ) {       // class definition for a specific tag
127                        $styledefinition = trim($styles[5][$i]);
128                        $styletag = trim($styles[2][$i]);
129                        $styleclass = trim($styles[4][$i]);
130                        $pattern = "!<(".$styletag.")([^>]* class\=['\"][^'\"]*".$styleclass."[^'\"]*['\"][^>]*(?= /)|[^>]* class\=['\"][^'\"]*".$styleclass."[^'\"]*['\"][^>]*)( />)?>!mi";
131                        $body = preg_replace_callback ($pattern, 'facteur_addstyle' , $body);
132                        $styles[6][$i]=1; // mark as injected inline
133
134                }
135                elseif ($type=="." && $styles[3][$i]=="" ) {    // general class definition for any tag
136                        $styledefinition = trim($styles[5][$i]);
137                        $styletag = "";
138                        $styleclass = trim($styles[2][$i]);
139                        $pattern = "!<(\w+)([^>]* class\=['\"]".$styleclass."['\"][^>]*(?= /)|[^>]* class\=['\"]".$styleclass."['\"][^>]*)( />)?>!mi";
140                        $body = preg_replace_callback ($pattern, 'facteur_addstyle' , $body);
141                        $styles[6][$i]=1; // mark as injected inline
142                }
143        }
144
145
146        /* move all style declarations that weren't injected from <head> to a <body> <style> section,
147                 including but not limited to:
148                 - pseudo-classes like a:hover {...} as they can't be set inline
149                 - declaration chains like UL LI {...}
150                 - #divname {...}. These are not supported by email clients like Mac/Entourage anyway, it seems. */
151        foreach ($styles[1] as $i => $type) {
152                if ($styles[6][$i]=="") {
153                        // add a <style type="text/css"> section after <body> if there's isn't one yet
154                        if (preg_match ("!<body[^>]*>\s*<style!mi", $body)==0) {
155                                $body = preg_replace ("/(<body[^>]*>)/i", "\n\$1\n".'<style type="text/css">'."\n<!--\n-->\n</style>\n", $body);
156                        }
157                        // append a copy of the pseudo-element declaration to that body style section
158                        $styledefinition = trim($styles[5][$i]);
159                        $styledefinition = preg_replace ("!\s+!mi", " ", $styledefinition ); // convert style definition to a one-liner (optional)
160                        $declaration = $styles[1][$i].trim($styles[2][$i]).$styles[3][$i].trim($styles[4][$i])." { ".$styledefinition." }";
161                        $body = preg_replace ("!(<body[^>]*>\s*<style[^>]*>\s*<\!\-\-[^>]*)"."(\s*\-\->\s*</style>)!si", "\$1".$declaration."\n\$2", $body);
162                        $styles[6][$i]= 2; // mark as moved to <style> section in <body>
163                }
164        }
165
166        // remove stylesheet declaration(s) from <head> section (comment following line out if not wanted)
167        //$body = preg_replace ("!(<head>.*)<style type.*</style>(.*</head>)!si", "\$1\$2" , $body);
168
169        // check what styles have been injected
170#                       print_r($styles);
171
172        return $body;
173}
174
175/**
176 * facteur_addstyle
177 * @author Eric Dols
178 *
179 * @param $matches
180 * @return string
181 */
182function facteur_addstyle($matches) {
183
184        // $matches[1]=tag, $matches[2]=tag attributes (if any), $matches[3]=xhtml closing (if any)
185
186        // variables values set in calling function
187        global $styledefinition, $styletag, $styleclass;
188
189        // convert the style definition to a one-liner
190        $styledefinition = preg_replace ("!\s+!mi", " ", $styledefinition );
191        // convert all double-quotes to single-quotes
192        $styledefinition = preg_replace ('/"/','\'', $styledefinition );
193
194        if (preg_match ("/style\=/i", $matches[2])) {
195                        // add styles to existing style attribute if any already in the tag
196                        $pattern = "!(.* style\=)[\"]([^\"]*)[\"](.*)!mi";
197                        $replacement = "\$1".'"'."\$2 ".$styledefinition.'"'."\$3";
198                        $attributes = preg_replace ($pattern, $replacement , $matches[2]);
199        } else {
200                        // otherwise add new style attribute to tag (none was present)
201                        $attributes = $matches[2].' style="'.$styledefinition.'"';
202        }
203
204        if ($styleclass!="") {
205                // if we were injecting a class style, remove the now useless class attribute from the html tag
206
207                // Single class in tag case (class="classname"): remove class attribute altogether
208                $pattern = "!(.*) class\=['\"]".$styleclass."['\"](.*)!mi";
209                $replacement = "\$1\$2";
210                $attributes = preg_replace ( $pattern, $replacement, $attributes);
211
212                // Multiple classes in tag case (class="classname anotherclass..."): remove class name from class attribute.
213                // classes are injected inline and removed by order of appearance in <head> stylesheet
214                // exact same behavior as where last declared class attributes in <style> take over (IE6 tested only)
215                $pattern = "!(.* class\=['\"][^\"]*)(".$styleclass." | ".$styleclass.")([^\"]*['\"].*)!mi";
216                $replacement = "\$1\$3";
217                $attributes = preg_replace ( $pattern, $replacement, $attributes);
218
219        }
220
221        return "<".$matches[1].$attributes.$matches[3].">";
222}
223
224/**
225 * Un filtre pour transformer les retour ligne texte en br si besoin (si pas autobr actif)
226 *
227 * @param string $texte
228 * @return string
229 */
230function facteur_nl2br_si_pas_autobr($texte){
231        return (_AUTOBR?$texte:nl2br($texte));
232}
233
234?>
Note: See TracBrowser for help on using the repository browser.