dolibarr 23.0.3
datapolicycron.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
3 * Copyright (C) 2018-2025 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
5 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
6 * Copyright (C) 2025 Quentin VIAL--GOUTEYRON <quentin.vial-gouteyron@atm-consulting.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 */
21
32{
34 public $db;
36 public $error;
38 public $output;
40 private $nbupdated = 0;
42 private $nbdeleted = 0;
44 private $errorCount = 0;
46 private $errorMessages = array();
47
52 public function __construct(DoliDB $db)
53 {
54 $this->db = $db;
55 }
56
63 public function getDataPolicies()
64 {
65 $prefix = $this->db->prefix();
66
67 $arrayofpolicies = array(
68 // --- Third Parties ---
69 'tiers_client' => array(
70 'group' => 'ThirdParty',
71 'label_key' => 'DATAPOLICY_TIERS_CLIENT',
72 'picto' => img_picto('', 'company', 'class="pictofixedwidth"'),
73 'const_delete' => '',
74 'const_anonymize' => 'DATAPOLICY_TIERS_CLIENT_ANONYMIZE_DELAY',
75 'sql_template' => "SELECT s.rowid FROM ".$prefix."societe as s WHERE s.entity = __ENTITY__ AND s.client = ".Societe::CUSTOMER." AND s.fournisseur = 0 AND s.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
76 'class' => 'Societe',
77 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php',
78 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_alias' => 'MAKEANONYMOUS', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone' => '---', 'email' => 'anonymous+__ID__@example.com', 'url' => '---', 'fax' => '---', 'siret' => '---', 'siren' => '---', 'ape' => '---', 'idprof4' => '---', 'idprof5' => '---', 'idprof6' => '---', 'tva_intra' => '---', 'capital' => 0, 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
79 'call_params' => array(
80 'delete' => array('id', 'user'), // $object->delete($id, $user)
81 'update' => array('id', 'user') // $object->update($id, $user)
82 )
83 ),
84 'tiers_prospect' => array(
85 'group' => 'ThirdParty',
86 'label_key' => 'DATAPOLICY_TIERS_PROSPECT',
87 'picto' => img_picto('', 'company', 'class="pictofixedwidth"'),
88 'const_delete' => '',
89 'const_anonymize' => 'DATAPOLICY_TIERS_PROSPECT_ANONYMIZE_DELAY',
90 'sql_template' => "SELECT s.rowid FROM ".$prefix."societe as s WHERE s.entity = __ENTITY__ AND s.client = ".Societe::PROSPECT." AND s.fournisseur = 0 AND s.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
91 'class' => 'Societe',
92 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php',
93 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_alias' => 'MAKEANONYMOUS', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone' => '---', 'email' => 'anonymous+__ID__@example.com', 'url' => '---', 'fax' => '---', 'siret' => '---', 'siren' => '---', 'ape' => '---', 'idprof4' => '---', 'idprof5' => '---', 'idprof6' => '---', 'tva_intra' => '---', 'capital' => 0, 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
94 'call_params' => array(
95 'delete' => array('id', 'user'), // $object->delete($id, $user)
96 'update' => array('id', 'user') // $object->update($id, $user)
97 )
98 ),
99 'tiers_prospect_client' => array(
100 'group' => 'ThirdParty',
101 'label_key' => 'DATAPOLICY_TIERS_PROSPECT_CLIENT',
102 'picto' => img_picto('', 'company', 'class="pictofixedwidth"'),
103 'const_delete' => '',
104 'const_anonymize' => 'DATAPOLICY_TIERS_PROSPECT_CLIENT_ANONYMIZE_DELAY',
105 'sql_template' => "SELECT s.rowid FROM ".$prefix."societe as s WHERE s.entity = __ENTITY__ AND s.client = ".Societe::CUSTOMER_AND_PROSPECT." AND s.fournisseur = 0 AND s.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
106 'class' => 'Societe',
107 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php',
108 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_alias' => 'MAKEANONYMOUS', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone' => '---', 'email' => 'anonymous+__ID__@example.com', 'url' => '---', 'fax' => '---', 'siret' => '---', 'siren' => '---', 'ape' => '---', 'idprof4' => '---', 'idprof5' => '---', 'idprof6' => '---', 'tva_intra' => '---', 'capital' => 0, 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
109 'call_params' => array(
110 'delete' => array('id', 'user'), // $object->delete($id, $user)
111 'update' => array('id', 'user') // $object->update($id, $user)
112 )
113 ),
114 'tiers_niprosp_niclient' => array(
115 'group' => 'ThirdParty',
116 'label_key' => 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT',
117 'picto' => img_picto('', 'company', 'class="pictofixedwidth"'),
118 'const_delete' => 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT_DELETE_DELAY',
119 'const_anonymize' => 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT_ANONYMIZE_DELAY',
120 'sql_template' => "SELECT s.rowid FROM ".$prefix."societe as s WHERE s.entity = __ENTITY__ AND s.client = ".Societe::NO_CUSTOMER." AND s.fournisseur = 0 AND s.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
121 'sql_template_delete' => "SELECT s.rowid FROM ".$prefix."societe as s WHERE s.entity = __ENTITY__ AND s.client = ".Societe::NO_CUSTOMER." AND s.fournisseur = 0 AND s.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
122 'class' => 'Societe',
123 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php',
124 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_alias' => 'MAKEANONYMOUS', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone' => '---', 'email' => 'anonymous+__ID__@example.com', 'url' => '---', 'fax' => '---', 'siret' => '---', 'siren' => '---', 'ape' => '---', 'idprof4' => '---', 'idprof5' => '---', 'idprof6' => '---', 'tva_intra' => '---', 'capital' => 0, 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
125 'call_params' => array(
126 'delete' => array('id', 'user'), // $object->delete($id, $user)
127 'update' => array('id', 'user') // $object->update($id, $user)
128 )
129 ),
130 'tiers_fournisseur' => array(
131 'group' => 'ThirdParty',
132 'label_key' => 'DATAPOLICY_TIERS_FOURNISSEUR',
133 'picto' => img_picto('', 'supplier', 'class="pictofixedwidth"'),
134 'const_delete' => '',
135 'const_anonymize' => 'DATAPOLICY_TIERS_FOURNISSEUR_ANONYMIZE_DELAY',
136 // Suppliers must be checked against llx_facture_fourn (purchase invoices),
137 // not llx_facture (sales invoices). A pure supplier with no sales would
138 // otherwise always pass the filter and get anonymized regardless of
139 // purchase history (#38788).
140 'sql_template' => "SELECT s.rowid FROM ".$prefix."societe as s WHERE s.entity = __ENTITY__ AND s.fournisseur = 1 AND s.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture_fourn as f WHERE f.fk_soc = s.rowid)",
141 'class' => 'Societe',
142 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php',
143 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_alias' => 'MAKEANONYMOUS', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone' => '---', 'email' => 'anonymous+__ID__@example.com', 'url' => '---', 'fax' => '---', 'siret' => '---', 'siren' => '---', 'ape' => '---', 'idprof4' => '---', 'idprof5' => '---', 'idprof6' => '---', 'tva_intra' => '---', 'capital' => 0, 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
144 'call_params' => array(
145 'delete' => array('id', 'user'), // $object->delete($id, $user)
146 'update' => array('id', 'user') // $object->update($id, $user)
147 )
148 ),
149 // --- Contacts ---
150 'contact_client' => array(
151 'group' => 'Contact',
152 'label_key' => 'DATAPOLICY_CONTACT_CLIENT',
153 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'),
154 'const_delete' => '',
155 'const_anonymize' => 'DATAPOLICY_CONTACT_CLIENT_ANONYMIZE_DELAY',
156 'sql_template' => "SELECT c.rowid FROM ".$prefix."socpeople as c INNER JOIN ".$prefix."societe as s ON s.rowid = c.fk_soc WHERE c.entity = __ENTITY__ AND c.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND s.client = ".Societe::CUSTOMER." AND s.fournisseur = 0 AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
157 'class' => 'Contact',
158 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php',
159 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => 'MAKEANONYMOUS', 'poste' => '---', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone_pro' => '---', 'phone_perso' => '---', 'phone_mobile' => '---', 'email' => 'anonymous+__ID__@example.com', 'photo' => '', 'url' => '---', 'fax' => '---', 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
160 'call_params' => array(
161 'delete' => array('user'), // $object->delete($user)
162 'update' => array('id', 'user') // $object->update($id, $user)
163 )
164 ),
165 'contact_prospect' => array(
166 'group' => 'Contact',
167 'label_key' => 'DATAPOLICY_CONTACT_PROSPECT',
168 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'),
169 'const_delete' => '',
170 'const_anonymize' => 'DATAPOLICY_CONTACT_PROSPECT_ANONYMIZE_DELAY',
171 'sql_template' => "SELECT c.rowid FROM ".$prefix."socpeople as c INNER JOIN ".$prefix."societe as s ON s.rowid = c.fk_soc WHERE c.entity = __ENTITY__ AND c.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND s.client = ".Societe::PROSPECT." AND s.fournisseur = 0 AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
172 'class' => 'Contact',
173 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php',
174 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => 'MAKEANONYMOUS', 'poste' => '---', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone_pro' => '---', 'phone_perso' => '---', 'phone_mobile' => '---', 'email' => 'anonymous+__ID__@example.com', 'photo' => '', 'url' => '---', 'fax' => '---', 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
175 'call_params' => array(
176 'delete' => array('user'), // $object->delete($user)
177 'update' => array('id', 'user') // $object->update($id, $user)
178 )
179 ),
180 'contact_prospect_client' => array(
181 'group' => 'Contact',
182 'label_key' => 'DATAPOLICY_CONTACT_PROSPECT_CLIENT',
183 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'),
184 'const_delete' => '',
185 'const_anonymize' => 'DATAPOLICY_CONTACT_PROSPECT_CLIENT_ANONYMIZE_DELAY',
186 'sql_template' => "SELECT c.rowid FROM ".$prefix."socpeople as c INNER JOIN ".$prefix."societe as s ON s.rowid = c.fk_soc WHERE c.entity = __ENTITY__ AND c.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND s.client = ".Societe::CUSTOMER_AND_PROSPECT." AND s.fournisseur = 0 AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
187 'class' => 'Contact',
188 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php',
189 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => 'MAKEANONYMOUS', 'poste' => '---', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone_pro' => '---', 'phone_perso' => '---', 'phone_mobile' => '---', 'email' => 'anonymous+__ID__@example.com', 'photo' => '', 'url' => '---', 'fax' => '---', 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
190 'call_params' => array(
191 'delete' => array('user'), // $object->delete($user)
192 'update' => array('id', 'user') // $object->update($id, $user)
193 )
194 ),
195 'contact_niprosp_niclient' => array(
196 'group' => 'Contact',
197 'label_key' => 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT',
198 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'),
199 'const_delete' => '',
200 'const_anonymize' => 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT_ANONYMIZE_DELAY',
201 'sql_template' => "SELECT c.rowid FROM ".$prefix."socpeople as c INNER JOIN ".$prefix."societe as s ON s.rowid = c.fk_soc WHERE c.entity = __ENTITY__ AND c.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND s.client = ".Societe::NO_CUSTOMER." AND s.fournisseur = 0 AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture as f WHERE f.fk_soc = s.rowid)",
202 'class' => 'Contact',
203 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php',
204 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => 'MAKEANONYMOUS', 'poste' => '---', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone_pro' => '---', 'phone_perso' => '---', 'phone_mobile' => '---', 'email' => 'anonymous+__ID__@example.com', 'photo' => '', 'url' => '---', 'fax' => '---', 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
205 'call_params' => array(
206 'delete' => array('user'), // $object->delete($user)
207 'update' => array('id', 'user') // $object->update($id, $user)
208 )
209 ),
210 'contact_fournisseur' => array(
211 'group' => 'Contact',
212 'label_key' => 'DATAPOLICY_CONTACT_FOURNISSEUR',
213 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'),
214 'const_delete' => '',
215 'const_anonymize' => 'DATAPOLICY_CONTACT_FOURNISSEUR_ANONYMIZE_DELAY',
216 // Same as tiers_fournisseur above: a contact attached to a pure supplier
217 // must be checked against llx_facture_fourn, not llx_facture (#38788).
218 'sql_template' => "SELECT c.rowid FROM ".$prefix."socpeople as c INNER JOIN ".$prefix."societe as s ON s.rowid = c.fk_soc WHERE c.entity = __ENTITY__ AND c.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND s.fournisseur = 1 AND NOT EXISTS (SELECT a.id FROM ".$prefix."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)) AND NOT EXISTS (SELECT f.rowid FROM ".$prefix."facture_fourn as f WHERE f.fk_soc = s.rowid)",
219 'class' => 'Contact',
220 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php',
221 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => 'MAKEANONYMOUS', 'poste' => '---', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone_pro' => '---', 'phone_perso' => '---', 'phone_mobile' => '---', 'email' => 'anonymous+__ID__@example.com', 'photo' => '', 'url' => '---', 'fax' => '---', 'socialnetworks' => [], 'geolat' => 0, 'geolong' => 0, 'ip' => '0.0.0.0'),
222 'call_params' => array(
223 'delete' => array('user'), // $object->delete($user)
224 'update' => array('id', 'user') // $object->update($id, $user)
225 )
226 )
227 );
228 if (isModEnabled('member')) {
229 // --- Members ---
230 $sqltemplate = "SELECT a.rowid FROM ".$prefix."adherent as a WHERE a.entity = __ENTITY__ AND a.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH)";
231 $sqltemplate .= " AND NOT EXISTS (SELECT ac.id FROM ".$prefix."actioncomm as ac WHERE ac.fk_element = a.rowid AND ac.elementtype = 'member' AND ac.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH))";
232 $sqltemplate .= " AND NOT EXISTS (SELECT s.rowid FROM ".$prefix."subscription as s WHERE s.fk_adherent = a.rowid AND s.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH))";
233
234 $arrayofpolicies['adherent'] = array(
235 'group' => 'Member',
236 'label_key' => 'DATAPOLICY_ADHERENT',
237 'picto' => img_picto('', 'member', 'class="pictofixedwidth"'),
238 'const_delete' => '',
239 'const_anonymize' => 'DATAPOLICY_ADHERENT_ANONYMIZE_DELAY',
240 'sql_template' => $sqltemplate,
241 'class' => 'Adherent',
242 'file' => DOL_DOCUMENT_ROOT . '/adherents/class/adherent.class.php',
243 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => 'MAKEANONYMOUS', 'societe' => '---', 'address' => '---', 'town' => '---', 'zip' => '---', 'phone' => '---', 'phone_perso' => '---', 'phone_mobile' => '---', 'email' => 'anonymous+__ID__@example.com', 'birth' => dol_mktime(0, 0, 0, 1, 1, 1900), 'photo' => '', 'url' => '---', 'fax' => '---', 'socialnetworks' => [], 'ip' => '0.0.0.0'),
244 'call_params' => array(
245 'delete' => array('user'), // $object->delete($user)
246 'update' => array('user') // $object->update($user)
247 )
248 );
249 }
250
251 if (isModEnabled('recruitment')) {
252 // --- Recruitment ---
253 $arrayofpolicies['recruitment_candidature'] = array(
254 'group' => 'Recruitment',
255 'label_key' => 'DATAPOLICY_RECRUITMENT_CANDIDATURE',
256 'picto' => img_picto('', 'recruitmentcandidature', 'class="pictofixedwidth"'),
257 'const_delete' => 'DATAPOLICY_RECRUITMENT_CANDIDATURE_DELETE_DELAY',
258 'const_anonymize' => '', // Anonymization not applicable
259 'sql_template_delete' => "SELECT c.rowid FROM ".$prefix."recruitment_recruitmentcandidature as c WHERE c.entity = __ENTITY__ AND c.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) AND NOT EXISTS (SELECT ac.id FROM ".$prefix."actioncomm as ac WHERE ac.elementtype = 'recruitmentcandidature@recruitment' AND ac.fk_element = c.rowid AND ac.tms > DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH))",
260 'class' => 'RecruitmentCandidature',
261 'file' => DOL_DOCUMENT_ROOT . '/recruitment/class/recruitmentcandidature.class.php',
262 'anonymize_fields' => array(),
263 'call_params' => array(
264 'delete' => array('user'), // $object->delete($user)
265 'update' => array('user') // $object->update($user)
266 )
267 );
268 }
269
270 // TODO Allow an external module to add an entry into the array
271
272
273 return $arrayofpolicies;
274 }
275
282 public function cleanDataForDataPolicy(): int
283 {
284 global $conf, $user;
285
286 // Reset state properties for this specific execution run.
287 $this->nbupdated = 0;
288 $this->nbdeleted = 0;
289 $this->errorCount = 0;
290 $this->errorMessages = array();
291
292 // Tracks record IDs that have been processed in this run to prevent duplicate actions (e.g., anonymizing a just-deleted record).
293 $processedIds = array();
294 // Caches object instances to avoid redundant 'new Class()' calls, improving performance.
295 $objectInstances = array();
296
297 // Retrieve the master list of all data policies. This separates configuration from execution.
298 $dataPolicies = $this->getDataPolicies();
299
300 $this->db->begin();
301
302 // Iterate through each defined policy to apply its rules.
303 foreach ($dataPolicies as $policy) {
304 // Instantiate object only once per class type for efficiency.
305 if (! isset($objectInstances[$policy['class']])) {
306 require_once $policy['file'];
307 $classtoinit = $policy['class'];
308 $objectInstances[$policy['class']] = new $classtoinit($this->db);
309 }
310 $object = $objectInstances[$policy['class']];
311
312 // Process actions.
313 // ->errorCount, ->nbupdated and ->nbdelete will be set after that.
314
315 // The order of operations is critical: deletion is always processed before anonymization.
316 // This ensures that if a record meets criteria for both, it is deleted as the first and final action.
317 $this->_processPolicyAction($policy, 'delete', $object, $processedIds, $conf, $user);
318 // Now process anonymization
319 $this->_processPolicyAction($policy, 'anonymize', $object, $processedIds, $conf, $user);
320 }
321
322 // Finalize the transaction based on the outcome of all operations.
323 if (! $this->errorCount) {
324 $this->db->commit();
325 $this->output = $this->nbupdated . ' record(s) anonymized, ' . $this->nbdeleted . ' record(s) deleted.';
326 } else {
327 $this->db->rollback();
328 $this->error = implode("\n", $this->errorMessages);
329 }
330
331 return $this->errorCount ? 1 : 0;
332 }
333
346 private function _processPolicyAction($policy, $action, $object, &$processedIds, $conf, $user)
347 {
348 $constName = $policy['const_' . $action] ?? null;
349 $delay = $constName ? getDolGlobalInt($constName) : 0;
350
351 if ($delay <= 0) {
352 return;
353 }
354
355 // Prepare SQL query
356 $sqlPlaceholders = array(
357 '__ENTITY__' => (string) $conf->entity,
358 '__DELAY__' => (string) $delay,
359 '__NOW__' => "'" . $this->db->idate(dol_now()) . "'"
360 );
361 $sql = str_replace(array_keys($sqlPlaceholders), array_values($sqlPlaceholders), $policy['sql_template'.($action == 'delete' ? '_delete' : '')]);
362
363 $resql = $this->db->query($sql);
364
365 if (! $resql) {
366 $this->errorCount++;
367 $this->errorMessages[] = 'Error executing ' . $action . ' query for policy ' . $constName . ': ' . $this->db->lasterror();
368
369 return;
370 }
371
372 // Define the handler method for the action
373 $handlerMethod = '_handle' . ucfirst($action);
374
375 // Process the records found by the query
376 while ($obj = $this->db->fetch_object($resql)) {
377 if (in_array($obj->rowid, $processedIds) || ! method_exists($this, $handlerMethod)) {
378 continue;
379 }
381 $object = clone $object;
382 $object->fetch($obj->rowid);
383
384 // isObjectUsed() is a referential-integrity guard for deletion (cannot drop a
385 // thirdparty that still has quotes/orders/contracts pointing at it). Anonymization
386 // must still be allowed: personal data has to be erasable even when commercial
387 // documents from the retention period are kept (#38786).
388 if ($action === 'delete' && !empty($object->childtables) && method_exists($object, 'isObjectUsed') && $object->isObjectUsed() != 0) {
389 continue; // Not an error, just skipping.
390 }
391
392 // Dynamically call the appropriate handler (_handleDelete or _handleAnonymize)
393 $result = $this->$handlerMethod($object, $user, $policy);
394
395 // Record the outcome and add to processed list on success
396 $this->_recordActionResult($result, $object, $action);
397 $processedIds[] = $obj->rowid;
398 }
399 }
400
409 private function _handleDelete($object, $user, $policy): int
410 {
411 $callArgs = $this->_buildCallArguments($object, $user, $policy, 'delete');
412
413 return $object->delete(...$callArgs);
414 }
415
424 private function _handleAnonymize($object, $user, $policy): int
425 {
426 foreach ($policy['anonymize_fields'] as $field => $val) {
427 if ($val == 'MAKEANONYMOUS') {
428 // For each field with rule "MAKEANONYMOUS, set the new value, keeping the ID.
429 $object->$field = $field . '-anon-' . $object->id;
430 } else {
431 // For others, force the value, but only if not already empty.
432 if (!empty($object->$field)) {
433 $newval = str_replace('__ID__', $object->id ? (string) $object->id : '0', $val);
434 $object->$field = $newval;
435 }
436 }
437 }
438
439 $callArgs = $this->_buildCallArguments($object, $user, $policy, 'update');
440
441 return $object->update(...$callArgs);
442 }
443
453 private function _buildCallArguments($object, $user, $policy, $method)
454 {
455 $availableArgs = array(
456 'id' => $object->id,
457 'user' => $user
458 );
459
460 $paramConfig = $policy['call_params'][$method] ?? [];
461
462 return array_map(
467 static function (string $paramName) use ($availableArgs) {
468 return $availableArgs[$paramName];
469 },
470 $paramConfig
471 );
472 }
473
482 private function _recordActionResult($result, $object, $action)
483 {
484 if ($result < 0) {
485 $this->errorCount++;
486 $this->errorMessages[] = 'Failed to ' . $action . ' record ID ' . $object->id . ' from class ' . get_class($object) . '. Error: ' . $object->errorsToString();
487 } else {
488 if ($action === 'delete') {
489 $this->nbdeleted++;
490 } elseif ($action === 'anonymize') {
491 // Only count as updated if the update method returns a positive result
492 $this->nbupdated++;
493 }
494 }
495 }
496}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
Class DataPolicyCron.
getDataPolicies()
Defines and returns the centralized data policy configuration.
_recordActionResult($result, $object, $action)
Records the result of an action, updating counters and error messages.
cleanDataForDataPolicy()
Main cron task execution method.
_buildCallArguments($object, $user, $policy, $method)
Builds the dynamic argument list for method calls based on policy configuration.
_handleAnonymize($object, $user, $policy)
Handles the specific logic for anonymizing an object.
_handleDelete($object, $user, $policy)
Handles the specific logic for deleting an object.
__construct(DoliDB $db)
Constructor.
Class to manage Dolibarr database access.
const PROSPECT
Third party type is a prospect.
const CUSTOMER_AND_PROSPECT
Third party type is a customer and a prospect.
const CUSTOMER
Third party type is a customer.
const NO_CUSTOMER
Third party type is no customer.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
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)
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
isModEnabled($module)
Is Dolibarr module enabled.