dolibarr 25.0.0-alpha
DolibarrModules.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
4 * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
5 * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
6 * Copyright (C) 2005-2013 Laurent Destailleur <eldy@users.sourceforge.net>
7 * Copyright (C) 2005-2024 Regis Houssin <regis.houssin@inodbox.com>
8 * Copyright (C) 2014 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
9 * Copyright (C) 2018 Josep Lluís Amador <joseplluis@lliuretic.cat>
10 * Copyright (C) 2019-2026 Frédéric France <frederic.france@free.fr>
11 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25 */
26
38class DolibarrModules // Can not be abstract, because we need to instantiate it into unActivateModule to be able to disable a module whose files were removed.
39{
43 public $db;
44
49 public $numero;
50
54 public $editor_name;
55
59 public $editor_url;
60
64 public $editor_squarred_logo;
65
73 public $family;
74
87 public $familyinfo;
88
92 public $module_position = '50';
93
102 public $name;
103
109 public $dirs = array();
110
114 public $boxes = array();
115
120 public $const = array();
121
125 public $cronjobs = array();
126
130 public $rights;
131
135 public $rights_admin_allowed;
136
140 public $rights_class;
141
142 const URL_FOR_BLACKLISTED_MODULES = 'https://www.dolibarr.org/modules-blacklist.php';
143
144 const KEY_ID = 0;
145 const KEY_LABEL = 1;
146 const KEY_TYPE = 2; // deprecated
147 const KEY_DEFAULT = 3;
148 const KEY_FIRST_LEVEL = 4;
149 const KEY_SECOND_LEVEL = 5;
150 const KEY_MODULE = 6;
151 const KEY_ENABLED = 7;
152
156 public $menu = array();
157
185 public $module_parts = array();
186
190 public $error;
191
195 public $errors;
196
207 public $version;
208
212 public $version_if_core = 0;
213
218 public $lastVersion = '';
219
224 public $needUpdate = false;
225
231 public $description;
232
239 public $descriptionlong;
240
244 public $dictionaries = array();
245
249 public $tabs;
250
251 // For exports
252
256 public $export_code;
257
261 public $export_label;
262
266 public $export_icon;
267
271 public $export_enabled;
275 public $export_permission;
279 public $export_fields_array;
283 public $export_TypeFields_array; // Array of key=>type where type can be 'Numeric', 'Date', 'Text', 'Boolean', 'Status', 'List:xxx:fieldlabel:rowid'
287 public $export_entities_array;
291 public $export_aggregate_array;
295 public $export_examplevalues_array;
299 public $export_help_array;
306 public $export_special_array; // special or computed field
313 public $export_dependencies_array;
317 public $export_sql_start;
321 public $export_sql_end;
325 public $export_sql_order;
326
327
328 // For import
329
333 public $import_code;
334
338 public $import_label;
339
343 public $import_icon;
347 public $import_entities_array;
351 public $import_tables_array;
355 public $import_tables_creator_array;
359 public $import_fields_array;
363 public $import_fieldshidden_array;
367 public $import_convertvalue_array;
371 public $import_regex_array;
375 public $import_examplevalues_array;
379 public $import_updatekeys_array;
383 public $import_run_sql_after_array;
387 public $import_TypeFields_array;
391 public $import_help_array;
392
396 public $const_name;
397
401 public $always_enabled;
402
406 public $disabled;
407
411 public $automatic_activation = array();
412
416 public $core_enabled;
417
424 public $picto;
425
432 public $config_page_url;
433
434
443 public $depends;
444
449 public $requiredby;
450
455 public $conflictwith;
456
460 public $langfiles;
461
467 public $warnings_activation;
468
474 public $warnings_activation_ext;
475
481 public $warnings_unactivation;
482
487 public $phpmin;
488
492 public $phpmax;
493
498 public $need_dolibarr_version;
499
504 public $max_dolibarr_version;
505
509 public $need_javascript_ajax;
510
514 public $enabled_bydefault;
515
519 public $hidden = false;
520
524 public $url_last_version;
525
526
532 public function __construct($db)
533 {
534 $this->db = $db;
535 }
536 // We should but can't set this as abstract because this will make dolibarr hang
537 // after migration due to old module not implementing. We must wait PHP is able to make
538 // a try catch on Fatal error to manage this correctly.
539 // We need constructor into function unActivateModule into admin.lib.php
540
541
542 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
553 protected function _init($array_sql, $options = '')
554 {
555 // phpcs:enable
556 global $conf;
557 $err = 0;
558
559 $this->db->begin();
560
561 // Insert activation module constant
562 if (!$err) {
563 $err += $this->_active();
564 }
565
566 // Insert new pages for tabs (into llx_const)
567 if (!$err) {
568 $err += $this->insert_tabs();
569 }
570
571 // Insert activation of module's parts. Copy website templates into doctemplates.
572 if (!$err) {
573 $err += $this->insert_module_parts();
574 }
575
576 // Insert constant defined by modules (into llx_const) if no existing yet
577 if (!$err && !preg_match('/newboxdefonly/', $options)) {
578 $err += $this->insert_const(); // Test on newboxdefonly to avoid to erase value during upgrade
579 }
580
581 // Insert boxes def (into llx_boxes_def) and boxes setup (into llx_boxes)
582 if (!$err && !preg_match('/noboxes/', $options)) {
583 $err += $this->insert_boxes($options);
584 }
585
586 // Insert cron job entries (entry in llx_cronjobs)
587 if (!$err) {
588 $err += $this->insert_cronjobs();
589 }
590
591 // Insert permission definitions of module into llx_rights_def. If user is admin, grant this permission to user.
592 if (!$err) {
593 $err += $this->insert_permissions(1, null, 1);
594 }
595
596 // Insert specific menus entries into database
597 if (!$err) {
598 $err += $this->insert_menus();
599 }
600
601 // Create module's directories
602 if (!$err) {
603 $err += $this->create_dirs();
604 }
605
606 // Execute addons requests
607 $num = count($array_sql);
608 for ($i = 0; $i < $num; $i++) {
609 if (!$err) {
610 $sql = $array_sql[$i];
611 $val = $sql;
612 $ignoreerror = 0;
613 if (is_array($val)) {
614 $sql = $val['sql'];
615 $ignoreerror = $val['ignoreerror'] ?? 0;
616 }
617 // Add current entity id
618 $sql = str_replace('__ENTITY__', (string) ((int) $conf->entity), $sql);
619
620 dol_syslog(get_class($this)."::_init ignoreerror=".$ignoreerror, LOG_DEBUG);
621 $result = $this->db->query($sql, $ignoreerror);
622 if (!$result) {
623 if (!$ignoreerror) {
624 $this->error = $this->db->lasterror();
625 $err++;
626 } else {
627 dol_syslog(get_class($this)."::_init Warning ".$this->db->lasterror(), LOG_WARNING);
628 }
629 }
630 }
631 }
632
633 // Return code
634 if (!$err) {
635 $this->db->commit();
636
637 $moduleNameInConf = strtolower(preg_replace('/^MAIN_MODULE_/', '', $this->const_name));
638 // two exceptions to handle
639 if ($moduleNameInConf === 'propale') {
640 $moduleNameInConf = 'propal';
641 } elseif ($moduleNameInConf === 'supplierproposal') {
642 $moduleNameInConf = 'supplier_proposal';
643 }
644
645 $conf->modules[$moduleNameInConf] = $moduleNameInConf; // Add this module in list of enabled modules so isModEnabled() will work (conf->module->enabled must no more be used)
646
647 return 1;
648 } else {
649 $this->db->rollback();
650 return 0;
651 }
652 }
653
654 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
663 protected function _remove($array_sql, $options = '')
664 {
665 global $conf;
666 // phpcs:enable
667 $err = 0;
668
669 $this->db->begin();
670
671 // Remove activation module line (constant MAIN_MODULE_MYMODULE in llx_const)
672 if (!$err) {
673 $err += $this->_unactive();
674 }
675
676 // Remove activation of module's new tabs (MAIN_MODULE_MYMODULE_TABS_XXX in llx_const)
677 if (!$err) {
678 $err += $this->delete_tabs();
679 }
680
681 // Remove activation of module's parts (MAIN_MODULE_MYMODULE_XXX in llx_const)
682 if (!$err) {
683 $err += $this->delete_module_parts();
684 }
685
686 // Remove constants defined by modules
687 if (!$err) {
688 $err += $this->delete_const();
689 }
690
691 // Remove list of module's available boxes (entry in llx_boxes)
692 if (!$err && !preg_match('/(newboxdefonly|noboxes)/', $options)) {
693 $err += $this->delete_boxes(); // We don't have to delete if option ask to keep boxes safe or ask to add new box def only
694 }
695
696 // Remove list of module's cron job entries (entry in llx_cronjobs)
697 if (!$err) {
698 $err += $this->delete_cronjobs();
699 }
700
701 // Remove module's permissions from list of available permissions (entries in llx_rights_def)
702 if (!$err) {
703 $err += $this->delete_permissions();
704 }
705
706 // Remove module's menus (entries in llx_menu)
707 if (!$err) {
708 $err += $this->delete_menus();
709 }
710
711 // Remove module's directories
712 if (!$err) {
713 $err += $this->delete_dirs();
714 }
715
716 // Run complementary sql requests
717 $num = count((array) $array_sql);
718 for ($i = 0; $i < $num; $i++) {
719 if (!$err) {
720 dol_syslog(get_class($this)."::_remove", LOG_DEBUG);
721 $result = $this->db->query($array_sql[$i]);
722 if (!$result) {
723 $this->error = $this->db->error();
724 $err++;
725 }
726 }
727 }
728
729 // Return code
730 if (!$err) {
731 $this->db->commit();
732
733 // Disable modules
734 $moduleNameInConf = strtolower(preg_replace('/^MAIN_MODULE_/', '', $this->const_name));
735 // two exceptions to handle
736 if ($moduleNameInConf === 'propale') {
737 $moduleNameInConf = 'propal';
738 } elseif ($moduleNameInConf === 'supplierproposal') {
739 $moduleNameInConf = 'supplier_proposal';
740 }
741
742 unset($conf->modules[$moduleNameInConf]); // Add this module in list of enabled modules so isModEnabled() will work (conf->module->enabled must no more be used)
743
744 return 1;
745 } else {
746 $this->db->rollback();
747 return 0;
748 }
749 }
750
751
758 public function getName()
759 {
760 global $langs;
761 $langs->load("admin");
762
763 if ($langs->transnoentitiesnoconv("Module".$this->numero."Name") != "Module".$this->numero."Name") {
764 // If module name translation exists
765 return $langs->transnoentitiesnoconv("Module".$this->numero."Name");
766 } else {
767 // If module name translation using it's unique id does not exist, we try to use its name to find translation
768 if (is_array($this->langfiles)) {
769 foreach ($this->langfiles as $val) {
770 if ($val) {
771 $langs->load($val);
772 }
773 }
774 }
775
776 if ($langs->trans("Module".$this->name."Name") != "Module".$this->name."Name") {
777 // If module name translation exists
778 return $langs->transnoentitiesnoconv("Module".$this->name."Name");
779 }
780
781 // Last chance with simple label
782 return $langs->transnoentitiesnoconv($this->name);
783 }
784 }
785
786
793 public function getDesc($foruseinpopupdesc = 0)
794 {
795 global $langs;
796 $langs->load("admin");
797
798 if ($langs->transnoentitiesnoconv("Module".$this->numero."Desc") != "Module".$this->numero."Desc") {
799 // If module description translation exists
800 return $langs->transnoentitiesnoconv("Module".$this->numero."Desc");
801 } else {
802 // If module description translation does not exist using its unique id, we can use its name to find translation
803 if (is_array($this->langfiles)) {
804 foreach ($this->langfiles as $val) {
805 if ($val) {
806 $langs->load($val);
807 }
808 }
809 }
810
811 if ($langs->transnoentitiesnoconv("Module".$this->name."Desc") != "Module".$this->name."Desc") {
812 // If module name translation exists
813 return $langs->trans("Module".$this->name."Desc");
814 }
815
816 // Last chance with simple label
817 return $langs->trans($this->description);
818 }
819 }
820
827 public function getDescLong()
828 {
829 global $langs;
830 $langs->load("admin");
831
832 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
833 include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
834
835 $content = '';
836 $pathoffile = $this->getDescLongReadmeFound();
837
838 if ($pathoffile) { // Mostly for external modules
839 $content = file_get_contents($pathoffile, false, null, 0, 1024 * 1024); // Max size loaded 1Mb
840
841 if ((float) DOL_VERSION >= 6.0) { // @phpstan-ignore-line
842 @include_once DOL_DOCUMENT_ROOT.'/core/lib/parsemd.lib.php';
843
844 $content = dolMd2Html(
845 $content,
846 'parsedown',
847 array(
848 'doc/' => dol_buildpath(strtolower($this->name).'/doc/', 1),
849 'img/' => dol_buildpath(strtolower($this->name).'/img/', 1),
850 'images/' => dol_buildpath(strtolower($this->name).'/images/', 1),
851 )
852 );
853
854 $content = preg_replace('/<a href="/', '<a target="_blank" rel="noopener noreferrer" href="', $content);
855 } else {
856 $content = nl2br($content);
857 }
858 } else {
859 // Mostly for internal modules
860 if (!empty($this->descriptionlong)) {
861 if (is_array($this->langfiles)) {
862 foreach ($this->langfiles as $val) {
863 if ($val) {
864 $langs->load($val);
865 }
866 }
867 }
868
869 $content = $langs->transnoentitiesnoconv($this->descriptionlong);
870 }
871 }
872
873 return '<div class="moduledesclong">'.$content.'</div>';
874 }
875
881 public function getDescLongReadmeFound()
882 {
883 global $langs;
884
885 $filefound = false;
886
887 // Define path to file README.md.
888 // First check README-la_LA.md then README-la.md then README.md
889 $pathoffile = dol_buildpath(strtolower($this->name).'/README-'.$langs->defaultlang.'.md', 0);
890 if (dol_is_file($pathoffile)) {
891 $filefound = true;
892 }
893 if (!$filefound) {
894 $tmp = explode('_', $langs->defaultlang);
895 $pathoffile = dol_buildpath(strtolower($this->name).'/README-'.$tmp[0].'.md', 0);
896 if (dol_is_file($pathoffile)) {
897 $filefound = true;
898 }
899 }
900 if (!$filefound) {
901 $pathoffile = dol_buildpath(strtolower($this->name).'/README.md', 0);
902 if (dol_is_file($pathoffile)) {
903 $filefound = true;
904 }
905 }
906
907 return ($filefound ? $pathoffile : '');
908 }
909
910
916 public function getChangeLog()
917 {
918 global $langs;
919 $langs->load("admin");
920
921 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
922 include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
923
924 $filefound = false;
925
926 // Define path to file README.md.
927 // First check ChangeLog-la_LA.md then ChangeLog.md
928 $pathoffile = dol_buildpath(strtolower($this->name).'/ChangeLog-'.$langs->defaultlang.'.md', 0);
929 if (dol_is_file($pathoffile)) {
930 $filefound = true;
931 }
932 if (!$filefound) {
933 $pathoffile = dol_buildpath(strtolower($this->name).'/ChangeLog.md', 0);
934 if (dol_is_file($pathoffile)) {
935 $filefound = true;
936 }
937 }
938
939 if ($filefound) { // Mostly for external modules
940 $content = file_get_contents($pathoffile);
941
942 if ((float) DOL_VERSION >= 6.0) { // @phpstan-ignore-line
943 @include_once DOL_DOCUMENT_ROOT.'/core/lib/parsemd.lib.php';
944
945 $content = dolMd2Html($content, 'parsedown', array('doc/' => dol_buildpath(strtolower($this->name).'/doc/', 1)));
946 } else {
947 $content = nl2br($content);
948 }
949 } else {
950 $content = '';
951 }
952
953 return $content;
954 }
955
961 public function getPublisher()
962 {
963 return $this->editor_name;
964 }
965
971 public function getPublisherUrl()
972 {
973 return $this->editor_url;
974 }
975
984 public function getVersion($translated = 1)
985 {
986 global $langs;
987 $langs->load("admin");
988
989 $ret = '';
990
991 $newversion = preg_replace('/_deprecated/', '', $this->version);
992 if ($newversion == 'experimental') {
993 $ret = ($translated ? $langs->transnoentitiesnoconv("VersionExperimental") : $newversion);
994 } elseif ($newversion == 'development') {
995 $ret = ($translated ? $langs->transnoentitiesnoconv("VersionDevelopment") : $newversion);
996 } elseif ($newversion == 'dolibarr') {
997 $ret = DOL_VERSION;
998 } elseif ($newversion) {
999 $ret = $newversion;
1000 } else {
1001 $ret = ($translated ? $langs->transnoentitiesnoconv("VersionUnknown") : 'unknown');
1002 }
1003
1004 if (preg_match('/_deprecated/', $this->version)) {
1005 $ret .= ($translated ? ' ('.$langs->transnoentitiesnoconv("Deprecated").')' : $this->version);
1006 }
1007 return $ret;
1008 }
1009
1015 public function getModulePosition()
1016 {
1017 if (in_array($this->version, array('dolibarr', 'dolibarr_deprecated', 'experimental', 'development'))) { // core module
1018 return $this->module_position;
1019 } else { // external module
1020 if ($this->module_position >= 100000) {
1021 return $this->module_position;
1022 } else {
1023 $position = intval($this->module_position) + 100000;
1024 return strval($position);
1025 }
1026 }
1027 }
1028
1035 public function isCoreOrExternalModule()
1036 {
1037 if ($this->version == 'dolibarr' || $this->version == 'dolibarr_deprecated') {
1038 return 'core';
1039 }
1040 if (!empty($this->version) && !in_array($this->version, array('experimental', 'development')) && empty($this->version_if_core)) {
1041 return 'external';
1042 }
1043 if (!empty($this->editor_name) || !empty($this->editor_url) && empty($this->version_if_core)) {
1044 return 'external';
1045 }
1046 if ($this->numero >= 100000) {
1047 return 'external';
1048 }
1049 return 'unknown';
1050 }
1051
1052
1058 public function getLangFilesArray()
1059 {
1060 return $this->langfiles;
1061 }
1062
1070 public function getExportDatasetLabel($r)
1071 {
1072 global $langs;
1073
1074 $langstring = "ExportDataset_".$this->export_code[$r];
1075 if ($langs->trans($langstring) == $langstring) {
1076 // Translation not found
1077 return $langs->trans($this->export_label[$r]);
1078 } else {
1079 // Translation found
1080 return $langs->trans($langstring);
1081 }
1082 }
1083
1084
1092 public function getImportDatasetLabel($r)
1093 {
1094 global $langs;
1095
1096 $langstring = "ImportDataset_".$this->import_code[$r];
1097 //print "x".$langstring;
1098 if ($langs->trans($langstring) == $langstring) {
1099 // Translation not found
1100 return $langs->transnoentitiesnoconv($this->import_label[$r]);
1101 } else {
1102 // Translation found
1103 return $langs->transnoentitiesnoconv($langstring);
1104 }
1105 }
1106
1107
1113 public function getLastActivationDate()
1114 {
1115 global $conf;
1116
1117 $err = 0;
1118
1119 $sql = "SELECT tms FROM ".MAIN_DB_PREFIX."const";
1120 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
1121 $sql .= " AND entity IN (0, ".((int) $conf->entity).")";
1122
1123 dol_syslog(get_class($this)."::getLastActiveDate", LOG_DEBUG);
1124 $resql = $this->db->query($sql);
1125 if (!$resql) {
1126 $err++;
1127 } else {
1128 $obj = $this->db->fetch_object($resql);
1129 if ($obj) {
1130 return $this->db->jdate($obj->tms);
1131 }
1132 }
1133
1134 return '';
1135 }
1136
1137
1143 public function getLastActivationInfo()
1144 {
1145 global $conf;
1146
1147 $err = 0;
1148
1149 $sql = "SELECT tms, note FROM ".MAIN_DB_PREFIX."const";
1150 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
1151 $sql .= " AND entity IN (0, ".((int) $conf->entity).")";
1152
1153 dol_syslog(get_class($this)."::getLastActiveDate", LOG_DEBUG);
1154 $resql = $this->db->query($sql);
1155 if (!$resql) {
1156 $err++;
1157 } else {
1158 $obj = $this->db->fetch_object($resql);
1159 if ($obj) {
1160 $tmp = array();
1161 if ($obj->note) {
1162 $tmp = json_decode($obj->note, true);
1163 }
1164 return array(
1165 'authorid' => empty($tmp['authorid']) ? '' : (int) $tmp['authorid'],
1166 'ip' => empty($tmp['ip']) ? '' : (string) $tmp['ip'],
1167 'lastactivationdate' => $this->db->jdate($obj->tms),
1168 'lastactivationversion' => (!empty($tmp['lastactivationversion']) ? (string) $tmp['lastactivationversion'] : 'unknown'),
1169 );
1170 }
1171 }
1172
1173 return array();
1174 }
1175
1176
1177 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1183 protected function _active()
1184 {
1185 // phpcs:enable
1186 global $conf, $user;
1187
1188 $err = 0;
1189
1190 // Common module
1191 $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
1192
1193 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1194 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
1195 $sql .= " AND entity IN (0, ".((int) $entity).")";
1196
1197 dol_syslog(get_class($this)."::_active delete activation constant", LOG_DEBUG);
1198 $resql = $this->db->query($sql);
1199 if (!$resql) {
1200 $err++;
1201 }
1202
1203 $note = json_encode(
1204 array(
1205 'authorid' => (is_object($user) ? $user->id : 0),
1206 'ip' => (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']),
1207 'lastactivationversion' => $this->version,
1208 )
1209 );
1210
1211 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (name, value, visible, entity, note) VALUES";
1212 $sql .= " (".$this->db->encrypt($this->const_name);
1213 $sql .= ", ".$this->db->encrypt('1');
1214 $sql .= ", 0, ".((int) $entity);
1215 $sql .= ", '".$this->db->escape($note)."')";
1216
1217 dol_syslog(get_class($this)."::_active insert activation constant", LOG_DEBUG);
1218 $resql = $this->db->query($sql);
1219 if (!$resql) {
1220 $err++;
1221 }
1222
1223 return $err;
1224 }
1225
1226
1227 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1233 protected function _unactive()
1234 {
1235 // phpcs:enable
1236 global $conf;
1237
1238 $err = 0;
1239
1240 // Common module
1241 $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
1242
1243 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1244 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
1245 $sql .= " AND entity IN (0, ".((int) $entity).")";
1246
1247 dol_syslog(get_class($this)."::_unactive", LOG_DEBUG);
1248 $this->db->query($sql);
1249
1250 return $err;
1251 }
1252
1253
1254 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps,PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1273 protected function _load_tables($reldir, $onlywithsuffix = '')
1274 {
1275 // phpcs:enable
1276 global $conf;
1277
1278 $error = 0;
1279 $dirfound = 0;
1280 $ok = 1;
1281
1282 if (empty($reldir)) {
1283 return 1;
1284 }
1285
1286 include_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
1287
1288 foreach ($conf->file->dol_document_root as $dirroot) {
1289 if ($ok == 1) {
1290 $dirsql = $dirroot.$reldir; // Not sql, just sql file @phan-suppress-current-line SqlInjection
1291 $ok = 0;
1292
1293 // We will loop on xxx/, xxx/tables/, xxx/data/
1294 $listofsubdir = array('', 'tables/', 'data/');
1295 if ($this->db->type == 'pgsql') {
1296 $listofsubdir[] = '../pgsql/functions/';
1297 }
1298
1299 foreach ($listofsubdir as $subdir) {
1300 $dir = $dirsql.$subdir;
1301
1302 $handle = @opendir($dir); // Dir may not exists
1303 if (is_resource($handle)) {
1304 $dirfound++;
1305
1306 // Run llx_mytable.sql files, then llx_mytable_*.sql
1307 $files = array();
1308 while (($file = readdir($handle)) !== false) {
1309 $files[] = $file;
1310 }
1311 sort($files);
1312 foreach ($files as $file) {
1313 if ($onlywithsuffix) {
1314 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1315 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1316 continue;
1317 } else {
1318 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1319 }
1320 }
1321 if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'llx_') {
1322 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1323 if ($result <= 0) {
1324 $error++;
1325 }
1326 }
1327 }
1328
1329 rewinddir($handle);
1330
1331 // Run llx_mytable.key.sql files (Must be done after llx_mytable.sql) then then llx_mytable_*.key.sql
1332 $files = array();
1333 while (($file = readdir($handle)) !== false) {
1334 $files[] = $file;
1335 }
1336 sort($files);
1337 foreach ($files as $file) {
1338 if ($onlywithsuffix) {
1339 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1340 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1341 continue;
1342 } else {
1343 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1344 }
1345 }
1346 if (preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'llx_') {
1347 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1348 if ($result <= 0) {
1349 $error++;
1350 }
1351 }
1352 }
1353
1354 rewinddir($handle);
1355
1356 // Run functions-xxx.sql files (Must be done after llx_mytable.key.sql)
1357 $files = array();
1358 while (($file = readdir($handle)) !== false) {
1359 $files[] = $file;
1360 }
1361 sort($files);
1362 foreach ($files as $file) {
1363 if ($onlywithsuffix) {
1364 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1365 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1366 continue;
1367 } else {
1368 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1369 }
1370 }
1371 if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 9) == 'functions') {
1372 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1373 if ($result <= 0) {
1374 $error++;
1375 }
1376 }
1377 }
1378
1379 rewinddir($handle);
1380
1381 // Run data_xxx.sql files (Must be done after llx_mytable.key.sql)
1382 $files = array();
1383 while (($file = readdir($handle)) !== false) {
1384 $files[] = $file;
1385 }
1386 sort($files);
1387 foreach ($files as $file) {
1388 if ($onlywithsuffix) {
1389 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1390 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1391 continue;
1392 } else {
1393 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1394 }
1395 }
1396 if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 4) == 'data') {
1397 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1398 if ($result <= 0) {
1399 $error++;
1400 }
1401 }
1402 }
1403
1404 rewinddir($handle);
1405
1406 // Run update_xxx.sql files (sorted by name)
1407 $files = array();
1408 while (($file = readdir($handle)) !== false) {
1409 $files[] = $file;
1410 }
1411 sort($files);
1412 foreach ($files as $file) {
1413 if ($onlywithsuffix) {
1414 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1415 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1416 continue;
1417 } else {
1418 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1419 }
1420 }
1421
1422 if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && substr($file, 0, 6) == 'update') {
1423 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1424 if ($result <= 0) {
1425 $error++;
1426 }
1427 }
1428 }
1429
1430 closedir($handle);
1431 }
1432 }
1433
1434 if ($error == 0) {
1435 $ok = 1;
1436 }
1437 }
1438 }
1439
1440 if (!$dirfound) {
1441 dol_syslog("A module wants to load sql files from ".$reldir." but this directory was not found.", LOG_WARNING);
1442 }
1443 return $ok;
1444 }
1445
1446
1447 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1455 public function insert_boxes($option = '')
1456 {
1457 // phpcs:enable
1458 include_once DOL_DOCUMENT_ROOT.'/core/class/infobox.class.php';
1459
1460 global $conf;
1461
1462 $err = 0;
1463
1464 if (is_array($this->boxes)) {
1465 dol_syslog(get_class($this)."::insert_boxes", LOG_DEBUG);
1466
1467 $pos_name = InfoBox::getListOfPagesForBoxes();
1468
1469 foreach ($this->boxes as $key => $value) {
1470 $file = isset($this->boxes[$key]['file']) ? $this->boxes[$key]['file'] : '';
1471 $note = isset($this->boxes[$key]['note']) ? $this->boxes[$key]['note'] : '';
1472 $enabledbydefaulton = isset($this->boxes[$key]['enabledbydefaulton']) ? $this->boxes[$key]['enabledbydefaulton'] : 'Home';
1473
1474 if (empty($file)) {
1475 $file = isset($this->boxes[$key][1]) ? $this->boxes[$key][1] : ''; // For backward compatibility
1476 }
1477 if (empty($note)) {
1478 $note = isset($this->boxes[$key][2]) ? $this->boxes[$key][2] : ''; // For backward compatibility
1479 }
1480
1481 // Search if boxes def already present
1482 $sql = "SELECT count(*) as nb FROM ".MAIN_DB_PREFIX."boxes_def";
1483 $sql .= " WHERE file = '".$this->db->escape($file)."'";
1484 $sql .= " AND entity = ".((int) $conf->entity);
1485 if ($note) {
1486 $sql .= " AND note ='".$this->db->escape($note)."'";
1487 }
1488
1489 $result = $this->db->query($sql);
1490 if ($result) {
1491 $obj = $this->db->fetch_object($result);
1492 if ($obj->nb == 0) {
1493 $this->db->begin();
1494
1495 if (!$err) {
1496 $sql = "INSERT INTO ".MAIN_DB_PREFIX."boxes_def (file, entity, note)";
1497 $sql .= " VALUES ('".$this->db->escape($file)."', ";
1498 $sql .= ((int) $conf->entity).", ";
1499 $sql .= $note ? "'".$this->db->escape($note)."'" : "null";
1500 $sql .= ")";
1501
1502 dol_syslog(get_class($this)."::insert_boxes", LOG_DEBUG);
1503 $resql = $this->db->query($sql);
1504 if (!$resql) {
1505 $err++;
1506 }
1507 }
1508 if (!$err && !preg_match('/newboxdefonly/', $option)) {
1509 $lastid = $this->db->last_insert_id(MAIN_DB_PREFIX."boxes_def", "rowid");
1510
1511 foreach ($pos_name as $key2 => $val2) {
1512 //print 'key2='.$key2.'-val2='.$val2."<br>\n";
1513 if ($enabledbydefaulton && $val2 != $enabledbydefaulton) {
1514 continue; // Not enabled by default onto this page.
1515 }
1516
1517 $sql = "INSERT INTO ".MAIN_DB_PREFIX."boxes (box_id, position, box_order, fk_user, entity)";
1518 $sql .= " VALUES (".((int) $lastid).", ".((int) $key2).", '0', 0, ".((int) $conf->entity).")";
1519
1520 dol_syslog(get_class($this)."::insert_boxes onto page ".$key2."=".$val2, LOG_DEBUG);
1521 $resql = $this->db->query($sql);
1522 if (!$resql) {
1523 $err++;
1524 }
1525 }
1526 }
1527
1528 if (!$err) {
1529 $this->db->commit();
1530 } else {
1531 $this->error = $this->db->lasterror();
1532 $this->db->rollback();
1533 }
1534 }
1535 // else box already registered into database
1536 } else {
1537 $this->error = $this->db->lasterror();
1538 $err++;
1539 }
1540 }
1541 }
1542
1543 return $err;
1544 }
1545
1546
1547 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1553 public function delete_boxes()
1554 {
1555 // phpcs:enable
1556 global $conf;
1557
1558 $err = 0;
1559
1560 if (is_array($this->boxes)) {
1561 foreach ($this->boxes as $key => $value) {
1562 //$titre = $this->boxes[$key][0];
1563 if (empty($this->boxes[$key]['file'])) {
1564 $file = isset($this->boxes[$key][1]) ? $this->boxes[$key][1] : ''; // For backward compatibility
1565 } else {
1566 $file = $this->boxes[$key]['file'];
1567 }
1568
1569 //$note = $this->boxes[$key][2];
1570
1571 // TODO If the box is also included by another module and the other module is still on, we should not remove it.
1572 // For the moment, we manage this with hard coded exception
1573 //print "Remove box ".$file.'<br>';
1574 if ($file == 'box_graph_product_distribution.php') {
1575 if (isModEnabled("product") || isModEnabled("service")) {
1576 dol_syslog("We discard deleting module ".$file." because another module still active requires it.");
1577 continue;
1578 }
1579 }
1580
1581 if ($this->db->type == 'sqlite3') {
1582 // sqlite doesn't support "USING" syntax.
1583 // TODO: remove this dependency.
1584 $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes ";
1585 $sql .= "WHERE ".MAIN_DB_PREFIX."boxes.box_id IN (";
1586 $sql .= "SELECT ".MAIN_DB_PREFIX."boxes_def.rowid ";
1587 $sql .= "FROM ".MAIN_DB_PREFIX."boxes_def ";
1588 $sql .= "WHERE ".MAIN_DB_PREFIX."boxes_def.file = '".$this->db->escape($file)."') ";
1589 $sql .= "AND ".MAIN_DB_PREFIX."boxes.entity = ".((int) $conf->entity);
1590 } else {
1591 $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes";
1592 $sql .= " USING ".MAIN_DB_PREFIX."boxes, ".MAIN_DB_PREFIX."boxes_def";
1593 $sql .= " WHERE ".MAIN_DB_PREFIX."boxes.box_id = ".MAIN_DB_PREFIX."boxes_def.rowid";
1594 $sql .= " AND ".MAIN_DB_PREFIX."boxes_def.file = '".$this->db->escape($file)."'";
1595 $sql .= " AND ".MAIN_DB_PREFIX."boxes.entity = ".((int) $conf->entity);
1596 }
1597
1598 dol_syslog(get_class($this)."::delete_boxes", LOG_DEBUG);
1599 $resql = $this->db->query($sql);
1600 if (!$resql) {
1601 $this->error = $this->db->lasterror();
1602 $err++;
1603 }
1604
1605 $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes_def";
1606 $sql .= " WHERE file = '".$this->db->escape($file)."'";
1607 $sql .= " AND entity = ".((int) $conf->entity); // Do not use getEntity here, we want to delete only in current company
1608
1609 dol_syslog(get_class($this)."::delete_boxes", LOG_DEBUG);
1610 $resql = $this->db->query($sql);
1611 if (!$resql) {
1612 $this->error = $this->db->lasterror();
1613 $err++;
1614 }
1615 }
1616 }
1617
1618 return $err;
1619 }
1620
1621 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1627 public function insert_cronjobs()
1628 {
1629 // phpcs:enable
1630 include_once DOL_DOCUMENT_ROOT . '/core/class/infobox.class.php';
1631 include_once DOL_DOCUMENT_ROOT . '/cron/class/cronjob.class.php';
1632 include_once DOL_DOCUMENT_ROOT . '/user/class/user.class.php';
1633
1634 global $conf, $user;
1635
1636 if (empty($user)) {
1637 $user = new User($this->db);
1638 }
1639
1640 $err = 0;
1641
1642 if (is_array($this->cronjobs)) {
1643 dol_syslog(get_class($this) . "::insert_cronjobs", LOG_DEBUG);
1644
1645 foreach ($this->cronjobs as $key => $value) {
1646 $now = dol_now();
1647
1648 $entity = isset($value['entity']) ? $value['entity'] : $conf->entity;
1649 $label = isset($value['label']) ? $value['label'] : '';
1650 $jobtype = isset($value['jobtype']) ? $value['jobtype'] : '';
1651 $classesname = isset($value['class']) ? $value['class'] : '';
1652 $objectname = isset($value['objectname']) ? $value['objectname'] : '';
1653 $methodename = isset($value['method']) ? $value['method'] : '';
1654 $command = isset($value['command']) ? $value['command'] : '';
1655 $params = isset($value['parameters']) ? $value['parameters'] : '';
1656 $md5params = isset($value['md5params']) ? $value['md5params'] : '';
1657 $comment = isset($value['comment']) ? $value['comment'] : '';
1658 $frequency = isset($value['frequency']) ? $value['frequency'] : '';
1659 $unitfrequency = isset($value['unitfrequency']) ? $value['unitfrequency'] : '';
1660 $priority = isset($value['priority']) ? $value['priority'] : '';
1661 $datestart = isset($value['datestart']) ? $value['datestart'] : '';
1662 $dateend = isset($value['dateend']) ? $value['dateend'] : '';
1663 $datenextrun = isset($value['datenextrun']) ? $value['datenextrun'] : $now;
1664 $status = isset($value['status']) ? $value['status'] : '';
1665 $maxrun = isset($value['maxrun']) ? $value['maxrun'] : 0;
1666 $libname = isset($value['libname']) ? $value['libname'] : '';
1667 $test = isset($value['test']) ? $value['test'] : ''; // Line must be enabled or not (so visible or not)
1668
1669 // Search if cron entry already present
1670 $sql = "SELECT count(*) as nb FROM " . MAIN_DB_PREFIX . "cronjob";
1671 //$sql .= " WHERE module_name = '" . $this->db->escape(empty($this->rights_class) ? strtolower($this->name) : $this->rights_class) . "'";
1672 $sql .= " WHERE label = '".$this->db->escape($label)."'";
1673 /* unique key is on label,entity so no need for this test
1674 if ($classesname) {
1675 $sql .= " AND classesname = '" . $this->db->escape($classesname) . "'";
1676 }
1677 if ($objectname) {
1678 $sql .= " AND objectname = '" . $this->db->escape($objectname) . "'";
1679 }
1680 if ($methodename) {
1681 $sql .= " AND methodename = '" . $this->db->escape($methodename) . "'";
1682 }
1683 if ($command) {
1684 $sql .= " AND command = '" . $this->db->escape($command) . "'";
1685 }
1686 if ($params) {
1687 $sql .= " AND params = '" . $this->db->escape($params) . "'";
1688 }
1689 */
1690 $sql .= " AND entity = " . ((int) $entity); // Must be exact entity
1691
1692 $result = $this->db->query($sql);
1693 if (!$result) {
1694 $this->error = $this->db->lasterror();
1695 $err++;
1696 break;
1697 // else box already registered into database
1698 }
1699
1700 $obj = $this->db->fetch_object($result);
1701 if ($obj->nb > 0) {
1702 continue;
1703 }
1704
1705 $cronjob = new Cronjob($this->db);
1706
1707 $cronjob->entity = $entity;
1708 $cronjob->label = $label;
1709 $cronjob->jobtype = $jobtype;
1710 $cronjob->classesname = $classesname;
1711 $cronjob->objectname = $objectname;
1712 $cronjob->methodename = $methodename;
1713 $cronjob->command = $command;
1714 $cronjob->params = $params;
1715 $cronjob->md5params = $md5params;
1716 $cronjob->note_private = $comment;
1717 $cronjob->frequency = $frequency;
1718 $cronjob->unitfrequency = $unitfrequency;
1719 $cronjob->priority = $priority;
1720 $cronjob->datestart = $datestart;
1721 $cronjob->dateend = $dateend;
1722 $cronjob->datenextrun = $datenextrun;
1723 $cronjob->maxrun = $maxrun;
1724 $cronjob->status = $status;
1725 $cronjob->test = $test;
1726 $cronjob->libname = $libname;
1727 $cronjob->module_name = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
1728
1729 $retCreate = $cronjob->create($user);
1730
1731 if ($retCreate < 0) {
1732 $this->error = implode("\n", array_merge([$cronjob->error], $cronjob->errors));
1733 return -1;
1734 }
1735 }
1736 }
1737
1738 return $err;
1739 }
1740
1741 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1747 public function delete_cronjobs()
1748 {
1749 // phpcs:enable
1750 global $conf;
1751
1752 $err = 0;
1753
1754 if (is_array($this->cronjobs)) {
1755 $sql = "DELETE FROM ".MAIN_DB_PREFIX."cronjob";
1756 $sql .= " WHERE module_name = '".$this->db->escape(empty($this->rights_class) ? strtolower($this->name) : $this->rights_class)."'";
1757 $sql .= " AND entity = ".((int) $conf->entity);
1758 $sql .= " AND test = '1'"; // We delete on lines that are not set with a complete test that is '$conf->module->enabled' so when module is disabled, the cron is also removed.
1759 // For crons declared with a '$conf->module->enabled', there is no need to delete the line, so we don't loose setup if we reenable module.
1760
1761 dol_syslog(get_class($this)."::delete_cronjobs", LOG_DEBUG);
1762 $resql = $this->db->query($sql);
1763 if (!$resql) {
1764 $this->error = $this->db->lasterror();
1765 $err++;
1766 }
1767 }
1768
1769 return $err;
1770 }
1771
1772 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1778 public function delete_tabs()
1779 {
1780 // phpcs:enable
1781 global $conf;
1782
1783 $err = 0;
1784
1785 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1786 $sql .= " WHERE ".$this->db->decrypt('name')." LIKE '".$this->db->escape($this->const_name)."_TABS_%'";
1787 $sql .= " AND entity = ".((int) $conf->entity);
1788
1789 dol_syslog(get_class($this)."::delete_tabs", LOG_DEBUG);
1790 if (!$this->db->query($sql)) {
1791 $this->error = $this->db->lasterror();
1792 $err++;
1793 }
1794
1795 return $err;
1796 }
1797
1798 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1804 public function insert_tabs()
1805 {
1806 // phpcs:enable
1807 global $conf;
1808
1809 $err = 0;
1810
1811 if (!empty($this->tabs)) {
1812 dol_syslog(get_class($this)."::insert_tabs", LOG_DEBUG);
1813
1814 $i = 0;
1815 foreach ($this->tabs as $key => $value) {
1816 if (is_array($value) && count($value) == 0) {
1817 continue; // Discard empty arrays
1818 }
1819
1820 $entity = $conf->entity;
1821 $newvalue = $value;
1822
1823 if (is_array($value)) {
1824 $newvalue = $value['data'];
1825 if (isset($value['entity'])) {
1826 $entity = $value['entity'];
1827 }
1828 }
1829
1830 if ($newvalue) {
1831 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (";
1832 $sql .= "name";
1833 $sql .= ", type";
1834 $sql .= ", value";
1835 $sql .= ", note";
1836 $sql .= ", visible";
1837 $sql .= ", entity";
1838 $sql .= ")";
1839 $sql .= " VALUES (";
1840 $sql .= $this->db->encrypt($this->const_name."_TABS_".$i);
1841 $sql .= ", 'chaine'";
1842 $sql .= ", ".$this->db->encrypt($newvalue);
1843 $sql .= ", null";
1844 $sql .= ", '0'";
1845 $sql .= ", ".((int) $entity);
1846 $sql .= ")";
1847
1848 $resql = $this->db->query($sql);
1849 if (!$resql) {
1850 dol_syslog($this->db->lasterror(), LOG_ERR);
1851 if ($this->db->lasterrno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1852 $this->error = $this->db->lasterror();
1853 $this->errors[] = $this->db->lasterror();
1854 $err++;
1855 break;
1856 }
1857 }
1858 }
1859 $i++;
1860 }
1861 }
1862 return $err;
1863 }
1864
1865 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1871 public function insert_const()
1872 {
1873 // phpcs:enable
1874 global $conf;
1875
1876 $err = 0;
1877
1878 if (empty($this->const)) {
1879 return 0;
1880 }
1881
1882 dol_syslog(__METHOD__, LOG_DEBUG);
1883
1884 foreach ($this->const as $key => $value) {
1885 // Property to search if entry exists
1886 $name = $this->const[$key][0];
1887 $entity = (!empty($this->const[$key][5]) && $this->const[$key][5] != 'current') ? 0 : $conf->entity;
1888
1889 // Property to update if it does not exists
1890 $type = $this->const[$key][1];
1891 $val = $this->const[$key][2];
1892 $note = isset($this->const[$key][3]) ? $this->const[$key][3] : '';
1893 $visible = isset($this->const[$key][4]) ? $this->const[$key][4] : 0;
1894
1895 // Clean
1896 if (empty($visible)) {
1897 $visible = '0';
1898 }
1899 if (empty($val) && $val != '0') {
1900 $val = '';
1901 }
1902
1903 if (!empty($name)) {
1904 $sql = "SELECT count(*) as nb";
1905 $sql .= " FROM ".MAIN_DB_PREFIX."const";
1906 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($name)."'";
1907 $sql .= " AND entity = ".((int) $entity);
1908
1909 $result = $this->db->query($sql);
1910 if ($result) {
1911 $row = $this->db->fetch_row($result);
1912
1913 if ($row[0] == 0) { // If not found
1914 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (name, type, value, note, visible, entity)";
1915 $sql .= " VALUES (";
1916 $sql .= $this->db->encrypt($name); // Note: encrypt returns SQL expression or quoted string
1917 $sql .= ", '".$this->db->escape($type)."'";
1918 $sql .= ", ".(($val != '') ? $this->db->encrypt($val) : "''"); // Note: encrypt returns SQL expression or quoted string
1919 $sql .= ", ".($note ? "'".$this->db->escape($note)."'" : "null");
1920 $sql .= ", '".$this->db->escape($visible)."'";
1921 $sql .= ", ".((int) $entity);
1922 $sql .= ")";
1923
1924 if (!$this->db->query($sql)) {
1925 $err++;
1926 } else {
1927 // Set also the variable in running environment
1928 $conf->global->$name = $val;
1929 }
1930 } else {
1931 dol_syslog(__METHOD__." constant '".$name."' already exists", LOG_DEBUG);
1932 }
1933 } else {
1934 $err++;
1935 }
1936 }
1937 }
1938
1939 return $err;
1940 }
1941
1942 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1948 public function delete_const()
1949 {
1950 // phpcs:enable
1951 global $conf;
1952
1953 $err = 0;
1954
1955 if (empty($this->const)) {
1956 return 0;
1957 }
1958
1959 foreach ($this->const as $key => $value) {
1960 $name = $this->const[$key][0];
1961 $deleteonunactive = (!empty($this->const[$key][6])) ? 1 : 0;
1962
1963 if ($deleteonunactive) {
1964 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1965 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($name)."'";
1966 $sql .= " AND entity in (0, ".((int) $conf->entity).")";
1967 dol_syslog(get_class($this)."::delete_const", LOG_DEBUG);
1968 if (!$this->db->query($sql)) {
1969 $this->error = $this->db->lasterror();
1970 $err++;
1971 }
1972 }
1973 }
1974
1975 return $err;
1976 }
1977
1978 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1991 public function insert_permissions($reinitadminperms = 0, $force_entity = null, $notrigger = 0, $existingrightsdefids = null)
1992 {
1993 // phpcs:enable
1994 global $conf, $user;
1995
1996 $err = 0;
1997 $entity = (!empty($force_entity) ? $force_entity : $conf->entity);
1998
1999 dol_syslog(get_class($this)."::insert_permissions", LOG_DEBUG);
2000
2001 // Test if module is activated
2002 $sql_del = "SELECT ".$this->db->decrypt('value')." as value";
2003 $sql_del .= " FROM ".MAIN_DB_PREFIX."const";
2004 $sql_del .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
2005 $sql_del .= " AND entity IN (0,".((int) $entity).")";
2006
2007 $resql = $this->db->query($sql_del);
2008
2009 if ($resql) {
2010 $obj = $this->db->fetch_object($resql);
2011
2012 if ($obj !== null && !empty($obj->value) && !empty($this->rights)) {
2013 include_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
2014
2015 // If the module is active
2016 foreach ($this->rights as $key => $value) {
2017 $r_id = $this->rights[$key][self::KEY_ID]; // permission id in llx_rights_def (not unique because primary key is couple id-entity)
2018 $r_label = $this->rights[$key][self::KEY_LABEL];
2019 $r_type = $this->rights[$key][self::KEY_TYPE] ?? 'w'; // TODO deprecated
2020 $r_default = $this->rights[$key][self::KEY_DEFAULT] ?? 0;
2021 $r_perms = $this->rights[$key][self::KEY_FIRST_LEVEL] ?? '';
2022 $r_subperms = $this->rights[$key][self::KEY_SECOND_LEVEL] ?? '';
2023
2024 // KEY_FIRST_LEVEL (perms) must not be empty
2025 if (empty($r_perms)) {
2026 continue;
2027 }
2028
2029 // name of module (default: current module name)
2030 $r_module = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
2031
2032 // name of the module from which the right comes (default: empty means same module the permission is for)
2033 $r_module_origin = '';
2034
2035 if (isset($this->rights[$key][self::KEY_MODULE])) {
2036 // name of the module to which the right must be applied
2037 $r_module = $this->rights[$key][self::KEY_MODULE];
2038 // name of the module from which the right comes
2039 $r_module_origin = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
2040 }
2041
2042 if (!empty($r_module_origin) && $r_module !== $r_module_origin) {
2043 // This right is filed under a different module's section of the permission
2044 // grid (KEY_MODULE) than the one declaring it: use that target module's own
2045 // family/position so it appears grouped with its native rights instead of
2046 // opening a second, misplaced section for the same module.
2047 $r_targetmoduleinfo = $this->getModuleInfoByRightsClass($r_module);
2048 $r_module_position = $r_targetmoduleinfo['position'];
2049 $r_family = $r_targetmoduleinfo['family'];
2050 } else {
2051 $r_module_position = $this->getModulePosition();
2052 $r_family = $this->family;
2053 }
2054 $r_family_position = 0;
2055
2056 // condition to show or hide a user right (default: 1) (eg isModEnabled('anothermodule') or ($conf->global->MAIN_FEATURES_LEVEL > 0) or etc..)
2057 $r_enabled = $this->rights[$key][self::KEY_ENABLED] ?? '1';
2058
2059 // Search if perm already present
2060 if ($existingrightsdefids !== null) {
2061 $rightalreadyexists = !empty($existingrightsdefids[$r_id]);
2062 } else {
2063 $sql = "SELECT count(*) as nb FROM ".MAIN_DB_PREFIX."rights_def";
2064 $sql .= " WHERE entity = ".((int) $entity);
2065 $sql .= " AND id = ".((int) $r_id);
2066
2067 $rightalreadyexists = true; // Assume it exists if the select fails, so we never try to insert on a query error
2068 $resqlselect = $this->db->query($sql);
2069 if ($resqlselect) {
2070 $objcount = $this->db->fetch_object($resqlselect);
2071 $rightalreadyexists = !($objcount && $objcount->nb == 0);
2072 $this->db->free($resqlselect);
2073 }
2074 }
2075
2076 if (!$rightalreadyexists) {
2077 $sql = "INSERT INTO ".MAIN_DB_PREFIX."rights_def (";
2078 $sql .= "id";
2079 $sql .= ", entity";
2080 $sql .= ", libelle";
2081 $sql .= ", module";
2082 $sql .= ", module_origin";
2083 $sql .= ", module_position"; // Not that module_position can be fixed eynamically when accessing page user/perms.php
2084 $sql .= ", family";
2085 $sql .= ", family_position";
2086 $sql .= ", type"; // Not used yet
2087 $sql .= ", bydefault";
2088 $sql .= ", perms";
2089 $sql .= ", subperms";
2090 $sql .= ", enabled";
2091 $sql .= ") VALUES (";
2092 $sql .= ((int) $r_id);
2093 $sql .= ", ".((int) $entity);
2094 $sql .= ", '".$this->db->escape($r_label)."'";
2095 $sql .= ", '".$this->db->escape($r_module)."'";
2096 $sql .= ", '".$this->db->escape($r_module_origin)."'";
2097 $sql .= ", '".$this->db->escape((string) $r_module_position)."'";
2098 $sql .= ", '".$this->db->escape($r_family)."'";
2099 $sql .= ", '".$this->db->escape((string) $r_family_position)."'";
2100 $sql .= ", '".$this->db->escape($r_type)."'"; // Not used yet
2101 $sql .= ", ".((int) $r_default);
2102 $sql .= ", '".$this->db->escape($r_perms)."'";
2103 $sql .= ", '".$this->db->escape($r_subperms)."'";
2104 $sql .= ", '".$this->db->escape($r_enabled)."'";
2105 $sql .= ")";
2106
2107 $resqlinsert = $this->db->query($sql, 1);
2108
2109 if (!$resqlinsert) {
2110 if ($this->db->errno() != "DB_ERROR_RECORD_ALREADY_EXISTS") {
2111 $this->error = $this->db->lasterror();
2112 $err++;
2113 break;
2114 } else {
2115 dol_syslog(get_class($this)."::insert_permissions record already exists", LOG_INFO);
2116 }
2117 }
2118
2119 $this->db->free($resqlinsert);
2120 }
2121
2122 // If we want to init permissions on admin users
2123 if (!empty($reinitadminperms)) {
2124 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."user WHERE admin = 1";
2125 dol_syslog(get_class($this)."::insert_permissions Search all admin users", LOG_DEBUG);
2126
2127 $resqlseladmin = $this->db->query($sql, 1);
2128
2129 if ($resqlseladmin) {
2130 $num = $this->db->num_rows($resqlseladmin);
2131 $i = 0;
2132 while ($i < $num) {
2133 $obj2 = $this->db->fetch_object($resqlseladmin);
2134 dol_syslog(get_class($this)."::insert_permissions Add permission id ".$r_id." to user id=".$obj2->rowid);
2135
2136 $tmpuser = new User($this->db);
2137 $result = $tmpuser->fetch($obj2->rowid);
2138 if ($result > 0) {
2139 $tmpuser->addrights($r_id, '', '', 0, 1);
2140 } else {
2141 dol_syslog(get_class($this)."::insert_permissions Failed to add the permission to user because fetch return an error", LOG_ERR);
2142 }
2143 $i++;
2144 }
2145 } else {
2146 dol_print_error($this->db);
2147 }
2148 }
2149 }
2150
2151 if (!empty($reinitadminperms) && !empty($user->admin)) { // Reload permission for current user if defined
2152 // We reload permissions
2153 $user->clearrights();
2154 $user->loadRights();
2155 }
2156 }
2157 $this->db->free($resql);
2158 } else {
2159 $this->error = $this->db->lasterror();
2160 $err++;
2161 }
2162
2163 return $err;
2164 }
2165
2177 protected function getModuleInfoByRightsClass($rightsclass)
2178 {
2179 global $conf, $db;
2180
2181 if (isset($conf->cache['keyModuleInfoCache'][$rightsclass])) {
2182 return $conf->cache['keyModuleInfoCache'][$rightsclass];
2183 }
2184
2185 // Fallback: if no module with this rights_class is found (typo, or module removed from disk),
2186 // still register something under its own family/position rather than leaving it undefined.
2187 $result = array('family' => $this->family, 'position' => (int) $this->getModulePosition());
2188
2189 $modulesdir = dolGetModulesDirs();
2190 foreach ($modulesdir as $dir) {
2191 $handle = @opendir(dol_osencode($dir));
2192 if (is_resource($handle)) {
2193 while (($file = readdir($handle)) !== false) {
2194 if (is_readable($dir.$file) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') {
2195 $modName = substr($file, 0, dol_strlen($file) - 10);
2196 if ($modName && $modName != get_class($this)) {
2197 include_once $dir.$file;
2198 if (class_exists($modName)) {
2199 '@phan-var-force class-string<DolibarrModules> $modName';
2200 $objMod = new $modName($db);
2201 '@phan-var-force DolibarrModules $objMod';
2202 if (!empty($objMod->rights_class) && $objMod->rights_class === $rightsclass) {
2203 $result = array('family' => $objMod->family, 'position' => (int) $objMod->getModulePosition());
2204 break 2;
2205 }
2206 }
2207 }
2208 }
2209 }
2210 closedir($handle);
2211 }
2212 }
2213
2214 $conf->cache['keyModuleInfoCache'][$rightsclass] = $result;
2215
2216 return $result;
2217 }
2218
2219
2220 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2226 public function delete_permissions()
2227 {
2228 // phpcs:enable
2229 global $conf;
2230
2231 $err = 0;
2232
2233 $module = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
2234
2235 $sql = "DELETE FROM ".MAIN_DB_PREFIX."rights_def";
2236 $sql .= " WHERE (module = '".$this->db->escape($module)."' OR module_origin = '".$this->db->escape($module)."')";
2237
2238 // Delete all entities if core module
2239 if (empty($this->core_enabled)) {
2240 $sql .= " AND entity = ".((int) $conf->entity);
2241 }
2242
2243 dol_syslog(get_class($this)."::delete_permissions", LOG_DEBUG);
2244 if (!$this->db->query($sql)) {
2245 $this->error = $this->db->lasterror();
2246 $err++;
2247 }
2248
2249 return $err;
2250 }
2251
2252
2253 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2259 public function insert_menus()
2260 {
2261 // phpcs:enable
2262 global $conf, $user;
2263
2264 if (!is_array($this->menu) || empty($this->menu)) {
2265 return 0;
2266 }
2267
2268 include_once DOL_DOCUMENT_ROOT.'/core/class/menubase.class.php';
2269
2270 dol_syslog(get_class($this)."::insert_menus", LOG_DEBUG);
2271
2272 $err = 0;
2273
2274 // Common module
2275 $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
2276
2277 $this->db->begin();
2278
2279 foreach ($this->menu as $key => $value) {
2280 $menu = new Menubase($this->db);
2281 $menu->menu_handler = 'all';
2282
2283 //$menu->module=strtolower($this->name); TODO When right_class will be same than module name
2284 $menu->module = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
2285
2286 if (!$this->menu[$key]['fk_menu']) {
2287 $menu->fk_menu = 0;
2288 } else {
2289 $foundparent = 0;
2290 $fk_parent = $this->menu[$key]['fk_menu'];
2291 $reg = array();
2292 if (preg_match('/^r=/', $fk_parent)) { // old deprecated method
2293 $fk_parent = str_replace('r=', '', $fk_parent);
2294 if (isset($this->menu[$fk_parent]['rowid'])) {
2295 $menu->fk_menu = $this->menu[$fk_parent]['rowid'];
2296 $foundparent = 1;
2297 }
2298 } elseif (preg_match('/^fk_mainmenu=([a-zA-Z0-9_]+),fk_leftmenu=([a-zA-Z0-9_]+)$/', $fk_parent, $reg)) {
2299 $menu->fk_menu = -1;
2300 $menu->fk_mainmenu = $reg[1];
2301 $menu->fk_leftmenu = $reg[2];
2302 $foundparent = 1;
2303 } elseif (preg_match('/^fk_mainmenu=([a-zA-Z0-9_]+)$/', $fk_parent, $reg)) {
2304 $menu->fk_menu = -1;
2305 $menu->fk_mainmenu = $reg[1];
2306 $menu->fk_leftmenu = '';
2307 $foundparent = 1;
2308 }
2309 if (!$foundparent) {
2310 $this->error = "ErrorBadDefinitionOfMenuArrayInModuleDescriptor";
2311 dol_syslog(get_class($this)."::insert_menus ".$this->error." ".$this->menu[$key]['fk_menu'], LOG_ERR);
2312 $err++;
2313 }
2314 }
2315 $menu->type = $this->menu[$key]['type'];
2316 $menu->mainmenu = isset($this->menu[$key]['mainmenu']) ? $this->menu[$key]['mainmenu'] : (isset($menu->fk_mainmenu) ? $menu->fk_mainmenu : '');
2317 $menu->leftmenu = isset($this->menu[$key]['leftmenu']) ? $this->menu[$key]['leftmenu'] : '';
2318 $menu->title = $this->menu[$key]['titre'];
2319 $menu->prefix = isset($this->menu[$key]['prefix']) ? $this->menu[$key]['prefix'] : '';
2320 $menu->url = $this->menu[$key]['url'];
2321 $menu->langs = isset($this->menu[$key]['langs']) ? $this->menu[$key]['langs'] : '';
2322 $menu->position = $this->menu[$key]['position'];
2323 $menu->perms = $this->menu[$key]['perms'];
2324 $menu->target = isset($this->menu[$key]['target']) ? $this->menu[$key]['target'] : '';
2325 $menu->user = $this->menu[$key]['user'];
2326 $menu->enabled = isset($this->menu[$key]['enabled']) ? $this->menu[$key]['enabled'] : 0;
2327 $menu->position = $this->menu[$key]['position'];
2328 $menu->entity = $entity;
2329
2330 if (!$err) {
2331 $result = $menu->create($user); // Save menu entry into table llx_menu
2332 if ($result > 0) {
2333 $this->menu[$key]['rowid'] = $result;
2334 } else {
2335 $this->error = $menu->error;
2336 dol_syslog(get_class($this).'::insert_menus result='.$result." ".$this->error, LOG_ERR);
2337 $err++;
2338 break;
2339 }
2340 }
2341 }
2342
2343 if (!$err) {
2344 $this->db->commit();
2345 } else {
2346 dol_syslog(get_class($this)."::insert_menus ".$this->error, LOG_ERR);
2347 $this->db->rollback();
2348 }
2349
2350 return $err;
2351 }
2352
2353
2354 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2360 public function delete_menus()
2361 {
2362 // phpcs:enable
2363 global $conf;
2364
2365 $err = 0;
2366
2367 //$module=strtolower($this->name); TODO When right_class will be same than module name
2368 $module = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
2369
2370 $sql = "DELETE FROM ".MAIN_DB_PREFIX."menu";
2371 $sql .= " WHERE module = '".$this->db->escape($module)."'";
2372 $sql .= " AND menu_handler = 'all'"; // We delete only lines that were added manually or by the module activation. We keep entry added by menuhandler like 'auguria'
2373 $sql .= " AND entity IN (0, ".((int) $conf->entity).")";
2374
2375 dol_syslog(get_class($this)."::delete_menus", LOG_DEBUG);
2376 $resql = $this->db->query($sql);
2377 if (!$resql) {
2378 $this->error = $this->db->lasterror();
2379 $err++;
2380 }
2381
2382 return $err;
2383 }
2384
2385 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2391 public function create_dirs()
2392 {
2393 // phpcs:enable
2394 global $langs, $conf;
2395
2396 $err = 0;
2397 $name = '';
2398
2399 if (isset($this->dirs) && is_array($this->dirs)) {
2400 foreach ($this->dirs as $key => $value) {
2401 $addtodatabase = 0;
2402
2403 if (!is_array($value)) {
2404 $dir = $value; // Default simple mode
2405 } else {
2406 $constname = $this->const_name."_DIR_";
2407 $dir = $this->dirs[$key][1];
2408 $addtodatabase = empty($this->dirs[$key][2]) ? '' : $this->dirs[$key][2]; // Create constante in llx_const
2409 $subname = empty($this->dirs[$key][3]) ? '' : strtoupper($this->dirs[$key][3]); // Add submodule name (ex: $conf->module->submodule->dir_output)
2410 $forcename = empty($this->dirs[$key][4]) ? '' : strtoupper($this->dirs[$key][4]); // Change the module name if different
2411
2412 if (!empty($forcename)) {
2413 $constname = 'MAIN_MODULE_'.$forcename."_DIR_";
2414 }
2415 if (!empty($subname)) {
2416 $constname = $constname.$subname."_";
2417 }
2418
2419 $name = $constname.strtoupper($this->dirs[$key][0]);
2420 }
2421
2422 // Define directory full path ($dir must start with "/")
2423 if (!getDolGlobalString('MAIN_MODULE_MULTICOMPANY') || $conf->entity == 1) {
2424 $fulldir = DOL_DATA_ROOT.$dir;
2425 } else {
2426 $fulldir = DOL_DATA_ROOT."/".$conf->entity.$dir;
2427 }
2428 // Create dir if it does not exists
2429 if (!empty($fulldir) && !file_exists($fulldir)) {
2430 if (dol_mkdir($fulldir, DOL_DATA_ROOT) < 0) {
2431 $this->error = $langs->trans("ErrorCanNotCreateDir", $fulldir);
2432 dol_syslog(get_class($this)."::_init ".$this->error, LOG_ERR);
2433 $err++;
2434 }
2435 }
2436
2437 // Define the constant in database if requested (not the default mode)
2438 if (!empty($addtodatabase) && !empty($name)) {
2439 $result = $this->insert_dirs($name, $dir);
2440 if ($result) {
2441 $err++;
2442 }
2443 }
2444 }
2445 }
2446
2447 return $err;
2448 }
2449
2450
2451 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2460 public function insert_dirs($name, $dir)
2461 {
2462 // phpcs:enable
2463 global $conf;
2464
2465 $err = 0;
2466
2467 $sql = "SELECT count(*)";
2468 $sql .= " FROM ".MAIN_DB_PREFIX."const";
2469 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($name)."'";
2470 $sql .= " AND entity = ".((int) $conf->entity);
2471
2472 dol_syslog(get_class($this)."::insert_dirs", LOG_DEBUG);
2473 $result = $this->db->query($sql);
2474 if ($result) {
2475 $row = $this->db->fetch_row($result);
2476
2477 if ($row[0] == 0) {
2478 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (name, type, value, note, visible, entity)";
2479 $sql .= " VALUES (".$this->db->encrypt($name).", 'chaine', ".$this->db->encrypt($dir).", '".$this->db->escape("Directory for module ".$this->name)."', '0', ".((int) $conf->entity).")";
2480
2481 dol_syslog(get_class($this)."::insert_dirs", LOG_DEBUG);
2482 $this->db->query($sql);
2483 }
2484 } else {
2485 $this->error = $this->db->lasterror();
2486 $err++;
2487 }
2488
2489 return $err;
2490 }
2491
2492
2493 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2499 public function delete_dirs()
2500 {
2501 // phpcs:enable
2502 global $conf;
2503
2504 $err = 0;
2505
2506 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
2507 $sql .= " WHERE ".$this->db->decrypt('name')." LIKE '".$this->db->escape($this->const_name)."_DIR_%'";
2508 $sql .= " AND entity = ".((int) $conf->entity);
2509
2510 dol_syslog(get_class($this)."::delete_dirs", LOG_DEBUG);
2511 if (!$this->db->query($sql)) {
2512 $this->error = $this->db->lasterror();
2513 $err++;
2514 }
2515
2516 return $err;
2517 }
2518
2519 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2526 public function insert_module_parts()
2527 {
2528 // phpcs:enable
2529 global $conf, $langs;
2530
2531 $error = 0;
2532
2533 if (is_array($this->module_parts)) {
2534 if (empty($this->module_parts['icon']) && !empty($this->picto) && preg_match('/^fa\-/', $this->picto)) {
2535 $this->module_parts['icon'] = $this->picto;
2536 }
2537
2538 foreach ($this->module_parts as $key => $value) {
2539 if (is_array($value) && count($value) == 0) {
2540 continue; // Discard empty arrays
2541 }
2542
2543 // If module brings website templates, we must generate the zip like we do whenenabling the website module
2544 if ($key == 'websitetemplates' && $value == 1) {
2545 $srcroot = dol_buildpath('/'.strtolower($this->name).'/doctemplates/websites');
2546
2547 // Copy templates in dir format (recommended) into zip file
2548 $docs = dol_dir_list($srcroot, 'directories', 0, 'website_.*$');
2549 foreach ($docs as $cursorfile) {
2550 $src = $srcroot.'/'.$cursorfile['name'];
2551 $dest = DOL_DATA_ROOT.'/doctemplates/websites/'.$cursorfile['name'];
2552
2553 dol_delete_file($dest.'.zip');
2554
2555 // Compress it
2556 global $errormsg; // Used by dol_compress_dir
2557 $errormsg = '';
2558 $result = dol_compress_dir($src, $dest.'.zip', 'zip');
2559 if ($result < 0) {
2560 $error++;
2561 $this->error = ($errormsg ? $errormsg : $langs->trans('ErrorFailToCreateZip', $dest));
2562 $this->errors[] = ($errormsg ? $errormsg : $langs->trans('ErrorFailToCreateZip', $dest));
2563 }
2564 }
2565
2566 // Copy also the preview website_xxx.jpg file
2567 $docs = dol_dir_list($srcroot, 'files', 0, 'website_.*\.jpg$');
2568 foreach ($docs as $cursorfile) {
2569 $src = $srcroot.'/'.$cursorfile['name'];
2570 $dest = DOL_DATA_ROOT.'/doctemplates/websites/'.$cursorfile['name'];
2571
2572 dol_copy($src, $dest);
2573 }
2574 }
2575
2576 $entity = $conf->entity; // Reset the current entity
2577 $newvalue = $value;
2578
2579 // Serialize array parameters
2580 if (is_array($value)) {
2581 // Can defined other parameters
2582 // Example when $key='hooks', then $value is an array('data'=>array('hookcontext1','hookcontext2'), 'entity'=>X)
2583 if (isset($value['data']) && is_array($value['data'])) {
2584 $newvalue = json_encode($value['data']);
2585 if (isset($value['entity'])) {
2586 $entity = $value['entity'];
2587 }
2588 } elseif (isset($value['data']) && !is_array($value['data'])) {
2589 $newvalue = $value['data'];
2590 if (isset($value['entity'])) {
2591 $entity = $value['entity'];
2592 }
2593 } else { // when hook is declared with syntax 'hook'=>array('hookcontext1','hookcontext2',...)
2594 $newvalue = json_encode($value);
2595 }
2596 }
2597
2598 if (!empty($newvalue)) {
2599 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (";
2600 $sql .= "name";
2601 $sql .= ", type";
2602 $sql .= ", value";
2603 $sql .= ", note";
2604 $sql .= ", visible";
2605 $sql .= ", entity";
2606 $sql .= ")";
2607 $sql .= " VALUES (";
2608 $sql .= " ".$this->db->encrypt($this->const_name."_".strtoupper($key), 1);
2609 $sql .= ", 'chaine'";
2610 $sql .= ", ".$this->db->encrypt($newvalue, 1);
2611 $sql .= ", null";
2612 $sql .= ", '0'";
2613 $sql .= ", ".((int) $entity);
2614 $sql .= ")";
2615
2616 dol_syslog(get_class($this)."::insert_module_parts for key=".$this->const_name."_".strtoupper($key), LOG_DEBUG);
2617
2618 $resql = $this->db->query($sql, 1);
2619 if (!$resql) {
2620 if ($this->db->lasterrno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2621 $error++;
2622 $this->error = $this->db->lasterror();
2623 } else {
2624 dol_syslog(get_class($this)."::insert_module_parts for ".$this->const_name."_".strtoupper($key)." Record already exists.", LOG_WARNING);
2625 }
2626 }
2627 }
2628 }
2629 }
2630 return $error;
2631 }
2632
2633 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2639 public function delete_module_parts()
2640 {
2641 // phpcs:enable
2642 global $conf;
2643
2644 $err = 0;
2645
2646 if (is_array($this->module_parts)) {
2647 dol_syslog(get_class($this)."::delete_module_parts", LOG_DEBUG);
2648
2649 if (empty($this->module_parts['icon']) && !empty($this->picto) && preg_match('/^fa\-/', $this->picto)) {
2650 $this->module_parts['icon'] = $this->picto;
2651 }
2652
2653 foreach ($this->module_parts as $key => $value) {
2654 // If entity is defined
2655 if (is_array($value) && isset($value['entity'])) {
2656 $entity = $value['entity'];
2657 } else {
2658 $entity = $conf->entity;
2659 }
2660
2661 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
2662 $sql .= " WHERE ".$this->db->decrypt('name')." LIKE '".$this->db->escape($this->const_name)."_".strtoupper($key)."'";
2663 $sql .= " AND entity = ".((int) $entity);
2664
2665 if (!$this->db->query($sql)) {
2666 $this->error = $this->db->lasterror();
2667 $err++;
2668 }
2669 }
2670 }
2671 return $err;
2672 }
2673
2683 public function init($options = '')
2684 {
2685 return $this->_init(array(), $options);
2686 }
2687
2696 public function remove($options = '')
2697 {
2698 return $this->_remove(array(), $options);
2699 }
2700
2701
2709 public function getKanbanView($codeenabledisable = '', $codetoconfig = '')
2710 {
2711 global $langs;
2712
2713 // Define imginfo
2714 $imginfo = "info";
2715 if ($this->isCoreOrExternalModule() == 'external') {
2716 $imginfo = "info_black";
2717 }
2718
2719 $const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($this)));
2720
2721 $version = $this->getVersion(0);
2722 $versiontrans = '';
2723 if (preg_match('/development/i', $version)) {
2724 $versiontrans .= 'warning';
2725 }
2726 if (preg_match('/experimental/i', $version)) {
2727 $versiontrans .= 'warning';
2728 }
2729 if (preg_match('/deprecated/i', $version)) {
2730 $versiontrans .= 'warning';
2731 }
2732
2733 $return = '
2734 <div class="box-flex-item info-box-module'
2735 .(getDolGlobalString($const_name) ? '' : ' --disabled')
2736 .($this->isCoreOrExternalModule() == 'external' ? ' --external' : '')
2737 .($this->needUpdate ? ' --need-update' : '')
2738 .'">
2739 <div class="info-box info-box-sm info-box-module">
2740 <div class="info-box-icon'.(!getDolGlobalString($const_name) ? '' : ' info-box-icon-module-enabled'.($versiontrans ? ' info-box-icon-module-warning' : '')).'">';
2741
2742 $alttext = '';
2743 //if (is_array($objMod->need_dolibarr_version)) $alttext.=($alttext?' - ':'').'Dolibarr >= '.join('.',$objMod->need_dolibarr_version);
2744 //if (is_array($objMod->phpmin)) $alttext.=($alttext?' - ':'').'PHP >= '.join('.',$objMod->phpmin);
2745 if (!empty($this->picto)) {
2746 if (preg_match('/^\//i', $this->picto)) {
2747 $return .= img_picto($alttext, $this->picto, 'class="inline-block valignmiddle"', 1);
2748 } else {
2749 $return .= img_object($alttext, $this->picto, 'class="inline-block valignmiddle"');
2750 }
2751 } else {
2752 $return .= img_object($alttext, 'generic', 'class="inline-block valignmiddle"');
2753 }
2754
2755 if ($this->isCoreOrExternalModule() == 'external' || preg_match('/development|experimental|deprecated/i', $version)) {
2756 $versionTitle = $langs->trans("Version").' '.$this->getVersion(1);
2757 if ($this->needUpdate) {
2758 $versionTitle .= '<br>'.$langs->trans('ModuleUpdateAvailable').' : '.$this->lastVersion;
2759 }
2760
2761 $return .= '<span class="info-box-icon-version'.($versiontrans ? ' '.$versiontrans : '').' classfortooltip" title="'.dol_escape_js($versionTitle).'" >';
2762 $return .= $this->getVersion(1);
2763 $return .= '</span>';
2764 }
2765
2766 $return .= '</div>
2767 <div class="info-box-content info-box-text-module'.(!getDolGlobalString($const_name) ? '' : ' info-box-module-enabled'.($versiontrans ? ' info-box-content-warning' : '')).'">
2768 <span class="info-box-title noopacity">'.$this->getName().'</span>
2769 <span class="info-box-desc twolinesmax opacitymedium" title="'.dol_escape_htmltag($this->getDesc()).'">'.nl2br($this->getDesc()).'</span>';
2770
2771 $return .= '<div class="valignmiddle inline-block info-box-more">';
2772 //if ($versiontrans) print img_warning($langs->trans("Version").' '.$this->getVersion(1)).' ';
2773 $return .= '<a class="valignmiddle inline-block" href="javascript:document_preview(\''.DOL_URL_ROOT.'/admin/modulehelp.php?id='.((int) $this->numero).'\',\'text/html\',\''.dol_escape_js($langs->trans("Module")).'\')">'.img_picto(($this->isCoreOrExternalModule() == 'external' ? $langs->trans("ExternalModule").' - ' : '').$langs->trans("ClickToShowDescription"), $imginfo).'</a>';
2774 $return .= '</div><br>';
2775
2776 $return .= '<div class="valignmiddle inline-block info-box-actions">';
2777 $return .= '<div class="valignmiddle inline-block info-box-setup">';
2778 $return .= $codetoconfig;
2779 $return .= '</div>';
2780 $return .= '<div class="valignmiddle inline-block marginleftonly marginrightonly">';
2781 $return .= $codeenabledisable;
2782 $return .= '</div>';
2783 $return .= '</div>';
2784
2785 $return .= '
2786 </div><!-- /.info-box-content -->
2787 </div><!-- /.info-box -->
2788 </div>';
2789
2790 return $return;
2791 }
2792
2801 public function checkForUpdate()
2802 {
2803 require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
2804 if (!empty($this->url_last_version)) {
2805 $lastVersion = getURLContent($this->url_last_version, 'GET', '', 1, array(), array('http', 'https'), 0); // Accept http or https links on external remote server only
2806 if (isset($lastVersion['content']) && strlen($lastVersion['content']) < 30) {
2807 // Security warning : be careful with remote data content, the module editor could be hacked (or evil) so limit to a-z A-Z 0-9 _ . -
2808 $this->lastVersion = preg_replace("/[^a-zA-Z0-9_\.\-]+/", "", $lastVersion['content']);
2809 if (version_compare($this->lastVersion, $this->version) > 0) {
2810 $this->needUpdate = true;
2811 return 1;
2812 } else {
2813 $this->needUpdate = false;
2814 return 0;
2815 }
2816 } else {
2817 return -1;
2818 }
2819 }
2820 return 0;
2821 }
2822
2830 public function checkForCompliance($nametocheck = '')
2831 {
2832 global $conf, $langs;
2833
2834 if (empty($nametocheck)) {
2835 $nametocheck = $this->name;
2836 }
2837
2838 // Get list of illegal modules name or ID
2839 if (empty($conf->cache['noncompliantmodules'])) {
2840 require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
2841
2842 $result = getURLContent(self::URL_FOR_BLACKLISTED_MODULES, 'GET', '', 1, array(), array('http', 'https'), 0); // Accept http or https links on external remote server only
2843 if (isset($result['content']) && $result['http_code'] == 200) {
2844 $langs->load("errors");
2845
2846 // Security warning : be careful with remote data content, the module editor could be hacked (or evil) so limit to a-z A-Z 0-9 _ . -
2847 $arrayoflines = preg_split("/[\n,]/", $result['content']);
2848 foreach ($arrayoflines as $line) {
2849 $tmpfieldsofline = explode(';', $line);
2850 $modulekey = strtolower(trim($tmpfieldsofline[0]));
2851 if (empty($modulekey)) {
2852 continue;
2853 }
2854 $conf->cache['noncompliantmodules'][$modulekey]['name'] = trim($tmpfieldsofline[0]);
2855 $conf->cache['noncompliantmodules'][$modulekey]['id'] = (isset($tmpfieldsofline[1]) ? trim($tmpfieldsofline[1]) : '');
2856 $conf->cache['noncompliantmodules'][$modulekey]['signature'] = (isset($tmpfieldsofline[2]) ? trim($tmpfieldsofline[2]) : '');
2857 $conf->cache['noncompliantmodules'][$modulekey]['message'] = $langs->trans(empty(isset($tmpfieldsofline[3]) ? trim($tmpfieldsofline[3]) : '') ? 'WarningModuleAffiliatedToAReportedCompany' : trim($tmpfieldsofline[3]));
2858 if (!empty($tmpfieldsofline[4])) {
2859 $message2 = $langs->trans("WarningModuleAffiliatedToAPiratPlatform", '{s}');
2860 $listofillegalurl = '';
2861 foreach (explode(" ", $tmpfieldsofline[4]) as $illegalurl) {
2862 $listofillegalurl .= ($listofillegalurl ? ' '.$langs->trans("or").' ' : '').'<b>'.preg_replace('/[^a-z0-9\.\-]/', '', $illegalurl).'</b>';
2863 }
2864 $message2 = str_replace('{s}', $listofillegalurl, $message2);
2865 $conf->cache['noncompliantmodules'][$modulekey]['message2'] = $message2;
2866 }
2867 }
2868 }
2869 }
2870
2871 if (!empty($conf->cache['noncompliantmodules'])) {
2872 $modulekey = strtolower($nametocheck);
2873 if (in_array($modulekey, array_keys($conf->cache['noncompliantmodules']))) {
2874 $answer = trim($conf->cache['noncompliantmodules'][$modulekey]['message']);
2875 if (!empty($conf->cache['noncompliantmodules'][$modulekey]['message2'])) {
2876 $answer .= '<br>'.$conf->cache['noncompliantmodules'][$modulekey]['message2'];
2877 }
2878 return $answer;
2879 }
2880 }
2881
2882 return 0;
2883 }
2884
2903 protected function declareNewDictionary($dictionaryArray, $langs = '')
2904 {
2905 $fields = array('name', 'lib', 'sql', 'sqlsort', 'field', 'fieldvalue', 'fieldinsert', 'rowid', 'cond', 'help', 'fieldcheck');
2906
2907 foreach ($fields as $field) {
2908 if (isset($dictionaryArray[$field])) {
2909 // @phan-suppress-next-line PhanTypeMismatchProperty
2910 $this->dictionaries['tab'.$field][] = $dictionaryArray[$field];
2911 }
2912 }
2913 if ($langs && !in_array($langs, $this->dictionaries[$langs])) {
2914 $this->dictionaries['langs'][] = $langs;
2915 }
2916 }
2917}
run_sql($sqlfile, $silent=1, $entity=0, $usesavepoint=1, $handler='', $okerror='default', $linelengthlimit=32768, $nocommentremoval=0, $offsetforchartofaccount=0, $colspan=0, $onlysqltoimportwebsite=0, $database='')
Launch a sql file.
Cron Job class.
Class DolibarrModules.
delete_permissions()
Removes access rights.
getImportDatasetLabel($r)
Gives translated label of an import dataset.
_init($array_sql, $options='')
Enables a module.
getLastActivationDate()
Gives the last date of activation.
delete_cronjobs()
Removes boxes.
getKanbanView($codeenabledisable='', $codetoconfig='')
Return Kanban view of a module.
getModulePosition()
Gives the module position.
insert_const()
Adds constants.
__construct($db)
Constructor.
delete_boxes()
Removes boxes.
isCoreOrExternalModule()
Tells if module is core or external.
getDescLongReadmeFound()
Return path of file if a README file was found.
delete_menus()
Removes menu entries.
delete_const()
Removes constants tagged 'deleteonunactive'.
_active()
Insert constants for module activation.
_remove($array_sql, $options='')
Disable function.
getDescLong()
Gives the long description of a module.
insert_boxes($option='')
Adds boxes.
getDesc($foruseinpopupdesc=0)
Gives the translated module description if translation exists in admin.lang or the default module des...
getModuleInfoByRightsClass($rightsclass)
Look up the family and module_position of another module by its rights_class, so a right filed under ...
getPublisher()
Gives the publisher name.
getChangeLog()
Gives the changelog.
insert_cronjobs()
Adds cronjobs.
create_dirs()
Creates directories.
delete_module_parts()
Removes generic parts.
insert_permissions($reinitadminperms=0, $force_entity=null, $notrigger=0, $existingrightsdefids=null)
Adds access rights.
getVersion($translated=1)
Gives module version (translated if param $translated is on) For 'experimental' modules,...
_unactive()
Module deactivation.
getExportDatasetLabel($r)
Gives translated label of an export dataset.
getName()
Gives the translated module name if translation exists in admin.lang or into language files of module...
_load_tables($reldir, $onlywithsuffix='')
Create tables and keys required by module:
insert_dirs($name, $dir)
Adds directories definitions.
getLangFilesArray()
Gives module related language files list.
insert_menus()
Adds menu entries.
insert_module_parts()
Save configuration for generic features.
getPublisherUrl()
Gives the publisher url.
getLastActivationInfo()
Gives the last author of activation.
delete_dirs()
Removes directories.
init($options='')
Function called when module is enabled.
static getListOfPagesForBoxes()
Name of positions (See below)
Class to manage menu entries.
Class to manage Dolibarr users.
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_copy($srcfile, $destfile, $newmask='0', $overwriteifexists=1, $testvirus=0, $indexdatabase=0)
Copy a file to another file.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:65
dolGetModulesDirs($subdir='')
Return list of directories that contain modules.
dol_now($mode='gmt')
Return date for now.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
Definition html.lib.php:172
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
dolMd2Html($content, $parser='parsedown', $replaceimagepath=null)
Function to parse MD content into HTML.
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:133