dolibarr 25.0.0-alpha
modules_mailings.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2008 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
5 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
6 * Copyright (C) 2025-2026 Frédéric France <frederic.france@free.fr>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 * or see https://www.gnu.org/
21 */
22
33class MailingTargets // This can't be abstract as it is used for some method
34{
38 public $db;
39
43 public $error = '';
44
48 public $errors;
49
53 public $enabled;
54
58 public $name;
59
63 public $desc;
64
68 public $tooltip = '';
69
73 public $sql;
74
75
79 public $evenunsubscribe = 0;
80
81
87 public function __construct($db)
88 {
89 $this->db = $db;
90 }
91
97 public function getDesc()
98 {
99 global $langs, $form;
100
101 $langs->load("mails");
102 $transstring = "MailingModuleDesc".$this->name;
103 $s = '';
104
105 if ($langs->trans($this->name) != $this->name) {
106 $s = $langs->trans($this->name);
107 } elseif ($langs->trans($transstring) != $transstring) {
108 $s = $langs->trans($transstring);
109 } else {
110 $s = $this->desc;
111 }
112
113 if ($this->tooltip && is_object($form)) {
114 $s .= ' '.$form->textwithpicto('', $langs->trans($this->tooltip), 1, 'help');
115 }
116 return $s;
117 }
118
127 public function getSqlToExcludeUnsubscribed($emailfield)
128 {
129 global $conf;
130
131 if (!empty($this->evenunsubscribe)) {
132 return '';
133 }
134
135 // $emailfield is a column expression of the outer query (e.g. 's.email'); it is compared to mu.email.
136 $sql = " AND NOT EXISTS (SELECT rowid FROM ".$this->db->prefix()."mailing_unsubscribe as mu";
137 $sql .= " WHERE ".$this->db->sanitize($emailfield)." = mu.email";
138 $sql .= " AND mu.entity = ".((int) $conf->entity).")";
139
140 return $sql;
141 }
142
148 public function getNbOfRecords()
149 {
150 return 0;
151 }
152
159 public function getNbOfRecipients($sql)
160 {
161 $result = $this->db->query($sql);
162 if ($result) {
163 $total = 0;
164 while ($obj = $this->db->fetch_object($result)) {
165 $total += (int) $obj->nb;
166 }
167 return $total;
168 } else {
169 $this->error = $this->db->lasterror();
170 return -1;
171 }
172 }
173
179 public function formFilter()
180 {
181 return '';
182 }
183
184 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
191 public function update_nb($mailing_id)
192 {
193 // phpcs:enable
194 // Update the number of recipients in the mailing table
195 $sql = "SELECT COUNT(*) nb FROM ".$this->db->prefix()."mailing_cibles";
196 $sql .= " WHERE fk_mailing = ".((int) $mailing_id);
197 $result = $this->db->query($sql);
198 if ($result) {
199 $obj = $this->db->fetch_object($result);
200 $nb = (int) $obj->nb;
201
202 $sql = "UPDATE ".$this->db->prefix()."mailing";
203 $sql .= " SET nbemail = ".((int) $nb)." WHERE rowid = ".((int) $mailing_id);
204 if (!$this->db->query($sql)) {
205 dol_syslog($this->db->error());
206 $this->error = $this->db->error();
207 return -1;
208 }
209 } else {
210 return -1;
211 }
212 return $nb;
213 }
214
222 public function addTargetsToDatabase($mailing_id, $cibles)
223 {
224 global $conf;
225
226 $this->db->begin();
227
228
229 // Insert emailing targets from array into database
230 $j = 0;
231 $num = count($cibles);
232 foreach ($cibles as $targetarray) {
233 if (!empty($targetarray['email'])) { // avoid empty email address
234 $sql = "INSERT INTO ".$this->db->prefix()."mailing_cibles";
235 $sql .= " (fk_mailing,";
236 $sql .= " fk_contact,";
237 $sql .= " lastname, firstname, email, other, source_url, source_id,";
238 $sql .= " tag,";
239 $sql .= " source_type)";
240 $sql .= " VALUES (".((int) $mailing_id).",";
241 $sql .= (empty($targetarray['fk_contact']) ? '0' : (int) $targetarray['fk_contact']).",";
242 $sql .= "'".$this->db->escape($targetarray['lastname'])."',";
243 $sql .= "'".$this->db->escape($targetarray['firstname'])."',";
244 $sql .= "'".$this->db->escape($targetarray['email'])."',";
245 $sql .= "'".$this->db->escape($targetarray['other'])."',";
246 $sql .= "'".$this->db->escape($targetarray['source_url'])."',";
247 $sql .= (empty($targetarray['source_id']) ? 'null' : (int) $targetarray['source_id']).",";
248 $sql .= "'".$this->db->escape(dol_hash($conf->file->instance_unique_id.";".$targetarray['email'].";".$targetarray['lastname'].";".((int) $mailing_id).";".getDolGlobalString('MAILING_EMAIL_UNSUBSCRIBE_KEY'), 'md5'))."',";
249 $sql .= "'".$this->db->escape($targetarray['source_type'])."')";
250 dol_syslog(__METHOD__, LOG_DEBUG);
251 $result = $this->db->query($sql);
252 if ($result) {
253 $j++;
254 } else {
255 if ($this->db->errno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
256 // If error other than duplicate
257 dol_syslog($this->db->error().' : '.$targetarray['email']);
258 $this->error = $this->db->error().' : '.$targetarray['email'];
259 $this->db->rollback();
260 return -1;
261 }
262 }
263 }
264 }
265
266 dol_syslog(__METHOD__.": mailing ".$j." targets added");
267
268 /*
269 //Update the status to show third-party emails that no longer wish to be contacted'
270 $sql = "UPDATE ".$this->db->prefix()."mailing_cibles";
271 $sql .= " SET statut=3";
272 $sql .= " WHERE fk_mailing = ".((int) $mailing_id)." AND email in (SELECT email FROM ".$this->db->prefix()."societe where fk_stcomm=-1)";
273 $sql .= " AND source_type='thirdparty'";
274 dol_syslog(__METHOD__.": mailing update status to display third-party emails that no longer wish to be contacted");
275 $result=$this->db->query($sql);
276
277 //Update the status to show contact emails that no longer wish to be contacted'
278 $sql = "UPDATE ".$this->db->prefix()."mailing_cibles";
279 $sql .= " SET statut=3";
280 $sql .= " WHERE fk_mailing = ".((int) $mailing_id)." AND source_type='contact' AND (email in (SELECT sc.email FROM ".$this->db->prefix()."socpeople AS sc ";
281 $sql .= " INNER JOIN ".$this->db->prefix()."societe s ON s.rowid=sc.fk_soc WHERE s.fk_stcomm=-1 OR no_email=1))";
282 dol_syslog(__METHOD__.": mailing update status to display contact emails that no longer wish to be contacted",LOG_DEBUG);
283 $result=$this->db->query($sql);
284 */
285
286 if (empty($this->evenunsubscribe)) {
287 $sql = "UPDATE ".$this->db->prefix()."mailing_cibles as mc";
288 $sql .= " SET statut = 3";
289 $sql .= " WHERE fk_mailing = ".((int) $mailing_id);
290 $sql .= " AND EXISTS (SELECT rowid FROM ".$this->db->prefix()."mailing_unsubscribe as mu WHERE mu.email = mc.email and mu.entity = ".((int) $conf->entity).")";
291
292 dol_syslog(__METHOD__.":mailing update status to display emails that do not want to be contacted anymore", LOG_DEBUG);
293 $result = $this->db->query($sql);
294 if (!$result) {
295 dol_print_error($this->db);
296 }
297 }
298
299 // Update nb of recipient into emailing record
300 $this->update_nb($mailing_id);
301
302 $this->db->commit();
303
304 return $j;
305 }
306
307 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
314 public function clear_target($mailing_id)
315 {
316 // phpcs:enable
317 $sql = "DELETE FROM ".$this->db->prefix()."mailing_cibles";
318 $sql .= " WHERE fk_mailing = ".((int) $mailing_id);
319
320 if (!$this->db->query($sql)) {
321 dol_syslog($this->db->error());
322 }
323
324 $this->update_nb($mailing_id);
325 }
326
327
335 public static function getEmailingSelectorsList($forcedir = null)
336 {
337 global $langs, $db;
338
339 $files = array();
340 $fullpath = array();
341 $relpath = array();
342 $iscoreorexternal = array();
343 $modules = array();
344 $orders = array();
345 $i = 0;
346
347 $diremailselector = array('/core/modules/mailings/'); // $conf->modules_parts['emailings'] is not required
348 if (is_array($forcedir)) {
349 $diremailselector = $forcedir;
350 }
351
352 foreach ($diremailselector as $reldir) {
353 $dir = dol_buildpath($reldir, 0);
354 $newdir = dol_osencode($dir);
355
356 // Check if directory exists (we do not use dol_is_dir to avoid loading files.lib.php at each call)
357 if (!is_dir($newdir)) {
358 continue;
359 }
360
361 $handle = opendir($newdir);
362 if (is_resource($handle)) {
363 while (($file = readdir($handle)) !== false) {
364 $reg = array();
365 if (is_readable($newdir.'/'.$file) && preg_match('/^(.+)\.modules.php/', $file, $reg)) {
366 if (preg_match('/\.back$/', $file) || preg_match('/^(.+)\.disabled\.php/', $file)) {
367 continue;
368 }
369
370 $part1 = $reg[1];
371
372 //$modName = ucfirst($reg[1]);
373 $modName = 'mailing_'.$reg[1]; // name of selector submodule
374 //print "file=$file modName=$modName"; exit;
375 if (in_array($modName, $modules)) {
376 $langs->load("errors");
377 print '<div class="error">'.$langs->trans("Error").' : '.$langs->trans("ErrorDuplicateEmalingSelector", $modName, "").'</div>';
378 } else {
379 try {
380 //print $newdir.'/'.$file;
381 include_once $newdir.'/'.$file;
382 } catch (Exception $e) {
383 print $e->getMessage();
384 }
385 }
386
387 $files[$i] = $file;
388 $fullpath[$i] = $dir.'/'.$file;
389 $relpath[$i] = preg_replace('/^\//', '', $reldir).'/'.$file;
390 $iscoreorexternal[$i] = ($reldir == '/core/modules/mailings/' ? 'internal' : 'external');
391 $modules[$i] = $modName;
392 $orders[$i] = $part1; // Set sort criteria value
393
394 $i++;
395 }
396 }
397 closedir($handle);
398 }
399 }
400 //echo "<pre>";print_r($modules);echo "</pre>";
401
402 asort($orders);
403
404 $widget = array();
405 $j = 0;
406
407 // Loop on each emailing selector
408 foreach ($orders as $key => $value) {
409 $modName = $modules[$key];
410 if (empty($modName)) {
411 continue;
412 }
413
414 if (!class_exists($modName)) {
415 print 'Error: An emailing selector file was found but its class "'.$modName.'" was not found.'."<br>\n";
416 continue;
417 }
418
419 $objMod = new $modName($db);
420 if (is_object($objMod)) {
421 '@phan-var-force ModeleBoxes $objMod';
422 // Define disabledbyname and disabledbymodule
423 $disabledbyname = 0;
424 $disabledbymodule = 0; // TODO Set to 2 if module is not enabled
425 $module = '';
426
427 // Check if widget file is disabled by name
428 if (preg_match('/NORUN$/i', $files[$key])) {
429 $disabledbyname = 1;
430 }
431
432 // We set info of modules @phan-suppress-next-line PhanUndeclaredProperty
433 $widget[$j]['picto'] = (empty($objMod->picto) ? (empty($objMod->boximg) ? img_object('', 'generic') : $objMod->boximg) : img_object('', $objMod->picto));
434 $widget[$j]['file'] = $files[$key];
435 $widget[$j]['fullpath'] = $fullpath[$key];
436 $widget[$j]['relpath'] = $relpath[$key];
437 $widget[$j]['iscoreorexternal'] = $iscoreorexternal[$key];
438 $widget[$j]['version'] = empty($objMod->version) ? '' : $objMod->version;
439 $widget[$j]['status'] = img_picto($langs->trans("Active"), 'tick', 'class="pictofixedwidth"');
440 if ($disabledbyname > 0 || $disabledbymodule > 1) {
441 $widget[$j]['status'] = '';
442 }
443
444 $text = '<b>'.$langs->trans("Description").':</b><br>';
445 $text .= $objMod->boxlabel.'<br>';
446 $text .= '<br><b>'.$langs->trans("Status").':</b><br>';
447 if ($disabledbymodule == 2) {
448 $text .= $langs->trans("WidgetDisabledAsModuleDisabled", $module).'<br>';
449 }
450
451 $widget[$j]['info'] = $text;
452 }
453 $j++;
454 }
455
456 return $widget;
457 }
458
459
468 public function getSqlArrayForStats()
469 {
470 // Needs to be implemented in child class
471 $msg = get_class($this)."::".__FUNCTION__." not implemented";
472 dol_syslog($msg, LOG_ERR);
473 return array();
474 }
475
476 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
483 public function add_to_target($mailing_id)
484 {
485 // phpcs:enable
486 // Needs to be implemented in child class
487 $msg = get_class($this)."::".__FUNCTION__." not implemented";
488 dol_syslog($msg, LOG_ERR);
489 return -1;
490 }
491}
Parent class of emailing target selectors modules.
getSqlToExcludeUnsubscribed($emailfield)
Return the SQL fragment that excludes email addresses which opted out of emailings for the current en...
__construct($db)
Constructor.
add_to_target($mailing_id)
Add destinations in the targets table.
static getEmailingSelectorsList($forcedir=null)
Return list of widget.
addTargetsToDatabase($mailing_id, $cibles)
Add a list of targets into the database.
getDesc()
Return description of email selector.
getNbOfRecipients($sql)
Return the number of recipients.
clear_target($mailing_id)
Deletes all recipients from the targets table.
update_nb($mailing_id)
Update the number of recipients.
formFilter()
Displays filter form that appears on the mailing recipient selection page.
getNbOfRecords()
Return number of records for email selector.
getSqlArrayForStats()
On the main mailing area, there is a box with statistics.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:133
dol_hash($chain, $type='0', $nosalt=0, $mode=0)
Returns a hash (non reversible encryption) of a string.