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) 2024 Lenin Rivas <lenin.rivas777@gmail.com>
11 * Copyright (C) 2019-2026 Frédéric France <frederic.france@free.fr>
12 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program. If not, see <https://www.gnu.org/licenses/>.
26 */
27
39class DolibarrModules // Can not be abstract, because we need to instantiate it into unActivateModule to be able to disable a module whose files were removed.
40{
44 public $db;
45
50 public $numero;
51
55 public $editor_name;
56
60 public $editor_url;
61
65 public $editor_squarred_logo;
66
74 public $family;
75
88 public $familyinfo;
89
93 public $module_position = '50';
94
103 public $name;
104
110 public $dirs = array();
111
115 public $boxes = array();
116
121 public $const = array();
122
126 public $overwrite_translation = array();
127
131 public $cronjobs = array();
132
136 public $rights;
137
141 public $rights_admin_allowed;
142
146 public $rights_class;
147
148 const URL_FOR_BLACKLISTED_MODULES = 'https://www.dolibarr.org/modules-blacklist.php';
149
150 const KEY_ID = 0;
151 const KEY_LABEL = 1;
152 const KEY_TYPE = 2; // deprecated
153 const KEY_DEFAULT = 3;
154 const KEY_FIRST_LEVEL = 4;
155 const KEY_SECOND_LEVEL = 5;
156 const KEY_MODULE = 6;
157 const KEY_ENABLED = 7;
158
162 public $menu = array();
163
191 public $module_parts = array();
192
196 public $error;
197
201 public $errors;
202
213 public $version;
214
218 public $version_if_core = 0;
219
224 public $lastVersion = '';
225
230 public $needUpdate = false;
231
237 public $description;
238
245 public $descriptionlong;
246
250 public $dictionaries = array();
251
255 public $tabs;
256
257 // For exports
258
262 public $export_code;
263
267 public $export_label;
268
272 public $export_icon;
273
277 public $export_enabled;
281 public $export_permission;
285 public $export_fields_array;
289 public $export_TypeFields_array; // Array of key=>type where type can be 'Numeric', 'Date', 'Text', 'Boolean', 'Status', 'List:xxx:fieldlabel:rowid'
293 public $export_entities_array;
297 public $export_aggregate_array;
301 public $export_examplevalues_array;
305 public $export_help_array;
312 public $export_special_array; // special or computed field
319 public $export_dependencies_array;
323 public $export_sql_start;
327 public $export_sql_end;
331 public $export_sql_order;
332
333
334 // For import
335
339 public $import_code;
340
344 public $import_label;
345
349 public $import_icon;
353 public $import_entities_array;
357 public $import_tables_array;
361 public $import_tables_creator_array;
365 public $import_fields_array;
369 public $import_fieldshidden_array;
373 public $import_convertvalue_array;
377 public $import_regex_array;
381 public $import_examplevalues_array;
385 public $import_updatekeys_array;
389 public $import_preselected_updatekeys_array;
393 public $import_run_sql_after_array;
397 public $import_TypeFields_array;
401 public $import_help_array;
402
406 public $const_name;
407
411 public $always_enabled;
412
416 public $disabled;
417
421 public $automatic_activation = array();
422
426 public $core_enabled;
427
434 public $picto;
435
442 public $config_page_url;
443
444
453 public $depends;
454
459 public $requiredby;
460
465 public $conflictwith;
466
470 public $langfiles;
471
477 public $warnings_activation;
478
484 public $warnings_activation_ext;
485
491 public $warnings_unactivation;
492
497 public $phpmin;
498
502 public $phpmax;
503
508 public $need_dolibarr_version;
509
514 public $max_dolibarr_version;
515
519 public $need_javascript_ajax;
520
524 public $enabled_bydefault;
525
529 public $hidden = false;
530
534 public $url_last_version;
535
536
542 public function __construct($db)
543 {
544 $this->db = $db;
545 }
546 // We should but can't set this as abstract because this will make dolibarr hang
547 // after migration due to old module not implementing. We must wait PHP is able to make
548 // a try catch on Fatal error to manage this correctly.
549 // We need constructor into function unActivateModule into admin.lib.php
550
551
552 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
563 protected function _init($array_sql, $options = '')
564 {
565 // phpcs:enable
566 global $conf;
567 $err = 0;
568
569 $this->db->begin();
570
571 // Insert activation module constant
572 if (!$err) {
573 $err += $this->_active();
574 }
575
576 // Insert new pages for tabs (into llx_const)
577 if (!$err) {
578 $err += $this->insert_tabs();
579 }
580
581 // Insert activation of module's parts. Copy website templates into doctemplates.
582 if (!$err) {
583 $err += $this->insert_module_parts();
584 }
585
586 // Insert constant defined by modules (into llx_const) if no existing yet
587 if (!$err && !preg_match('/newboxdefonly/', $options)) {
588 $err += $this->insert_const(); // Test on newboxdefonly to avoid to erase value during upgrade
589 }
590
591 // Insert overwrite trans defined by modules (into llx_overwrite_trans)
592 if (!$err && !preg_match('/newboxdefonly/', $options)) {
593 $err += $this->insert_overwrite_translation(); // Test on newboxdefonly to avoid to erase value during upgrade
594 }
595
596 // Insert boxes def (into llx_boxes_def) and boxes setup (into llx_boxes)
597 if (!$err && !preg_match('/noboxes/', $options)) {
598 $err += $this->insert_boxes($options);
599 }
600
601 // Insert cron job entries (entry in llx_cronjobs)
602 if (!$err) {
603 $err += $this->insert_cronjobs();
604 }
605
606 // Insert permission definitions of module into llx_rights_def. If user is admin, grant this permission to user.
607 if (!$err) {
608 $err += $this->insert_permissions(1, null, 1);
609 }
610
611 // Insert specific menus entries into database
612 if (!$err) {
613 $err += $this->insert_menus();
614 }
615
616 // Create module's directories
617 if (!$err) {
618 $err += $this->create_dirs();
619 }
620
621 // Execute addons requests
622 $num = count($array_sql);
623 for ($i = 0; $i < $num; $i++) {
624 if (!$err) {
625 $sql = $array_sql[$i];
626 $val = $sql;
627 $ignoreerror = 0;
628 if (is_array($val)) {
629 $sql = $val['sql'];
630 $ignoreerror = $val['ignoreerror'] ?? 0;
631 }
632 // Add current entity id
633 $sql = str_replace('__ENTITY__', (string) ((int) $conf->entity), $sql);
634
635 dol_syslog(get_class($this)."::_init ignoreerror=".$ignoreerror, LOG_DEBUG);
636 $result = $this->db->query($sql, $ignoreerror);
637 if (!$result) {
638 if (!$ignoreerror) {
639 $this->error = $this->db->lasterror();
640 $err++;
641 } else {
642 dol_syslog(get_class($this)."::_init Warning ".$this->db->lasterror(), LOG_WARNING);
643 }
644 }
645 }
646 }
647
648 // Return code
649 if (!$err) {
650 $this->db->commit();
651
652 $moduleNameInConf = strtolower(preg_replace('/^MAIN_MODULE_/', '', $this->const_name));
653 // two exceptions to handle
654 if ($moduleNameInConf === 'propale') {
655 $moduleNameInConf = 'propal';
656 } elseif ($moduleNameInConf === 'supplierproposal') {
657 $moduleNameInConf = 'supplier_proposal';
658 }
659
660 $conf->modules[$moduleNameInConf] = $moduleNameInConf; // Add this module in list of enabled modules so isModEnabled() will work (conf->module->enabled must no more be used)
661
662 return 1;
663 } else {
664 $this->db->rollback();
665 return 0;
666 }
667 }
668
669 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
678 protected function _remove($array_sql, $options = '')
679 {
680 global $conf;
681 // phpcs:enable
682 $err = 0;
683
684 $this->db->begin();
685
686 // Remove activation module line (constant MAIN_MODULE_MYMODULE in llx_const)
687 if (!$err) {
688 $err += $this->_unactive();
689 }
690
691 // Remove activation of module's new tabs (MAIN_MODULE_MYMODULE_TABS_XXX in llx_const)
692 if (!$err) {
693 $err += $this->delete_tabs();
694 }
695
696 // Remove activation of module's parts (MAIN_MODULE_MYMODULE_XXX in llx_const)
697 if (!$err) {
698 $err += $this->delete_module_parts();
699 }
700
701 // Remove constants defined by modules
702 if (!$err) {
703 $err += $this->delete_const();
704 }
705
706 // Remove list of module's available boxes (entry in llx_boxes)
707 if (!$err && !preg_match('/(newboxdefonly|noboxes)/', $options)) {
708 $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
709 }
710
711 // Remove list of module's cron job entries (entry in llx_cronjobs)
712 if (!$err) {
713 $err += $this->delete_cronjobs();
714 }
715
716 // Remove module's permissions from list of available permissions (entries in llx_rights_def)
717 if (!$err) {
718 $err += $this->delete_permissions();
719 }
720
721 // Remove module's menus (entries in llx_menu)
722 if (!$err) {
723 $err += $this->delete_menus();
724 }
725
726 // Remove module's directories
727 if (!$err) {
728 $err += $this->delete_dirs();
729 }
730
731 // Run complementary sql requests
732 $num = count((array) $array_sql);
733 for ($i = 0; $i < $num; $i++) {
734 if (!$err) {
735 dol_syslog(get_class($this)."::_remove", LOG_DEBUG);
736 $result = $this->db->query($array_sql[$i]);
737 if (!$result) {
738 $this->error = $this->db->error();
739 $err++;
740 }
741 }
742 }
743
744 // Return code
745 if (!$err) {
746 $this->db->commit();
747
748 // Disable modules
749 $moduleNameInConf = strtolower(preg_replace('/^MAIN_MODULE_/', '', $this->const_name));
750 // two exceptions to handle
751 if ($moduleNameInConf === 'propale') {
752 $moduleNameInConf = 'propal';
753 } elseif ($moduleNameInConf === 'supplierproposal') {
754 $moduleNameInConf = 'supplier_proposal';
755 }
756
757 unset($conf->modules[$moduleNameInConf]); // Add this module in list of enabled modules so isModEnabled() will work (conf->module->enabled must no more be used)
758
759 return 1;
760 } else {
761 $this->db->rollback();
762 return 0;
763 }
764 }
765
766
773 public function getName()
774 {
775 global $langs;
776 $langs->load("admin");
777
778 if ($langs->transnoentitiesnoconv("Module".$this->numero."Name") != "Module".$this->numero."Name") {
779 // If module name translation exists
780 return $langs->transnoentitiesnoconv("Module".$this->numero."Name");
781 } else {
782 // If module name translation using it's unique id does not exist, we try to use its name to find translation
783 if (is_array($this->langfiles)) {
784 foreach ($this->langfiles as $val) {
785 if ($val) {
786 $langs->load($val);
787 }
788 }
789 }
790
791 if ($langs->trans("Module".$this->name."Name") != "Module".$this->name."Name") {
792 // If module name translation exists
793 return $langs->transnoentitiesnoconv("Module".$this->name."Name");
794 }
795
796 // Last chance with simple label
797 return $langs->transnoentitiesnoconv($this->name);
798 }
799 }
800
801
808 public function getDesc($foruseinpopupdesc = 0)
809 {
810 global $langs;
811 $langs->load("admin");
812
813 if ($langs->transnoentitiesnoconv("Module".$this->numero."Desc") != "Module".$this->numero."Desc") {
814 // If module description translation exists
815 return $langs->transnoentitiesnoconv("Module".$this->numero."Desc");
816 } else {
817 // If module description translation does not exist using its unique id, we can use its name to find translation
818 if (is_array($this->langfiles)) {
819 foreach ($this->langfiles as $val) {
820 if ($val) {
821 $langs->load($val);
822 }
823 }
824 }
825
826 if ($langs->transnoentitiesnoconv("Module".$this->name."Desc") != "Module".$this->name."Desc") {
827 // If module name translation exists
828 return $langs->trans("Module".$this->name."Desc");
829 }
830
831 // Last chance with simple label
832 return $langs->trans($this->description);
833 }
834 }
835
842 public function getDescLong()
843 {
844 global $langs;
845 $langs->load("admin");
846
847 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
848 include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
849
850 $content = '';
851 $pathoffile = $this->getDescLongReadmeFound();
852
853 if ($pathoffile) { // Mostly for external modules
854 $content = file_get_contents($pathoffile, false, null, 0, 1024 * 1024); // Max size loaded 1Mb
855
856 if ((float) DOL_VERSION >= 6.0) { // @phpstan-ignore-line
857 @include_once DOL_DOCUMENT_ROOT.'/core/lib/parsemd.lib.php';
858
859 $content = dolMd2Html(
860 $content,
861 'parsedown',
862 array(
863 'doc/' => dol_buildpath(strtolower($this->name).'/doc/', 1),
864 'img/' => dol_buildpath(strtolower($this->name).'/img/', 1),
865 'images/' => dol_buildpath(strtolower($this->name).'/images/', 1),
866 )
867 );
868
869 $content = preg_replace('/<a href="/', '<a target="_blank" rel="noopener noreferrer" href="', $content);
870 } else {
871 $content = nl2br($content);
872 }
873 } else {
874 // Mostly for internal modules
875 if (!empty($this->descriptionlong)) {
876 if (is_array($this->langfiles)) {
877 foreach ($this->langfiles as $val) {
878 if ($val) {
879 $langs->load($val);
880 }
881 }
882 }
883
884 $content = $langs->transnoentitiesnoconv($this->descriptionlong);
885 }
886 }
887
888 return '<div class="moduledesclong">'.$content.'</div>';
889 }
890
896 public function getDescLongReadmeFound()
897 {
898 global $langs;
899
900 $filefound = false;
901
902 // Define path to file README.md.
903 // First check README-la_LA.md then README-la.md then README.md
904 $pathoffile = dol_buildpath(strtolower($this->name).'/README-'.$langs->defaultlang.'.md', 0);
905 if (dol_is_file($pathoffile)) {
906 $filefound = true;
907 }
908 if (!$filefound) {
909 $tmp = explode('_', $langs->defaultlang);
910 $pathoffile = dol_buildpath(strtolower($this->name).'/README-'.$tmp[0].'.md', 0);
911 if (dol_is_file($pathoffile)) {
912 $filefound = true;
913 }
914 }
915 if (!$filefound) {
916 $pathoffile = dol_buildpath(strtolower($this->name).'/README.md', 0);
917 if (dol_is_file($pathoffile)) {
918 $filefound = true;
919 }
920 }
921
922 return ($filefound ? $pathoffile : '');
923 }
924
925
931 public function getChangeLog()
932 {
933 global $langs;
934 $langs->load("admin");
935
936 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
937 include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
938
939 $filefound = false;
940
941 // Define path to file README.md.
942 // First check ChangeLog-la_LA.md then ChangeLog.md
943 $pathoffile = dol_buildpath(strtolower($this->name).'/ChangeLog-'.$langs->defaultlang.'.md', 0);
944 if (dol_is_file($pathoffile)) {
945 $filefound = true;
946 }
947 if (!$filefound) {
948 $pathoffile = dol_buildpath(strtolower($this->name).'/ChangeLog.md', 0);
949 if (dol_is_file($pathoffile)) {
950 $filefound = true;
951 }
952 }
953
954 if ($filefound) { // Mostly for external modules
955 $content = file_get_contents($pathoffile);
956
957 if ((float) DOL_VERSION >= 6.0) { // @phpstan-ignore-line
958 @include_once DOL_DOCUMENT_ROOT.'/core/lib/parsemd.lib.php';
959
960 $content = dolMd2Html($content, 'parsedown', array('doc/' => dol_buildpath(strtolower($this->name).'/doc/', 1)));
961 } else {
962 $content = nl2br($content);
963 }
964 } else {
965 $content = '';
966 }
967
968 return $content;
969 }
970
976 public function getPublisher()
977 {
978 return $this->editor_name;
979 }
980
986 public function getPublisherUrl()
987 {
988 return $this->editor_url;
989 }
990
999 public function getVersion($translated = 1)
1000 {
1001 global $langs;
1002 $langs->load("admin");
1003
1004 $ret = '';
1005
1006 $newversion = preg_replace('/_deprecated/', '', $this->version);
1007 if ($newversion == 'experimental') {
1008 $ret = ($translated ? $langs->transnoentitiesnoconv("VersionExperimental") : $newversion);
1009 } elseif ($newversion == 'development') {
1010 $ret = ($translated ? $langs->transnoentitiesnoconv("VersionDevelopment") : $newversion);
1011 } elseif ($newversion == 'dolibarr') {
1012 $ret = DOL_VERSION;
1013 } elseif ($newversion) {
1014 $ret = $newversion;
1015 } else {
1016 $ret = ($translated ? $langs->transnoentitiesnoconv("VersionUnknown") : 'unknown');
1017 }
1018
1019 if (preg_match('/_deprecated/', $this->version)) {
1020 $ret .= ($translated ? ' ('.$langs->transnoentitiesnoconv("Deprecated").')' : $this->version);
1021 }
1022 return $ret;
1023 }
1024
1030 public function getModulePosition()
1031 {
1032 if (in_array($this->version, array('dolibarr', 'dolibarr_deprecated', 'experimental', 'development'))) { // core module
1033 return $this->module_position;
1034 } else { // external module
1035 if ($this->module_position >= 100000) {
1036 return $this->module_position;
1037 } else {
1038 $position = intval($this->module_position) + 100000;
1039 return strval($position);
1040 }
1041 }
1042 }
1043
1050 public function isCoreOrExternalModule()
1051 {
1052 if ($this->version == 'dolibarr' || $this->version == 'dolibarr_deprecated') {
1053 return 'core';
1054 }
1055 if (!empty($this->version) && !in_array($this->version, array('experimental', 'development')) && empty($this->version_if_core)) {
1056 return 'external';
1057 }
1058 if (!empty($this->editor_name) || !empty($this->editor_url) && empty($this->version_if_core)) {
1059 return 'external';
1060 }
1061 if ($this->numero >= 100000) {
1062 return 'external';
1063 }
1064 return 'unknown';
1065 }
1066
1067
1073 public function getLangFilesArray()
1074 {
1075 return $this->langfiles;
1076 }
1077
1085 public function getExportDatasetLabel($r)
1086 {
1087 global $langs;
1088
1089 $langstring = "ExportDataset_".$this->export_code[$r];
1090 if ($langs->trans($langstring) == $langstring) {
1091 // Translation not found
1092 return $langs->trans($this->export_label[$r]);
1093 } else {
1094 // Translation found
1095 return $langs->trans($langstring);
1096 }
1097 }
1098
1099
1107 public function getImportDatasetLabel($r)
1108 {
1109 global $langs;
1110
1111 $langstring = "ImportDataset_".$this->import_code[$r];
1112 //print "x".$langstring;
1113 if ($langs->trans($langstring) == $langstring) {
1114 // Translation not found
1115 return $langs->transnoentitiesnoconv($this->import_label[$r]);
1116 } else {
1117 // Translation found
1118 return $langs->transnoentitiesnoconv($langstring);
1119 }
1120 }
1121
1122
1128 public function getLastActivationDate()
1129 {
1130 global $conf;
1131
1132 $err = 0;
1133
1134 $sql = "SELECT tms FROM ".MAIN_DB_PREFIX."const";
1135 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
1136 $sql .= " AND entity IN (0, ".((int) $conf->entity).")";
1137
1138 dol_syslog(get_class($this)."::getLastActiveDate", LOG_DEBUG);
1139 $resql = $this->db->query($sql);
1140 if (!$resql) {
1141 $err++;
1142 } else {
1143 $obj = $this->db->fetch_object($resql);
1144 if ($obj) {
1145 return $this->db->jdate($obj->tms);
1146 }
1147 }
1148
1149 return '';
1150 }
1151
1152
1158 public function getLastActivationInfo()
1159 {
1160 global $conf;
1161
1162 $err = 0;
1163
1164 $sql = "SELECT tms, note FROM ".MAIN_DB_PREFIX."const";
1165 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
1166 $sql .= " AND entity IN (0, ".((int) $conf->entity).")";
1167
1168 dol_syslog(get_class($this)."::getLastActiveDate", LOG_DEBUG);
1169 $resql = $this->db->query($sql);
1170 if (!$resql) {
1171 $err++;
1172 } else {
1173 $obj = $this->db->fetch_object($resql);
1174 if ($obj) {
1175 $tmp = array();
1176 if ($obj->note) {
1177 $tmp = json_decode($obj->note, true);
1178 }
1179 return array(
1180 'authorid' => empty($tmp['authorid']) ? '' : (int) $tmp['authorid'],
1181 'ip' => empty($tmp['ip']) ? '' : (string) $tmp['ip'],
1182 'lastactivationdate' => $this->db->jdate($obj->tms),
1183 'lastactivationversion' => (!empty($tmp['lastactivationversion']) ? (string) $tmp['lastactivationversion'] : 'unknown'),
1184 );
1185 }
1186 }
1187
1188 return array();
1189 }
1190
1191
1192 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1198 protected function _active()
1199 {
1200 // phpcs:enable
1201 global $conf, $user;
1202
1203 $err = 0;
1204
1205 // Common module
1206 $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
1207
1208 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1209 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
1210 $sql .= " AND entity IN (0, ".((int) $entity).")";
1211
1212 dol_syslog(get_class($this)."::_active delete activation constant", LOG_DEBUG);
1213 $resql = $this->db->query($sql);
1214 if (!$resql) {
1215 $err++;
1216 }
1217
1218 $note = json_encode(
1219 array(
1220 'authorid' => (is_object($user) ? $user->id : 0),
1221 'ip' => (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']),
1222 'lastactivationversion' => $this->version,
1223 )
1224 );
1225
1226 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (name, value, visible, entity, note) VALUES";
1227 $sql .= " (".$this->db->encrypt($this->const_name);
1228 $sql .= ", ".$this->db->encrypt('1');
1229 $sql .= ", 0, ".((int) $entity);
1230 $sql .= ", '".$this->db->escape($note)."')";
1231
1232 dol_syslog(get_class($this)."::_active insert activation constant", LOG_DEBUG);
1233 $resql = $this->db->query($sql);
1234 if (!$resql) {
1235 $err++;
1236 }
1237
1238 return $err;
1239 }
1240
1241
1242 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1248 protected function _unactive()
1249 {
1250 // phpcs:enable
1251 global $conf;
1252
1253 $err = 0;
1254
1255 // Common module
1256 $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
1257
1258 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1259 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
1260 $sql .= " AND entity IN (0, ".((int) $entity).")";
1261
1262 dol_syslog(get_class($this)."::_unactive", LOG_DEBUG);
1263 $this->db->query($sql);
1264
1265 return $err;
1266 }
1267
1268
1269 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps,PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1288 protected function _load_tables($reldir, $onlywithsuffix = '')
1289 {
1290 // phpcs:enable
1291 global $conf;
1292
1293 $error = 0;
1294 $dirfound = 0;
1295 $ok = 1;
1296
1297 if (empty($reldir)) {
1298 return 1;
1299 }
1300
1301 include_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
1302
1303 foreach ($conf->file->dol_document_root as $dirroot) {
1304 if ($ok == 1) {
1305 $dirsql = $dirroot.$reldir; // Not sql, just sql file @phan-suppress-current-line SqlInjection
1306 $ok = 0;
1307
1308 // We will loop on xxx/, xxx/tables/, xxx/data/
1309 $listofsubdir = array('', 'tables/', 'data/');
1310 if ($this->db->type == 'pgsql') {
1311 $listofsubdir[] = '../pgsql/functions/';
1312 }
1313
1314 foreach ($listofsubdir as $subdir) {
1315 $dir = $dirsql.$subdir;
1316
1317 $handle = @opendir($dir); // Dir may not exists
1318 if (is_resource($handle)) {
1319 $dirfound++;
1320
1321 // Run llx_mytable.sql files, then llx_mytable_*.sql
1322 $files = array();
1323 while (($file = readdir($handle)) !== false) {
1324 $files[] = $file;
1325 }
1326 sort($files);
1327 foreach ($files as $file) {
1328 if ($onlywithsuffix) {
1329 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1330 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1331 continue;
1332 } else {
1333 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1334 }
1335 }
1336 if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && dol_substr($file, 0, 4) == 'llx_') {
1337 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1338 if ($result <= 0) {
1339 $error++;
1340 }
1341 }
1342 }
1343
1344 rewinddir($handle);
1345
1346 // Run llx_mytable.key.sql files (Must be done after llx_mytable.sql) then then llx_mytable_*.key.sql
1347 $files = array();
1348 while (($file = readdir($handle)) !== false) {
1349 $files[] = $file;
1350 }
1351 sort($files);
1352 foreach ($files as $file) {
1353 if ($onlywithsuffix) {
1354 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1355 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1356 continue;
1357 } else {
1358 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1359 }
1360 }
1361 if (preg_match('/\.key\.sql$/i', $file) && dol_substr($file, 0, 4) == 'llx_') {
1362 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1363 if ($result <= 0) {
1364 $error++;
1365 }
1366 }
1367 }
1368
1369 rewinddir($handle);
1370
1371 // Run functions-xxx.sql files (Must be done after llx_mytable.key.sql)
1372 $files = array();
1373 while (($file = readdir($handle)) !== false) {
1374 $files[] = $file;
1375 }
1376 sort($files);
1377 foreach ($files as $file) {
1378 if ($onlywithsuffix) {
1379 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1380 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1381 continue;
1382 } else {
1383 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1384 }
1385 }
1386 if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && dol_substr($file, 0, 9) == 'functions') {
1387 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1388 if ($result <= 0) {
1389 $error++;
1390 }
1391 }
1392 }
1393
1394 rewinddir($handle);
1395
1396 // Run data_xxx.sql files (Must be done after llx_mytable.key.sql)
1397 $files = array();
1398 while (($file = readdir($handle)) !== false) {
1399 $files[] = $file;
1400 }
1401 sort($files);
1402 foreach ($files as $file) {
1403 if ($onlywithsuffix) {
1404 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1405 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1406 continue;
1407 } else {
1408 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1409 }
1410 }
1411 if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && dol_substr($file, 0, 4) == 'data') {
1412 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1413 if ($result <= 0) {
1414 $error++;
1415 }
1416 }
1417 }
1418
1419 rewinddir($handle);
1420
1421 // Run update_xxx.sql files (sorted by name)
1422 $files = array();
1423 while (($file = readdir($handle)) !== false) {
1424 $files[] = $file;
1425 }
1426 sort($files);
1427 foreach ($files as $file) {
1428 if ($onlywithsuffix) {
1429 if (!preg_match('/\-'.preg_quote($onlywithsuffix, '/').'\./i', $file)) {
1430 //print 'File '.$file.' does not match suffix '.$onlywithsuffix.' so it is discarded<br>'."\n";
1431 continue;
1432 } else {
1433 //print 'File '.$file.' match suffix '.$onlywithsuffix.' so we keep it<br>'."\n";
1434 }
1435 }
1436
1437 if (preg_match('/\.sql$/i', $file) && !preg_match('/\.key\.sql$/i', $file) && dol_substr($file, 0, 6) == 'update') {
1438 $result = run_sql($dir.$file, !getDolGlobalString('MAIN_DISPLAY_SQL_INSTALL_LOG') ? 1 : 0, 0, 1);
1439 if ($result <= 0) {
1440 $error++;
1441 }
1442 }
1443 }
1444
1445 closedir($handle);
1446 }
1447 }
1448
1449 if ($error == 0) {
1450 $ok = 1;
1451 }
1452 }
1453 }
1454
1455 if (!$dirfound) {
1456 dol_syslog("A module wants to load sql files from ".$reldir." but this directory was not found.", LOG_WARNING);
1457 }
1458 return $ok;
1459 }
1460
1461
1462 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1470 public function insert_boxes($option = '')
1471 {
1472 // phpcs:enable
1473 include_once DOL_DOCUMENT_ROOT.'/core/class/infobox.class.php';
1474
1475 global $conf;
1476
1477 $err = 0;
1478
1479 if (is_array($this->boxes)) {
1480 dol_syslog(get_class($this)."::insert_boxes", LOG_DEBUG);
1481
1482 $pos_name = InfoBox::getListOfPagesForBoxes();
1483
1484 foreach ($this->boxes as $key => $value) {
1485 $file = isset($this->boxes[$key]['file']) ? $this->boxes[$key]['file'] : '';
1486 $note = isset($this->boxes[$key]['note']) ? $this->boxes[$key]['note'] : '';
1487 $enabledbydefaulton = isset($this->boxes[$key]['enabledbydefaulton']) ? $this->boxes[$key]['enabledbydefaulton'] : 'Home';
1488
1489 if (empty($file)) {
1490 $file = isset($this->boxes[$key][1]) ? $this->boxes[$key][1] : ''; // For backward compatibility
1491 }
1492 if (empty($note)) {
1493 $note = isset($this->boxes[$key][2]) ? $this->boxes[$key][2] : ''; // For backward compatibility
1494 }
1495
1496 // Search if boxes def already present
1497 $sql = "SELECT count(*) as nb FROM ".MAIN_DB_PREFIX."boxes_def";
1498 $sql .= " WHERE file = '".$this->db->escape($file)."'";
1499 $sql .= " AND entity = ".((int) $conf->entity);
1500 if ($note) {
1501 $sql .= " AND note ='".$this->db->escape($note)."'";
1502 }
1503
1504 $result = $this->db->query($sql);
1505 if ($result) {
1506 $obj = $this->db->fetch_object($result);
1507 if ($obj->nb == 0) {
1508 $this->db->begin();
1509
1510 if (!$err) {
1511 $sql = "INSERT INTO ".MAIN_DB_PREFIX."boxes_def (file, entity, note)";
1512 $sql .= " VALUES ('".$this->db->escape($file)."', ";
1513 $sql .= ((int) $conf->entity).", ";
1514 $sql .= $note ? "'".$this->db->escape($note)."'" : "null";
1515 $sql .= ")";
1516
1517 dol_syslog(get_class($this)."::insert_boxes", LOG_DEBUG);
1518 $resql = $this->db->query($sql);
1519 if (!$resql) {
1520 $err++;
1521 }
1522 }
1523 if (!$err && !preg_match('/newboxdefonly/', $option)) {
1524 $lastid = $this->db->last_insert_id(MAIN_DB_PREFIX."boxes_def", "rowid");
1525
1526 foreach ($pos_name as $key2 => $val2) {
1527 //print 'key2='.$key2.'-val2='.$val2."<br>\n";
1528 if ($enabledbydefaulton && $val2 != $enabledbydefaulton) {
1529 continue; // Not enabled by default onto this page.
1530 }
1531
1532 $sql = "INSERT INTO ".MAIN_DB_PREFIX."boxes (box_id, position, box_order, fk_user, entity)";
1533 $sql .= " VALUES (".((int) $lastid).", ".((int) $key2).", '0', 0, ".((int) $conf->entity).")";
1534
1535 dol_syslog(get_class($this)."::insert_boxes onto page ".$key2."=".$val2, LOG_DEBUG);
1536 $resql = $this->db->query($sql);
1537 if (!$resql) {
1538 $err++;
1539 }
1540 }
1541 }
1542
1543 if (!$err) {
1544 $this->db->commit();
1545 } else {
1546 $this->error = $this->db->lasterror();
1547 $this->db->rollback();
1548 }
1549 }
1550 // else box already registered into database
1551 } else {
1552 $this->error = $this->db->lasterror();
1553 $err++;
1554 }
1555 }
1556 }
1557
1558 return $err;
1559 }
1560
1561
1562 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1568 public function delete_boxes()
1569 {
1570 // phpcs:enable
1571 global $conf;
1572
1573 $err = 0;
1574
1575 if (is_array($this->boxes)) {
1576 foreach ($this->boxes as $key => $value) {
1577 //$titre = $this->boxes[$key][0];
1578 if (empty($this->boxes[$key]['file'])) {
1579 $file = isset($this->boxes[$key][1]) ? $this->boxes[$key][1] : ''; // For backward compatibility
1580 } else {
1581 $file = $this->boxes[$key]['file'];
1582 }
1583
1584 //$note = $this->boxes[$key][2];
1585
1586 // TODO If the box is also included by another module and the other module is still on, we should not remove it.
1587 // For the moment, we manage this with hard coded exception
1588 //print "Remove box ".$file.'<br>';
1589 if ($file == 'box_graph_product_distribution.php') {
1590 if (isModEnabled("product") || isModEnabled("service")) {
1591 dol_syslog("We discard deleting module ".$file." because another module still active requires it.");
1592 continue;
1593 }
1594 }
1595
1596 if ($this->db->type == 'sqlite3') {
1597 // sqlite doesn't support "USING" syntax.
1598 // TODO: remove this dependency.
1599 $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes ";
1600 $sql .= "WHERE ".MAIN_DB_PREFIX."boxes.box_id IN (";
1601 $sql .= "SELECT ".MAIN_DB_PREFIX."boxes_def.rowid ";
1602 $sql .= "FROM ".MAIN_DB_PREFIX."boxes_def ";
1603 $sql .= "WHERE ".MAIN_DB_PREFIX."boxes_def.file = '".$this->db->escape($file)."') ";
1604 $sql .= "AND ".MAIN_DB_PREFIX."boxes.entity = ".((int) $conf->entity);
1605 } else {
1606 $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes";
1607 $sql .= " USING ".MAIN_DB_PREFIX."boxes, ".MAIN_DB_PREFIX."boxes_def";
1608 $sql .= " WHERE ".MAIN_DB_PREFIX."boxes.box_id = ".MAIN_DB_PREFIX."boxes_def.rowid";
1609 $sql .= " AND ".MAIN_DB_PREFIX."boxes_def.file = '".$this->db->escape($file)."'";
1610 $sql .= " AND ".MAIN_DB_PREFIX."boxes.entity = ".((int) $conf->entity);
1611 }
1612
1613 dol_syslog(get_class($this)."::delete_boxes", LOG_DEBUG);
1614 $resql = $this->db->query($sql);
1615 if (!$resql) {
1616 $this->error = $this->db->lasterror();
1617 $err++;
1618 }
1619
1620 $sql = "DELETE FROM ".MAIN_DB_PREFIX."boxes_def";
1621 $sql .= " WHERE file = '".$this->db->escape($file)."'";
1622 $sql .= " AND entity = ".((int) $conf->entity); // Do not use getEntity here, we want to delete only in current company
1623
1624 dol_syslog(get_class($this)."::delete_boxes", LOG_DEBUG);
1625 $resql = $this->db->query($sql);
1626 if (!$resql) {
1627 $this->error = $this->db->lasterror();
1628 $err++;
1629 }
1630 }
1631 }
1632
1633 return $err;
1634 }
1635
1636 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1642 public function insert_cronjobs()
1643 {
1644 // phpcs:enable
1645 include_once DOL_DOCUMENT_ROOT . '/core/class/infobox.class.php';
1646 include_once DOL_DOCUMENT_ROOT . '/cron/class/cronjob.class.php';
1647 include_once DOL_DOCUMENT_ROOT . '/user/class/user.class.php';
1648
1649 global $conf, $user;
1650
1651 if (empty($user)) {
1652 $user = new User($this->db);
1653 }
1654
1655 $err = 0;
1656
1657 if (is_array($this->cronjobs)) {
1658 dol_syslog(get_class($this) . "::insert_cronjobs", LOG_DEBUG);
1659
1660 foreach ($this->cronjobs as $key => $value) {
1661 $now = dol_now();
1662
1663 $entity = isset($value['entity']) ? $value['entity'] : $conf->entity;
1664 $label = isset($value['label']) ? $value['label'] : '';
1665 $jobtype = isset($value['jobtype']) ? $value['jobtype'] : '';
1666 $classesname = isset($value['class']) ? $value['class'] : '';
1667 $objectname = isset($value['objectname']) ? $value['objectname'] : '';
1668 $methodename = isset($value['method']) ? $value['method'] : '';
1669 $command = isset($value['command']) ? $value['command'] : '';
1670 $params = isset($value['parameters']) ? $value['parameters'] : '';
1671 $md5params = isset($value['md5params']) ? $value['md5params'] : '';
1672 $comment = isset($value['comment']) ? $value['comment'] : '';
1673 $frequency = isset($value['frequency']) ? $value['frequency'] : '';
1674 $unitfrequency = isset($value['unitfrequency']) ? $value['unitfrequency'] : '';
1675 $priority = isset($value['priority']) ? $value['priority'] : '';
1676 $datestart = isset($value['datestart']) ? $value['datestart'] : '';
1677 $dateend = isset($value['dateend']) ? $value['dateend'] : '';
1678 $datenextrun = isset($value['datenextrun']) ? $value['datenextrun'] : $now;
1679 $status = isset($value['status']) ? $value['status'] : '';
1680 $maxrun = isset($value['maxrun']) ? $value['maxrun'] : 0;
1681 $libname = isset($value['libname']) ? $value['libname'] : '';
1682 $test = isset($value['test']) ? $value['test'] : ''; // Line must be enabled or not (so visible or not)
1683
1684 // Search if cron entry already present
1685 $sql = "SELECT count(*) as nb FROM " . MAIN_DB_PREFIX . "cronjob";
1686 //$sql .= " WHERE module_name = '" . $this->db->escape(empty($this->rights_class) ? strtolower($this->name) : $this->rights_class) . "'";
1687 $sql .= " WHERE label = '".$this->db->escape($label)."'";
1688 /* unique key is on label,entity so no need for this test
1689 if ($classesname) {
1690 $sql .= " AND classesname = '" . $this->db->escape($classesname) . "'";
1691 }
1692 if ($objectname) {
1693 $sql .= " AND objectname = '" . $this->db->escape($objectname) . "'";
1694 }
1695 if ($methodename) {
1696 $sql .= " AND methodename = '" . $this->db->escape($methodename) . "'";
1697 }
1698 if ($command) {
1699 $sql .= " AND command = '" . $this->db->escape($command) . "'";
1700 }
1701 if ($params) {
1702 $sql .= " AND params = '" . $this->db->escape($params) . "'";
1703 }
1704 */
1705 $sql .= " AND entity = " . ((int) $entity); // Must be exact entity
1706
1707 $result = $this->db->query($sql);
1708 if (!$result) {
1709 $this->error = $this->db->lasterror();
1710 $err++;
1711 break;
1712 // else box already registered into database
1713 }
1714
1715 $obj = $this->db->fetch_object($result);
1716 if ($obj->nb > 0) {
1717 continue;
1718 }
1719
1720 $cronjob = new Cronjob($this->db);
1721
1722 $cronjob->entity = $entity;
1723 $cronjob->label = $label;
1724 $cronjob->jobtype = $jobtype;
1725 $cronjob->classesname = $classesname;
1726 $cronjob->objectname = $objectname;
1727 $cronjob->methodename = $methodename;
1728 $cronjob->command = $command;
1729 $cronjob->params = $params;
1730 $cronjob->md5params = $md5params;
1731 $cronjob->note_private = $comment;
1732 $cronjob->frequency = $frequency;
1733 $cronjob->unitfrequency = $unitfrequency;
1734 $cronjob->priority = $priority;
1735 $cronjob->datestart = $datestart;
1736 $cronjob->dateend = $dateend;
1737 $cronjob->datenextrun = $datenextrun;
1738 $cronjob->maxrun = $maxrun;
1739 $cronjob->status = $status;
1740 $cronjob->test = $test;
1741 $cronjob->libname = $libname;
1742 $cronjob->module_name = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
1743
1744 $retCreate = $cronjob->create($user);
1745
1746 if ($retCreate < 0) {
1747 $this->error = implode("\n", array_merge([$cronjob->error], $cronjob->errors));
1748 return -1;
1749 }
1750 }
1751 }
1752
1753 return $err;
1754 }
1755
1756 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1762 public function delete_cronjobs()
1763 {
1764 // phpcs:enable
1765 global $conf;
1766
1767 $err = 0;
1768
1769 if (is_array($this->cronjobs)) {
1770 $sql = "DELETE FROM ".MAIN_DB_PREFIX."cronjob";
1771 $sql .= " WHERE module_name = '".$this->db->escape(empty($this->rights_class) ? strtolower($this->name) : $this->rights_class)."'";
1772 $sql .= " AND entity = ".((int) $conf->entity);
1773 $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.
1774 // 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.
1775
1776 dol_syslog(get_class($this)."::delete_cronjobs", LOG_DEBUG);
1777 $resql = $this->db->query($sql);
1778 if (!$resql) {
1779 $this->error = $this->db->lasterror();
1780 $err++;
1781 }
1782 }
1783
1784 return $err;
1785 }
1786
1787 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1793 public function delete_tabs()
1794 {
1795 // phpcs:enable
1796 global $conf;
1797
1798 $err = 0;
1799
1800 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1801 $sql .= " WHERE ".$this->db->decrypt('name')." LIKE '".$this->db->escape($this->const_name)."_TABS_%'";
1802 $sql .= " AND entity = ".((int) $conf->entity);
1803
1804 dol_syslog(get_class($this)."::delete_tabs", LOG_DEBUG);
1805 if (!$this->db->query($sql)) {
1806 $this->error = $this->db->lasterror();
1807 $err++;
1808 }
1809
1810 return $err;
1811 }
1812
1813 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1819 public function insert_tabs()
1820 {
1821 // phpcs:enable
1822 global $conf;
1823
1824 $err = 0;
1825
1826 if (!empty($this->tabs)) {
1827 dol_syslog(get_class($this)."::insert_tabs", LOG_DEBUG);
1828
1829 $i = 0;
1830 foreach ($this->tabs as $key => $value) {
1831 if (is_array($value) && count($value) == 0) {
1832 continue; // Discard empty arrays
1833 }
1834
1835 $entity = $conf->entity;
1836 $newvalue = $value;
1837
1838 if (is_array($value)) {
1839 $newvalue = $value['data'];
1840 if (isset($value['entity'])) {
1841 $entity = $value['entity'];
1842 }
1843 }
1844
1845 if ($newvalue) {
1846 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (";
1847 $sql .= "name";
1848 $sql .= ", type";
1849 $sql .= ", value";
1850 $sql .= ", note";
1851 $sql .= ", visible";
1852 $sql .= ", entity";
1853 $sql .= ")";
1854 $sql .= " VALUES (";
1855 $sql .= $this->db->encrypt($this->const_name."_TABS_".$i);
1856 $sql .= ", 'chaine'";
1857 $sql .= ", ".$this->db->encrypt($newvalue);
1858 $sql .= ", null";
1859 $sql .= ", '0'";
1860 $sql .= ", ".((int) $entity);
1861 $sql .= ")";
1862
1863 $resql = $this->db->query($sql);
1864 if (!$resql) {
1865 dol_syslog($this->db->lasterror(), LOG_ERR);
1866 if ($this->db->lasterrno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1867 $this->error = $this->db->lasterror();
1868 $this->errors[] = $this->db->lasterror();
1869 $err++;
1870 break;
1871 }
1872 }
1873 }
1874 $i++;
1875 }
1876 }
1877 return $err;
1878 }
1879
1880 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1886 public function insert_const()
1887 {
1888 // phpcs:enable
1889 global $conf;
1890
1891 $err = 0;
1892
1893 if (empty($this->const)) {
1894 return 0;
1895 }
1896
1897 dol_syslog(__METHOD__, LOG_DEBUG);
1898
1899 foreach ($this->const as $key => $value) {
1900 // Property to search if entry exists
1901 $name = $this->const[$key][0];
1902 $entity = (!empty($this->const[$key][5]) && $this->const[$key][5] != 'current') ? 0 : $conf->entity;
1903
1904 // Property to update if it does not exists
1905 $type = $this->const[$key][1];
1906 $val = $this->const[$key][2];
1907 $note = isset($this->const[$key][3]) ? $this->const[$key][3] : '';
1908 $visible = isset($this->const[$key][4]) ? $this->const[$key][4] : 0;
1909
1910 // Clean
1911 if (empty($visible)) {
1912 $visible = '0';
1913 }
1914 if (empty($val) && $val != '0') {
1915 $val = '';
1916 }
1917
1918 if (!empty($name)) {
1919 $sql = "SELECT count(*) as nb";
1920 $sql .= " FROM ".MAIN_DB_PREFIX."const";
1921 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($name)."'";
1922 $sql .= " AND entity = ".((int) $entity);
1923
1924 $result = $this->db->query($sql);
1925 if ($result) {
1926 $row = $this->db->fetch_row($result);
1927
1928 if ($row[0] == 0) { // If not found
1929 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (name, type, value, note, visible, entity)";
1930 $sql .= " VALUES (";
1931 $sql .= $this->db->encrypt($name); // Note: encrypt returns SQL expression or quoted string
1932 $sql .= ", '".$this->db->escape($type)."'";
1933 $sql .= ", ".(($val != '') ? $this->db->encrypt($val) : "''"); // Note: encrypt returns SQL expression or quoted string
1934 $sql .= ", ".($note ? "'".$this->db->escape($note)."'" : "null");
1935 $sql .= ", '".$this->db->escape($visible)."'";
1936 $sql .= ", ".((int) $entity);
1937 $sql .= ")";
1938
1939 if (!$this->db->query($sql)) {
1940 $err++;
1941 } else {
1942 // Set also the variable in running environment
1943 $conf->global->$name = $val;
1944 }
1945 } else {
1946 dol_syslog(__METHOD__." constant '".$name."' already exists", LOG_DEBUG);
1947 }
1948 } else {
1949 $err++;
1950 }
1951 }
1952 }
1953
1954 return $err;
1955 }
1956
1957 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1963 public function delete_const()
1964 {
1965 // phpcs:enable
1966 global $conf;
1967
1968 $err = 0;
1969
1970 if (empty($this->const)) {
1971 return 0;
1972 }
1973
1974 foreach ($this->const as $key => $value) {
1975 $name = $this->const[$key][0];
1976 $deleteonunactive = (!empty($this->const[$key][6])) ? 1 : 0;
1977
1978 if ($deleteonunactive) {
1979 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
1980 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($name)."'";
1981 $sql .= " AND entity in (0, ".((int) $conf->entity).")";
1982 dol_syslog(get_class($this)."::delete_const", LOG_DEBUG);
1983 if (!$this->db->query($sql)) {
1984 $this->error = $this->db->lasterror();
1985 $err++;
1986 }
1987 }
1988 }
1989
1990 return $err;
1991 }
1992
1993 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2000 {
2001 // phpcs:enable
2002 global $conf;
2003
2004 $err = 0;
2005
2006 if (empty($this->overwrite_translation)) {
2007 return 0;
2008 }
2009
2010 dol_syslog(get_class($this)."::insert_overwrite_translation", LOG_DEBUG);
2011
2012 foreach ($this->overwrite_translation as $key => $value) {
2013 $arkey = explode(':', $key);
2014 $lang = $arkey[0];
2015 $transkey = $arkey[1];
2016 $transvalue = $value;
2017 $entity = (!empty($arkey[3]) && $arkey[3] != 'current') ? 0 : $conf->entity;
2018
2019 // valid
2020 if (empty($lang) || empty($transkey) || empty($transvalue)) {
2021 continue;
2022 }
2023
2024 $sql = "SELECT count(*) as nb";
2025 $sql .= " FROM ".MAIN_DB_PREFIX."overwrite_trans";
2026 $sql .= " WHERE ".$this->db->decrypt("transkey")." = '".$this->db->escape($transkey)."'";
2027 $sql .= " AND entity = ".((int) $entity);
2028
2029 $result = $this->db->query($sql);
2030 if ($result) {
2031 $row = $this->db->fetch_row($result);
2032
2033 if ($row[0] == 0) { // If not found
2034 $sql = "INSERT INTO ".MAIN_DB_PREFIX."overwrite_trans (entity,lang,transkey,transvalue)";
2035 $sql .= " VALUES (";
2036 $sql .= (int) $entity;
2037 $sql .= ",'".$this->db->escape($lang)."'";
2038 $sql .= ",'".$this->db->escape($transkey)."'";
2039 $sql .= ",'".$this->db->escape($transvalue)."'";
2040 $sql .= ")";
2041
2042 if (!$this->db->query($sql)) {
2043 $err++;
2044 }
2045 } else {
2046 dol_syslog(get_class($this)."::insert_overwrite_translation overwrite trans for key $transkey already exists", LOG_DEBUG);
2047 }
2048 } else {
2049 $err++;
2050 }
2051 }
2052
2053 return $err;
2054 }
2055
2056 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2069 public function insert_permissions($reinitadminperms = 0, $force_entity = null, $notrigger = 0, $existingrightsdefids = null)
2070 {
2071 // phpcs:enable
2072 global $conf, $user;
2073
2074 $err = 0;
2075 $entity = (!empty($force_entity) ? $force_entity : $conf->entity);
2076
2077 dol_syslog(get_class($this)."::insert_permissions", LOG_DEBUG);
2078
2079 // Test if module is activated
2080 $sql_del = "SELECT ".$this->db->decrypt('value')." as value";
2081 $sql_del .= " FROM ".MAIN_DB_PREFIX."const";
2082 $sql_del .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($this->const_name)."'";
2083 $sql_del .= " AND entity IN (0,".((int) $entity).")";
2084
2085 $resql = $this->db->query($sql_del);
2086
2087 if ($resql) {
2088 $obj = $this->db->fetch_object($resql);
2089
2090 if ($obj !== null && !empty($obj->value) && !empty($this->rights)) {
2091 include_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
2092
2093 // If the module is active
2094 foreach ($this->rights as $key => $value) {
2095 $r_id = $this->rights[$key][self::KEY_ID]; // permission id in llx_rights_def (not unique because primary key is couple id-entity)
2096 $r_label = $this->rights[$key][self::KEY_LABEL];
2097 $r_type = $this->rights[$key][self::KEY_TYPE] ?? 'w'; // TODO deprecated
2098 $r_default = $this->rights[$key][self::KEY_DEFAULT] ?? 0;
2099 $r_perms = $this->rights[$key][self::KEY_FIRST_LEVEL] ?? '';
2100 $r_subperms = $this->rights[$key][self::KEY_SECOND_LEVEL] ?? '';
2101
2102 // KEY_FIRST_LEVEL (perms) must not be empty
2103 if (empty($r_perms)) {
2104 continue;
2105 }
2106
2107 // name of module (default: current module name)
2108 $r_module = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
2109
2110 // name of the module from which the right comes (default: empty means same module the permission is for)
2111 $r_module_origin = '';
2112
2113 if (isset($this->rights[$key][self::KEY_MODULE])) {
2114 // name of the module to which the right must be applied
2115 $r_module = $this->rights[$key][self::KEY_MODULE];
2116 // name of the module from which the right comes
2117 $r_module_origin = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
2118 }
2119
2120 if (!empty($r_module_origin) && $r_module !== $r_module_origin) {
2121 // This right is filed under a different module's section of the permission
2122 // grid (KEY_MODULE) than the one declaring it: use that target module's own
2123 // family/position so it appears grouped with its native rights instead of
2124 // opening a second, misplaced section for the same module.
2125 $r_targetmoduleinfo = $this->getModuleInfoByRightsClass($r_module);
2126 $r_module_position = $r_targetmoduleinfo['position'];
2127 $r_family = $r_targetmoduleinfo['family'];
2128 } else {
2129 $r_module_position = $this->getModulePosition();
2130 $r_family = $this->family;
2131 }
2132 $r_family_position = 0;
2133
2134 // condition to show or hide a user right (default: 1) (eg isModEnabled('anothermodule') or ($conf->global->MAIN_FEATURES_LEVEL > 0) or etc..)
2135 $r_enabled = $this->rights[$key][self::KEY_ENABLED] ?? '1';
2136
2137 // Search if perm already present
2138 if ($existingrightsdefids !== null) {
2139 $rightalreadyexists = !empty($existingrightsdefids[$r_id]);
2140 } else {
2141 $sql = "SELECT count(*) as nb FROM ".MAIN_DB_PREFIX."rights_def";
2142 $sql .= " WHERE entity = ".((int) $entity);
2143 $sql .= " AND id = ".((int) $r_id);
2144
2145 $rightalreadyexists = true; // Assume it exists if the select fails, so we never try to insert on a query error
2146 $resqlselect = $this->db->query($sql);
2147 if ($resqlselect) {
2148 $objcount = $this->db->fetch_object($resqlselect);
2149 $rightalreadyexists = !($objcount && $objcount->nb == 0);
2150 $this->db->free($resqlselect);
2151 }
2152 }
2153
2154 if (!$rightalreadyexists) {
2155 $sql = "INSERT INTO ".MAIN_DB_PREFIX."rights_def (";
2156 $sql .= "id";
2157 $sql .= ", entity";
2158 $sql .= ", libelle";
2159 $sql .= ", module";
2160 $sql .= ", module_origin";
2161 $sql .= ", module_position"; // Not that module_position can be fixed eynamically when accessing page user/perms.php
2162 $sql .= ", family";
2163 $sql .= ", family_position";
2164 $sql .= ", type"; // Not used yet
2165 $sql .= ", bydefault";
2166 $sql .= ", perms";
2167 $sql .= ", subperms";
2168 $sql .= ", enabled";
2169 $sql .= ") VALUES (";
2170 $sql .= ((int) $r_id);
2171 $sql .= ", ".((int) $entity);
2172 $sql .= ", '".$this->db->escape($r_label)."'";
2173 $sql .= ", '".$this->db->escape($r_module)."'";
2174 $sql .= ", '".$this->db->escape($r_module_origin)."'";
2175 $sql .= ", '".$this->db->escape((string) $r_module_position)."'";
2176 $sql .= ", '".$this->db->escape($r_family)."'";
2177 $sql .= ", '".$this->db->escape((string) $r_family_position)."'";
2178 $sql .= ", '".$this->db->escape($r_type)."'"; // Not used yet
2179 $sql .= ", ".((int) $r_default);
2180 $sql .= ", '".$this->db->escape($r_perms)."'";
2181 $sql .= ", '".$this->db->escape($r_subperms)."'";
2182 $sql .= ", '".$this->db->escape($r_enabled)."'";
2183 $sql .= ")";
2184
2185 $resqlinsert = $this->db->query($sql, 1);
2186
2187 if (!$resqlinsert) {
2188 if ($this->db->errno() != "DB_ERROR_RECORD_ALREADY_EXISTS") {
2189 $this->error = $this->db->lasterror();
2190 $err++;
2191 break;
2192 } else {
2193 dol_syslog(get_class($this)."::insert_permissions record already exists", LOG_INFO);
2194 }
2195 }
2196
2197 $this->db->free($resqlinsert);
2198 }
2199
2200 // If we want to init permissions on admin users
2201 if (!empty($reinitadminperms)) {
2202 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."user WHERE admin = 1";
2203 dol_syslog(get_class($this)."::insert_permissions Search all admin users", LOG_DEBUG);
2204
2205 $resqlseladmin = $this->db->query($sql, 1);
2206
2207 if ($resqlseladmin) {
2208 $num = $this->db->num_rows($resqlseladmin);
2209 $i = 0;
2210 while ($i < $num) {
2211 $obj2 = $this->db->fetch_object($resqlseladmin);
2212 dol_syslog(get_class($this)."::insert_permissions Add permission id ".$r_id." to user id=".$obj2->rowid);
2213
2214 $tmpuser = new User($this->db);
2215 $result = $tmpuser->fetch($obj2->rowid);
2216 if ($result > 0) {
2217 $tmpuser->addrights($r_id, '', '', 0, 1);
2218 } else {
2219 dol_syslog(get_class($this)."::insert_permissions Failed to add the permission to user because fetch return an error", LOG_ERR);
2220 }
2221 $i++;
2222 }
2223 } else {
2224 dol_print_error($this->db);
2225 }
2226 }
2227 }
2228
2229 if (!empty($reinitadminperms) && !empty($user->admin)) { // Reload permission for current user if defined
2230 // We reload permissions
2231 $user->clearrights();
2232 $user->loadRights();
2233 }
2234 }
2235 $this->db->free($resql);
2236 } else {
2237 $this->error = $this->db->lasterror();
2238 $err++;
2239 }
2240
2241 return $err;
2242 }
2243
2255 protected function getModuleInfoByRightsClass($rightsclass)
2256 {
2257 global $conf, $db;
2258
2259 if (isset($conf->cache['keyModuleInfoCache'][$rightsclass])) {
2260 return $conf->cache['keyModuleInfoCache'][$rightsclass];
2261 }
2262
2263 // Fallback: if no module with this rights_class is found (typo, or module removed from disk),
2264 // still register something under its own family/position rather than leaving it undefined.
2265 $result = array('family' => $this->family, 'position' => (int) $this->getModulePosition());
2266
2267 $modulesdir = dolGetModulesDirs();
2268 foreach ($modulesdir as $dir) {
2269 $handle = @opendir(dol_osencode($dir));
2270 if (is_resource($handle)) {
2271 while (($file = readdir($handle)) !== false) {
2272 if (is_readable($dir.$file) && dol_substr($file, 0, 3) == 'mod' && dol_substr($file, dol_strlen($file) - 10) == '.class.php') {
2273 $modName = dol_substr($file, 0, dol_strlen($file) - 10);
2274 if ($modName && $modName != get_class($this)) {
2275 include_once $dir.$file;
2276 if (class_exists($modName)) {
2277 '@phan-var-force class-string<DolibarrModules> $modName';
2278 $objMod = new $modName($db);
2279 '@phan-var-force DolibarrModules $objMod';
2280 if (!empty($objMod->rights_class) && $objMod->rights_class === $rightsclass) {
2281 $result = array('family' => $objMod->family, 'position' => (int) $objMod->getModulePosition());
2282 break 2;
2283 }
2284 }
2285 }
2286 }
2287 }
2288 closedir($handle);
2289 }
2290 }
2291
2292 $conf->cache['keyModuleInfoCache'][$rightsclass] = $result;
2293
2294 return $result;
2295 }
2296
2297
2298 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2304 public function delete_permissions()
2305 {
2306 // phpcs:enable
2307 global $conf;
2308
2309 $err = 0;
2310
2311 $module = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
2312
2313 $sql = "DELETE FROM ".MAIN_DB_PREFIX."rights_def";
2314 $sql .= " WHERE (module = '".$this->db->escape($module)."' OR module_origin = '".$this->db->escape($module)."')";
2315
2316 // Delete all entities if core module
2317 if (empty($this->core_enabled)) {
2318 $sql .= " AND entity = ".((int) $conf->entity);
2319 }
2320
2321 dol_syslog(get_class($this)."::delete_permissions", LOG_DEBUG);
2322 if (!$this->db->query($sql)) {
2323 $this->error = $this->db->lasterror();
2324 $err++;
2325 }
2326
2327 return $err;
2328 }
2329
2330
2331 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2337 public function insert_menus()
2338 {
2339 // phpcs:enable
2340 global $conf, $user;
2341
2342 if (!is_array($this->menu) || empty($this->menu)) {
2343 return 0;
2344 }
2345
2346 include_once DOL_DOCUMENT_ROOT.'/core/class/menubase.class.php';
2347
2348 dol_syslog(get_class($this)."::insert_menus", LOG_DEBUG);
2349
2350 $err = 0;
2351
2352 // Common module
2353 $entity = ((!empty($this->always_enabled) || !empty($this->core_enabled)) ? 0 : $conf->entity);
2354
2355 $this->db->begin();
2356
2357 foreach ($this->menu as $key => $value) {
2358 $menu = new Menubase($this->db);
2359 $menu->menu_handler = 'all';
2360
2361 //$menu->module=strtolower($this->name); TODO When right_class will be same than module name
2362 $menu->module = (empty($this->rights_class) ? strtolower($this->name) : $this->rights_class);
2363
2364 if (!$this->menu[$key]['fk_menu']) {
2365 $menu->fk_menu = 0;
2366 } else {
2367 $foundparent = 0;
2368 $fk_parent = $this->menu[$key]['fk_menu'];
2369 $reg = array();
2370 if (preg_match('/^r=/', $fk_parent)) { // old deprecated method
2371 $fk_parent = str_replace('r=', '', $fk_parent);
2372 if (isset($this->menu[$fk_parent]['rowid'])) {
2373 $menu->fk_menu = $this->menu[$fk_parent]['rowid'];
2374 $foundparent = 1;
2375 }
2376 } elseif (preg_match('/^fk_mainmenu=([a-zA-Z0-9_]+),fk_leftmenu=([a-zA-Z0-9_]+)$/', $fk_parent, $reg)) {
2377 $menu->fk_menu = -1;
2378 $menu->fk_mainmenu = $reg[1];
2379 $menu->fk_leftmenu = $reg[2];
2380 $foundparent = 1;
2381 } elseif (preg_match('/^fk_mainmenu=([a-zA-Z0-9_]+)$/', $fk_parent, $reg)) {
2382 $menu->fk_menu = -1;
2383 $menu->fk_mainmenu = $reg[1];
2384 $menu->fk_leftmenu = '';
2385 $foundparent = 1;
2386 }
2387 if (!$foundparent) {
2388 $this->error = "ErrorBadDefinitionOfMenuArrayInModuleDescriptor";
2389 dol_syslog(get_class($this)."::insert_menus ".$this->error." ".$this->menu[$key]['fk_menu'], LOG_ERR);
2390 $err++;
2391 }
2392 }
2393 $menu->type = $this->menu[$key]['type'];
2394 $menu->mainmenu = isset($this->menu[$key]['mainmenu']) ? $this->menu[$key]['mainmenu'] : (isset($menu->fk_mainmenu) ? $menu->fk_mainmenu : '');
2395 $menu->leftmenu = isset($this->menu[$key]['leftmenu']) ? $this->menu[$key]['leftmenu'] : '';
2396 $menu->title = $this->menu[$key]['titre'];
2397 $menu->prefix = isset($this->menu[$key]['prefix']) ? $this->menu[$key]['prefix'] : '';
2398 $menu->url = $this->menu[$key]['url'];
2399 $menu->langs = isset($this->menu[$key]['langs']) ? $this->menu[$key]['langs'] : '';
2400 $menu->position = $this->menu[$key]['position'];
2401 $menu->perms = $this->menu[$key]['perms'];
2402 $menu->target = isset($this->menu[$key]['target']) ? $this->menu[$key]['target'] : '';
2403 $menu->user = $this->menu[$key]['user'];
2404 $menu->enabled = isset($this->menu[$key]['enabled']) ? $this->menu[$key]['enabled'] : 0;
2405 $menu->position = $this->menu[$key]['position'];
2406 $menu->entity = $entity;
2407
2408 if (!$err) {
2409 $result = $menu->create($user); // Save menu entry into table llx_menu
2410 if ($result > 0) {
2411 $this->menu[$key]['rowid'] = $result;
2412 } else {
2413 $this->error = $menu->error;
2414 dol_syslog(get_class($this).'::insert_menus result='.$result." ".$this->error, LOG_ERR);
2415 $err++;
2416 break;
2417 }
2418 }
2419 }
2420
2421 if (!$err) {
2422 $this->db->commit();
2423 } else {
2424 dol_syslog(get_class($this)."::insert_menus ".$this->error, LOG_ERR);
2425 $this->db->rollback();
2426 }
2427
2428 return $err;
2429 }
2430
2431
2432 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2438 public function delete_menus()
2439 {
2440 // phpcs:enable
2441 global $conf;
2442
2443 $err = 0;
2444
2445 //$module=strtolower($this->name); TODO When right_class will be same than module name
2446 $module = empty($this->rights_class) ? strtolower($this->name) : $this->rights_class;
2447
2448 $sql = "DELETE FROM ".MAIN_DB_PREFIX."menu";
2449 $sql .= " WHERE module = '".$this->db->escape($module)."'";
2450 $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'
2451 $sql .= " AND entity IN (0, ".((int) $conf->entity).")";
2452
2453 dol_syslog(get_class($this)."::delete_menus", LOG_DEBUG);
2454 $resql = $this->db->query($sql);
2455 if (!$resql) {
2456 $this->error = $this->db->lasterror();
2457 $err++;
2458 }
2459
2460 return $err;
2461 }
2462
2463 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2469 public function create_dirs()
2470 {
2471 // phpcs:enable
2472 global $langs, $conf;
2473
2474 $err = 0;
2475 $name = '';
2476
2477 if (isset($this->dirs) && is_array($this->dirs)) {
2478 foreach ($this->dirs as $key => $value) {
2479 $addtodatabase = 0;
2480
2481 if (!is_array($value)) {
2482 $dir = $value; // Default simple mode
2483 } else {
2484 $constname = $this->const_name."_DIR_";
2485 $dir = $this->dirs[$key][1];
2486 $addtodatabase = empty($this->dirs[$key][2]) ? '' : $this->dirs[$key][2]; // Create constante in llx_const
2487 $subname = empty($this->dirs[$key][3]) ? '' : strtoupper($this->dirs[$key][3]); // Add submodule name (ex: $conf->module->submodule->dir_output)
2488 $forcename = empty($this->dirs[$key][4]) ? '' : strtoupper($this->dirs[$key][4]); // Change the module name if different
2489
2490 if (!empty($forcename)) {
2491 $constname = 'MAIN_MODULE_'.$forcename."_DIR_";
2492 }
2493 if (!empty($subname)) {
2494 $constname = $constname.$subname."_";
2495 }
2496
2497 $name = $constname.strtoupper($this->dirs[$key][0]);
2498 }
2499
2500 // Define directory full path ($dir must start with "/")
2501 if (!getDolGlobalString('MAIN_MODULE_MULTICOMPANY') || $conf->entity == 1) {
2502 $fulldir = DOL_DATA_ROOT.$dir;
2503 } else {
2504 $fulldir = DOL_DATA_ROOT."/".$conf->entity.$dir;
2505 }
2506 // Create dir if it does not exists
2507 if (!empty($fulldir) && !file_exists($fulldir)) {
2508 if (dol_mkdir($fulldir, DOL_DATA_ROOT) < 0) {
2509 $this->error = $langs->trans("ErrorCanNotCreateDir", $fulldir);
2510 dol_syslog(get_class($this)."::_init ".$this->error, LOG_ERR);
2511 $err++;
2512 }
2513 }
2514
2515 // Define the constant in database if requested (not the default mode)
2516 if (!empty($addtodatabase) && !empty($name)) {
2517 $result = $this->insert_dirs($name, $dir);
2518 if ($result) {
2519 $err++;
2520 }
2521 }
2522 }
2523 }
2524
2525 return $err;
2526 }
2527
2528
2529 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2538 public function insert_dirs($name, $dir)
2539 {
2540 // phpcs:enable
2541 global $conf;
2542
2543 $err = 0;
2544
2545 $sql = "SELECT count(*)";
2546 $sql .= " FROM ".MAIN_DB_PREFIX."const";
2547 $sql .= " WHERE ".$this->db->decrypt('name')." = '".$this->db->escape($name)."'";
2548 $sql .= " AND entity = ".((int) $conf->entity);
2549
2550 dol_syslog(get_class($this)."::insert_dirs", LOG_DEBUG);
2551 $result = $this->db->query($sql);
2552 if ($result) {
2553 $row = $this->db->fetch_row($result);
2554
2555 if ($row[0] == 0) {
2556 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (name, type, value, note, visible, entity)";
2557 $sql .= " VALUES (".$this->db->encrypt($name).", 'chaine', ".$this->db->encrypt($dir).", '".$this->db->escape("Directory for module ".$this->name)."', '0', ".((int) $conf->entity).")";
2558
2559 dol_syslog(get_class($this)."::insert_dirs", LOG_DEBUG);
2560 $this->db->query($sql);
2561 }
2562 } else {
2563 $this->error = $this->db->lasterror();
2564 $err++;
2565 }
2566
2567 return $err;
2568 }
2569
2570
2571 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2577 public function delete_dirs()
2578 {
2579 // phpcs:enable
2580 global $conf;
2581
2582 $err = 0;
2583
2584 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
2585 $sql .= " WHERE ".$this->db->decrypt('name')." LIKE '".$this->db->escape($this->const_name)."_DIR_%'";
2586 $sql .= " AND entity = ".((int) $conf->entity);
2587
2588 dol_syslog(get_class($this)."::delete_dirs", LOG_DEBUG);
2589 if (!$this->db->query($sql)) {
2590 $this->error = $this->db->lasterror();
2591 $err++;
2592 }
2593
2594 return $err;
2595 }
2596
2597 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2604 public function insert_module_parts()
2605 {
2606 // phpcs:enable
2607 global $conf, $langs;
2608
2609 $error = 0;
2610
2611 if (is_array($this->module_parts)) {
2612 if (empty($this->module_parts['icon']) && !empty($this->picto) && preg_match('/^fa\-/', $this->picto)) {
2613 $this->module_parts['icon'] = $this->picto;
2614 }
2615
2616 foreach ($this->module_parts as $key => $value) {
2617 if (is_array($value) && count($value) == 0) {
2618 continue; // Discard empty arrays
2619 }
2620
2621 // If module brings website templates, we must generate the zip like we do whenenabling the website module
2622 if ($key == 'websitetemplates' && $value == 1) {
2623 $srcroot = dol_buildpath('/'.strtolower($this->name).'/doctemplates/websites');
2624
2625 // Copy templates in dir format (recommended) into zip file
2626 $docs = dol_dir_list($srcroot, 'directories', 0, 'website_.*$');
2627 foreach ($docs as $cursorfile) {
2628 $src = $srcroot.'/'.$cursorfile['name'];
2629 $dest = DOL_DATA_ROOT.'/doctemplates/websites/'.$cursorfile['name'];
2630
2631 dol_delete_file($dest.'.zip');
2632
2633 // Compress it
2634 global $errormsg; // Used by dol_compress_dir
2635 $errormsg = '';
2636 $result = dol_compress_dir($src, $dest.'.zip', 'zip');
2637 if ($result < 0) {
2638 $error++;
2639 $this->error = ($errormsg ? $errormsg : $langs->trans('ErrorFailToCreateZip', $dest));
2640 $this->errors[] = ($errormsg ? $errormsg : $langs->trans('ErrorFailToCreateZip', $dest));
2641 }
2642 }
2643
2644 // Copy also the preview website_xxx.jpg file
2645 $docs = dol_dir_list($srcroot, 'files', 0, 'website_.*\.jpg$');
2646 foreach ($docs as $cursorfile) {
2647 $src = $srcroot.'/'.$cursorfile['name'];
2648 $dest = DOL_DATA_ROOT.'/doctemplates/websites/'.$cursorfile['name'];
2649
2650 dol_copy($src, $dest);
2651 }
2652 }
2653
2654 $entity = $conf->entity; // Reset the current entity
2655 $newvalue = $value;
2656
2657 // Serialize array parameters
2658 if (is_array($value)) {
2659 // Can defined other parameters
2660 // Example when $key='hooks', then $value is an array('data'=>array('hookcontext1','hookcontext2'), 'entity'=>X)
2661 if (isset($value['data']) && is_array($value['data'])) {
2662 $newvalue = json_encode($value['data']);
2663 if (isset($value['entity'])) {
2664 $entity = $value['entity'];
2665 }
2666 } elseif (isset($value['data']) && !is_array($value['data'])) {
2667 $newvalue = $value['data'];
2668 if (isset($value['entity'])) {
2669 $entity = $value['entity'];
2670 }
2671 } else { // when hook is declared with syntax 'hook'=>array('hookcontext1','hookcontext2',...)
2672 $newvalue = json_encode($value);
2673 }
2674 }
2675
2676 if (!empty($newvalue)) {
2677 $sql = "INSERT INTO ".MAIN_DB_PREFIX."const (";
2678 $sql .= "name";
2679 $sql .= ", type";
2680 $sql .= ", value";
2681 $sql .= ", note";
2682 $sql .= ", visible";
2683 $sql .= ", entity";
2684 $sql .= ")";
2685 $sql .= " VALUES (";
2686 $sql .= " ".$this->db->encrypt($this->const_name."_".strtoupper($key), 1);
2687 $sql .= ", 'chaine'";
2688 $sql .= ", ".$this->db->encrypt($newvalue, 1);
2689 $sql .= ", null";
2690 $sql .= ", '0'";
2691 $sql .= ", ".((int) $entity);
2692 $sql .= ")";
2693
2694 dol_syslog(get_class($this)."::insert_module_parts for key=".$this->const_name."_".strtoupper($key), LOG_DEBUG);
2695
2696 $resql = $this->db->query($sql, 1);
2697 if (!$resql) {
2698 if ($this->db->lasterrno() != 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2699 $error++;
2700 $this->error = $this->db->lasterror();
2701 } else {
2702 dol_syslog(get_class($this)."::insert_module_parts for ".$this->const_name."_".strtoupper($key)." Record already exists.", LOG_WARNING);
2703 }
2704 }
2705 }
2706 }
2707 }
2708 return $error;
2709 }
2710
2711 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2717 public function delete_module_parts()
2718 {
2719 // phpcs:enable
2720 global $conf;
2721
2722 $err = 0;
2723
2724 if (is_array($this->module_parts)) {
2725 dol_syslog(get_class($this)."::delete_module_parts", LOG_DEBUG);
2726
2727 if (empty($this->module_parts['icon']) && !empty($this->picto) && preg_match('/^fa\-/', $this->picto)) {
2728 $this->module_parts['icon'] = $this->picto;
2729 }
2730
2731 foreach ($this->module_parts as $key => $value) {
2732 // If entity is defined
2733 if (is_array($value) && isset($value['entity'])) {
2734 $entity = $value['entity'];
2735 } else {
2736 $entity = $conf->entity;
2737 }
2738
2739 $sql = "DELETE FROM ".MAIN_DB_PREFIX."const";
2740 $sql .= " WHERE ".$this->db->decrypt('name')." LIKE '".$this->db->escape($this->const_name)."_".strtoupper($key)."'";
2741 $sql .= " AND entity = ".((int) $entity);
2742
2743 if (!$this->db->query($sql)) {
2744 $this->error = $this->db->lasterror();
2745 $err++;
2746 }
2747 }
2748 }
2749 return $err;
2750 }
2751
2761 public function init($options = '')
2762 {
2763 return $this->_init(array(), $options);
2764 }
2765
2774 public function remove($options = '')
2775 {
2776 return $this->_remove(array(), $options);
2777 }
2778
2779
2787 public function getKanbanView($codeenabledisable = '', $codetoconfig = '')
2788 {
2789 global $langs;
2790
2791 // Define imginfo
2792 $imginfo = "info";
2793 if ($this->isCoreOrExternalModule() == 'external') {
2794 $imginfo = "info_black";
2795 }
2796
2797 $const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($this)));
2798
2799 $version = $this->getVersion(0);
2800 $versiontrans = '';
2801 if (preg_match('/development/i', $version)) {
2802 $versiontrans .= 'warning';
2803 }
2804 if (preg_match('/experimental/i', $version)) {
2805 $versiontrans .= 'warning';
2806 }
2807 if (preg_match('/deprecated/i', $version)) {
2808 $versiontrans .= 'warning';
2809 }
2810
2811 $return = '
2812 <div class="box-flex-item info-box-module'
2813 .(getDolGlobalString($const_name) ? '' : ' --disabled')
2814 .($this->isCoreOrExternalModule() == 'external' ? ' --external' : '')
2815 .($this->needUpdate ? ' --need-update' : '')
2816 .'">
2817 <div class="info-box info-box-sm info-box-module">
2818 <div class="info-box-icon'.(!getDolGlobalString($const_name) ? '' : ' info-box-icon-module-enabled'.($versiontrans ? ' info-box-icon-module-warning' : '')).'">';
2819
2820 $alttext = '';
2821 //if (is_array($objMod->need_dolibarr_version)) $alttext.=($alttext?' - ':'').'Dolibarr >= '.join('.',$objMod->need_dolibarr_version);
2822 //if (is_array($objMod->phpmin)) $alttext.=($alttext?' - ':'').'PHP >= '.join('.',$objMod->phpmin);
2823 if (!empty($this->picto)) {
2824 if (preg_match('/^\//i', $this->picto)) {
2825 $return .= img_picto($alttext, $this->picto, 'class="inline-block valignmiddle"', 1);
2826 } else {
2827 $return .= img_object($alttext, $this->picto, 'class="inline-block valignmiddle"');
2828 }
2829 } else {
2830 $return .= img_object($alttext, 'generic', 'class="inline-block valignmiddle"');
2831 }
2832
2833 if ($this->isCoreOrExternalModule() == 'external' || preg_match('/development|experimental|deprecated/i', $version)) {
2834 $versionTitle = $langs->trans("Version").' '.$this->getVersion(1);
2835 if ($this->needUpdate) {
2836 $versionTitle .= '<br>'.$langs->trans('ModuleUpdateAvailable').' : '.$this->lastVersion;
2837 }
2838
2839 $return .= '<span class="info-box-icon-version'.($versiontrans ? ' '.$versiontrans : '').' classfortooltip" title="'.dolPrintHTMLForAttribute($versionTitle).'" >';
2840 $return .= $this->getVersion(1);
2841 $return .= '</span>';
2842 }
2843
2844 $return .= '</div>
2845 <div class="info-box-content info-box-text-module'.(!getDolGlobalString($const_name) ? '' : ' info-box-module-enabled'.($versiontrans ? ' info-box-content-warning' : '')).'">
2846 <span class="info-box-title noopacity">'.$this->getName().'</span>
2847 <span class="info-box-desc twolinesmax opacitymedium" title="'.dol_escape_htmltag($this->getDesc()).'">'.nl2br($this->getDesc()).'</span>';
2848
2849 $return .= '<div class="valignmiddle inline-block info-box-more">';
2850 //if ($versiontrans) print img_warning($langs->trans("Version").' '.$this->getVersion(1)).' ';
2851 $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>';
2852 $return .= '</div><br>';
2853
2854 $return .= '<div class="valignmiddle inline-block info-box-actions">';
2855 $return .= '<div class="valignmiddle inline-block info-box-setup">';
2856 $return .= $codetoconfig;
2857 $return .= '</div>';
2858 $return .= '<div class="valignmiddle inline-block marginleftonly marginrightonly">';
2859 $return .= $codeenabledisable;
2860 $return .= '</div>';
2861 $return .= '</div>';
2862
2863 $return .= '
2864 </div><!-- /.info-box-content -->
2865 </div><!-- /.info-box -->
2866 </div>';
2867
2868 return $return;
2869 }
2870
2879 public function checkForUpdate()
2880 {
2881 require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
2882 if (!empty($this->url_last_version)) {
2883 $lastVersion = getURLContent($this->url_last_version, 'GET', '', 1, array(), array('http', 'https'), 0); // Accept http or https links on external remote server only
2884 if (isset($lastVersion['content']) && strlen($lastVersion['content']) < 30) {
2885 // 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 _ . -
2886 $this->lastVersion = preg_replace("/[^a-zA-Z0-9_\.\-]+/", "", $lastVersion['content']);
2887 if (version_compare($this->lastVersion, $this->version) > 0) {
2888 $this->needUpdate = true;
2889 return 1;
2890 } else {
2891 $this->needUpdate = false;
2892 return 0;
2893 }
2894 } else {
2895 return -1;
2896 }
2897 }
2898 return 0;
2899 }
2900
2908 public function checkForCompliance($nametocheck = '')
2909 {
2910 global $conf, $langs;
2911
2912 if (empty($nametocheck)) {
2913 $nametocheck = $this->name;
2914 }
2915
2916 // Get list of illegal modules name or ID
2917 if (empty($conf->cache['noncompliantmodules'])) {
2918 require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
2919
2920 $result = getURLContent(self::URL_FOR_BLACKLISTED_MODULES, 'GET', '', 1, array(), array('http', 'https'), 0); // Accept http or https links on external remote server only
2921 if (isset($result['content']) && $result['http_code'] == 200) {
2922 $langs->load("errors");
2923
2924 // 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 _ . -
2925 $arrayoflines = preg_split("/[\n,]/", $result['content']);
2926 foreach ($arrayoflines as $line) {
2927 $tmpfieldsofline = explode(';', $line);
2928 $modulekey = strtolower(trim($tmpfieldsofline[0]));
2929 if (empty($modulekey)) {
2930 continue;
2931 }
2932 $conf->cache['noncompliantmodules'][$modulekey]['name'] = trim($tmpfieldsofline[0]);
2933 $conf->cache['noncompliantmodules'][$modulekey]['id'] = (isset($tmpfieldsofline[1]) ? trim($tmpfieldsofline[1]) : '');
2934 $conf->cache['noncompliantmodules'][$modulekey]['signature'] = (isset($tmpfieldsofline[2]) ? trim($tmpfieldsofline[2]) : '');
2935 $conf->cache['noncompliantmodules'][$modulekey]['message'] = $langs->trans(empty(isset($tmpfieldsofline[3]) ? trim($tmpfieldsofline[3]) : '') ? 'WarningModuleAffiliatedToAReportedCompany' : trim($tmpfieldsofline[3]));
2936 if (!empty($tmpfieldsofline[4])) {
2937 $message2 = $langs->trans("WarningModuleAffiliatedToAPiratPlatform", '{s}');
2938 $listofillegalurl = '';
2939 foreach (explode(" ", $tmpfieldsofline[4]) as $illegalurl) {
2940 $listofillegalurl .= ($listofillegalurl ? ' '.$langs->trans("or").' ' : '').'<b>'.preg_replace('/[^a-z0-9\.\-]/', '', $illegalurl).'</b>';
2941 }
2942 $message2 = str_replace('{s}', $listofillegalurl, $message2);
2943 $conf->cache['noncompliantmodules'][$modulekey]['message2'] = $message2;
2944 }
2945 }
2946 }
2947 }
2948
2949 if (!empty($conf->cache['noncompliantmodules'])) {
2950 $modulekey = strtolower($nametocheck);
2951 if (in_array($modulekey, array_keys($conf->cache['noncompliantmodules']))) {
2952 $answer = trim($conf->cache['noncompliantmodules'][$modulekey]['message']);
2953 if (!empty($conf->cache['noncompliantmodules'][$modulekey]['message2'])) {
2954 $answer .= '<br>'.$conf->cache['noncompliantmodules'][$modulekey]['message2'];
2955 }
2956 return $answer;
2957 }
2958 }
2959
2960 return 0;
2961 }
2962
2981 protected function declareNewDictionary($dictionaryArray, $langs = '')
2982 {
2983 $fields = array('name', 'lib', 'sql', 'sqlsort', 'field', 'fieldvalue', 'fieldinsert', 'rowid', 'cond', 'help', 'fieldcheck');
2984
2985 foreach ($fields as $field) {
2986 if (isset($dictionaryArray[$field])) {
2987 // @phan-suppress-next-line PhanTypeMismatchProperty
2988 $this->dictionaries['tab'.$field][] = $dictionaryArray[$field];
2989 }
2990 }
2991 if ($langs && !in_array($langs, $this->dictionaries[$langs])) {
2992 $this->dictionaries['langs'][] = $langs;
2993 }
2994 }
2995}
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,...
insert_overwrite_translation()
Adds overwrite translations.
_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.
if(! $sortfield) if(! $sortorder) $module
Definition list.php:193
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:
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_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
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)
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
$conf browser version
Definition main.inc.php:229
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:140