[PLUGINS] ~maj globale
[lhc/web/www.git] / www / plugins / facteur / facteur_fonctions.php
1 <?php
2 /*
3 * Plugin Facteur 2
4 * (c) 2009-2011 Collectif SPIP
5 * Distribue sous licence GPL
6 *
7 */
8
9 if (!defined("_ECRIRE_INC_VERSION")) return;
10
11 function facteur_affiche_password_masque($pass){
12 $l = strlen($pass);
13 if ($l<=8){
14 return str_pad('',$l,'*');
15 }
16 $e = intval(ceil($l/10));
17 $mid = str_pad('',$l-2*$e,'*');
18 if (strlen($mid)>8){
19 $mid = '***...***';
20 }
21 return substr($pass,0,$e) . $mid . substr($pass,-$e);
22 }
23
24 /**
25 * Transformer un mail texte ou HTML simplifie en mail HTML complet avec le wrapper emails/texte.html
26 * Si le mail est un mail texte :
27 * la premiere ligne est le sujet
28 * le reste est le corps du mail
29 *
30 * Si le mail est un mail HTML simplifie :
31 * le sujet est entre <title></title>
32 * le corps est entre <body></body>
33 * une eventuelle intro peut etre fournie entre <intro></intro>
34 *
35 * @param string $texte_ou_html
36 * @return string
37 */
38 function facteur_email_wrap_to_html($texte_ou_html){
39 $texte_ou_html = trim($texte_ou_html);
40 // attention : si pas de contenu on renvoi du vide aussi (mail vide = mail vide)
41 if (!strlen(trim($texte_ou_html)))
42 return $texte_ou_html;
43
44 $contexte = array("sujet"=>"","texte"=>"","intro"=>"");
45
46 // tester si le mail est en html (simplifie)
47 if (substr($texte_ou_html,0,1)=="<"
48 AND substr($texte_ou_html,-1,1)==">"
49 AND stripos($texte_ou_html,"</body>")!==false){
50
51 // dans ce cas on ruse un peu : extraire le sujet du title
52 $sujet = "";
53 if (preg_match(",<title>(.*)</title>,Uims",$texte_ou_html,$m)){
54 $contexte['sujet'] = $m[1];
55 $texte_ou_html = preg_replace(",<title>(.*)</title>,Uims","",$texte_ou_html,1);
56 $texte_ou_html = trim($texte_ou_html);
57 }
58 if (preg_match(",<intro>(.*)</intro>,Uims",$texte_ou_html,$m)){
59 $contexte['intro'] = $m[1];
60 $texte_ou_html = preg_replace(",<intro>(.*)</intro>,Uims","",$texte_ou_html,1);
61 $texte_ou_html = trim($texte_ou_html);
62 }
63 $contexte['html'] = preg_replace(",</?body>,ims","",$texte_ou_html);
64 }
65 else {
66 // la premiere ligne est toujours le sujet
67 $texte_ou_html = explode("\n",$texte_ou_html);
68 $contexte['sujet'] = trim(array_shift($texte_ou_html));
69 $contexte['texte'] = trim(implode("\n",$texte_ou_html));
70 }
71
72 // attention : si pas de contenu on renvoi du vide aussi (mail vide = mail vide)
73 if (!strlen(trim(implode("",$contexte))))
74 return "";
75
76 return recuperer_fond("emails/texte",$contexte);
77 }
78
79 /*
80
81 Written by Eric Dols - edols@auditavenue.com
82
83 You may freely use or modify this, provided
84 you leave credits to the original coder.
85 Feedback about (un)successfull uses, bugs and improvements done
86 are much appreciated, but don't expect actual support.
87
88 PURPOSE OF THIS FUNCTION
89 It is designed to process html emails relying
90 on a css stylesheet placed in the <head> for layout in
91 order to enhance compatibility with email clients,
92 including webmail services.
93 Provided you use minimal css, you can keep styling separate
94 from the content in your email template, and let this function
95 "inject" those styles inline in your email html tags on-the-fly,
96 just before sending.
97 Technically, it grabs the style declarations found in the
98 <head> section and inserts each declaration inline,
99 inside the corresponding html tags in the email message.
100
101 Supports both HTML and XHTML markup seamlessly. Thus
102 tolerant to email message writers using non-xhtml tag,
103 even when template is xhtml compliant (e.g. they would
104 add <img ...> instead of a xhtml compliant <img ... />).
105
106 NEW 10 dec. 2003:
107 - code revised, including a few regexp bugs fixed.
108 - multiple class for a tag are now allowed <p class="firstclass secondclass">
109 - all unsupported css styles are now moved to the body section (not just a:hover etc...)
110
111 USE
112 Add this function to a function library include, like "inline.inc"
113 and include it near the beginning of your php page:
114 require ("inline.inc");
115
116 load the html source of message into a variable
117 like $html_source and process it using:
118 $html_source = sheet2inline($html_source)
119
120
121 STYLE DEFINITIONS SUPPORTED
122 TAG { ... }
123 TAG1, TAG2, ... { ... }
124 TAG.class { ... }
125 .class { ...)
126 TAG:pseudo { ... }
127
128
129 CSS definitions may be freely formatted (spaces, tabs, linefeeds...),
130 they are converted to oneliners before inserting them inline in the html tags.
131
132 .class definitions are processed AFTER tag definitions,
133 thus appended inline after any existing tag styling to
134 preserve the normal css priority behavior.
135
136 Existing style="..." attributes in tags are NOT stripped. However they MUST
137 be with double quotes. If not, an addtional style="..." attribute will be added
138
139
140 KNOWN LIMITATIONS
141 - style info should be placed in <head> section. I believe
142 it shouldnt be too hard to modify to point to an external
143 stylesheet instead.
144 - no support (yet?):
145 * chains like P UL LI { .... } or P UL LI.class { .... }
146 * #divname p { ... } and <tag id="...">
147 * a:hover, a:visited {...} multiple class:pseudo
148 They require a significantly more complicated processing likely
149 based on stylesheet and document trees parsing.
150 Many email clients don't handle more than what is supported
151 by this script anyway.
152 - pseudo-classes like a:hover {...} can't be inserted inline
153 in the html tags: they are moved to a <style> declaration in
154 the <body> instead. This is a limitation from html, not this script.
155 - It is still up to you to check if target email clients render
156 your css styled templates correctly, especially webmail services
157 like Hotmail, in which the email becomes a sub-part of an html page,
158 with styles already in place.
159 */
160 function facteur_convertir_styles_inline($body){
161 // variables to be accessed in the callback sub-function too
162 global $styledefinition, $styletag, $styleclass;
163
164 // Let's first load the stylesheet information in a $styles array using a regexp
165 preg_match_all ( "/^[ \t]*([.]?)([\w, #]+)([.:])?(\S*)\s+{([^}]+)}/mi", $body , $styles);
166 /*
167 $styles[1] = . or '' => .class or tag (empty)
168 $styles[2] = name of class or tag(s)
169 $styles[3] = : . or '' => followed by pseudo-element, class separator or nothing (empty)
170 $styles[4] = name of pseudo-element after a tag, if any
171 $styles[5] = the style definition itself, i.e. what's between the { }
172 */
173
174 // Now loop through the styles found and act accordingly;
175
176 // process TAG {...} & TAG1, TAG2,... {...} definitions only first by order of appearance
177 foreach ($styles[1] as $i => $type) {
178 if ($type=="" && $styles[3][$i]=="") {
179 $styledefinition = trim($styles[5][$i]);
180 $styletag = preg_replace("/ *, */", "|", trim($styles[2][$i])); //echo $styletag."<br />";
181 $styleclass = "";
182 // process TAG {...} and TAG1, TAG2 {...} but not TAG1 TAG2 {...} or #divname styles
183 if (!preg_match("/ /", $styletag) && !preg_match("/#/", $styletag)) {
184 $pattern = "!<(".$styletag.")([^>]*(?= /)|[^>]*)( /)?>!mi";
185 $body = preg_replace_callback ($pattern, 'facteur_addstyle' , $body);
186 $styles[6][$i]=1; // mark as injected inline
187 }
188 }
189 }
190
191 // append additional .CLASS {...} and TAG.CLASS {...} styling by order of appearance
192 // important to do so after TAG {...} definitions, so that class attributes override TAG styles when needed
193 foreach ($styles[1] as $i => $type) {
194 if ($type!="." && $styles[3][$i]=="." ) { // class definition for a specific tag
195 $styledefinition = trim($styles[5][$i]);
196 $styletag = trim($styles[2][$i]);
197 $styleclass = trim($styles[4][$i]);
198 $pattern = "!<(".$styletag.")([^>]* class\=['\"][^'\"]*".$styleclass."[^'\"]*['\"][^>]*(?= /)|[^>]* class\=['\"][^'\"]*".$styleclass."[^'\"]*['\"][^>]*)( />)?>!mi";
199 $body = preg_replace_callback ($pattern, 'facteur_addstyle' , $body);
200 $styles[6][$i]=1; // mark as injected inline
201
202 }
203 elseif ($type=="." && $styles[3][$i]=="" ) { // general class definition for any tag
204 $styledefinition = trim($styles[5][$i]);
205 $styletag = "";
206 $styleclass = trim($styles[2][$i]);
207 $pattern = "!<(\w+)([^>]* class\=['\"]".$styleclass."['\"][^>]*(?= /)|[^>]* class\=['\"]".$styleclass."['\"][^>]*)( />)?>!mi";
208 $body = preg_replace_callback ($pattern, 'facteur_addstyle' , $body);
209 $styles[6][$i]=1; // mark as injected inline
210 }
211 }
212
213
214 /* move all style declarations that weren't injected from <head> to a <body> <style> section,
215 including but not limited to:
216 - pseudo-classes like a:hover {...} as they can't be set inline
217 - declaration chains like UL LI {...}
218 - #divname {...}. These are not supported by email clients like Mac/Entourage anyway, it seems. */
219 foreach ($styles[1] as $i => $type) {
220 if ($styles[6][$i]=="") {
221 // add a <style type="text/css"> section after <body> if there's isn't one yet
222 if (preg_match ("!<body[^>]*>\s*<style!mi", $body)==0) {
223 $body = preg_replace ("/(<body[^>]*>)/i", "\n\$1\n".'<style type="text/css">'."\n<!--\n-->\n</style>\n", $body);
224 }
225 // append a copy of the pseudo-element declaration to that body style section
226 $styledefinition = trim($styles[5][$i]);
227 $styledefinition = preg_replace ("!\s+!mi", " ", $styledefinition ); // convert style definition to a one-liner (optional)
228 $declaration = $styles[1][$i].trim($styles[2][$i]).$styles[3][$i].trim($styles[4][$i])." { ".$styledefinition." }";
229 $body = preg_replace ("!(<body[^>]*>\s*<style[^>]*>\s*<\!\-\-[^>]*)"."(\s*\-\->\s*</style>)!si", "\$1".$declaration."\n\$2", $body);
230 $styles[6][$i]= 2; // mark as moved to <style> section in <body>
231 }
232 }
233
234 // remove stylesheet declaration(s) from <head> section (comment following line out if not wanted)
235 //$body = preg_replace ("!(<head>.*)<style type.*</style>(.*</head>)!si", "\$1\$2" , $body);
236
237 // check what styles have been injected
238 # print_r($styles);
239
240 return $body;
241 }
242
243 /**
244 * facteur_addstyle
245 * @author Eric Dols
246 *
247 * @param $matches
248 * @return string
249 */
250 function facteur_addstyle($matches) {
251
252 // $matches[1]=tag, $matches[2]=tag attributes (if any), $matches[3]=xhtml closing (if any)
253
254 // variables values set in calling function
255 global $styledefinition, $styletag, $styleclass;
256
257 // convert the style definition to a one-liner
258 $styledefinition = preg_replace ("!\s+!mi", " ", $styledefinition );
259 // convert all double-quotes to single-quotes
260 $styledefinition = preg_replace ('/"/','\'', $styledefinition );
261
262 if (preg_match ("/style\=/i", $matches[2])) {
263 // add styles to existing style attribute if any already in the tag
264 $pattern = "!(.* style\=)[\"]([^\"]*)[\"](.*)!mi";
265 $replacement = "\$1".'"'."\$2 ".$styledefinition.'"'."\$3";
266 $attributes = preg_replace ($pattern, $replacement , $matches[2]);
267 } else {
268 // otherwise add new style attribute to tag (none was present)
269 $attributes = $matches[2].' style="'.$styledefinition.'"';
270 }
271
272 if ($styleclass!="") {
273 // if we were injecting a class style, remove the now useless class attribute from the html tag
274
275 // Single class in tag case (class="classname"): remove class attribute altogether
276 $pattern = "!(.*) class\=['\"]".$styleclass."['\"](.*)!mi";
277 $replacement = "\$1\$2";
278 $attributes = preg_replace ( $pattern, $replacement, $attributes);
279
280 // Multiple classes in tag case (class="classname anotherclass..."): remove class name from class attribute.
281 // classes are injected inline and removed by order of appearance in <head> stylesheet
282 // exact same behavior as where last declared class attributes in <style> take over (IE6 tested only)
283 $pattern = "!(.* class\=['\"][^\"]*)(".$styleclass." | ".$styleclass.")([^\"]*['\"].*)!mi";
284 $replacement = "\$1\$3";
285 $attributes = preg_replace ( $pattern, $replacement, $attributes);
286
287 }
288
289 return "<".$matches[1].$attributes.$matches[3].">";
290 }
291
292 /**
293 * Un filtre pour transformer les retour ligne texte en br si besoin (si pas autobr actif)
294 *
295 * @param string $texte
296 * @return string
297 */
298 function facteur_nl2br_si_pas_autobr($texte){
299 if (_AUTOBR) return $texte;
300 include_spip("inc/filtres");
301 $texte = post_autobr($texte);
302 return $texte;
303 }
304
305 /**
306 * Transformer un mail HTML en mail Texte proprement :
307 * - les tableaux de mise en page sont utilis�s pour structurer le mail texte
308 * - le reste du HTML est markdownifie car c'est un format texte lisible et conventionnel
309 *
310 * @param string $html
311 * @return string
312 */
313 function facteur_mail_html2text($html){
314 // nettoyer les balises de mise en page html
315 $html = preg_replace(",</(td|th)>,Uims","<br/>",$html);
316 $html = preg_replace(",</(table)>,Uims","@@@hr@@@",$html);
317 $html = preg_replace(",</?(html|body|table|td|th|tbody|thead|center|article|section|span)[^>]*>,Uims","\n\n",$html);
318
319 // commentaires html et conditionnels
320 $html = preg_replace(",<!--.*-->,Uims","\n",$html);
321 $html = preg_replace(",<!\[.*\]>,Uims","\n",$html);
322
323 $html = preg_replace(",<(/?)(div|tr|caption)([^>]*>),Uims","<\\1p>",$html);
324 $html = preg_replace(",(<p>\s*)+,ims","<p>",$html);
325 $html = preg_replace(",<br/?>\s*</p>,ims","</p>",$html);
326 $html = preg_replace(",</p>\s*<br/?>,ims","</p>",$html);
327 $html = preg_replace(",(</p>\s*(@@@hr@@@)?\s*)+,ims","</p>\\2",$html);
328 $html = preg_replace(",(<p>\s*</p>),ims","",$html);
329
330 // succession @@@hr@@@<hr> et <hr>@@@hr@@@
331 $html = preg_replace(",@@@hr@@@\s*(<[^>]*>\s*)?<hr[^>]*>,ims","@@@hr@@@\n",$html);
332 $html = preg_replace(",<hr[^>]*>\s*(<[^>]*>\s*)?@@@hr@@@,ims","\n@@@hr@@@",$html);
333
334 $html = preg_replace(",<textarea[^>]*spip_cadre[^>]*>(.*)</textarea>,Uims","<code>\n\\1\n</code>",$html);
335
336 // vider le contenu de qqunes :
337 $html = preg_replace(",<head[^>]*>.*</head>,Uims","\n",$html);
338
339 // Liens :
340 // Nettoyage des liens des notes de bas de page
341 $html = preg_replace("@<a href=\"#n(b|h)[0-9]+-[0-9]+\" name=\"n(b|h)[0-9]+-[0-9]+\" class=\"spip_note\">([0-9]+)</a>@", "\\3", $html);
342 // Supprimer tous les liens internes
343 $html = preg_replace("/\<a href=['\"]#(.*?)['\"][^>]*>(.*?)<\/a>/ims","\\2", $html);
344 // Remplace tous les liens
345 preg_match_all("/\<a href=['\"](.*?)['\"][^>]*>(.*?)<\/a>/ims", $html,$matches,PREG_SET_ORDER);
346 $prelinks = $postlinks = array();
347 if (!function_exists('url_absolue'))
348 include_spip('inc/filtres');
349 foreach ($matches as $k => $match){
350 $link = "@@@link$k@@@";
351 $url = str_replace("&amp;","&",$match[1]);
352 if ($match[2]==$match[1] OR $match[2]==$url){
353 // si le texte est l'url :
354 $prelinks[$match[0]] = "$link";
355 }
356 else {
357 // texte + url
358 $prelinks[$match[0]] = $match[2] . " ($link)";
359 }
360 // passer l'url en absolu dans le texte sinon elle n'est pas clicable ni utilisable
361 $postlinks[$link] = url_absolue($url);
362 }
363 $html = str_replace(array_keys($prelinks), array_values($prelinks),$html);
364
365 // les images par leur alt ?
366 // au moins les puces
367 $html = preg_replace(',<img\s[^>]*alt="-"[^>]*>,Uims','-',$html);
368 // les autres
369 $html = preg_replace(',<img\s[^>]*alt=[\'"]([^\'"]*)[\'"][^>]*>,Uims',"\\1",$html);
370 // on vire celles sans alt
371 $html = preg_replace(",</?(img)[^>]*>,Uims","\n",$html);
372
373 // espaces
374 $html = str_replace("&nbsp;"," ",$html);
375 $html = preg_replace(",<p>\s+,ims","<p>",$html);
376
377 #return $html;
378 include_spip("lib/markdownify/markdownify");
379 $parser = new Markdownify('inline',false,false);
380 $texte = $parser->parseString($html);
381
382 $texte = str_replace(array_keys($postlinks), array_values($postlinks),$texte);
383
384
385 // trim et sauts de ligne en trop ou pas assez
386 $texte = trim($texte);
387 $texte = str_replace("<br />\n","\n",$texte);
388 $texte = preg_replace(",(@@@hr@@@\s*)+\Z,ims","",$texte);
389 $texte = preg_replace(",(@@@hr@@@\s*\n)+,ims","\n\n\n".str_pad("-",75,"-")."\n\n\n",$texte);
390 $texte = preg_replace(",(\n#+\s),ims","\n\n\\1",$texte);
391 $texte = preg_replace(",(\n\s*)(\n\s*)+(\n)+,ims","\n\n\n",$texte);
392
393
394 // <p> et </p> restants
395 $texte = str_replace(array("<p>","</p>"),array("",""),$texte);
396
397 // entites restantes ? (dans du code...)
398 include_spip('inc/charsets');
399 $texte = unicode2charset($texte);
400 $texte = str_replace(array('&#039;', '&#034;'),array("'",'"'), $texte);
401
402
403 // Faire des lignes de 75 caracteres maximum
404 return trim(wordwrap($texte));
405 }
406 ?>