Differences From Artifact [7c23d0c913]:

To Artifact [9e5e9828f3]:


1
2
3
4



5
6
7
8
9
10
11
12
13
14
15
16


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36


37
38
39
40
41
42
43

44
45
46
47

48

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113




114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132





133


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

176
177
178
179
180
181
182
183
184




185
186
187
188
189
190
191
192

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
<?php

namespace Garradin;




class Rappels
{
	/**
	 * Vérification des champs fournis pour la modification de donnée
	 * @param  array $data Tableau contenant les champs à ajouter/modifier
	 * @return void
	 */
	protected function _checkFields(&$data)
	{
		$db = DB::getInstance();

        if (empty($data['id_cotisation'])


        	|| !$db->firstColumn('SELECT 1 FROM cotisations WHERE id = ?;', (int) $data['id_cotisation']))
        {
            throw new UserException('Cotisation inconnue.');
        }

		$data['id_cotisation'] = (int) $data['id_cotisation'];

		if ((trim($data['delai']) === '') || !is_numeric($data['delai']))
		{
			throw new UserException('Délai avant rappel invalide : doit être indiqué en nombre de jours.');
		}

		$data['delai'] = (int) $data['delai'];

		if (!isset($data['sujet']) || trim($data['sujet']) === '')
		{
			throw new UserException('Le sujet du rappel ne peut être vide.');
		}

		$data['sujet'] = trim($data['sujet']);



		if (!isset($data['texte']) || trim($data['texte']) === '')
		{
			throw new UserException('Le contenu du rappel ne peut être vide.');
		}

		$data['texte'] = trim($data['texte']);

	}

	/**
	 * Ajouter un rappel

	 * @param array $data Données du rappel

	 * @return integer Numéro ID du rappel créé
	 */
	public function add($data)
	{
		$db = DB::getInstance();

		$this->_checkFields($data);

		$db->insert('rappels', $data);

		return $db->lastInsertRowId();
	}

	/**
	 * Modifier un rappel automatique
	 * @param  integer 	$id   Numéro du rappel
	 * @param  array 	$data Données du rappel
	 * @return boolean        TRUE si tout s'est bien passé
	 * @throws UserException  En cas d'erreur dans une donnée à modifier
	 */
	public function edit($id, $data)
	{
		$db = DB::getInstance();

		$this->_checkFields($data);

		return $db->update('rappels', $data, 'id = ' . (int)$id);
	}

	/**
	 * Supprimer un rappel automatique
	 * @param  integer $id Numéro du rappel
	 * @param  boolean $delete_history Effacer aussi l'historique des rappels envoyés
	 * @return boolean     TRUE en cas de succès
	 */
	public function delete($id, $delete_history = false)
	{
		$db = DB::getInstance();

		$db->begin();

		if ($delete_history)
		{
			$db->delete('rappels_envoyes', 'id_rappel = ?', (int) $id);


		}
		else
		{
			$db->update('rappels_envoyes', ['id_rappel' => null], 'id_rappel = ' . (int)$id);
		}

		$db->delete('rappels', 'id = ?', (int) $id);
		
		$db->commit();

		return true;
	}

	/**
	 * Renvoie les données sur un rappel
	 * @param  integer $id Numéro du rappel
	 * @return array     Données du rappel
	 */
	public function get($id)
	{
		return DB::getInstance()->first('SELECT * FROM rappels WHERE id = ?;', (int)$id);




	}

	/**
	 * Renvoie le nombre de rappels automatiques enregistrés
	 * @return integer Nombre de rappels
	 */
	public function countAll()
	{
		return DB::getInstance()->firstColumn('SELECT COUNT(*) FROM rappels;');
	}

	/**
	 * Liste des rappels triés par cotisation
	 * @return array Liste des rappels
	 */
	public function listByCotisation()
	{
		return DB::getInstance()->get('SELECT r.*,
			c.intitule, c.montant, c.duree, c.debut, c.fin





			FROM rappels AS r


			INNER JOIN cotisations AS c ON c.id = r.id_cotisation
			ORDER BY r.id_cotisation, r.delai, r.sujet;');
	}

	/**
	 * Liste des rappels pour une cotisation donnée
	 * @param  integer $id Numéro du rappel
	 * @return array     Liste des rappels
	 */
	public function listForCotisation($id)
	{
		return DB::getInstance()->get('SELECT * FROM rappels 
			WHERE id_cotisation = ? ORDER BY delai, sujet;', (int)$id);
	}

	/**
	 * Envoi des rappels automatiques par e-mail
	 * @return boolean TRUE en cas de succès
	 */
	public function sendPending()
	{
		$db = DB::getInstance();
		$config = Config::getInstance();

		// Requête compliquée qui fait tout le boulot
		// la logique est un JOIN des tables rappels, cotisations, cotisations_membres et membres
		// pour récupérer la liste des membres qui doivent recevoir une cotisation
		$query = '
		SELECT
			*,
			/* Nombre de jours avant ou après expiration */
			(julianday(date()) - julianday(expiration)) AS nb_jours,
			/* Date de mise en œuvre du rappel */
			date(expiration, delai || \' days\') AS date_rappel
		FROM (
			SELECT m.*, MIN(r.delai) AS delai, r.sujet, r.texte, r.id_cotisation, r.id AS id_rappel,
				m.'.$config->get('champ_identite').' AS identite,
				CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\')
				WHEN c.fin IS NOT NULL THEN c.fin ELSE 0 END AS expiration
			FROM rappels AS r
				INNER JOIN cotisations AS c ON c.id = r.id_cotisation
				INNER JOIN cotisations_membres AS cm ON cm.id_cotisation = c.id

				INNER JOIN membres AS m ON m.id = cm.id_membre
			WHERE
				/* Inutile de sélectionner les membres sans email */
				m.email IS NOT NULL AND m.email != \'\'
				/* Les cotisations ponctuelles ne comptent pas */
				AND (c.fin IS NOT NULL OR c.duree IS NOT NULL)
				/* Rien nest envoyé aux membres des catégories cachées, logique */
				AND m.id_categorie NOT IN (SELECT id FROM membres_categories WHERE cacher = 1)
    		/* Grouper par membre, pour n\'envoyer qu\'un seul rappel par membre/cotise */




	    	GROUP BY m.id, r.id_cotisation
			ORDER BY r.delai ASC
		)
		WHERE nb_jours >= delai
			/* Pour ne pas spammer on n\'envoie pas de rappel antérieur au dernier rappel déjà effectué */
			AND id NOT IN (SELECT id_membre FROM rappels_envoyes AS re
				WHERE id_cotisation = re.id_cotisation
				AND re.date >= date(expiration, delai || \' days\')

			)
		ORDER BY nb_jours DESC;';

		$db->begin();
		$re = new Rappels_Envoyes;

		foreach ($db->iterate($query) as $row)
		{
			$re->sendAuto($row);
		}

		$db->commit();
		return true;
	}
}


|

>
>
>
|

<
<
<
<
<
|

|
|
<
>
>
|
<
<
|

<
|
<
|
<
<
|
<
|
<
<
<
<
|
<
>
>
|
<
|
|
<
|
<
>



<
>
|
>
|

|

|
|
|
|
|
|
|
<
|
<
<
<
<
<
<
<
<
<
<
|
<
|
<
|
|
<
<
<
<
<
<
<
|
<
|
<
|
<
<
<
>
>
|
<
<
<


<
<
<
<
|



|
|
|

|

|
>
>
>
>
|
|
<
<
<
<
|
|
|
<
|
<
<
<
<
<
|
|
<
>
>
>
>
>
|
>
>
|
|
|
|
<
<
<
<
<
<
|
|
<






|




<
<
<
<
|
<
<
<
<
|
<
<
|
<
<
|
|
<
>
|
<
<
|
<
<
<
|
<
>
>
>
>
|
|
|
<
<
<
<
<
>
|
<
<

<

|

|






1
2
3
4
5
6
7
8
9





10
11
12
13

14
15
16


17
18

19

20


21

22




23

24
25
26

27
28

29

30
31
32
33

34
35
36
37
38
39
40
41
42
43
44
45
46
47

48










49

50

51
52







53

54

55



56
57
58



59
60




61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77




78
79
80

81





82
83

84
85
86
87
88
89
90
91
92
93
94
95






96
97

98
99
100
101
102
103
104
105
106
107
108




109




110


111


112
113

114
115


116



117

118
119
120
121
122
123
124





125
126


127

128
129
130
131
132
133
134
135
136
137
<?php

namespace Garradin\Services;

use Garradin\Config;
use Garradin\DB;

class Reminders
{





	static public function listSentForUser(int $user_id)
	{
		return DB::getInstance()->get('SELECT rs.date AS sent_date, r.delay, s.label, rs.id AS sent_id, s.id AS service_id
			FROM services_reminders_sent rs

			INNER JOIN services_reminders r ON r.id = rs.id_reminder
			INNER JOIN services s ON s.id = rs.id_service
			WHERE rs.id_user = ?;', $user_id);


	}


	static public function listSentForReminder(int $reminder_id)

	{


		return DB::getInstance()->get('SELECT rs.date AS sent_date, r.delay, s.label, rs.id AS sent_id, s.id AS service_id

			FROM services_reminders_sent rs




			INNER JOIN services_reminders r ON r.id = rs.id_reminder

			INNER JOIN services s ON s.id = rs.id_service
			WHERE rs.id_reminder = ?;', $user_id);
	}


	static public function listForService(int $service_id)

	{

		return DB::getInstance()->get('SELECT * FROM services_reminders WHERE id_service = ? ORDER BY delay, subject;', $service_id);
	}

	/**

	 * Remplacer les tags dans le contenu/sujet du mail
	 * @param  string $content Chaîne à traiter
	 * @param  array  $data    Données supplémentaires à utiliser comme tags (tableau associatif)
	 * @return string          $content dont les tags ont été remplacés par le contenu correct
	 */
	static public function replaceTagsInContent(string $content, ?array $data = null)
	{
		$config = Config::getInstance();
		$tags = [
			'#NOM_ASSO'		=>	$config->get('nom_asso'),
			'#ADRESSE_ASSO'	=>	$config->get('adresse_asso'),
			'#EMAIL_ASSO'	=>	$config->get('email_asso'),
			'#SITE_ASSO'	=>	$config->get('site_asso'),
			'#URL_RACINE'	=>	WWW_URL,

			'#URL_SITE'		=>	WWW_URL,










			'#URL_ADMIN'	=>	ADMIN_URL,

		];


		if (!empty($data) && is_array($data))







		{

			foreach ($data as $key=>$value)

			{



				$key = '#' . strtoupper($key);
				$tags[$key] = $value;
			}



		}





		return strtr($content, $tags);
	}

	/**
	 * Envoi de mail pour rappel automatisé
	 * @param  array $data Données du rappel automatisé
	 * @return boolean     TRUE
	 */
	static public function sendAuto($reminder)
	{
		$replace = (array) $data;
		$replace['date_rappel'] = Utils::sqliteDateToFrench($replace['date_rappel']);
		$replace['date_expiration'] = Utils::sqliteDateToFrench($replace['expiration']);
		$replace['nb_jours'] = abs($replace['nb_jours']);
		$replace['delai'] = abs($replace['delai']);

		$subject = self::replaceTagsInContent($data->sujet, $replace);




		$text = self::replaceTagsInContent($data->texte, $replace);

		// Envoi du mail

		Utils::sendEmail(Utils::EMAIL_CONTEXT_PRIVATE, $data->email, $subject, $text, $data->id);






		$db = DB::getInstance();

		$db->insert('services_reminders_sent', [
			'id_service'  => $reminder->id_service,
			'id_user'     => $reminder->id_user,
			'id_reminder' => $reminder->id_reminder,
			// On enregistre la date de mise en œuvre du rappel
			// et non pas la date d'envoi effective du rappel
			// car l'envoi du rappel peut ne pas être effectué
			// le jour où il aurait dû être envoyé (la magie des cron)
			'date'        => $reminder->sent_date,
		]);

		Plugin::fireSignal('rappels.auto', $reminder);







		return true;

	}

	/**
	 * Envoi des rappels automatiques par e-mail
	 * @return boolean TRUE en cas de succès
	 */
	static public function sendPending()
	{
		$db = DB::getInstance();
		$config = Config::getInstance();





		$sql = 'SELECT MIN(sr.delay), sr.subject, sr.body,




			s.label, s.description, su.id_service, su.id_user,


			m.email, m.%s AS identity, su.expiry_date


			FROM services_users su
			INNER JOIN services_reminders sr ON sr.id_service = su.id_service

			-- Join with users, but not ones part of a hidden category
			INNER JOIN membres m ON su.id_user = m.id


				AND m.email IS NOT NULL



				AND (m.id_categorie NOT IN (SELECT id FROM membres_categories WHERE cacher = 1))

			-- Join with sent reminders to exclude users that already have received this reminder
			LEFT JOIN services_reminders_sent srs ON srs.id_reminder = sr.id AND srs.id_user = su.id_user
			WHERE date() > date(su.expiry_date, sr.delay || \' days\')
			AND srs.id IS NULL
			GROUP BY su.id_user, su.id_service
			ORDER BY su.id_user;';






		$sql = sprintf($sql, $config->get('champ_identite'));



		$db->begin();


		foreach ($db->iterate($sql) as $row)
		{
			self::sendAuto($row);
		}

		$db->commit();
		return true;
	}
}