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
84 public $require_module = array();
85
89 public $require_admin = 0;
90
94 public $picto = false;
95
96
102 public function __construct($db)
103 {
104 $this->db = $db;
105 }
106
112 public function getDesc()
113 {
114 global $langs, $form;
115
116 $langs->load("mails");
117 $transstring = "MailingModuleDesc".$this->name;
118 $s = '';
119
120 if ($langs->trans($this->name) != $this->name) {
121 $s = $langs->trans($this->name);
122 } elseif ($langs->trans($transstring) != $transstring) {
123 $s = $langs->trans($transstring);
124 } else {
125 $s = $this->desc;
126 }
127
128 if ($this->tooltip && is_object($form)) {
129 $s .= ' '.$form->textwithpicto('', $langs->trans($this->tooltip), 1, 'help');
130 }
131 return $s;
132 }
133
142 public function getSqlToExcludeUnsubscribed($emailfield)
143 {
144 global $conf;
145
146 if (!empty($this->evenunsubscribe)) {
147 return '';
148 }
149
150 // $emailfield is a column expression of the outer query (e.g. 's.email'); it is compared to mu.email.
151 $sql = " AND NOT EXISTS (SELECT rowid FROM ".$this->db->prefix()."mailing_unsubscribe as mu";
152 $sql .= " WHERE ".$this->db->sanitize($emailfield)." = mu.email";
153 $sql .= " AND mu.entity = ".((int) $conf->entity).")";
154
155 return $sql;
156 }
157
163 public function getNbOfRecords()
164 {
165 return 0;
166 }
167
174 public function getNbOfRecipients($sql)
175 {
176 $result = $this->db->query($sql);
177 if ($result) {
178 $total = 0;
179 while ($obj = $this->db->fetch_object($result)) {
180 $total += (int) $obj->nb;
181 }
182 return $total;
183 } else {
184 $this->error = $this->db->lasterror();
185 return -1;
186 }
187 }
188
194 public function formFilter()
195 {
196 return '';
197 }
198
199 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
206 public function update_nb($mailing_id)
207 {
208 // phpcs:enable
209 // Update the number of recipients in the mailing table
210 $sql = "SELECT COUNT(*) nb FROM ".$this->db->prefix()."mailing_cibles";
211 $sql .= " WHERE fk_mailing = ".((int) $mailing_id);
212 $result = $this->db->query($sql);
213 if ($result) {
214 $obj = $this->db->fetch_object($result);
215 $nb = (int) $obj->nb;
216
217 $sql = "UPDATE ".$this->db->prefix()."mailing";
218 $sql .= " SET nbemail = ".((int) $nb)." WHERE rowid = ".((int) $mailing_id);
219 if (!$this->db->query($sql)) {
220 dol_syslog($this->db->error());
221 $this->error = $this->db->error();
222 return -1;
223 }
224 } else {
225 return -1;
226 }
227 return $nb;
228 }
229
237 public function addTargetsToDatabase($mailing_id, $cibles)
238 {
239 global $conf;
240
241 $this->db->begin();
242
243
244 // Insert emailing targets from array into database
245 $j = 0;
246 $num = count($cibles);
247 foreach ($cibles as $targetarray) {
248 if (!empty($targetarray['email'])) { // avoid empty email address
249 $sql = "INSERT INTO ".$this->db->prefix()."mailing_cibles";
250 $sql .= " (fk_mailing,";
251 $sql .= " fk_contact,";
252 $sql .= " lastname, firstname, email, other, source_url, source_id,";
253 $sql .= " tag,";
254 $sql .= " source_type)";
255 $sql .= " VALUES (".((int) $mailing_id).",";
256 $sql .= (empty($targetarray['fk_contact']) ? '0' : (int) $targetarray['fk_contact']).",";
257 $sql .= "'".$this->db->escape($targetarray['lastname'])."',";
258 $sql .= "'".$this->db->escape($targetarray['firstname'])."',";
259 $sql .= "'".$this->db->escape($targetarray['email'])."',";
260 $sql .= "'".$this->db->escape($targetarray['other'])."',";
261 $sql .= "'".$this->db->escape($targetarray['source_url'])."',";
262 $sql .= (empty($targetarray['source_id']) ? 'null' : (int) $targetarray['source_id']).",";
263 $sql .= "'".$this->db->escape(dol_hash($conf->file->instance_unique_id.";".$targetarray['email'].";".$targetarray['lastname'].";".((int) $mailing_id).";".getDolGlobalString('MAILING_EMAIL_UNSUBSCRIBE_KEY'), 'md5'))."',";
264 $sql .= "'".$this->db->escape($targetarray['source_type'])."')";
265 dol_syslog(__METHOD__, LOG_DEBUG);
266 $result = $this->db->query($sql);
267 if ($result) {
268 $j++;
269 } else {
270 if ($this->db->errno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
271 // If error other than duplicate
272 dol_syslog($this->db->error().' : '.$targetarray['email']);
273 $this->error = $this->db->error().' : '.$targetarray['email'];
274 $this->db->rollback();
275 return -1;
276 }
277 }
278 }
279 }
280
281 dol_syslog(__METHOD__.": mailing ".$j." targets added");
282
283 /*
284 //Update the status to show third-party emails that no longer wish to be contacted'
285 $sql = "UPDATE ".$this->db->prefix()."mailing_cibles";
286 $sql .= " SET statut=3";
287 $sql .= " WHERE fk_mailing = ".((int) $mailing_id)." AND email in (SELECT email FROM ".$this->db->prefix()."societe where fk_stcomm=-1)";
288 $sql .= " AND source_type='thirdparty'";
289 dol_syslog(__METHOD__.": mailing update status to display third-party emails that no longer wish to be contacted");
290 $result=$this->db->query($sql);
291
292 //Update the status to show contact emails that no longer wish to be contacted'
293 $sql = "UPDATE ".$this->db->prefix()."mailing_cibles";
294 $sql .= " SET statut=3";
295 $sql .= " WHERE fk_mailing = ".((int) $mailing_id)." AND source_type='contact' AND (email in (SELECT sc.email FROM ".$this->db->prefix()."socpeople AS sc ";
296 $sql .= " INNER JOIN ".$this->db->prefix()."societe s ON s.rowid=sc.fk_soc WHERE s.fk_stcomm=-1 OR no_email=1))";
297 dol_syslog(__METHOD__.": mailing update status to display contact emails that no longer wish to be contacted",LOG_DEBUG);
298 $result=$this->db->query($sql);
299 */
300
301 if (empty($this->evenunsubscribe)) {
302 $sql = "UPDATE ".$this->db->prefix()."mailing_cibles as mc";
303 $sql .= " SET statut = 3";
304 $sql .= " WHERE fk_mailing = ".((int) $mailing_id);
305 $sql .= " AND EXISTS (SELECT rowid FROM ".$this->db->prefix()."mailing_unsubscribe as mu WHERE mu.email = mc.email and mu.entity = ".((int) $conf->entity).")";
306
307 dol_syslog(__METHOD__.":mailing update status to display emails that do not want to be contacted anymore", LOG_DEBUG);
308 $result = $this->db->query($sql);
309 if (!$result) {
310 dol_print_error($this->db);
311 }
312 }
313
314 // Update nb of recipient into emailing record
315 $this->update_nb($mailing_id);
316
317 $this->db->commit();
318
319 return $j;
320 }
321
322 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
329 public function clear_target($mailing_id)
330 {
331 // phpcs:enable
332 $sql = "DELETE FROM ".$this->db->prefix()."mailing_cibles";
333 $sql .= " WHERE fk_mailing = ".((int) $mailing_id);
334
335 if (!$this->db->query($sql)) {
336 dol_syslog($this->db->error());
337 }
338
339 $this->update_nb($mailing_id);
340 }
341
342
350 public static function getEmailingSelectorsList($forcedir = null)
351 {
352 global $langs, $db;
353
354 $files = array();
355 $fullpath = array();
356 $relpath = array();
357 $iscoreorexternal = array();
358 $modules = array();
359 $orders = array();
360 $i = 0;
361
362 $diremailselector = array('/core/modules/mailings/'); // $conf->modules_parts['emailings'] is not required
363 if (is_array($forcedir)) {
364 $diremailselector = $forcedir;
365 }
366
367 foreach ($diremailselector as $reldir) {
368 $dir = dol_buildpath($reldir, 0);
369 $newdir = dol_osencode($dir);
370
371 // Check if directory exists (we do not use dol_is_dir to avoid loading files.lib.php at each call)
372 if (!is_dir($newdir)) {
373 continue;
374 }
375
376 $handle = opendir($newdir);
377 if (is_resource($handle)) {
378 while (($file = readdir($handle)) !== false) {
379 $reg = array();
380 if (is_readable($newdir.'/'.$file) && preg_match('/^(.+)\.modules.php/', $file, $reg)) {
381 if (preg_match('/\.back$/', $file) || preg_match('/^(.+)\.disabled\.php/', $file)) {
382 continue;
383 }
384
385 $part1 = $reg[1];
386
387 //$modName = ucfirst($reg[1]);
388 $modName = 'mailing_'.$reg[1]; // name of selector submodule
389 //print "file=$file modName=$modName"; exit;
390 if (in_array($modName, $modules)) {
391 $langs->load("errors");
392 print '<div class="error">'.$langs->trans("Error").' : '.$langs->trans("ErrorDuplicateEmalingSelector", $modName, "").'</div>';
393 } else {
394 try {
395 //print $newdir.'/'.$file;
396 include_once $newdir.'/'.$file;
397 } catch (Exception $e) {
398 print $e->getMessage();
399 }
400 }
401
402 $files[$i] = $file;
403 $fullpath[$i] = $dir.'/'.$file;
404 $relpath[$i] = preg_replace('/^\//', '', $reldir).'/'.$file;
405 $iscoreorexternal[$i] = ($reldir == '/core/modules/mailings/' ? 'internal' : 'external');
406 $modules[$i] = $modName;
407 $orders[$i] = $part1; // Set sort criteria value
408
409 $i++;
410 }
411 }
412 closedir($handle);
413 }
414 }
415 //echo "<pre>";print_r($modules);echo "</pre>";
416
417 asort($orders);
418
419 $widget = array();
420 $j = 0;
421
422 // Loop on each emailing selector
423 foreach ($orders as $key => $value) {
424 $modName = $modules[$key];
425 if (empty($modName)) {
426 continue;
427 }
428
429 if (!class_exists($modName)) {
430 print 'Error: An emailing selector file was found but its class "'.$modName.'" was not found.'."<br>\n";
431 continue;
432 }
433
434 $objMod = new $modName($db);
435 if (is_object($objMod)) {
436 '@phan-var-force ModeleBoxes $objMod';
437 // Define disabledbyname and disabledbymodule
438 $disabledbyname = 0;
439 $disabledbymodule = 0; // TODO Set to 2 if module is not enabled
440 $module = '';
441
442 // Check if widget file is disabled by name
443 if (preg_match('/NORUN$/i', $files[$key])) {
444 $disabledbyname = 1;
445 }
446
447 // We set info of modules @phan-suppress-next-line PhanUndeclaredProperty
448 $widget[$j]['picto'] = (empty($objMod->picto) ? (empty($objMod->boximg) ? img_object('', 'generic') : $objMod->boximg) : img_object('', $objMod->picto));
449 $widget[$j]['file'] = $files[$key];
450 $widget[$j]['fullpath'] = $fullpath[$key];
451 $widget[$j]['relpath'] = $relpath[$key];
452 $widget[$j]['iscoreorexternal'] = $iscoreorexternal[$key];
453 $widget[$j]['version'] = empty($objMod->version) ? '' : $objMod->version;
454 $widget[$j]['status'] = img_picto($langs->trans("Active"), 'tick', 'class="pictofixedwidth"');
455 if ($disabledbyname > 0 || $disabledbymodule > 1) {
456 $widget[$j]['status'] = '';
457 }
458
459 $text = '<b>'.$langs->trans("Description").':</b><br>';
460 $text .= $objMod->boxlabel.'<br>';
461 $text .= '<br><b>'.$langs->trans("Status").':</b><br>';
462 if ($disabledbymodule == 2) {
463 $text .= $langs->trans("WidgetDisabledAsModuleDisabled", $module).'<br>';
464 }
465
466 $widget[$j]['info'] = $text;
467 }
468 $j++;
469 }
470
471 return $widget;
472 }
473
474
483 public function getSqlArrayForStats()
484 {
485 // Needs to be implemented in child class
486 $msg = get_class($this)."::".__FUNCTION__." not implemented";
487 dol_syslog($msg, LOG_ERR);
488 return array();
489 }
490
491 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
498 public function add_to_target($mailing_id)
499 {
500 // phpcs:enable
501 // Needs to be implemented in child class
502 $msg = get_class($this)."::".__FUNCTION__." not implemented";
503 dol_syslog($msg, LOG_ERR);
504 return -1;
505 }
506}
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(! $sortfield) if(! $sortorder) $module
Definition list.php:193
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
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.
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:140
dol_hash($chain, $type='0', $nosalt=0, $mode=0)
Returns a hash (non reversible encryption) of a string.