diff --git a/htdocs/core/modules/modDataPolicy.class.php b/htdocs/core/modules/modDataPolicy.class.php index d147af6d037..fa458f434fc 100644 --- a/htdocs/core/modules/modDataPolicy.class.php +++ b/htdocs/core/modules/modDataPolicy.class.php @@ -119,6 +119,7 @@ class modDataPolicy extends DolibarrModules array('DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT', 'chaine', '', $langs->trans('NUMBER_MONTH_BEFORE_DELETION'), 0), array('DATAPOLICY_CONTACT_FOURNISSEUR', 'chaine', '', $langs->trans('NUMBER_MONTH_BEFORE_DELETION'), 0), array('DATAPOLICY_ADHERENT', 'chaine', '', $langs->trans('NUMBER_MONTH_BEFORE_DELETION'), 0), + array('DATAPOLICY_RECRUITMENT_CANDIDATURE', 'chaine', '', $langs->trans('NUMBER_MONTH_BEFORE_DELETION'), 0), ); //$country = explode(":", getDolGlobalString('MAIN_INFO_SOCIETE_COUNTRY')); diff --git a/htdocs/datapolicy/admin/setup.php b/htdocs/datapolicy/admin/setup.php index f1cad850870..518f318ee77 100644 --- a/htdocs/datapolicy/admin/setup.php +++ b/htdocs/datapolicy/admin/setup.php @@ -2,7 +2,8 @@ /* Copyright (C) 2004-2017 Laurent Destailleur * Copyright (C) 2018 Nicolas ZABOURI * Copyright (C) 2024 MDW - * Copyright (C) 2024 Frédéric France + * Copyright (C) 2024 Frédéric France + * Copyright (C) 2025 Quentin VIAL--GOUTEYRON * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,8 +39,12 @@ require_once DOL_DOCUMENT_ROOT.'/datapolicy/lib/datapolicy.lib.php'; * @var User $user */ +if (!$langs instanceof Translate) { + trigger_error("Langs object was not initialized correctly.", E_USER_ERROR); +} + // Translations -$langs->loadLangs(array('admin', 'companies', 'cron', 'members', 'datapolicy')); +$langs->loadLangs(array('admin', 'companies', 'members', 'cron', 'datapolicy', 'recruitment')); // Parameters $action = GETPOST('action', 'aZ09'); @@ -49,40 +54,143 @@ if (empty($action)) { $action = 'edit'; } +// ============================================================================== +// == DATA-DRIVEN CONFIGURATION STRUCTURE +// ============================================================================== +// This array drives the entire page logic (saving and rendering). +// It is indexed by a logical key (e.g., 'tiers_client') for each data entity. +// Each entry defines: +// - 'label_key': The translation key for the row label. +// - 'picto': The icon to display. +// - 'config_keys': An associative array mapping an action ('anonymize', 'delete') +// to the specific constant name stored in the database. +// This structure allows defining anonymization, deletion, or both for any entity. $arrayofparameters = array(); + // ThirdParty $arrayofparameters['ThirdParty'] = array( - 'DATAPOLICY_TIERS_CLIENT'=>array('css'=>'minwidth200', 'picto'=>img_picto('', 'company', 'class="pictofixedwidth"')), - 'DATAPOLICY_TIERS_PROSPECT'=>array('css'=>'minwidth200', 'picto'=>img_picto('', 'company', 'class="pictofixedwidth"')), - 'DATAPOLICY_TIERS_PROSPECT_CLIENT'=>array('css'=>'minwidth200', 'picto'=>img_picto('', 'company', 'class="pictofixedwidth"')), - 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT'=>array('css'=>'minwidth200', 'picto'=>img_picto('', 'company', 'class="pictofixedwidth"')), - 'DATAPOLICY_TIERS_FOURNISSEUR'=>array('css'=>'minwidth200', 'picto'=>img_picto('', 'supplier', 'class="pictofixedwidth"')), - ); + 'tiers_client' => array( + 'label_key' => 'DATAPOLICY_TIERS_CLIENT', + 'picto' => img_picto('', 'company', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_TIERS_CLIENT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_TIERS_CLIENT_DELETE_DELAY' + ) + ), + 'tiers_prospect' => array( + 'label_key' => 'DATAPOLICY_TIERS_PROSPECT', + 'picto' => img_picto('', 'company', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_TIERS_PROSPECT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_TIERS_PROSPECT_DELETE_DELAY' + ) + ), + 'tiers_prospect_client' => array( + 'label_key' => 'DATAPOLICY_TIERS_PROSPECT_CLIENT', + 'picto' => img_picto('', 'company', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_TIERS_PROSPECT_CLIENT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_TIERS_PROSPECT_CLIENT_DELETE_DELAY' + ) + ), + 'tiers_niprosp_niclient' => array( + 'label_key' => 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT', + 'picto' => img_picto('', 'company', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT_DELETE_DELAY' + ) + ), + 'tiers_fournisseur' => array( + 'label_key' => 'DATAPOLICY_TIERS_FOURNISSEUR', + 'picto' => img_picto('', 'supplier', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_TIERS_FOURNISSEUR_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_TIERS_FOURNISSEUR_DELETE_DELAY' + ) + ) +); // Contact if (getDolGlobalString('DATAPOLICY_USE_SPECIFIC_DELAY_FOR_CONTACT')) { $arrayofparameters['Contact'] = array( - 'DATAPOLICY_CONTACT_CLIENT' => array('css' => 'minwidth200', 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"')), - 'DATAPOLICY_CONTACT_PROSPECT' => array('css' => 'minwidth200', 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"')), - 'DATAPOLICY_CONTACT_PROSPECT_CLIENT' => array('css' => 'minwidth200', 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"')), - 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT' => array('css' => 'minwidth200', 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"')), - 'DATAPOLICY_CONTACT_FOURNISSEUR' => array('css' => 'minwidth200', 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"')), + 'contact_client' => array( + 'label_key' => 'DATAPOLICY_CONTACT_CLIENT', + 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_CONTACT_CLIENT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_CONTACT_CLIENT_DELETE_DELAY' + ) + ), + 'contact_prospect' => array( + 'label_key' => 'DATAPOLICY_CONTACT_PROSPECT', + 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_CONTACT_PROSPECT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_CONTACT_PROSPECT_DELETE_DELAY' + ) + ), + 'contact_prospect_client' => array( + 'label_key' => 'DATAPOLICY_CONTACT_PROSPECT_CLIENT', + 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_CONTACT_PROSPECT_CLIENT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_CONTACT_PROSPECT_CLIENT_DELETE_DELAY' + ) + ), + 'contact_niprosp_niclient' => array( + 'label_key' => 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT', + 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT_DELETE_DELAY' + ) + ), + 'contact_fournisseur' => array( + 'label_key' => 'DATAPOLICY_CONTACT_FOURNISSEUR', + 'picto' => img_picto('', 'contact', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_CONTACT_FOURNISSEUR_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_CONTACT_FOURNISSEUR_DELETE_DELAY' + ) + ) ); } // Member if (isModEnabled('member')) { $arrayofparameters['Member'] = array( - 'DATAPOLICY_ADHERENT' => array('css' => 'minwidth200', 'picto' => img_picto('', 'member', 'class="pictofixedwidth"')), + 'adherent' => array( + 'label_key' => 'DATAPOLICY_ADHERENT', + 'picto' => img_picto('', 'member', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'anonymize' => 'DATAPOLICY_ADHERENT_ANONYMIZE_DELAY', + 'delete' => 'DATAPOLICY_ADHERENT_DELETE_DELAY' + ) + ) + ); +} +// Recruitment: This entry demonstrates flexibility. Only a 'delete' action is defined. +// The rendering logic will automatically leave the 'anonymize' column empty for this row. +if (isModEnabled('recruitment')) { + $arrayofparameters['Recruitment'] = array( + 'recruitment_candidature' => array( + 'label_key' => 'DATAPOLICY_RECRUITMENT_CANDIDATURE', + 'picto' => img_picto('', 'recruitmentcandidature', 'class="pictofixedwidth"'), + 'config_keys' => array( + 'delete' => 'DATAPOLICY_RECRUITMENT_CANDIDATURE_DELETE_DELAY' + ) + ) ); } +// Dropdown options for delay selection $valTab = array( - '' => $langs->trans('Never'), - '6' => $langs->trans('NB_MONTHS', 6), - '12' => $langs->trans('ONE_YEAR'), - '24' => $langs->trans('NB_YEARS', 2), - '36' => $langs->trans('NB_YEARS', 3), - '48' => $langs->trans('NB_YEARS', 4), - '60' => $langs->trans('NB_YEARS', 5), + '' => $langs->trans('Never'), + '6' => $langs->trans('NB_MONTHS', 6), + '12' => $langs->trans('ONE_YEAR'), + '24' => $langs->trans('NB_YEARS', 2), + '36' => $langs->trans('NB_YEARS', 3), + '48' => $langs->trans('NB_YEARS', 4), + '60' => $langs->trans('NB_YEARS', 5), '120' => $langs->trans('NB_YEARS', 10), '180' => $langs->trans('NB_YEARS', 15), '240' => $langs->trans('NB_YEARS', 20), @@ -96,45 +204,38 @@ if (!$user->admin) { accessforbidden(); } - -'@phan-var-force array> $arrayofparameters'; - /* * Actions */ +// Handle form submission to update constants +if ($action == 'update') { + $nbdone = 0; + $error = 0; -$nbdone = 0; -$error = 0; - -foreach ($arrayofparameters as $title => $tab) { - foreach ($tab as $key => $val) { - // Modify constant only if key was posted (avoid resetting key to the null value) - if (GETPOSTISSET($key)) { - if (preg_match('/category:/', (string) $val['type'])) { - if (GETPOSTINT($key) == '-1') { - $val_const = ''; - } else { - $val_const = GETPOSTINT($key); + // Loop through the data structure to find all possible constants to save. + foreach ($arrayofparameters as $tab) { + foreach ($tab as $logicalKey => $val) { + // Iterate through defined actions ('anonymize', 'delete') for the entity. + foreach ($val['config_keys'] as $actionType => $constKey) { + // Save the constant only if its value was submitted in the form. + if (GETPOSTISSET($constKey)) { + $val_const = GETPOST($constKey, 'alpha'); + if (dolibarr_set_const($db, $constKey, $val_const, 'chaine', 0, '', $conf->entity) >= 0) { + $nbdone++; + } else { + $error++; + } } - } else { - $val_const = GETPOST($key, 'alpha'); - } - - $result = dolibarr_set_const($db, $key, $val_const, 'chaine', 0, '', $conf->entity); - if ($result < 0) { - $error++; - break; - } else { - $nbdone++; } } } -} -if ($nbdone) { - setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); -} -if ($action == 'update') { + if ($nbdone > 0) { + setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); + } + if ($error > 0) { + setEventMessages($langs->trans("Error"), null, 'errors'); + } $action = 'edit'; } @@ -146,32 +247,25 @@ if ($action == 'update') { $page_name = "datapolicySetup"; llxHeader('', $langs->trans($page_name)); -// Subheader $linkback = ''.$langs->trans("BackToModuleList").''; - print load_fiche_titre($langs->trans($page_name), $linkback, 'generic'); -// Configuration header $head = datapolicyAdminPrepareHead(); print dol_get_fiche_head($head, 'settings', '', -1, ''); -// Setup page goes here print ''.$langs->trans("datapolicySetupPage").''; print $form->textwithpicto('', $langs->trans('DATAPOLICY_Tooltip_SETUP', $langs->trans("DATAPOLICYJob"), $langs->transnoentities("CronList"))); -print '
'; -print '
'; -print '
'; - -// TODO Show the last date of execution of the job DATAPOLICYJob +print '


'; if ($action == 'edit') { print '
'; print ''; print ''; - print '
'; // You can use div-table-responsive-no-min if you don't need reserved height for your table + print '
'; print ''; + // Table Headers print ''; print ''; if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) { @@ -179,6 +273,12 @@ if ($action == 'edit') { } print ''; + // ============================================================================== + // == DYNAMIC VIEW RENDERING + // ============================================================================== + + + // Loop through each configuration group (e.g., ThirdParty, Member). foreach ($arrayofparameters as $title => $tab) { print ''; print ''; @@ -187,26 +287,28 @@ if ($action == 'edit') { } print ''; - foreach ($tab as $key => $val) { + // Loop through each entity within the group to create a table row. + foreach ($tab as $logicalKey => $val) { print ''; + + // Column 1: Anonymization + print ''; + + // Column 2: Deletion if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2) { print ''; } - print ''; } } @@ -218,29 +320,8 @@ if ($action == 'edit') { print ''; print '
'; -} else { - print '
'.$langs->trans("DelayForAnonymization").'
'.$langs->trans($title).'
'; print $val['picto']; - print $langs->trans($key); - print ''; - print ''; - print ajax_combobox($key); + print $langs->trans($val['label_key']); print ''; + // Display the dropdown only if a constant key is defined for the 'anonymize' action. + if (isset($val['config_keys']['anonymize'])) { + print Form::selectarray($val['config_keys']['anonymize'], $valTab, getDolGlobalString($val['config_keys']['anonymize'])); + } + print ''; - print $langs->trans("FeatureNotYetAvailable"); + // Display the dropdown only if a constant key is defined for the 'delete' action. + print Form::selectarray($val['config_keys']['delete'], $valTab, getDolGlobalString($val['config_keys']['delete'])); print '
'; - - foreach ($arrayofparameters as $title => $tab) { - print ''; - foreach ($tab as $key => $val) { - print ''; - } - } - - print '
'.$langs->trans($title).'
'; - print $val['picto']; - print $langs->trans($key); - print ''.(getDolGlobalString($key) == '' ? ''.$valTab[''].'' : $valTab[getDolGlobalString($key)]).'
'; - - print '
'; - print ''.$langs->trans("Modify").''; - print '
'; } - -// Page end print dol_get_fiche_end(); - llxFooter(); $db->close(); diff --git a/htdocs/datapolicy/class/datapolicycron.class.php b/htdocs/datapolicy/class/datapolicycron.class.php index 6b588eda92c..405aca8da30 100644 --- a/htdocs/datapolicy/class/datapolicycron.class.php +++ b/htdocs/datapolicy/class/datapolicycron.class.php @@ -1,8 +1,9 @@ * Copyright (C) 2018-2024 Frédéric France - * Copyright (C) 2024 William Mead - * Copyright (C) 2024 MDW + * Copyright (C) 2024 William Mead + * Copyright (C) 2024 MDW + * Copyright (C) 2025 Quentin VIAL--GOUTEYRON * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,516 +30,349 @@ */ class DataPolicyCron { - /** - * @var DoliDB Database handler. - */ + /** @var DoliDB Database handler. */ public $db; - - /** - * @var string - */ + /** @var string Final error message if any. */ public $error; - - /** - * @var string - */ + /** @var string Final output message on success. */ public $output; - + /** @var int Counter for updated records. */ + private $nbupdated = 0; + /** @var int Counter for deleted records. */ + private $nbdeleted = 0; + /** @var int Counter for errors. */ + private $errorCount = 0; + /** @var string[] Array to store detailed error messages. */ + private $errorMessages = array(); /** - * Constructor - * - * @param DoliDB $db Database handler + * Constructor + * @param DoliDB $db Database handler */ public function __construct(DoliDB $db) { $this->db = $db; } - /** - * Function exec - * CAN BE A CRON TASK - * - * @return int if OK: 0 (this function is used also by cron so only 0 is OK) + * Main cron task execution method. + * Orchestrates the data cleaning process by iterating through all defined policies. + * @return int Returns 0 for success, 1 for failure, as required for cron jobs. */ - public function cleanDataForDataPolicy() + public function cleanDataForDataPolicy() : int { - global $conf, $langs, $user; + global $conf, $user; - $langs->load('datapolicy@datapolicy'); + // Reset state properties for this specific execution run. + $this->nbupdated = 0; + $this->nbdeleted = 0; + $this->errorCount = 0; + $this->errorMessages = array(); - $error = 0; - $errormsg = ''; - $nbupdated = $nbdeleted = 0; + // Tracks record IDs that have been processed in this run to prevent duplicate actions (e.g., anonymizing a just-deleted record). + $processedIds = array(); + // Caches object instances to avoid redundant 'new Class()' calls, improving performance. + $objectInstances = array(); - // FIXME Exclude data from the selection if there is at least 1 invoice. - $arrayofparameters = array( - 'DATAPOLICY_TIERS_CLIENT' => array( - 'sql' => " - SELECT s.rowid FROM ".MAIN_DB_PREFIX."societe as s - WHERE s.entity = %d - AND s.client = 1 - AND s.fournisseur = 0 - AND s.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Societe", - "file" => DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php', - 'fields_anonym' => array( - 'name' => 'MAKEANONYMOUS', - 'name_bis' => '', - 'name_alias' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_TIERS_PROSPECT' => array( - 'sql' => " - SELECT s.rowid FROM ".MAIN_DB_PREFIX."societe as s - WHERE s.entity = %d - AND s.client = 2 - AND s.fournisseur = 0 - AND s.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Societe", - "file" => DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php', - 'fields_anonym' => array( - 'name' => 'MAKEANONYMOUS', - 'name_bis' => '', - 'name_alias' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_TIERS_PROSPECT_CLIENT' => array( - 'sql' => " - SELECT s.rowid FROM ".MAIN_DB_PREFIX."societe as s - WHERE s.entity = %d - AND s.client = 3 - AND s.fournisseur = 0 - AND s.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Societe", - "file" => DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php', - 'fields_anonym' => array( - 'name' => 'MAKEANONYMOUS', - 'name_bis' => '', - 'name_alias' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT' => array( - 'sql' => " - SELECT s.rowid FROM ".MAIN_DB_PREFIX."societe as s - WHERE s.entity = %d - AND s.client = 0 - AND s.fournisseur = 0 - AND s.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Societe", - "file" => DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php', - 'fields_anonym' => array( - 'name' => 'MAKEANONYMOUS', - 'name_bis' => '', - 'name_alias' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_TIERS_FOURNISSEUR' => array( - 'sql' => " - SELECT s.rowid FROM ".MAIN_DB_PREFIX."societe as s - WHERE s.entity = %d - AND s.fournisseur = 1 - AND s.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_soc = s.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Societe", - "file" => DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php', - 'fields_anonym' => array( - 'name' => 'MAKEANONYMOUS', - 'name_bis' => '', - 'name_alias' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_CONTACT_CLIENT' => array( - 'sql' => " - SELECT c.rowid FROM ".MAIN_DB_PREFIX."socpeople as c - INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = c.fk_soc - WHERE c.entity = %d - AND c.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND s.client = 1 - AND s.fournisseur = 0 - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Contact", - "file" => DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php', - 'fields_anonym' => array( - 'lastname' => 'MAKEANONYMOUS', - 'firstname' => '', - 'civility_id' => '', - 'poste' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone_pro' => '', - 'phone_perso' => '', - 'phone_mobile' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_CONTACT_PROSPECT' => array( - 'sql' => " - SELECT c.rowid FROM ".MAIN_DB_PREFIX."socpeople as c - INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = c.fk_soc - WHERE c.entity = %d - AND c.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND s.client = 2 - AND s.fournisseur = 0 - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Contact", - "file" => DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php', - 'fields_anonym' => array( - 'lastname' => 'MAKEANONYMOUS', - 'firstname' => '', - 'civility_id' => '', - 'poste' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone_pro' => '', - 'phone_perso' => '', - 'phone_mobile' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_CONTACT_PROSPECT_CLIENT' => array( - 'sql' => " - SELECT c.rowid FROM ".MAIN_DB_PREFIX."socpeople as c - INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = c.fk_soc - WHERE c.entity = %d - AND c.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND s.client = 3 - AND s.fournisseur = 0 - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Contact", - "file" => DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php', - 'fields_anonym' => array( - 'lastname' => 'MAKEANONYMOUS', - 'firstname' => '', - 'civility_id' => '', - 'poste' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone_pro' => '', - 'phone_perso' => '', - 'phone_mobile' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT' => array( - 'sql' => " - SELECT c.rowid FROM ".MAIN_DB_PREFIX."socpeople as c - INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = c.fk_soc - WHERE c.entity = %d - AND c.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND s.client = 0 - AND s.fournisseur = 0 - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Contact", - "file" => DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php', - 'fields_anonym' => array( - 'lastname' => 'MAKEANONYMOUS', - 'firstname' => '', - 'civility_id' => '', - 'poste' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone_pro' => '', - 'phone_perso' => '', - 'phone_mobile' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_CONTACT_FOURNISSEUR' => array( - 'sql' => " - SELECT c.rowid FROM ".MAIN_DB_PREFIX."socpeople as c - INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = c.fk_soc - WHERE c.entity = %d - AND c.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND s.fournisseur = 1 - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_contact = c.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - ) - AND NOT EXISTS ( - SELECT rowid FROM ".MAIN_DB_PREFIX."facture as f WHERE f.fk_soc = s.rowid - ) - ", - "class" => "Contact", - "file" => DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php', - 'fields_anonym' => array( - 'lastname' => 'MAKEANONYMOUS', - 'firstname' => '', - 'civility_id' => '', - 'poste' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone_pro' => '', - 'phone_perso' => '', - 'phone_mobile' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - 'DATAPOLICY_ADHERENT' => array( - 'sql' => " - SELECT a.rowid FROM ".MAIN_DB_PREFIX."adherent as a - WHERE a.entity = %d - AND a.tms < DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) - AND NOT EXISTS ( - SELECT id FROM ".MAIN_DB_PREFIX."actioncomm as a WHERE a.fk_element = a.rowid AND a.tms > DATE_SUB('".$this->db->idate(dol_now())."', INTERVAL %d MONTH) AND a.elementtype LIKE 'member' - ) - ", - "class" => "Adherent", - "file" => DOL_DOCUMENT_ROOT.'/adherents/class/adherent.class.php', - 'fields_anonym' => array( - 'lastname' => 'MAKEANONYMOUS', - 'firstname' => 'MAKEANONYMOUS', - 'civility_id' => '', - 'societe' => '', - 'address' => '', - 'town' => '', - 'zip' => '', - 'phone' => '', - 'phone_perso' => '', - 'phone_mobile' => '', - 'email' => '', - 'url' => '', - 'fax' => '', - 'state' => '', - 'country' => '', - 'state_id' => 1, - 'socialnetworks' => [], - 'country_id' => 0, - ) - ), - ); + // Retrieve the master list of all data policies. This separates configuration from execution. + $dataPolicies = $this->_getDataPolicies(); $this->db->begin(); - // Loop on each type of data - foreach ($arrayofparameters as $key => $params) { - if (getDolGlobalInt($key) > 0) { - // @phan-suppress-next-line PhanPluginPrintfVariableFormatString - $sql = sprintf($params['sql'], (int) $conf->entity, (int) getDolGlobalInt($key), (int) getDolGlobalInt($key)); + // Iterate through each defined policy to apply its rules. + foreach ($dataPolicies as $policy) { + // Instantiate object only once per class type for efficiency. + if (! isset($objectInstances[$policy['class']])) { + require_once $policy['file']; + $objectInstances[$policy['class']] = new $policy['class']($this->db); + } + $object = $objectInstances[$policy['class']]; - $resql = $this->db->query($sql); + // The order of operations is critical: deletion is always processed before anonymization. + // This ensures that if a record meets criteria for both, it is deleted as the final action. + $this->_processPolicyAction($policy, 'delete', $object, $processedIds, $conf, $user); + $this->_processPolicyAction($policy, 'anonymize', $object, $processedIds, $conf, $user); + } - if ($resql && $this->db->num_rows($resql) > 0) { - $num = $this->db->num_rows($resql); - $i = 0; + // Finalize the transaction based on the outcome of all operations. + if (! $this->errorCount) { + $this->db->commit(); + $this->output = $this->nbupdated . ' record(s) anonymized, ' . $this->nbdeleted . ' record(s) deleted.'; + } else { + $this->db->rollback(); + $this->error = implode("\n", $this->errorMessages); + } - require_once $params['file']; - $object = new $params['class']($this->db); + return $this->errorCount ? 1 : 0; + } - while ($i < $num && !$error) { - $obj = $this->db->fetch_object($resql); + /** + * Processes a specific action (delete or anonymize) for a given policy. + * This method orchestrates the process by delegating to specialized handlers. + * + * @param array $policy The policy definition array. + * @param string $action The action to perform: 'delete' or 'anonymize'. + * @param CommonObject $object The instantiated Dolibarr object. + * @param int[] $processedIds Reference to the array of processed IDs. + * @param object $conf The global conf object. + * @param User $user The user object for history tracking. + * @return void + */ + private function _processPolicyAction($policy, $action, $object, &$processedIds, $conf, $user) + { + $constName = $policy['const_' . $action] ?? null; + $delay = $constName ? getDolGlobalInt($constName) : 0; - $object->fetch($obj->rowid); - $object->id = $obj->rowid; + if ($delay <= 0) { + return; + } - $action = 'anonymize'; // TODO Offer also action "delete" in the setup of the module + // Prepare SQL query + $sqlPlaceholders = array( + '__ENTITY__' => (string) $conf->entity, + '__DELAY__' => (string) $delay, + '__NOW__' => "'" . $this->db->idate(dol_now()) . "'" + ); + $sql = str_replace(array_keys($sqlPlaceholders), array_values($sqlPlaceholders), $policy['sql_template']); - // Manage action 'anonymize' - if ($action == 'anonymize') { - if ($object->isObjectUsed($obj->rowid) == 0) { // If object to clean is not used - // Loop on each field to anonymize - foreach ($params['fields_anonym'] as $field => $val) { - if ($val == 'MAKEANONYMOUS') { - $object->$field = $field.'-anonymous-'.$obj->rowid; // @phpstan-ignore-line - } else { - $object->$field = $val; - } - } + $resql = $this->db->query($sql); - // Update record - $result = $object->update($obj->rowid, $user); + if (! $resql) { + $this->errorCount++; + $this->errorMessages[] = 'Error executing ' . $action . ' query for policy ' . $constName . ': ' . $this->db->lasterror(); - if ($result > 0) { - $errormsg = $object->error; - $error++; - } - $nbupdated++; - } - } + return; + } - // Manage action 'deletion' - if ($action == 'delete') { // If object to clean is not used - $result = $object->delete($user); - if ($result < 0) { - $errormsg = $object->error; - $error++; - } + // Define the handler method for the action + $handlerMethod = '_handle' . ucfirst($action); - $nbdeleted++; - } + // Process the records found by the query + while ($obj = $this->db->fetch_object($resql)) { + if (in_array($obj->rowid, $processedIds) || ! method_exists($this, $handlerMethod)) { + continue; + } + /** @var CommonObject $object */ + $object = clone $object; + $object->fetch($obj->rowid); - $i++; - } - } + if (!empty($object->childtables) && method_exists($object, 'isObjectUsed') && $object->isObjectUsed() != 0) { + continue; // Not an error, just skipping. + } + + // Dynamically call the appropriate handler (_handleDelete or _handleAnonymize) + $result = $this->$handlerMethod($object, $user, $policy); + + // Record the outcome and add to processed list on success + $this->_recordActionResult($result, $object, $action); + $processedIds[] = $obj->rowid; + } + } + + /** + * Handles the specific logic for deleting an object. + * + * @param CommonObject $object The object to delete. + * @param User $user The user performing the action. + * @param array $policy The policy configuration. + * @return int The result of the delete operation. + */ + private function _handleDelete($object, $user, $policy): int + { + $callArgs = $this->_buildCallArguments($object, $user, $policy, 'delete'); + + return $object->delete(...$callArgs); + } + + /** + * Handles the specific logic for anonymizing an object. + * + * @param CommonObject $object The object to anonymize. + * @param User $user The user performing the action. + * @param array $policy The policy configuration. + * @return int The result of the update operation, or 0 if skipped. + */ + private function _handleAnonymize($object, $user, $policy) : int + { + foreach ($policy['anonymize_fields'] as $field => $val) { + $object->$field = ($val == 'MAKEANONYMOUS') ? $field . '-anonymous-' . $object->id : $val; + } + + $callArgs = $this->_buildCallArguments($object, $user, $policy, 'update'); + + return $object->update(...$callArgs); + } + + /** + * Builds the dynamic argument list for method calls based on policy configuration. + * + * @param CommonObject $object The target object. + * @param User $user The user object. + * @param array $policy The policy configuration. + * @param string $method The method key ('delete' or 'update'). + * @return mixed[] The list of arguments for the call. + */ + private function _buildCallArguments($object, $user, $policy, $method) + { + $availableArgs = array( + 'id' => $object->id, + 'user' => $user + ); + + $paramConfig = $policy['call_params'][$method] ?? []; + + return array_map(function (string $paramName) use ($availableArgs) { + return $availableArgs[$paramName]; + }, $paramConfig); + } + + /** + * Records the result of an action, updating counters and error messages. + * + * @param int $result The result code from the action (<0 for error). + * @param CommonObject $object The processed object. + * @param string $action The action that was performed ('delete' or 'anonymize'). + * @return void + */ + private function _recordActionResult($result, $object, $action) + { + if ($result <= 0) { + $this->errorCount++; + $this->errorMessages[] = 'Failed to ' . $action . ' record ID ' . $object->id . ' from class ' . get_class($object) . '. Error: ' . $object->errorsToString(); + } else { + if ($action === 'delete') { + $this->nbdeleted++; + } elseif ($action === 'anonymize') { + // Only count as updated if the update method returns a positive result + $this->nbupdated++; } } + } - $this->db->commit(); + /** + * Defines and returns the centralized data policy configuration. + * Separating this makes the main method cleaner. + * @return array> The array of all data policies. + */ + private function _getDataPolicies() : array + { + $prefix = $this->db->prefix(); - if (!$error) { - $this->output = $nbupdated.' record updated, '.$nbdeleted.' record deleted'; - } else { - $this->error = $errormsg; - } - - return 0; + return array( + // --- Third Parties --- + 'tiers_client' => array( + 'const_delete' => 'DATAPOLICY_TIERS_CLIENT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_TIERS_CLIENT_ANONYMIZE_DELAY', + 'sql_template' => "SELECT s.rowid FROM {$prefix}societe as s WHERE s.entity = __ENTITY__ AND s.client = 1 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)", + 'class' => 'Societe', 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php', 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_bis' => '', 'name_alias' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('id', 'user'), // $object->delete($id, $user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + 'tiers_prospect' => array( + 'const_delete' => 'DATAPOLICY_TIERS_PROSPECT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_TIERS_PROSPECT_ANONYMIZE_DELAY', + 'sql_template' => "SELECT s.rowid FROM {$prefix}societe as s WHERE s.entity = __ENTITY__ AND s.client = 2 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)", + 'class' => 'Societe', 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php', 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_bis' => '', 'name_alias' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('id', 'user'), // $object->delete($id, $user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + 'tiers_prospect_client' => array( + 'const_delete' => 'DATAPOLICY_TIERS_PROSPECT_CLIENT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_TIERS_PROSPECT_CLIENT_ANONYMIZE_DELAY', + 'sql_template' => "SELECT s.rowid FROM {$prefix}societe as s WHERE s.entity = __ENTITY__ AND s.client = 3 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)", + 'class' => 'Societe', 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php', 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_bis' => '', 'name_alias' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('id', 'user'), // $object->delete($id, $user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + 'tiers_niprosp_niclient' => array( + 'const_delete' => 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_TIERS_NIPROSPECT_NICLIENT_ANONYMIZE_DELAY', + 'sql_template' => "SELECT s.rowid FROM {$prefix}societe as s WHERE s.entity = __ENTITY__ AND s.client = 0 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)", + 'class' => 'Societe', 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php', 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_bis' => '', 'name_alias' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('id', 'user'), // $object->delete($id, $user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + 'tiers_fournisseur' => array( + 'const_delete' => 'DATAPOLICY_TIERS_FOURNISSEUR_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_TIERS_FOURNISSEUR_ANONYMIZE_DELAY', + '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 as f WHERE f.fk_soc = s.rowid)", + 'class' => 'Societe', 'file' => DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php', 'anonymize_fields' => array('name' => 'MAKEANONYMOUS', 'name_bis' => '', 'name_alias' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('id', 'user'), // $object->delete($id, $user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + // --- Contacts --- + 'contact_client' => array( + 'const_delete' => 'DATAPOLICY_CONTACT_CLIENT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_CONTACT_CLIENT_ANONYMIZE_DELAY', + '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 = 1 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)", + 'class' => 'Contact', 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php', 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => '', 'civility_id' => '', 'poste' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone_pro' => '', 'phone_perso' => '', 'phone_mobile' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('user'), // $object->delete($user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + 'contact_prospect' => array( + 'const_delete' => 'DATAPOLICY_CONTACT_PROSPECT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_CONTACT_PROSPECT_ANONYMIZE_DELAY', + '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 = 2 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)", + 'class' => 'Contact', 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php', 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => '', 'civility_id' => '', 'poste' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone_pro' => '', 'phone_perso' => '', 'phone_mobile' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('user'), // $object->delete($user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + 'contact_prospect_client' => array( + 'const_delete' => 'DATAPOLICY_CONTACT_PROSPECT_CLIENT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_CONTACT_PROSPECT_CLIENT_ANONYMIZE_DELAY', + '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 = 3 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)", + 'class' => 'Contact', 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php', 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => '', 'civility_id' => '', 'poste' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone_pro' => '', 'phone_perso' => '', 'phone_mobile' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('user'), // $object->delete($user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + 'contact_niprosp_niclient' => array( + 'const_delete' => 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT_ANONYMIZE_DELAY', + '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 = 0 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)", + 'class' => 'Contact', 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php', 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => '', 'civility_id' => '', 'poste' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone_pro' => '', 'phone_perso' => '', 'phone_mobile' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('user'), // $object->delete($user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + 'contact_fournisseur' => array( + 'const_delete' => 'DATAPOLICY_CONTACT_FOURNISSEUR_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_CONTACT_FOURNISSEUR_ANONYMIZE_DELAY', + '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 as f WHERE f.fk_soc = s.rowid)", + 'class' => 'Contact', 'file' => DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php', 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => '', 'civility_id' => '', 'poste' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone_pro' => '', 'phone_perso' => '', 'phone_mobile' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('user'), // $object->delete($user) + 'update' => array('id', 'user') // $object->update($id, $user) + ) + ), + // --- Members --- + 'adherent' => array( + 'const_delete' => 'DATAPOLICY_ADHERENT_DELETE_DELAY', 'const_anonymize' => 'DATAPOLICY_ADHERENT_ANONYMIZE_DELAY', + 'sql_template' => "SELECT a.rowid FROM {$prefix}adherent as a WHERE a.entity = __ENTITY__ AND a.tms < DATE_SUB(__NOW__, INTERVAL __DELAY__ MONTH) 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))", + 'class' => 'Adherent', 'file' => DOL_DOCUMENT_ROOT . '/adherents/class/adherent.class.php', 'anonymize_fields' => array('lastname' => 'MAKEANONYMOUS', 'firstname' => 'MAKEANONYMOUS', 'civility_id' => '', 'societe' => '', 'address' => '', 'town' => '', 'zip' => '', 'phone' => '', 'phone_perso' => '', 'phone_mobile' => '', 'email' => '', 'url' => '', 'fax' => '', 'state' => '', 'country' => '', 'state_id' => 1, 'socialnetworks' => [], 'country_id' => 0), + 'call_params' => array( + 'delete' => array('user'), // $object->delete($user) + 'update' => array('user') // $object->update($user) + ) + ), + // --- Recruitment --- + 'recruitment_candidature' => array( + 'const_delete' => 'DATAPOLICY_RECRUITMENT_CANDIDATURE_DELETE_DELAY', 'const_anonymize' => '', // Anonymization not applicable + 'sql_template' => "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))", + 'class' => 'RecruitmentCandidature', + 'file' => DOL_DOCUMENT_ROOT . '/recruitment/class/recruitmentcandidature.class.php', + 'anonymize_fields' => array(), + 'call_params' => array( + 'delete' => array('user'), // $object->delete($user) + 'update' => array('user') // $object->update($user) + ) + ) + ); } } diff --git a/htdocs/langs/en_US/datapolicy.lang b/htdocs/langs/en_US/datapolicy.lang index 3f8e33544fa..ab9ccb819be 100644 --- a/htdocs/langs/en_US/datapolicy.lang +++ b/htdocs/langs/en_US/datapolicy.lang @@ -34,6 +34,7 @@ DATAPOLICY_CONTACT_PROSPECT_CLIENT = Prospect/Customer DATAPOLICY_CONTACT_NIPROSPECT_NICLIENT = Nor prospect/Nor customer DATAPOLICY_CONTACT_FOURNISSEUR = Supplier DATAPOLICY_ADHERENT = Member +DATAPOLICY_RECRUITMENT_CANDIDATURE = Application DATAPOLICY_Tooltip_SETUP=The anonymization is done by the scheduled job "%s" ran by the module "%s", so this module must be enabled and working correctly. SendAgreementText = You can send a GDPR email to all your relevant contacts (who have not yet received an email and for which you have not registered anything about their GDPR agreement). To do this, use the following button. SendAgreement = Send emails