source: spip-zone/_plugins_/mailshot/trunk/inc/mailshot.php @ 112809

Last change on this file since 112809 was 112809, checked in by cedric@…, 22 months ago

Ajout d'une option graceful sur les envois qui evite d'envoyer la meme newsletter a un email qui l'a deja recue
Cela s'accompagne d'une option dans le formulaire d'envoi, sur les newsletters, decochee par defaut : [ ] Envoyer à nouveau aux destinataires qui l'ont déjà reçue
(par defaut donc, desormais, on n'envoie plus en double)
Techniquement il y a un champ graceful en base sur les mailshots.
Par contre quand on construit la liste des destinataires on enumere bien tous les destinataires comme avant, mais simplement pour chaque envoi on verifie d'abord si cet email a deja recu la newsletter et si oui on recupere directement le statut et la date du dernier envoi sans rien faire
(le destinataire apparait donc bien dans la liste, en sent ou read ou open, avec la date de l'envoi precedent)
A priori ca doit marcher, mais je n'ai pas encore teste en live

File size: 13.7 KB
Line 
1<?php
2/**
3 * Plugin MailShot
4 * (c) 2012 Cedric Morin
5 * Licence GNU/GPL
6 */
7
8if (!defined('_ECRIRE_INC_VERSION')) return;
9
10/**
11 * Mettre a jour les stats d'un envoi
12 * @param $id_mailshot
13 */
14function mailshot_compter_envois($id_mailshot){
15
16        // todo, sent, fail, [read, [clic]],[spam]
17        $total = sql_countsel("spip_mailshots_destinataires","id_mailshot=".intval($id_mailshot));
18        // si aucun destinataires en base on ne fait rien, pour ne pas risquer de tuer les stats d'un envoi purge
19        if (!$total) {
20                return;
21        }
22
23        $statuts = sql_allfetsel("statut,count(email) as nb","spip_mailshots_destinataires","id_mailshot=".intval($id_mailshot),"statut");
24        $statuts = array_combine(array_map('reset',$statuts),array_map('end',$statuts));
25        #var_dump($statuts);
26        $set = array(
27                'total' => $total,
28                'failed'  => 0,
29                "nb_read" => 0,
30                "nb_clic" => 0,
31                "nb_spam" => 0,
32        );
33        if (isset($statuts['fail']))
34                $set['failed'] = $statuts['fail'];
35        if (isset($statuts['read']))
36                $set['nb_read'] = $statuts['read'];
37        if (isset($statuts['clic'])){
38                $set['nb_read'] += $statuts['clic']; // les clics sont aussi des lus
39                $set['nb_clic'] = $statuts['clic'];
40        }
41        if (isset($statuts['spam']))
42                $set['nb_spam'] = $statuts['spam'];
43
44        // current c'est tous les envoyes (y compris fails)
45        unset($statuts['todo']);
46        $set['current'] = array_sum($statuts);
47        #var_dump($set);
48        sql_updateq("spip_mailshots",$set,"id_mailshot=".intval($id_mailshot));
49}
50
51
52/**
53 * Archiver un envoi (vieux en cron ou a la demande)
54 * @param $id_mailshot
55 */
56function mailshot_archiver($id_mailshot){
57        // mettre a jour les stats avant de purger
58        mailshot_compter_envois($id_mailshot);
59        sql_delete("spip_mailshots_destinataires",'id_mailshot='.intval($id_mailshot));
60        sql_updateq("spip_mailshots",array('statut'=>'archive'),'id_mailshot='.intval($id_mailshot));
61        spip_log("Archiver mailshot $id_mailshot","mailshot");
62}
63
64/**
65 * Definir la combinaison (periode,nb envois) pour respecter la cadence maxi configuree
66 *
67 * @return array
68 *   (periode du cron, nb envois a chaque appel)
69 */
70function mailshot_cadence(){
71        include_spip('inc/config');
72        if (lire_config("mailshot/boost_send")=='oui')
73                return array(30,100); // autant que possible, toutes les 30s
74
75        // cadence maxi
76        $cadence = array(60,10);
77        $max_rate = lire_config("mailshot/rate_limit");
78        if ($max_rate = intval($max_rate)){
79                $rate_one_per_one = 24*60*60/$cadence[0];
80                if ($max_rate<$rate_one_per_one){
81                        // 1 mail toutes les N secondes pour ne pas en envoyer plus que le rate demande
82                        $cadence = array(intval(ceil($rate_one_per_one/$max_rate*$cadence[0])),1);
83                }
84                else if($max_rate>$rate_one_per_one*$cadence[1]){
85                        // rien on garde la cadence maxi
86                }
87                else {
88                        // envoyer N mails toutes les M secondes pour respecter la cadence max
89                        $nb = $max_rate/$rate_one_per_one;
90                        // on se cale sur le N superieur et on ajuste le delai entre chaque envoi
91                        $nb = intval(ceil($nb));
92                        $cadence = array(intval(ceil($nb*$rate_one_per_one/$max_rate*$cadence[0])),$nb);
93                }
94        }
95
96        spip_log("cadence:".implode(",",$cadence),"mailshot");
97        return $cadence;
98}
99
100/**
101 * Mettre a jour la meta qui indique qu'au moins un envoi est en cours
102 * evite un acces sql a chaque hit du cron
103 *
104 * @param bool $force
105 * @return bool
106 */
107function mailshot_update_meta_processing($force = false){
108        $current = ((isset($GLOBALS['meta']['mailshot_processing']) AND $GLOBALS['meta']['mailshot_processing'])?$GLOBALS['meta']['mailshot_processing']:false);
109
110        $new = false;
111        if ($force OR sql_countsel("spip_mailshots","statut=".sql_quote('processing')))
112                $new = 'oui';
113        if ($new===false and $next = sql_getfetsel('date_start','spip_mailshots',"statut=".sql_quote('init'),'','date_start','0,1')){
114                $new = strtotime($next);
115                if ($new>$_SERVER['REQUEST_TIME']){
116                        $new = 'oui';
117                }
118        }
119
120        if ($new OR $new!==$current){
121                if ($new) {
122                        ecrire_meta("mailshot_processing",$new);
123                        // reprogrammer le cron
124                        include_spip('inc/genie');
125            genie_queue_watch_dist();
126                }
127                else
128                        effacer_meta('mailshot_processing');
129        }
130
131        return $new;
132}
133
134/**
135 * Envoyer une serie de mails
136 * @param int $nb_max
137 * @param int $offset
138 *   pour ne pas commencer au debut de la liste (utile pour les processus paralleles)
139 * @return int
140 *   nombre de mails envoyes
141 */
142function mailshot_envoyer_lot($nb_max=5,$offset=0){
143        $nb_restant = $nb_max;
144        $now = $_SERVER['REQUEST_TIME'];
145        if (!$now) $now=time();
146        define('_MAILSHOT_MAX_TIME',$now+25); // 25s maxi
147        define('_MAILSHOT_MAX_TRY',5); // 5 essais maxis par destinataires
148
149        $offset = intval($offset);
150        // on traite au maximum 2 serie d'envois dans un appel
151        $shot = sql_allfetsel("*","spip_mailshots","statut=".sql_quote('processing'),'','id_mailshot','0,2');
152        foreach($shot as $shoot){
153                spip_log("mailshot_envoyer_lot #".$shoot['id_mailshot']." ".$shoot['current']."/".$shoot['total']." (max $nb_max)","mailshot");
154
155                // verifier que la liste des destinataires est OK
156                mailshot_initialiser_destinataires($shoot);
157                if (time()>_MAILSHOT_MAX_TIME) return $nb_restant;
158                $listes = explode(',',$shoot['listes']);
159
160                // chercher les N prochains destinataires
161                $dests = sql_allfetsel("*","spip_mailshots_destinataires","id_mailshot=".intval($shoot['id_mailshot'])." AND statut=".sql_quote('todo'),'','try',"$offset,$nb_max");
162                if (count($dests)){
163                        $options = array('tracking_id'=>"mailshot".intval($shoot['id_mailshot'])."-".date('Ym',strtotime($shoot['date_start'])));
164                        $subscriber = charger_fonction("subscriber","newsletter");
165                        $send = charger_fonction("send","newsletter");
166                        $corps = array(
167                                'sujet' => &$shoot['sujet'],
168                                'html' => &$shoot['html'],
169                                'texte' => &$shoot['texte'],
170                                'from' => $shoot['from_email'],
171                                'nom_envoyeur' => $shoot['from_name'],
172                        );
173                        foreach($dests as $d){
174                                if (time()>_MAILSHOT_MAX_TIME) return $nb_restant;
175                                $s = $subscriber($d['email'],array('listes'=>$listes));
176                                $done = false;
177                                if (!$done = mailshot_verifier_email_envoi_bloque($d['email'], $s, $shoot)) {
178                                        spip_log("mailshot_envoyer_lot #".$shoot['id_mailshot']."/".$d['email']." send","mailshot");
179                                        $erreur = $send($s, $corps, $options);
180                                        $try = $d['try']+1;
181                                        if ($erreur){
182                                                if ($try>=_MAILSHOT_MAX_TRY
183                                                        OR preg_match(",@example\.org$,i",$s['email'])
184                                                  OR defined('_TEST_EMAIL_DEST')){
185                                                        $done = array(
186                                                                'fail' => true,
187                                                                'statut' => 'fail',
188                                                                'try' => $try,
189                                                                'date'=>date('Y-m-d H:i:s'),
190                                                                'log' => "ERREUR [$erreur] / failed apres $try essais",
191                                                        );
192                                                        // si c'est un fail max_try verifier et desinscrire eventuellement
193                                                        if ($try>1){
194                                                                mailshot_verifier_email_fail($d['email']);
195                                                        }
196                                                }
197                                                else {
198                                                        $done = array(
199                                                                'try' => $try,
200                                                                'date'=>date('Y-m-d H:i:s'),
201                                                                'log' => "INFO Probleme [$erreur] (essai $try)",
202                                                        );
203                                                }
204                                        }
205                                        else {
206                                                $done = array(
207                                                        'done' => true,
208                                                        'statut'=>'sent',
209                                                        'try'=>$try,
210                                                        'date'=>date('Y-m-d H:i:s'),
211                                                        'log' => "OK",
212                                                );
213                                        }
214                                }
215                                if ($done) {
216                                        if (isset($done['done'])) {
217                                                $nb_restant--;
218                                                unset($done['done']);
219                                                sql_update("spip_mailshots",array("current"=>"current+1"),"id_mailshot=".intval($shoot['id_mailshot']));
220                                        }
221                                        if (isset($done['fail'])) {
222                                                unset($done['fail']);
223                                                sql_update("spip_mailshots",array("current"=>"current+1","failed"=>"failed+1"),"id_mailshot=".intval($shoot['id_mailshot']));
224                                        }
225                                        if (isset($done['log'])) {
226                                                $loglevel = '';
227                                                if (strncmp($done['log'], 'ERREUR', 6) == 0) {
228                                                        $loglevel = _LOG_ERREUR;
229                                                }
230                                                elseif (strncmp($done['log'], 'INFO', 4) == 0) {
231                                                        $loglevel = _LOG_INFO_IMPORTANTE;
232                                                }
233                                                spip_log("mailshot_envoyer_lot #".$shoot['id_mailshot']."/".$d['email']." : " . $done['log'], "mailshot" . $loglevel);
234                                                unset($done['log']);
235                                        }
236                                        sql_updateq("spip_mailshots_destinataires", $done,"id_mailshot=".intval($shoot['id_mailshot'])." AND email=".sql_quote($d['email']));
237                                }
238                                $nb_max--;
239                        }
240                        // si $nb_max non nul verifier qu'il n'y a plus de dests sur cette envoi pour maj le statut juste en dessous
241                        if ($nb_max)
242                                $dests = sql_allfetsel("*","spip_mailshots_destinataires","id_mailshot=".intval($shoot['id_mailshot'])." AND statut=".sql_quote('todo'),'','try',"$offset,$nb_max");
243                }
244
245                if ($nb_max AND !count($dests) AND $offset==0){
246                        // plus de destinataires ? on a fini, on met a jour compteur et statut
247                        $set = array(
248                                'statut' => 'end',
249                                'date' => date('Y-m-d H:i:s'),
250                        );
251                        sql_updateq("spip_mailshots",$set,"id_mailshot=".intval($shoot['id_mailshot']));
252                        mailshot_compter_envois($shoot['id_mailshot']);
253                        mailshot_update_meta_processing();
254                }
255                if (!$nb_max OR time()>_MAILSHOT_MAX_TIME) return $nb_restant;
256        }
257
258        return 0; // plus rien a envoyer sur ce lot
259}
260
261/**
262 * Verifie avant nouvel envoi un email et bloque un envoi si besoin
263 *
264 * @param string $email
265 * @param array $subscriber
266 * @param array $shoot
267 * @return array|bool
268 */
269function mailshot_verifier_email_envoi_bloque($email, $subscriber, $shoot) {
270        static $envois_idem = array();
271
272        if (preg_match(",@example\.org$,i",$subscriber['email'])) {
273                return array(
274                        'fail' => true,
275                        'statut' => 'fail',
276                        'date' => date('Y-m-d H:i:s'),
277                        'log' => "INFO Envoi BLOQUE Email obfusque ".$subscriber['email']."",
278                );
279        }
280        if (_TEST_EMAIL_DEST) {
281                $erreur = _T('mailshot:erreur_envoi_mail_force_debug',array('email'=>_TEST_EMAIL_DEST));
282                return array(
283                        'fail' => true,
284                        'statut' => 'fail',
285                        'date' => date('Y-m-d H:i:s'),
286                        'log' => "INFO Envoi BLOQUE $erreur",
287                );
288        }
289
290        if ($shoot['graceful']) {
291                // trouver tous les envois pour le meme id
292                if (!isset($envois_idem[$shoot['id_mailshot']])) {
293                        $idem = sql_allfetsel('id_mailshot', 'spip_mailshots', 'id='.sql_quote($shoot['id'],'','text'));
294                        $envois_idem[$shoot['id_mailshot']] = array_column($idem, 'id_mailshot');
295                }
296                if ($last = sql_fetsel('*', 'spip_mailshots_destinataires',
297                        array(
298                                'email=' . sql_quote($email),
299                                sql_in('statut', array('todo', 'fail'), 'NOT'),
300                                sql_in('id_mailshot', $envois_idem[$shoot['id_mailshot']]),
301                        ),'', 'date DESC', '0,1')) {
302                        // on a trouve un envoi deja fait a la meme newsletter, on passe notre tour pout celui-ci
303                        $done = array(
304                                'done' => true,
305                                'statut' => $last['statut'],
306                                'date' => $last['date'],
307                                'log' => "INFO Envoi BLOQUE newsletter #".$shoot['id']." deja envoyee a $email par mailshot#" . $last['id_mailshot'] . " / " . $last['date']
308                        );
309                        return $done;
310                }
311        }
312
313        return false;
314
315}
316
317/**
318 * Verifier un email en fail et si plus de N fails consecutifs le desabonner (email foireux)
319 * (MAIS ignorer les envois qui n'ont que des fails car c'est un blocage du compte du prestataire d'envoi)
320 * @param $email
321 */
322function mailshot_verifier_email_fail($email) {
323        static $mailshot_valides = array();
324
325        if (_MAILSHOT_DESABONNER_FAILED != false) {
326                if (!defined('_MAILSHOT_MAX_FAIL')) {
327                        define('_MAILSHOT_MAX_FAIL', 3);
328                }
329
330                $historique = sql_allfetsel(
331                        'id_mailshot, date, statut, try',
332                        'spip_mailshots_destinataires',
333                        'statut!=' . sql_quote('todo') . ' AND email=' . sql_quote($email),
334                        '',
335                        'date DESC',
336                        "0," . _MAILSHOT_MAX_FAIL
337                );
338
339                $nb_failed = 0;
340                foreach ($historique as $h) {
341                        if ($h['statut'] == 'fail' AND $h['try'] > 1) {
342                                // on ne compte le fail que si l'envoi a reussi au moins une fois
343                                // pour ne pas perdre tous les abonnes quand le service d'envoi bloque le compte
344                                if (!isset($mailshot_valides[$h['id_mailshot']])) {
345                                        $mailshot_valides[$h['id_mailshot']] = sql_getfetsel('id_mailshot', 'spip_mailshots_destinataires', sql_in('statut', array('todo','fail'),'NOT').' AND id_mailshot='.intval($h['id_mailshot']),'','','0,1');
346                                }
347                                if ($mailshot_valides[$h['id_mailshot']]) {
348                                        $nb_failed++;
349                                }
350                        }
351                }
352                if ($nb_failed >= _MAILSHOT_MAX_FAIL) {
353                        $unsubscribe = charger_fonction("unsubscribe", "newsletter");
354                        $unsubscribe($email, array('notify' => false));
355                }
356        }
357}
358
359/**
360 * Initialiser les destinataires d'un envoi
361 * = noter tous les emails a qui envoyer, au debut
362 * (fige la liste en debut d'envoi, evite les risques de sauter un destinataire si on se base seulement sur un compteur
363 * et sur les abonnes en cours car certains peuvent se desabonner pendant le processus d'envoi qui dure dans le temps)
364 *
365 * @param array $shoot
366 */
367function mailshot_initialiser_destinataires($shoot){
368
369        // verifier qu'on a bien initialise tous les destinataires
370        $nbd = sql_countsel("spip_mailshots_destinataires","id_mailshot=".intval($shoot['id_mailshot']));
371        if ($nbd<$shoot['total']){
372                spip_log("mailshot_initialiser_destinataires #".$shoot['id_mailshot']." : $nbd/".$shoot['total'],"mailshot");
373
374                // sinon reprendre l'insertion
375                $nb_lot = 2500;
376                $current = $nbd;
377                $listes = explode(',',$shoot['listes']);
378                $now = date('Y-m-d H:i:s');
379                $subscribers = charger_fonction("subscribers","newsletter");
380                do {
381                        if (time()>_MAILSHOT_MAX_TIME) return;
382                        $limit = "$current,$nb_lot";
383                        $dests = $subscribers($listes,array('limit'=>$limit));
384
385                        if (count($dests)){
386                                // preparer les insertions
387                                $ins = array();
388                                foreach ($dests as $d){
389                                        $ins[] = array(
390                                                'id_mailshot' => $shoot['id_mailshot'],
391                                                'email' => $d['email'],
392                                                'date' => $now,
393                                                'statut' => 'todo',
394                                        );
395                                }
396
397                                if (!sql_insertq_multi('spip_mailshots_destinataires',$ins)){
398                                        foreach ($ins as $i){
399                                                sql_insertq('spip_mailshots_destinataires',$i);
400                                                if (time()>_MAILSHOT_MAX_TIME) return;
401                                        }
402                                }
403                        }
404                        $current = $current + count($dests);
405                }
406                while (count($dests));
407
408                // ici on a fini toutes les init des destinataires
409                // on remet a jour le compteur de total au cas ou
410                $nbd = sql_countsel("spip_mailshots_destinataires","id_mailshot=".intval($shoot['id_mailshot']));
411                if ($nbd<$shoot['total'])
412                        sql_updateq("spip_mailshots",array('total'=>$nbd),"id_mailshot=".intval($shoot['id_mailshot']));
413                spip_log("mailshot_initialiser_destinataires #".$shoot['id_mailshot']." OK ($nbd)","mailshot");
414        }
415
416}
Note: See TracBrowser for help on using the repository browser.