dolibarr 25.0.0-alpha
modules.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2003 Jean-Louis Bergamo <jlb@j1b.org>
4 * Copyright (C) 2004-2024 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
6 * Copyright (C) 2005-2017 Regis Houssin <regis.houssin@inodbox.com>
7 * Copyright (C) 2011-2023 Juanjo Menent <jmenent@2byte.es>
8 * Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
9 * Copyright (C) 2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
10 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
11 * Copyright (C) 2021-2026 Frédéric France <frederic.france@free.fr>
12 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
13 * Copyright (C) 2026 Charlene Benke <charlene@patas-monkey.com>
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 3 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program. If not, see <https://www.gnu.org/licenses/>.
27 */
28
34if (!defined('CSRFCHECK_WITH_TOKEN') && (empty($_GET['action']) || $_GET['action'] != 'reset')) { // We force security except to disable modules so we can do it if a problem occurs on a module
35 define('CSRFCHECK_WITH_TOKEN', '1'); // Force use of CSRF protection with tokens even for GET
36}
37
38// The modules list is a setup hub: force the menu context to "home" when the
39// caller did not provide one, so the previously visited module's menu does not
40// stick when the user comes back to this page (issue 38058).
41if (!isset($_GET['mainmenu']) && !isset($_POST['mainmenu'])) {
42 $_GET['mainmenu'] = 'home';
43}
44
45// Load Dolibarr environment
46require '../main.inc.php';
57'
58@phan-var-force string $dolibarr_main_url_root_alt
59';
60require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
61require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
62require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
63require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
64require_once DOL_DOCUMENT_ROOT.'/core/class/events.class.php';
65require_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
66require_once DOL_DOCUMENT_ROOT.'/admin/remotestore/class/externalModules.class.php';
67
68
69// Load translation files required by the page
70$langs->loadLangs(array("errors", "admin", "modulebuilder"));
71
72$action = GETPOST('action', 'aZ09');
73$page = GETPOSTINT('page');
74$page_y = GETPOSTINT('page_y');
75$optioncss = GETPOST('optioncss', 'aZ09');
76$sortfield = GETPOST('sortfield', 'aZ09');
77$sortorder = GETPOST('sortorder', 'aZ09');
78
79$mode = GETPOST('mode', 'alpha');
80$value = GETPOST('value', 'alpha');
81$search_keyword = GETPOST('search_keyword', 'alpha');
82$search_status = GETPOST('search_status', 'alpha');
83$search_nature = GETPOST('search_nature', 'alpha');
84$search_version = GETPOST('search_version', 'alpha');
85
86
87// For remotestore search
88$options = array();
89$options['per_page'] = 11;
90$options['no_page'] = (GETPOSTINT('no_page') ? GETPOSTINT('no_page') : 1);
91$options['categorie'] = (GETPOSTINT('categorie') ? GETPOSTINT('categorie') : 0);
92$options['search'] = GETPOST('search_keyword', 'alpha');
93
94// If it is a new search, we reset page to 1
95if (GETPOST('buttonsubmit', 'alphanohtml', 2)) {
96 $options['no_page'] = 1;
97}
98
99// MAIN_ENABLE_EXTERNALMODULES_DOLISTORE is 1 if we enabled the dolistore modules
100$options['search_source_dolistore'] = getDolGlobalInt('MAIN_ENABLE_EXTERNALMODULES_DOLISTORE');
101// MAIN_ENABLE_EXTERNALMODULES_COMMUNITY is 1 if we enabled the community modules
102$options['search_source_github'] = getDolGlobalInt('MAIN_ENABLE_EXTERNALMODULES_COMMUNITY');
103
104if (!$user->admin) {
106}
107
108$familyinfo = array(
109 'hr' => array('position' => '001', 'label' => $langs->trans("ModuleFamilyHr")),
110 'crm' => array('position' => '006', 'label' => $langs->trans("ModuleFamilyCrm")),
111 'srm' => array('position' => '007', 'label' => $langs->trans("ModuleFamilySrm")),
112 'financial' => array('position' => '009', 'label' => $langs->trans("ModuleFamilyFinancial")),
113 'products' => array('position' => '012', 'label' => $langs->trans("ModuleFamilyProducts")),
114 'projects' => array('position' => '015', 'label' => $langs->trans("ModuleFamilyProjects")),
115 'ecm' => array('position' => '018', 'label' => $langs->trans("ModuleFamilyECM")),
116 'technic' => array('position' => '021', 'label' => $langs->trans("ModuleFamilyTechnic")),
117 'portal' => array('position' => '040', 'label' => $langs->trans("ModuleFamilyPortal")),
118 'interface' => array('position' => '050', 'label' => $langs->trans("ModuleFamilyInterface")),
119 'base' => array('position' => '060', 'label' => $langs->trans("ModuleFamilyBase")),
120 'other' => array('position' => '100', 'label' => $langs->trans("ModuleFamilyOther")),
121);
122
123$param = '';
124if (!GETPOST('buttonreset', 'alpha')) {
125 if ($search_keyword) {
126 $param .= '&search_keyword='.urlencode($search_keyword);
127 }
128 if ($search_status && $search_status != '-1') {
129 $param .= '&search_status='.urlencode($search_status);
130 }
131 if ($search_nature && $search_nature != '-1') {
132 $param .= '&search_nature='.urlencode($search_nature);
133 }
134 if ($search_version && $search_version != '-1') {
135 $param .= '&search_version='.urlencode($search_version);
136 }
137}
138
139$dirins = DOL_DOCUMENT_ROOT.'/custom';
140$urldolibarrmodules = 'https://www.dolistore.com/';
141
142// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
143$hookmanager->initHooks(array('adminmodules', 'globaladmin'));
144
145// Increase limit of time. Works only if we are not in safe mode
146$max_execution_time_for_deploy = getDolGlobalInt('MODULE_UPLOAD_MAX_EXECUTION_TIME', 300); // 5mn if not defined
147if (!empty($max_execution_time_for_deploy)) {
148 $err = error_reporting();
149 error_reporting(0); // Disable all errors
150 //error_reporting(E_ALL);
151 @set_time_limit($max_execution_time_for_deploy);
152 error_reporting($err);
153}
154// Other method - TODO is this required ?
155$max_time = @ini_get("max_execution_time");
156if ($max_time && $max_time < $max_execution_time_for_deploy) {
157 dol_syslog("max_execution_time=".$max_time." is lower than max_execution_time_for_deploy=".$max_execution_time_for_deploy.". We try to increase it dynamically.");
158 @ini_set("max_execution_time", $max_execution_time_for_deploy); // This work only if safe mode is off. also web servers has timeout of 300
159}
160
161
162$dolibarrdataroot = preg_replace('/([\\/]+)$/i', '', DOL_DATA_ROOT);
163$allowonlineinstall = true;
164$allowfromweb = 1;
165if (dol_is_file($dolibarrdataroot.'/installmodules.lock')) {
166 $allowonlineinstall = false;
167}
168
169$debug = false;
170$remotestore = new ExternalModules($debug);
171
172if ($mode == 'marketplace') {
173 // Make remote calls
174 if (GETPOSTINT('dol_resetcache')) {
175 dol_delete_file($remotestore->cache_file);
176 }
177 $remotestore->loadRemoteSources(false);
178}
179
180$object = new stdClass();
181
182$now = dol_now();
183
184
185/*
186 * Actions
187 */
188
189$formconfirm = '';
190
191$parameters = array();
192$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
193if ($reshook < 0) {
194 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
195}
196
197// if we set another view list mode, we keep it (till we change one more time)
198if (GETPOSTISSET('mode')) {
199 $mode = GETPOST('mode', 'alpha');
200 if ($mode == 'common' && !getDolGlobalString('MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT')) {
201 dolibarr_set_const($db, "MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT", $mode, 'chaine', 0, '', $conf->entity);
202 }
203} else {
204 $mode = getDolGlobalString('MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT', 'commonkanban');
205}
206
207if (GETPOST('buttonreset', 'alpha')) {
208 $search_keyword = '';
209 $search_status = '';
210 $search_nature = '';
211 $search_version = '';
212}
213
214if ($action == 'install' && $allowonlineinstall) {
215 $error = 0;
216 $modulenameval = '';
217
218 $isExternalDownload = 0;
219 $producttoinstall = GETPOST('producttoinstall', 'array');
220
221 // $original_file should match format module_modulename-x.y[.z].zip
222 if ($producttoinstall) {
223 $isExternalDownload = 1;
224 $tmpExternalModuleZipFile = $remotestore->getModuleZIP($producttoinstall); // Return the zip file path.
225 if ($tmpExternalModuleZipFile) {
226 // We fill $_FILES with the zip file we just created to reuse the same code as if file was uploaded by user
227 $_FILES['fileinstall'] = array(
228 'name' => basename($tmpExternalModuleZipFile),
229 'type' => 'application/zip',
230 'tmp_name' => $tmpExternalModuleZipFile,
231 'error' => 0,
232 'size' => filesize($tmpExternalModuleZipFile)
233 );
234 }
235 }
236
237 $tmpfile = (string) $_FILES['fileinstall']['tmp_name'];
238 $original_file = basename($_FILES["fileinstall"]["name"]);
239 $original_file = preg_replace('/\s*\‍(\d+\‍)\.zip$/i', '.zip', $original_file);
240 $newfile = dol_sanitizePathName($conf->admin->dir_temp.'/'.$original_file.'/'.$original_file);
241
242 if (empty($tmpfile)) {
243 $langs->load("errors");
244 setEventMessages($langs->trans("ErrorFileNotUploaded"), null, 'errors');
245 $error++;
246 }
247
248 if (!$original_file) {
249 $langs->load("Error");
250 if ($isExternalDownload) {
251 setEventMessages($langs->trans("ErrorFailToDownloadModuleFromSource", $producttoinstall['name']), null, 'warnings');
252 } else {
253 setEventMessages($langs->trans("ErrorModuleFileRequired"), null, 'warnings');
254 }
255 $error++;
256 } else {
257 if (!$error && !preg_match('/\.zip$/i', $original_file)) {
258 $langs->load("errors");
259 setEventMessages($langs->trans("ErrorFileMustBeADolibarrPackage", $original_file), null, 'errors');
260 $error++;
261 }
262 if (!$error && !preg_match('/^(module[a-zA-Z0-9]*_|theme_|).*\-([0-9][0-9\.]*)(\s\‍(\d+\‍)\s)?\.zip$/i', $original_file)) {
263 $langs->load("errors");
264 setEventMessages($langs->trans("ErrorFilenameDosNotMatchDolibarrPackageRules", $original_file, 'modulename-x[.y.z].zip'), null, 'errors');
265 $error++;
266 }
267 }
268
269 if (!$error) {
270 if ($original_file) {
271 @dol_delete_dir_recursive($conf->admin->dir_temp.'/'.$original_file);
272 dol_mkdir($conf->admin->dir_temp.'/'.$original_file);
273 }
274
275 $tmpdir = preg_replace('/\.zip$/i', '', $original_file).'.dir';
276 if ($tmpdir) {
277 @dol_delete_dir_recursive($conf->admin->dir_temp.'/'.$tmpdir);
278 dol_mkdir($conf->admin->dir_temp.'/'.$tmpdir);
279 }
280
281 $result = dol_move_uploaded_file($tmpfile, $newfile, 1, 0, $_FILES['fileinstall']['error'], 0, 'addedfile', '', $isExternalDownload ? 1 : 0);
282 if ((int) $result > 0) {
283 $resultuncompress = dol_uncompress($newfile, $conf->admin->dir_temp.'/'.$tmpdir);
284
285 if (!empty($resultuncompress['error'])) {
286 $langs->load("errors");
287 setEventMessages($langs->trans($resultuncompress['error'], $original_file), null, 'errors');
288 $error++;
289 } else {
290 // Now we move the dir of the module
291 $modulename = preg_replace('/module_/', '', $original_file);
292 $modulename = preg_replace('/\-([0-9][0-9\.]*)\.zip$/i', '', $modulename);
293 // Search dir $modulename
294 $modulenamedir = $conf->admin->dir_temp.'/'.$tmpdir.'/'.$modulename; // Example ./mymodule
295
296 if (!dol_is_dir($modulenamedir)) {
297 $modulenamedir = $conf->admin->dir_temp.'/'.$tmpdir.'/htdocs/'.$modulename; // Example ./htdocs/mymodule
298 //var_dump($modulenamedir);
299 if (!dol_is_dir($modulenamedir)) {
300 setEventMessages($langs->trans("ErrorModuleFileSeemsToHaveAWrongFormat").'<br>'.$langs->trans("ErrorModuleFileSeemsToHaveAWrongFormat2", $modulename, 'htdocs/'.$modulename), null, 'errors');
301 $error++;
302 }
303 }
304
305 dol_syslog("Uncompress of module file is a success.");
306
307 // Load module into $objMod
308 /*
309 $modulesdir = array($modulenamedir.'/core/modules/');
310 foreach ($modulesdir as $dir) {
311 // Load modules attributes in arrays (name, numero, orders) from dir directory
312 //print $dir."\n<br>";
313 dol_syslog("Scan directory ".$dir." for module descriptor files (modXXX.class.php)");
314 $handle = @opendir($dir);
315 if (is_resource($handle)) {
316 while (($file = readdir($handle)) !== false) {
317 print $dir." ".$file."\n<br>";
318 if (is_readable($dir.$file) && dol_substr($file, 0, 3) == 'mod' && dol_substr($file, dol_strlen($file) - 10) == '.class.php') {
319 $modName = dol_substr($file, 0, dol_strlen($file) - 10);
320 if ($modName) {
321 try {
322 $res = include_once $dir.$file; // A class already exists in a different file will send a non catchable fatal error.
323 $modName = dol_substr($file, 0, dol_strlen($file) - 10);
324 if ($modName) {
325 if (class_exists($modName)) {
326 $objMod = new $modName($db);
327 '@phan-var-force DolibarrModules $objMod';
328
329 //var_dump($objMod);
330 }
331 }
332 } catch(Exception $e) {
333 // Nothing done
334 }
335 }
336 }
337 }
338 }
339 }
340 */
341
342 // Check if module is in the remote malware blacklist (at URL DolibarrModules::URL_FOR_BLACKLISTED_MODULES)
343 if (!$error) {
344 if (GETPOST('checkforcompliance') == 'on') {
345 try {
346 $res = include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
347 $dolibarrmodule = new DolibarrModules($db);
348 $checkRes = $dolibarrmodule->checkForcompliance($modulename);
349
350 if (!is_numeric($checkRes) && $checkRes != '') {
351 $langs->load("errors");
352 setEventMessages($modulename.' : '.$langs->trans($checkRes), null, 'errors');
353 $error++;
354 }
355 } catch (Exception $e) {
356 // Nothing done
357 }
358 }
359 }
360
361 if (!$error) {
362 // TODO Make more test ???
363 // Call validateZipFile() in functions2.lib.php ?
364 }
365
366 // We check if this is a metapackage (and wecomplete with child packages)
367 $modulenamearrays = array();
368 if (dol_is_file($modulenamedir.'/metapackage.conf')) {
369 // This is a meta package
370 $metafile = file_get_contents($modulenamedir.'/metapackage.conf');
371 $modulenamearrays = explode("\n", $metafile);
372 }
373 $modulenamearrays[$modulename] = $modulename;
374 //var_dump($modulenamearrays);exit;
375
376 // Lop on each packages (can have several if package is a metapackage)
377 if (! $error) {
378 foreach ($modulenamearrays as $modulenameval) {
379 if (strpos($modulenameval, '#') === 0) {
380 continue; // Discard comments
381 }
382 if (strpos($modulenameval, '//') === 0) {
383 continue; // Discard comments
384 }
385 if (!trim($modulenameval)) {
386 continue;
387 }
388
389 // Now we install the module
390 if (!$error) {
391 @dol_delete_dir_recursive($dirins.'/'.$modulenameval); // delete the target directory
392 $submodulenamedir = $conf->admin->dir_temp.'/'.$tmpdir.'/'.$modulenameval;
393 if (!dol_is_dir($submodulenamedir)) {
394 $submodulenamedir = $conf->admin->dir_temp.'/'.$tmpdir.'/htdocs/'.$modulenameval;
395 }
396 dol_syslog("We copy now directory ".$submodulenamedir." into target dir ".$dirins.'/'.$modulenameval);
397 $resultcopy = dolCopyDir($submodulenamedir, $dirins.'/'.$modulenameval, '0444', 1);
398 if ($resultcopy <= 0) {
399 dol_syslog('Failed to call dolCopyDir result='.$resultcopy." with param ".$submodulenamedir." and ".$dirins.'/'.$modulenameval, LOG_WARNING);
400 $langs->load("errors");
401 setEventMessages($langs->trans("ErrorFailToCopyDir", $submodulenamedir, $dirins.'/'.$modulenameval), null, 'errors');
402 $error++;
403 }
404 }
405 }
406 }
407 }
408 } else {
409 setEventMessages($langs->trans("ErrorFailToRenameFile", $tmpfile, $newfile).' - code = '.$result, null, 'errors');
410 $error++;
411 }
412 }
413
414 // Add event purge
415 $securityevent = new Events($db);
416 if ($error) {
417 $text = $langs->trans("SecurityModuleDeploymentError", dol_sanitizePathName($_FILES["fileinstall"]["name"]));
418 $securityevent->type = 'MODULE_DEPLOYMENT_ERROR';
419 } else {
420 $text = $langs->trans("SecurityModuleDeploymentSuccess", dol_sanitizePathName($_FILES["fileinstall"]["name"]));
421 $securityevent->type = 'MODULE_DEPLOYMENT_SUCCESS';
422 }
423 $securityevent->dateevent = $now;
424 $securityevent->description = $text;
425
426 $resultcreateevent = $securityevent->create($user);
427
428 if (!$error) {
429 $searchParams = array(
430 'search_keyword' => $modulenameval,
431 'search_status' => '-1',
432 'search_nature' => '-1',
433 'search_version' => '-1'
434 );
435 $redirectUrl = dolBuildUrl(DOL_URL_ROOT . '/admin/modules.php', $searchParams);
436
437 $message = $langs->trans("SetupIsReadyForUse", $redirectUrl, $langs->transnoentitiesnoconv("Home").' - '.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Modules"));
438
439 setEventMessages($message, null, 'warnings');
440 }
441} elseif ($action == 'install' && !$allowonlineinstall) {
442 httponly_accessforbidden("You try to bypass the protection to disallow deployment of an external module. Hack attempt ?");
443}
444
445if ($action == 'set' && $user->admin) {
446 // We made some check against evil eternal modules that try to low security options.
447 $checkOldValue = getDolGlobalInt('CHECKLASTVERSION_EXTERNALMODULE');
448 $csrfCheckOldValue = getDolGlobalInt('MAIN_SECURITY_CSRF_WITH_TOKEN');
449
450 $resarray = activateModule($value, 1, 0, 'acceptredirect');
451
452 if ($checkOldValue != getDolGlobalInt('CHECKLASTVERSION_EXTERNALMODULE')) {
453 setEventMessage($langs->trans('WarningModuleHasChangedLastVersionCheckParameter', $value), 'warnings');
454 }
455 if ($csrfCheckOldValue != getDolGlobalInt('MAIN_SECURITY_CSRF_WITH_TOKEN')) {
456 setEventMessage($langs->trans('WarningModuleHasChangedSecurityCsrfParameter', $value), 'warnings');
457 }
458
459 dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1, 'chaine', 0, '', $conf->entity);
460 if (!empty($resarray['errors'])) {
461 setEventMessages('', $resarray['errors'], 'errors');
462 } else {
463 //var_dump($resarray);exit;
464 if ($resarray['nbperms'] > 0) {
465 $tmpsql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."user WHERE admin <> 1";
466 $resqltmp = $db->query($tmpsql);
467 if ($resqltmp) {
468 $obj = $db->fetch_object($resqltmp);
469 //var_dump($obj->nb);exit;
470 if ($obj && $obj->nb > 1) {
471 $msg = $langs->trans('ModuleEnabledAdminMustCheckRights');
472 setEventMessages($msg, null, 'warnings');
473 }
474 } else {
475 dol_print_error($db);
476 }
477 }
478 }
479 header("Location: ".$_SERVER["PHP_SELF"]."?mode=".$mode.$param.($page_y ? '&page_y='.$page_y : ''));
480 exit;
481} elseif ($action == 'reset' && $user->admin && GETPOST('confirm') == 'yes') {
482 $result = unActivateModule($value);
483 dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1, 'chaine', 0, '', $conf->entity);
484 if ($result) {
485 setEventMessages($result, null, 'errors');
486 }
487 header("Location: ".$_SERVER["PHP_SELF"]."?mode=".$mode.$param.($page_y ? '&page_y='.$page_y : ''));
488 exit;
489} elseif (getDolGlobalInt("MAIN_FEATURES_LEVEL") > 1 && $action == 'reload' && $user->admin && GETPOST('confirm') == 'yes') {
490 $result = unActivateModule($value, 0, 'newboxdefonly'); // unactivate all module features but for widget, we reload only definition and we do not change position or setup
491 dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1, 'chaine', 0, '', $conf->entity);
492 if ($result) {
493 setEventMessages($result, null, 'errors');
494 header("Location: ".$_SERVER["PHP_SELF"]."?mode=".$mode.$param.($page_y ? '&page_y='.$page_y : ''));
495 exit;
496 }
497
498 $resarray = activateModule($value, 0, 1, 'acceptredirect');
499
500 dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", (getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1), 'chaine', 0, '', $conf->entity);
501 if (!empty($resarray['errors'])) {
502 setEventMessages('', $resarray['errors'], 'errors');
503 } else {
504 if ($resarray['nbperms'] > 0) {
505 $tmpsql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."user WHERE admin <> 1";
506 $resqltmp = $db->query($tmpsql);
507 if ($resqltmp) {
508 $obj = $db->fetch_object($resqltmp);
509 if ($obj && $obj->nb > 1) {
510 $msg = $langs->trans('ModuleEnabledAdminMustCheckRights');
511 setEventMessages($msg, null, 'warnings');
512 }
513 } else {
514 dol_print_error($db);
515 }
516 }
517 }
518 header("Location: ".$_SERVER["PHP_SELF"]."?mode=".$mode.$param.($page_y ? '&page_y='.$page_y : ''));
519 exit;
520}
521
522
523/*
524 * View
525 */
526
527$form = new Form($db);
528
529$morejs = array();
530$morecss = array("/admin/remotestore/css/store.css");
531
532// Set dir where external modules are installed
533if (!dol_is_dir($dirins)) {
534 dol_mkdir($dirins);
535}
536$dirins_ok = (dol_is_dir($dirins));
537
538$help_url = 'EN:First_setup|FR:Premiers_paramétrages|ES:Primeras_configuraciones';
539llxHeader('', $langs->trans("Setup"), $help_url, '', 0, 0, $morejs, $morecss, '', 'mod-admin page-modules');
540
541
542// Search modules dirs
543$modulesdir = dolGetModulesDirs();
544
545$arrayofnatures = array(
546 'core' => array('label' => $langs->transnoentitiesnoconv("NativeModules")),
547 'external' => array('label' => $langs->transnoentitiesnoconv("External").' - ['.$langs->trans("AllPublishers").']')
548);
549$arrayofwarnings = array(); // Array of warning each module want to show when activated
550$arrayofwarningsext = array(); // Array of warning each module want to show when we activate an external module
551$filename = array();
552$modules = array();
553$orders = array();
554$categ = array();
555$timestoinit = [];
556//$publisherlogoarray = array();
557
558$i = 0; // is a sequencer of modules found
559$j = 0; // j is module number. Automatically affected if module number not defined.
560$modNameLoaded = array();
561
562// Load $modules (required for the badge count)
563foreach ($modulesdir as $dir) {
564 // Load modules attributes in arrays (name, numero, orders) from dir directory
565 //print $dir."\n<br>";
566 dol_syslog("Scan directory ".$dir." for module descriptor files (modXXX.class.php)");
567 $handle = @opendir($dir);
568 $timestart = microtime(true);
569 if (is_resource($handle)) {
570 while (($file = readdir($handle)) !== false) {
571 //print "$i ".$file."\n<br>";
572 if (is_readable($dir.$file) && dol_substr($file, 0, 3) == 'mod' && dol_substr($file, dol_strlen($file) - 10) == '.class.php') {
573 $modName = dol_substr($file, 0, dol_strlen($file) - 10);
574
575 if ($modName) {
576 if (!empty($modNameLoaded[$modName])) { // In cache of already loaded modules ?
577 $mesg = "Error: Module ".$modName." was found twice: Into ".$modNameLoaded[$modName]." and ".$dir.". You probably have an old file on your disk.<br>";
578 setEventMessages($mesg, null, 'warnings');
579 dol_syslog($mesg, LOG_ERR);
580 continue;
581 }
582
583 try {
584 $res = include_once $dir.$file; // A class already exists in a different file will send a non catchable fatal error.
585 if (class_exists($modName)) {
586 $objMod = new $modName($db);
587 '@phan-var-force DolibarrModules $objMod';
589 $modNameLoaded[$modName] = $dir;
590 if (!$objMod->numero > 0 && $modName != 'modUser') {
591 dol_syslog('The module descriptor '.$modName.' must have a numero property', LOG_ERR);
592 }
593 $j = $objMod->numero;
594
595 $modulequalified = 1;
596
597 // We discard modules according to features level (PS: if module is activated we always show it)
598 $const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($objMod)));
599 if ($objMod->version == 'development' && (!getDolGlobalString($const_name) && (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2))) {
600 $modulequalified = 0;
601 }
602 if ($objMod->version == 'experimental' && (!getDolGlobalString($const_name) && (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1))) {
603 $modulequalified = 0;
604 }
605 if (preg_match('/deprecated/', $objMod->version) && (!getDolGlobalString($const_name) && (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 0))) {
606 $modulequalified = 0;
607 }
608
609 // We discard modules according to property ->hidden
610 if (!empty($objMod->hidden)) {
611 $modulequalified = 0;
612 }
613
614 if ($modulequalified > 0) {
615 $publisher = dol_escape_htmltag($objMod->getPublisher());
616 $external = ($objMod->isCoreOrExternalModule() == 'external');
617 if ($external) {
618 if ($publisher) {
619 // Check if there is a logo forpublisher
620 /* Do not show the company logo in combo. Make combo list dirty.
621 if (!empty($objMod->editor_squarred_logo)) {
622 $publisherlogoarray['external_'.$publisher] = img_picto('', $objMod->editor_squarred_logo, 'class="publisherlogoinline"');
623 }
624 $publisherlogo = empty($publisherlogoarray['external_'.$publisher]) ? '' : $publisherlogoarray['external_'.$publisher];
625 */
626 $arrayofnatures['external_'.$publisher] = array('label' => $langs->trans("External").' - '.$publisher, 'data-html' => $langs->trans("External").' - <span class="opacitymedium inine-block valignmiddle">'.$publisher.'</span>');
627 } else {
628 $arrayofnatures['external_'] = array('label' => $langs->trans("External").' - ['.$langs->trans("UnknownPublishers").']');
629 }
630 }
631 ksort($arrayofnatures);
632
633 // Define an array $categ with categ with at least one qualified module
634 $filename[$i] = $modName;
635 $modules[$modName] = $objMod;
636 $timestoinit[$modName] = round((microtime(true) - $timestart) * 1000, 3);
637
638 // Gives the possibility to the module, to provide his own family info and position of this family
639 if (is_array($objMod->familyinfo) && !empty($objMod->familyinfo)) {
640 $familyinfo = array_merge($familyinfo, $objMod->familyinfo);
641 $familykey = key($objMod->familyinfo);
642 } else {
643 $familykey = $objMod->family;
644 }
645 '@phan-var-force string $familykey'; // if not, phan considers $familykey may be null
646
647 $moduleposition = ($objMod->module_position ? $objMod->module_position : '50');
648 if ($objMod->isCoreOrExternalModule() == 'external' && $moduleposition < 100000) {
649 // an external module should never return a value lower than '80'.
650 $moduleposition = '80'; // External modules at end by default
651 }
652
653 // Add list of warnings to show into arrayofwarnings and arrayofwarningsext
654 if (!empty($objMod->warnings_activation)) {
655 $arrayofwarnings[$modName] = $objMod->warnings_activation;
656 }
657 if (!empty($objMod->warnings_activation_ext)) {
658 $arrayofwarningsext[$modName] = $objMod->warnings_activation_ext;
659 }
660
661 $familyposition = (empty($familyinfo[$familykey]['position']) ? '0' : $familyinfo[$familykey]['position']);
662 if ($external && !in_array($familykey, array_keys($familyinfo))) {
663 // If module is extern and into a custom group (not into an official predefined one), it must appear at end (custom groups should not be before official groups).
664 if (is_numeric($familyposition)) {
665 $familyposition = sprintf("%03d", (int) $familyposition + 100);
666 }
667 }
668
669 $orders[$i] = $familyposition."_".$familykey."_".$moduleposition."_".$j; // Sort by family, then by module position then number
670
671 // Set categ[$i]
672 $specialstring = 'unknown';
673 if ($objMod->version == 'development' || $objMod->version == 'experimental') {
674 $specialstring = 'expdev';
675 }
676 if (isset($categ[$specialstring])) {
677 $categ[$specialstring]++; // Array of all different modules categories
678 } else {
679 $categ[$specialstring] = 1;
680 }
681 $j++;
682 $i++;
683 } else {
684 dol_syslog("Module ".get_class($objMod)." not qualified");
685 }
686 } else {
687 // Skip warning for modules being refactored (class split in progress)
688 $silentModules = array('modSupplierOrder', 'modSupplierInvoice', 'modFournisseur');
689 if (!in_array($modName, $silentModules)) {
690 print info_admin("admin/modules.php Warning bad descriptor file : ".$dir.$file." (Class ".$modName." not found into file)", 0, 0, '1', 'warning');
691 }
692 }
693 } catch (Exception $e) {
694 dol_syslog("Failed to load ".$dir.$file." ".$e->getMessage(), LOG_ERR);
695 }
696 }
697 }
698 }
699 closedir($handle);
700 } else {
701 dol_syslog("htdocs/admin/modules.php: Failed to open directory ".$dir.". See permission and open_basedir option.", LOG_WARNING);
702 }
703}
704
705'@phan-var-force array<string,DolibarrModules> $modules';
708if ($action == 'reset_confirm' && $user->admin) {
709 if (!empty($modules[$value])) {
710 $objMod = $modules[$value];
711
712 if (!empty($objMod->langfiles)) {
713 $langs->loadLangs($objMod->langfiles);
714 }
715
716 $form = new Form($db);
717 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?value='.$value.'&mode='.$mode.$param, $langs->trans('ConfirmUnactivation'), $langs->trans(GETPOST('confirm_message_code')), 'reset', '', 'no', 1, 300, 550);
718 }
719}
720
721if ($action == 'reload_confirm' && $user->admin) {
722 if (!empty($modules[$value])) {
723 $objMod = $modules[$value];
724
725 if (!empty($objMod->langfiles)) {
726 $langs->loadLangs($objMod->langfiles);
727 }
728
729 $form = new Form($db);
730 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?value='.$value.'&mode='.$mode.$param, $langs->trans('ConfirmReload'), $langs->trans(GETPOST('confirm_message_code')), 'reload', '', 'no', 1);
731 }
732}
733
734print $formconfirm;
735
736asort($orders);
737//var_dump($orders);
738//var_dump($categ);
739//var_dump($modules);
740
741$nbofactivatedmodules = count($conf->modules);
742
743// Define $nbmodulesnotautoenabled - TODO This code is at different places
744$nbmodulesnotautoenabled = count($conf->modules);
745$listofmodulesautoenabled = array('user', 'agenda', 'fckeditor', 'export', 'import');
746foreach ($listofmodulesautoenabled as $moduleautoenable) {
747 if (in_array($moduleautoenable, $conf->modules)) {
748 $nbmodulesnotautoenabled--;
749 }
750}
751
752print load_fiche_titre($langs->trans("ModulesSetup"), '', 'title_setup');
753
754// Start to show page
755$deschelp = '';
756if ($mode == 'common' || $mode == 'commonkanban') {
757 $desc = $langs->trans("ModulesDesc", '{picto}');
758 $desc .= ' '.$langs->trans("ModulesDesc2", '{picto2}');
759 $desc = str_replace('{picto}', img_picto('', 'switch_off', 'class="size15x"'), $desc);
760 $desc = str_replace('{picto2}', img_picto('', 'setup', 'class="size15x"'), $desc);
761 if (getDolGlobalInt('MAIN_SETUP_MODULES_DESC') || $nbmodulesnotautoenabled < getDolGlobalInt('MAIN_MIN_NB_ENABLED_MODULE_FOR_WARNING', 1)) { // If only minimal initial modules enabled
762 $deschelp .= '<div class="info hideonsmartphone">'.$desc."<br></div>\n";
763 }
764 if (getDolGlobalString('MAIN_SETUP_MODULES_INFO')) { // Add a custom info message. A good usage for SaaS in combination with option MAIN_MIN_NB_ENABLED_MODULE_FOR_WARNING.
765 $deschelp .= '<div class="info">'.$langs->trans(getDolGlobalString('MAIN_SETUP_MODULES_INFO'))."<br></div>\n";
766 }
767 if ($deschelp) {
768 $deschelp .= '<br>';
769 }
770}
771if ($mode == 'deploy') {
772 $deschelp = '<div class="info hideonsmartphone">'.$langs->trans("ModulesDeployDesc", $langs->transnoentitiesnoconv("AvailableModules"))."<br></div><br>\n";
773}
774if ($mode == 'develop') {
775 $deschelp = '<div class="info hideonsmartphone">'.$langs->trans("ModulesDevelopDesc")."<br></div><br>\n";
776}
777
778$head = modules_prepare_head($nbofactivatedmodules, count($modules), $nbmodulesnotautoenabled);
779
780
781if ($mode == 'common' || $mode == 'commonkanban') {
782 dol_set_focus('#search_keyword');
783
784 print '<form method="POST" id="searchFormList" action="'.dolBuildUrl($_SERVER["PHP_SELF"]).'" spellcheck="false">';
785 print '<input type="hidden" name="token" value="'.newToken().'">';
786 if (isset($optioncss) && $optioncss != '') {
787 print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
788 }
789 if (isset($sortfield) && $sortfield != '') {
790 print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
791 }
792 if (isset($sortorder) && $sortorder != '') {
793 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
794 }
795 if (isset($page) && $page != '') {
796 print '<input type="hidden" name="page" value="'.$page.'">';
797 }
798 print '<input type="hidden" name="mode" value="'.$mode.'">';
799
800 print dol_get_fiche_head($head, 'modules', '', -1);
801
802 print $deschelp;
803
804 $moreforfilter = '<div class="valignmiddle">';
805
806 $moreforfilter .= '<div class="floatright right pagination paddingtop --module-list"><ul><li>';
807 $moreforfilter .= dolGetButtonTitle(
808 $langs->trans('CheckForModuleUpdate'),
809 $langs->trans('CheckForModuleUpdate').'<br><br>'.img_warning('', '', 'paddingright').$langs->trans('CheckForModuleUpdateHelp').' '.$langs->trans('CheckForModuleUpdateHelp2', DolibarrModules::URL_FOR_BLACKLISTED_MODULES).'<br>'.$langs->trans("YourIPWillBeRevealedToThisExternalProviders"),
810 'fa fa-sync',
811 $_SERVER["PHP_SELF"].'?action=checklastversion&token='.newToken().'&mode='.$mode.$param,
812 '',
813 1,
814 array('morecss' => 'reposition')
815 );
816 $moreforfilter .= dolGetButtonTitleSeparator();
817 $moreforfilter .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.$param, '', ($mode == 'common' ? 2 : 1), array('morecss' => 'reposition'));
818 $moreforfilter .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=commonkanban'.$param, '', ($mode == 'commonkanban' ? 2 : 1), array('morecss' => 'reposition'));
819 $moreforfilter .= '</li></ul></div>';
820
821 $moreforfilter .= '<div class="divfilteralone colorbacktimesheet float valignmiddle nopaddingtopimp nopaddingbottomimp">';
822 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
823 $moreforfilter .= img_picto($langs->trans("Filter"), 'filter', 'class="paddingright opacityhigh hideonsmartphone"').'<input type="text" id="search_keyword" name="search_keyword" class="maxwidth125" value="'.dol_escape_htmltag($search_keyword).'" spellcheck="false" placeholder="'.dol_escape_htmltag($langs->trans('Keyword')).'">';
824 $moreforfilter .= '</div>';
825 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
826 $moreforfilter .= $form->selectarray('search_nature', $arrayofnatures, dol_escape_htmltag($search_nature), $langs->trans('Origin'), 0, 0, '', 0, 0, 0, '', 'maxwidth250', 1);
827 $moreforfilter .= '</div>';
828
829 if (getDolGlobalInt('MAIN_FEATURES_LEVEL')) {
830 $array_version = array('stable' => $langs->transnoentitiesnoconv("Stable"));
831 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 0) {
832 $array_version['deprecated'] = $langs->trans("Deprecated");
833 }
834 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
835 $array_version['experimental'] = $langs->trans("Experimental");
836 }
837 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 1) {
838 $array_version['development'] = $langs->trans("Development");
839 }
840 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
841 $moreforfilter .= $form->selectarray('search_version', $array_version, $search_version, $langs->transnoentitiesnoconv('Version'), 0, 0, '', 0, 0, 0, '', 'maxwidth150', 1);
842 $moreforfilter .= '</div>';
843 }
844 $array_status = array('active' => $langs->transnoentitiesnoconv("Enabled"), 'disabled' => $langs->transnoentitiesnoconv("Disabled"));
845 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
846 $moreforfilter .= $form->selectarray('search_status', $array_status, $search_status, $langs->transnoentitiesnoconv('Status'), 0, 0, '', 0, 0, 0, '', 'maxwidth150', 1);
847 $moreforfilter .= '</div>';
848 $moreforfilter .= ' ';
849 $moreforfilter .= '<div class="divsearchfield valignmiddle inline-block">';
850 $moreforfilter .= '<input type="submit" name="buttonsubmit" class="button small nomarginleft" value="'.dolPrintHTMLForAttribute($langs->trans("Refresh")).'">';
851 if ($search_keyword || ($search_nature && $search_nature != '-1') || ($search_version && $search_version != '-1') || ($search_status && $search_status != '-1')) {
852 $moreforfilter .= ' ';
853 $moreforfilter .= '<input type="submit" name="buttonreset" class="buttonreset noborderall nomargintop nomarginbottom" value="'.dolPrintHTMLForAttribute($langs->trans("Reset")).'">';
854 }
855 $moreforfilter .= '</div>';
856 $moreforfilter .= '</div>';
857
858 $moreforfilter .= '</div>';
859
860 print $moreforfilter;
861 $parameters = array();
862 $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
863 print $hookmanager->resPrint;
864
865 $moreforfilter = '';
866
867 print '<div class="clearboth"></div><br><br>';
868
869 $object = new stdClass();
870 $parameters = array();
871 $reshook = $hookmanager->executeHooks('insertExtraHeader', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
872 if ($reshook < 0) {
873 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
874 }
875
876 $disabled_modules = array();
877 if (!empty($_SESSION["disablemodules"])) {
878 $disabled_modules = explode(',', $_SESSION["disablemodules"]);
879 }
880
881 // Show list of modules
882 $oldfamily = '';
883 $foundoneexternalmodulewithupdate = 0;
884 $linenum = 0;
885 $atleastonequalified = 0;
886 $atleastoneforfamily = 0;
887
888 print '<script type="text/javascript">
889 jQuery(document).ready(function() {
890 jQuery(".modulefamilygroup").each(function() {
891 var $group = jQuery(this);
892 var $title = $group.find(".titre.inline-block").first();
893 var $nextContainer = $group.nextAll(".div-table-responsive, .box-flex-container").first();
894 if ($title.length && !$title.children(".modulefamilytoggleicon").length) {
895 $title.prepend("<i class=\"fa modulefamilytoggleicon paddingleft paddingleftright\"></i> ");
896 }
897 var $icon = $title.children(".modulefamilytoggleicon").first();
898 var isVisible = $nextContainer.is(":visible");
899 if ($icon.length && $nextContainer.length) {
900 $icon.toggleClass("fa-folder-open", isVisible);
901 $icon.toggleClass("fa-folder", !isVisible);
902 }
903 });
904
905 jQuery(document).on("click", ".modulefamilygroup", function() {
906 var $group = jQuery(this);
907 var $nextContainer = $group.nextAll(".div-table-responsive, .box-flex-container").first();
908 if ($nextContainer.length) {
909 var $icon = $group.find(".modulefamilytoggleicon").first();
910 var isVisible = $nextContainer.is(":visible");
911 $nextContainer.stop(true, true).slideToggle(150);
912 if ($icon.length) {
913 $icon.toggleClass("fa-folder-open", !isVisible);
914 $icon.toggleClass("fa-folder", isVisible);
915 }
916 }
917 });
918 });
919 </script>';
920
921 foreach ($orders as $key => $value) {
922 $linenum++;
923 $tab = explode('_', $value);
924 $familykey = $tab[1];
925 $module_position = $tab[2];
926
927 $modName = $filename[$key];
928
930 $objMod = $modules[$modName];
931
932 if (!is_object($objMod)) {
933 continue;
934 }
935
936 //print $objMod->name." - ".$key." - ".$objMod->version."<br>";
937 if ($mode == 'expdev' && $objMod->version != 'development' && $objMod->version != 'experimental') {
938 continue; // Discard if not for current tab
939 }
940
941 if (!$objMod->getName()) {
942 dol_syslog("Error for module ".$key." - Property name of module looks empty", LOG_WARNING);
943 continue;
944 }
945
946 $modulenameshort = strtolower(preg_replace('/^mod/i', '', get_class($objMod)));
947 $const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($objMod)));
948
949 // Check filters
950 $modulename = $objMod->getName();
951 $moduletechnicalname = $objMod->name;
952 $moduledesc = $objMod->getDesc();
953 $moduledesclong = $objMod->getDescLong();
954 $moduleauthor = $objMod->getPublisher();
955
956 // We discard showing according to filters
957 if ($search_keyword) {
958 $qualified = 0;
959 $search_keyword_array = explode(' ', $search_keyword);
960 $foundkeyword = 1;
961 foreach ($search_keyword_array as $word) {
962 if (!preg_match('/'.preg_quote($word, '/').'/i', $modulename)
963 && !preg_match('/'.preg_quote($word, '/').'/i', $moduletechnicalname)
964 && !($moduledesc && preg_match('/'.preg_quote($word, '/').'/i', $moduledesc))
965 && !($moduledesclong && preg_match('/'.preg_quote($word, '/').'/i', $moduledesclong))
966 && !($moduleauthor && preg_match('/'.preg_quote($word, '/').'/i', $moduleauthor))
967 ) {
968 $foundkeyword = 0;
969 }
970 }
971 if ($foundkeyword) {
972 $qualified = 1;
973 }
974 if (!$qualified) {
975 continue;
976 }
977 }
978 if ($search_status) {
979 if ($search_status == 'active' && !getDolGlobalString($const_name)) {
980 continue;
981 }
982 if ($search_status == 'disabled' && getDolGlobalString($const_name)) {
983 continue;
984 }
985 }
986 if ($search_nature) {
987 if (preg_match('/^external/', $search_nature) && $objMod->isCoreOrExternalModule() != 'external') {
988 continue;
989 }
990 $reg = array();
991 if (preg_match('/^external_(.*)$/', $search_nature, $reg)) {
992 //print $reg[1].'-'.dol_escape_htmltag($objMod->getPublisher());
993 $publisher = dol_escape_htmltag($objMod->getPublisher());
994 if ($reg[1] && dol_escape_htmltag($reg[1]) != $publisher) {
995 continue;
996 }
997 if (!$reg[1] && !empty($publisher)) {
998 continue;
999 }
1000 }
1001 if ($search_nature == 'core' && $objMod->isCoreOrExternalModule() == 'external') {
1002 continue;
1003 }
1004 }
1005 if ($search_version) {
1006 if (($objMod->version == 'development' || $objMod->version == 'experimental' || preg_match('/deprecated/', $objMod->version)) && $search_version == 'stable') {
1007 continue;
1008 }
1009 if ($objMod->version != 'development' && ($search_version == 'development')) {
1010 continue;
1011 }
1012 if ($objMod->version != 'experimental' && ($search_version == 'experimental')) {
1013 continue;
1014 }
1015 if (!preg_match('/deprecated/', $objMod->version) && ($search_version == 'deprecated')) {
1016 continue;
1017 }
1018 }
1019
1020 $atleastonequalified++;
1021
1022 // Load all language files of the qualified module
1023 if (isset($objMod->langfiles) && is_array($objMod->langfiles)) {
1024 foreach ($objMod->langfiles as $domain) {
1025 $langs->load($domain);
1026 }
1027 }
1028
1029 // Print a separator if we change family
1030 if ($familykey != $oldfamily) {
1031 if ($oldfamily) {
1032 print '</table></div><br>';
1033 }
1034
1035 $familytext = empty($familyinfo[$familykey]['label']) ? $familykey : $familyinfo[$familykey]['label'];
1036
1037 print load_fiche_titre($familytext, '', '', 0, '', 'modulefamilygroup');
1038
1039 if ($mode == 'commonkanban') {
1040 print '<div class="box-flex-container kanban">';
1041 } else {
1042 print '<div class="div-table-responsive">';
1043 print '<table class="tagtable liste" summary="list_of_modules">'."\n";
1044 }
1045
1046 $atleastoneforfamily = 0;
1047 }
1048
1049 $atleastoneforfamily++;
1050
1051 if ($familykey != $oldfamily) {
1052 $familytext = empty($familyinfo[$familykey]['label']) ? $familykey : $familyinfo[$familykey]['label'];
1053 $oldfamily = $familykey;
1054 }
1055
1056 // Version (with picto warning or not)
1057 $version = $objMod->getVersion(0);
1058 $versiontrans = '';
1059 $warningstring = '';
1060 if (preg_match('/development/i', $version)) {
1061 $warningstring = $langs->trans("Development");
1062 }
1063 if (preg_match('/experimental/i', $version)) {
1064 $warningstring = $langs->trans("Experimental");
1065 }
1066 if (preg_match('/deprecated/i', $version)) {
1067 $warningstring = $langs->trans("Deprecated");
1068 }
1069
1070 if ($objMod->isCoreOrExternalModule() == 'external' || preg_match('/development|experimental|deprecated/i', $version)) {
1071 $versiontrans .= $objMod->getVersion(1);
1072 }
1073
1074 if ($objMod->isCoreOrExternalModule() == 'external' && getDolGlobalString($const_name) && ($action == 'checklastversion' || getDolGlobalString('CHECKLASTVERSION_EXTERNALMODULE'))) {
1075 // Check is done only for activated modules.
1076 // Setting CHECKLASTVERSION_EXTERNALMODULE to on is a bad practice to activate a check on an external access during the building of the admin page.
1077 // 1 external module can hang the application.
1078 // Adding a cron job could be a good idea: see DolibarrModules::checkForUpdate()
1079 $checkRes = $objMod->checkForUpdate();
1080 if ($checkRes > 0) {
1081 setEventMessages($objMod->getName().' : '.preg_replace('/[^a-z0-9_\.\-\s]/i', '', $versiontrans).' -> '.preg_replace('/[^a-z0-9_\.\-\s]/i', '', $objMod->lastVersion), null, 'warnings');
1082 } elseif ($checkRes < 0) {
1083 setEventMessages($objMod->getName().' '.$langs->trans('CheckVersionFail'), null, 'errors');
1084 }
1085 }
1086
1087 if ($objMod->isCoreOrExternalModule() == 'external' && getDolGlobalString($const_name) && $action == 'checklastversion' && !getDolGlobalString('DISABLE_CHECK_ON_MALWARE_MODULES')) {
1088 $checkRes = $objMod->checkForCompliance(); // Check if module is reported as non compliant with Dolibarr rules and law
1089 if (!is_numeric($checkRes) && $checkRes != '') {
1090 $langs->load("errors");
1091 setEventMessages($objMod->getName().' : '.$langs->trans($checkRes), null, 'errors');
1092 }
1093 }
1094
1095 // Define imginfo
1096 $imginfo = "info";
1097 if ($objMod->isCoreOrExternalModule() == 'external') {
1098 $imginfo = "info_black";
1099 }
1100
1101 $codeenabledisable = '';
1102 $codetoconfig = '';
1103
1104 // Force disable of module disabled into session (for demo for example)
1105 if (in_array($modulenameshort, $disabled_modules)) {
1106 $objMod->disabled = true;
1107 }
1108
1109 // Activate/Disable and Setup (2 columns)
1110 if (getDolGlobalString($const_name)) { // If module is already activated
1111 // Set $codeenabledisable
1112 $disableSetup = 0;
1113 if (!empty($arrayofwarnings[$modName])) {
1114 $codeenabledisable .= '<!-- This module has a warning to show when we activate it (note: your country is '.$mysoc->country_code.') -->'."\n";
1115 }
1116
1117 if (!empty($objMod->disabled)) {
1118 $codeenabledisable .= $langs->trans("Disabled");
1119 } elseif (is_object($objMod)
1120 && (!empty($objMod->always_enabled) || ((isModEnabled('multicompany') && $objMod->core_enabled) && ($user->entity || $conf->entity != 1)))) {
1121 // @phan-suppress-next-line PhanUndeclaredMethod
1122 if (method_exists($objMod, 'alreadyUsed') && $objMod->alreadyUsed()) {
1123 $codeenabledisable .= $langs->trans("Used");
1124 } else {
1125 $codeenabledisable .= img_picto($langs->trans("Required"), 'switch_on', '', 0, 0, 0, '', 'opacitymedium valignmiddle');
1126 //print $langs->trans("Required");
1127 }
1128 if (isModEnabled('multicompany') && $user->entity) {
1129 $disableSetup++;
1130 }
1131 } else {
1132 // @phan-suppress-next-line PhanUndeclaredMethod
1133 if (is_object($objMod) && !empty($objMod->warnings_unactivation[$mysoc->country_code]) && method_exists($objMod, 'alreadyUsed') && $objMod->alreadyUsed()) {
1134 $codeenabledisable .= '<a class="reposition valignmiddle" href="'.$_SERVER["PHP_SELF"].'?id='.((int) $objMod->numero).'&token='.newToken().'&module_position='.$module_position.'&action=reset_confirm&confirm_message_code='.urlencode($objMod->warnings_unactivation[$mysoc->country_code]).'&value='.urlencode($modName).'&mode='.urlencode($mode).$param.'">';
1135 $codeenabledisable .= img_picto($langs->trans("Activated").($warningstring ? ' '.$warningstring : ''), 'switch_on');
1136 $codeenabledisable .= '</a>';
1137 if (getDolGlobalInt("MAIN_FEATURES_LEVEL") > 1) {
1138 $codeenabledisable .= '&nbsp;';
1139 $codeenabledisable .= '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?id='.((int) $objMod->numero).'&token='.newToken().'&module_position='.$module_position.'&action=reload_confirm&value='.urlencode($modName).'&mode='.urlencode($mode).'&confirm=yes'.$param.'">';
1140 $codeenabledisable .= img_picto($langs->trans("Reload"), 'refresh', 'class="opacitymedium"');
1141 $codeenabledisable .= '</a>';
1142 }
1143 } else {
1144 // Check if some enabled modules depend on this one (requiredby). If yes, we show a confirmation popup before disabling.
1145 $warningmessagefordisable = '';
1146 if (is_array($objMod->requiredby) && count($objMod->requiredby) > 0) {
1147 $listofdependentmodules = array();
1148 foreach ($objMod->requiredby as $moduledisableclass) {
1149 if (empty($moduledisableclass)) {
1150 continue;
1151 }
1152 $moduleshortname = strtolower(preg_replace('/^mod/i', '', $moduledisableclass));
1153 if (in_array($moduleshortname, $conf->modules)) {
1154 $listofdependentmodules[] = isset($modules[$moduledisableclass]) ? $modules[$moduledisableclass]->getName() : $moduleshortname;
1155 }
1156 }
1157 if (count($listofdependentmodules) > 0) {
1158 $warningmessagefordisable = $langs->trans('ConfirmDisablingModuleWillDisableDependentModules', implode(', ', $listofdependentmodules));
1159 }
1160 }
1161
1162 $codeenabledisable .= '<a class="reposition valignmiddle" id="iddisable'.$objMod->numero.'" data-alreadyclicked="0" href="'.$_SERVER["PHP_SELF"].'?id='.((int) $objMod->numero).'&token='.newToken().'&module_position='.$module_position.'&action=reset&value='.urlencode($modName).'&mode='.urlencode($mode).'&confirm=yes'.$param.'"';
1163 if ($warningmessagefordisable) {
1164 $codeenabledisable .= ' onclick="return confirmDolibarr(\''.dol_escape_js($warningmessagefordisable).'\', \'iddisable'.$objMod->numero.'\', 600, 300, 0);"';
1165 }
1166 $codeenabledisable .= '>';
1167 $codeenabledisable .= img_picto($langs->trans("Activated").($warningstring ? ' '.$warningstring : ''), 'switch_on');
1168 $codeenabledisable .= '</a>';
1169 if (getDolGlobalInt("MAIN_FEATURES_LEVEL") > 1) {
1170 $codeenabledisable .= '&nbsp;';
1171 $codeenabledisable .= '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?id='.((int) $objMod->numero).'&token='.newToken().'&module_position='.$module_position.'&action=reload&value='.urlencode($modName).'&mode='.urlencode($mode).'&confirm=yes'.$param.'">';
1172 $codeenabledisable .= img_picto($langs->trans("Reload"), 'refresh', 'class="opacitymedium"');
1173 $codeenabledisable .= '</a>';
1174 }
1175 }
1176 }
1177
1178 // Set $codetoconfig
1179 if (!empty($objMod->config_page_url) && !$disableSetup) {
1180 $backtourlquery = [];
1181 if ($search_keyword != '') {
1182 $backtourlquery += ['search_keyword' => $search_keyword]; // No urlencode here, done later
1183 }
1184 if ($search_nature > -1) {
1185 $backtourlquery += ['search_nature' => $search_nature]; // No urlencode here, done later
1186 }
1187 if ($search_version > -1) {
1188 $backtourlquery += ['search_version' => $search_version]; // No urlencode here, done later
1189 }
1190 if ($search_status > -1) {
1191 $backtourlquery += ['search_status' => $search_status]; // No urlencode here, done later
1192 }
1193 $backtourl = dolBuildUrl($_SERVER["PHP_SELF"], $backtourlquery);
1194
1195 $regs = array();
1196 $query = [
1197 'save_lastsearch_values' => 1,
1198 'backtopage' => $backtourl,
1199 ];
1200 if (is_array($objMod->config_page_url)) {
1201 $i = 0;
1202 foreach ($objMod->config_page_url as $page) {
1203 $urlpage = $page;
1204 if ($i++) {
1205 $codetoconfig .= '<a href="'.$urlpage.'" title="'.$langs->trans($page).'">'.img_picto(ucfirst($page), "setup").'</a>';
1206 // print '<a href="'.$page.'">'.ucfirst($page).'</a>&nbsp;';
1207 } else {
1208 if (preg_match('/^([^@]+)@([^@]+)$/i', $urlpage, $regs)) {
1209 $urltouse = dol_buildpath('/'.$regs[2].'/admin/'.$regs[1], 1);
1210 $codetoconfig .= '<a href="'.$urltouse.(preg_match('/\?/', $urltouse) ? '&' : '?').'save_lastsearch_values=1&backtopage='.urlencode($backtourl).'" title="'.$langs->trans("Setup").'">'.img_picto($langs->trans("Setup"), "setup", 'style="padding-right: 6px"', 0, 0, 0, '', 'fa-15').'</a>';
1211 } else {
1212 $urltouse = $urlpage;
1213 $codetoconfig .= '<a href="'.$urltouse.(preg_match('/\?/', $urltouse) ? '&' : '?').'save_lastsearch_values=1&backtopage='.urlencode($backtourl).'" title="'.$langs->trans("Setup").'">'.img_picto($langs->trans("Setup"), "setup", 'style="padding-right: 6px"', 0, 0, 0, '', 'fa-15').'</a>';
1214 }
1215 }
1216 }
1217 } elseif (preg_match('/^([^@]+)@([^@]+)$/i', (string) $objMod->config_page_url, $regs)) {
1218 $codetoconfig .= '<a class="valignmiddle" href="'.dol_buildpath('/'.$regs[2].'/admin/'.$regs[1], 1).'?save_lastsearch_values=1&backtopage='.urlencode($backtourl).'" title="'.$langs->trans("Setup").'">'.img_picto($langs->trans("Setup"), "setup", 'style="padding-right: 6px"', 0, 0, 0, '', 'fa-15').'</a>';
1219 } else {
1220 $codetoconfig .= '<a class="valignmiddle" href="'.((string) $objMod->config_page_url).'?save_lastsearch_values=1&backtopage='.urlencode($backtourl).'" title="'.$langs->trans("Setup").'">'.img_picto($langs->trans("Setup"), "setup", 'style="padding-right: 6px"', 0, 0, 0, '', 'fa-15').'</a>';
1221 }
1222 } else {
1223 $codetoconfig .= img_picto($langs->trans("NothingToSetup"), "setup", 'class="opacitytransp" style="padding-right: 6px"', 0, 0, 0, '', 'fa-15');
1224 }
1225 } else { // Module not yet activated
1226 // Set $codeenabledisable
1227 if (!empty($objMod->always_enabled)) {
1228 // A 'always_enabled' module should not never be disabled. If this happen, we keep a link to re-enable it.
1229 $codeenabledisable .= '<!-- Message to show: an always_enabled module has been disabled -->'."\n";
1230 $codeenabledisable .= '<a class="reposition" id="idalways'.$objMod->numero.'" data-alreadyclicked="0" href="'.$_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&token='.newToken().'&module_position='.$module_position.'&action=set&token='.newToken().'&value='.$modName.'&mode='.$mode.$param.'"';
1231 $codeenabledisable .= '>';
1232 $codeenabledisable .= img_picto($langs->trans("Disabled"), 'switch_off');
1233 $codeenabledisable .= "</a>\n";
1234 } elseif (!empty($objMod->disabled)) {
1235 $codeenabledisable .= $langs->trans("Disabled");
1236 } else {
1237 // Module qualified for activation
1238 $warningmessage = '';
1239 $disableCancel = 0;
1240
1241 if (!empty($arrayofwarnings[$modName])) {
1242 $codeenabledisable .= '<!-- This module is a core module and it may have a warning to show when we activate it (note: your country is '.$mysoc->country_code.') -->'."\n";
1243 foreach ($arrayofwarnings[$modName] as $keycountry => $cursorwarningmessage) {
1244 if (preg_match('/^always/', $keycountry) || ($mysoc->country_code && preg_match('/^'.$mysoc->country_code.'/', $keycountry))) {
1245 if (!is_array($cursorwarningmessage)) {
1246 $cursorwarningmessage = array($cursorwarningmessage);
1247 }
1248 foreach ($cursorwarningmessage as $messagetoshow) {
1249 if (preg_match('/:1$/', $messagetoshow)) {
1250 $disableCancel = 1;
1251 }
1252 $messagetoshow = preg_replace('/:1$/', '', $messagetoshow);
1253
1254 // TODO Use a replacement instead of always adding the module name and the country code to the string message ?
1255 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans($messagetoshow, $objMod->getName(), $mysoc->country_code);
1256 }
1257 }
1258 }
1259 }
1260 if ($objMod->isCoreOrExternalModule() == 'external' && !empty($arrayofwarningsext)) {
1261 $codeenabledisable .= '<!-- This module is an external module and it may have a warning to show (note: your country is '.$mysoc->country_code.') -->'."\n";
1262 foreach ($arrayofwarningsext as $keymodule => $arrayofwarningsextbycountry) {
1263 $keymodulelowercase = strtolower(preg_replace('/^mod/', '', $keymodule));
1264 if (preg_match('/^always/', $keymodulelowercase) || in_array($keymodulelowercase, $conf->modules)) { // If module that trigger the warning is on
1265 foreach ($arrayofwarningsextbycountry as $keycountry => $cursorwarningmessage) {
1266 if (preg_match('/^always/', $keycountry) || ($mysoc->country_code && preg_match('/^'.$mysoc->country_code.'/', $keycountry))) {
1267 if (!is_array($cursorwarningmessage)) {
1268 $cursorwarningmessage = array($cursorwarningmessage);
1269 }
1270 foreach ($cursorwarningmessage as $messagetoshow) {
1271 // TODO Use replacement instead of always adding param module name to enable and country code to the string message and triggering module
1272 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans($messagetoshow, $objMod->getName(), $mysoc->country_code, $modules[$keymodule]->getName());
1273 }
1274 $warningmessage .= ($warningmessage ? "\n" : "").($warningmessage ? "\n" : "").$langs->trans("Module").' : '.$objMod->getName();
1275 if (!empty($objMod->editor_name)) {
1276 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans("Publisher").' : '.$objMod->editor_name;
1277 }
1278 if ($keymodulelowercase != 'always') {
1279 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans("ModuleTriggeringThisWarning").' : '.$modules[$keymodule]->getName();
1280 }
1281 }
1282 }
1283 }
1284 }
1285 }
1286
1287 $urltogo = $_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&token='.newToken().'&module_position='.$module_position.'&action=set&token='.newToken().'&value='.$modName.'&mode='.$mode.$param;
1288 $popupWidth = 600;
1289 $popupHeight = 300;
1290 $codeenabledisable .= '<!-- Message to show: '.$warningmessage.' -->'."\n";
1291 $codeenabledisable .= '<a class="reposition" id="idqualified'.$objMod->numero.'" data-alreadyclicked="0" href="'.$urltogo.'"';
1292 if ($warningmessage) {
1293 $codeenabledisable .= ' onclick="return confirmDolibarr(\''.dol_escape_js($warningmessage).'\', \'idqualified'.$objMod->numero.'\', '.$popupWidth.', '.$popupHeight.','.$disableCancel.');"';
1294 }
1295 $codeenabledisable .= '>';
1296 $codeenabledisable .= img_picto($langs->trans("Disabled"), 'switch_off');
1297 $codeenabledisable .= "</a>\n";
1298 }
1299
1300 // Set $codetoconfig
1301 $codetoconfig .= img_picto($langs->trans("NothingToSetup"), "setup", 'class="opacitytransp" style="padding-right: 6px"');
1302 }
1303
1304 if ($mode == 'commonkanban') {
1305 // Output Kanban
1306 print $objMod->getKanbanView($codeenabledisable, $codetoconfig);
1307 } else {
1308 print '<tr class="oddeven'.($warningstring ? ' info-box-content-warning' : '').'">'."\n";
1309 if (getDolGlobalString('MAIN_MODULES_SHOW_LINENUMBERS')) {
1310 print '<td class="width50">'.$linenum.'</td>';
1311 }
1312
1313 // Picto + Name of module
1314 print ' <td class="tdoverflowmax200 minwidth200imp" title="'.dol_escape_htmltag($objMod->getName()).'">';
1315 $alttext = '';
1316 //if (is_array($objMod->need_dolibarr_version)) $alttext.=($alttext?' - ':'').'Dolibarr >= '.join('.',$objMod->need_dolibarr_version);
1317 //if (is_array($objMod->phpmin)) $alttext.=($alttext?' - ':'').'PHP >= '.join('.',$objMod->phpmin);
1318 if (!empty($objMod->picto)) {
1319 if (preg_match('/^\//i', $objMod->picto)) {
1320 print img_picto($alttext, $objMod->picto, 'class="valignmiddle pictomodule paddingrightonly"', 1);
1321 } else {
1322 print img_object($alttext, $objMod->picto, 'class="valignmiddle pictomodule paddingrightonly"');
1323 }
1324 } else {
1325 print img_object($alttext, 'generic', 'class="valignmiddle paddingrightonly"');
1326 }
1327 print ' <span class="valignmiddle">'.$objMod->getName().'</span>';
1328 print "</td>\n";
1329
1330 // Desc
1331 print '<td class="valignmiddle tdoverflowmax300 minwidth200imp opacitylow">';
1332 print nl2br($objMod->getDesc());
1333 print "</td>\n";
1334
1335 // Help
1336 print '<td class="center nowrap" style="width: 82px;">';
1337 print '<a href="javascript:document_preview(\''.DOL_URL_ROOT.'/admin/modulehelp.php?id='.((int) $objMod->numero).'\',\'text/html\',\''.dol_escape_js($langs->trans("Module")).'\')">';
1338 print img_picto(($objMod->isCoreOrExternalModule() == 'external' ? $langs->trans("ExternalModule").' - ' : '').$langs->trans("ClickToShowDescription"), $imginfo, '', 0, 0, 0, '', 'purple');
1339 print '</a>';
1340 print($timestoinit[$modName] > 500 ? img_picto($langs->trans('InitModuleIsSlow'), 'fa-exclamation-circle') : '');
1341 print '</td>';
1342
1343 // Version
1344 print '<td class="center nowrap width150" title="'.dol_escape_htmltag(dol_string_nohtmltag($versiontrans)).'">';
1345 if ($objMod->needUpdate) {
1346 $versionTitle = $langs->trans('ModuleUpdateAvailable').' : '.$objMod->lastVersion;
1347 print '<span class="badge badge-warning classfortooltip" title="'.dol_escape_htmltag($versionTitle).'">'.$versiontrans.'</span>';
1348 } else {
1349 print $versiontrans;
1350 }
1351 print "</td>\n";
1352
1353 // Link enable/disable
1354 print '<td class="center valignmiddle left nowraponall" width="60px">';
1355 print $codeenabledisable;
1356 print "</td>\n";
1357
1358 // Link config
1359 print '<td class="tdsetuppicto right valignmiddle" width="60px">';
1360 print $codetoconfig;
1361 print '</td>';
1362
1363 print "</tr>\n";
1364 }
1365 if ($objMod->needUpdate) {
1366 $foundoneexternalmodulewithupdate++;
1367 }
1368 }
1369
1370 if ($action == 'checklastversion') {
1371 if ($foundoneexternalmodulewithupdate) {
1372 setEventMessages($langs->trans("ModuleUpdateAvailable"), null, 'warnings', '', 0, 1);
1373 } else {
1374 setEventMessages($langs->trans("NoExternalModuleWithUpdate"), null, 'mesgs');
1375 }
1376 }
1377
1378 if ($oldfamily) {
1379 if ($mode == 'commonkanban') {
1380 print '</div>';
1381 } else {
1382 print "</table>\n";
1383 print '</div>';
1384 }
1385 }
1386
1387 if (!$atleastonequalified) {
1388 print '<br><span class="opacitymedium">'.$langs->trans("NoDeployedModulesFoundWithThisSearchCriteria").'</span><br><br>';
1389 }
1390
1391 print dol_get_fiche_end();
1392
1393 print '<br>';
1394
1395 // Show warning about external users
1396 print info_admin(showModulesExludedForExternal($modules))."\n";
1397
1398 print '</form>';
1399}
1400
1401if ($mode == 'marketplace') {
1402 print dol_get_fiche_head($head, $mode, '', -1);
1403
1404 print $deschelp;
1405
1406 print '<br>';
1407
1408 print '<!-- summary of sources -->';
1409
1410 // Marketplace and community modules
1411 print '<div class="div-table-responsive-no-min">';
1412 print '<table summary="list_of_modules" class="noborder centpercent">'."\n";
1413 print '<tr class="liste_titre">'."\n";
1414 print '<td colspan="2">'.$form->textwithpicto($langs->trans("ModuleProviderSites"), $langs->trans("WebSiteDesc")).'</td>';
1415 print '<td class="hideonsmartphone">';
1416 print '</td>';
1417 print '<td></td>';
1418 print '</tr>';
1419
1420
1421 // Source Community github
1422 $url = 'https://github.com/Dolibarr/dolibarr-community-modules';
1423
1424 print '<tr class="oddeven nohover" height="100">'."\n";
1425 print '<td class="hideonsmartphone center width150 nopaddingleftimp nopaddingrightimp"><a href="'.$url.'" target="_blank" rel="noopener noreferrer external"><img border="0" class="imgautosize imgmaxwidth100" src="'.DOL_URL_ROOT.'/theme/dolibarr_logo.svg"></a></td>';
1426 print '<td class="minwidth500imp smallonsmartphone"><span class="opacitymedium">'.$langs->trans("CommunityModulesDesc").'</span><br>';
1427 print img_picto('', 'url', 'class="pictofixedwidth"').'<a href="'.$url.'" target="_blank" rel="noopener noreferrer external">'.$url.'</a></td>';
1428 print '<td>';
1429 print ajax_constantonoff('MAIN_ENABLE_EXTERNALMODULES_COMMUNITY', array(), null, 0, 0, 1);
1430 print '</td>';
1431 print '<td class="center">';
1432 if (!getDolGlobalString('MAIN_DISABLE_EXTERNALMODULES_COMMUNITY') && getDolGlobalInt('MAIN_ENABLE_EXTERNALMODULES_COMMUNITY')) {
1433 $messagetoadd = '<br><br><span class="small">Content of the repository index file '.$remotestore->file_source_url.' should be in the local cache file '.$remotestore->cache_file;
1434 $messagetoadd .= ' (Date: '.dol_print_date(dol_filemtime($remotestore->cache_file), 'dayhour', 'tzuserrel').')</span>';
1435 if ($remotestore->githubFileError) {
1436 $messagetoadd .= '<br><span class="error small">'.$remotestore->githubFileError.'</span>';
1437 }
1438 print $remotestore->libStatus($remotestore->githubFileStatus, 2, $messagetoadd);
1439 }
1440 print '</td>';
1441 print '</tr>';
1442
1443
1444 // Source Marketplace DoliStore
1445 $url = 'https://www.dolistore.com';
1446
1447 print '<tr class="oddeven nohover" height="100">'."\n";
1448 print '<td class="hideonsmartphone center width150 nopaddingleftimp nopaddingrightimp"><a href="'.$url.'" target="_blank" rel="noopener noreferrer external"><img border="0" class="imgautosize imgmaxwidth100" src="'.DOL_URL_ROOT.'/theme/dolistore_logo.svg"></a></td>';
1449 print '<td class="minwidth500imp smallonsmartphone"><span class="opacitymedium">'.$langs->trans("DoliStoreDesc").'</span><br>';
1450 print img_picto('', 'url', 'class="pictofixedwidth"').'<a href="'.$url.'" target="_blank" rel="noopener noreferrer external">'.$url.'</a></td>';
1451 print '<td>';
1452 print ajax_constantonoff('MAIN_ENABLE_EXTERNALMODULES_DOLISTORE', array(), null, 0, 0, 1);
1453 print '</td>';
1454 print '<td class="center">';
1455 if (!getDolGlobalString('MAIN_DISABLE_EXTERNALMODULES_DOLISTORE') && getDolGlobalInt('MAIN_ENABLE_EXTERNALMODULES_DOLISTORE')) {
1456 $messagetoadd = '<br><span class="small">';
1457 if ($remotestore->dolistoreApiStatus <= 0) {
1458 $messagetoadd = '<br>'.$remotestore->dolistoreApiError.'<br>Failed to get answer of remote API server<br>';
1459 }
1460
1461 $messagetoadd .= '<br>Using Shop address MAIN_MODULE_DOLISTORE_SHOP_URL = '.$remotestore->shop_url;
1462 $messagetoadd .= '<br>Using Remote API address MAIN_MODULE_DOLISTORE_API_URL = '.$remotestore->dolistore_api_url;
1463 $messagetoadd .= '<br>Using API public key MAIN_MODULE_DOLISTORE_API_KEY = '.$remotestore->dolistore_api_key;
1464 // Add basic auth if needed
1465 $basicAuthLogin = getDolGlobalString('MAIN_MODULE_DOLISTORE_BASIC_LOGIN');
1466 $basicAuthPassword = getDolGlobalString('MAIN_MODULE_DOLISTORE_BASIC_PASSWORD');
1467 if ($basicAuthLogin) {
1468 $messagetoadd .= '<br>Using basic auth login: base64('.$basicAuthLogin.':'.$basicAuthPassword.')';
1469 }
1470 $messagetoadd .= '</span>';
1471
1472 print $remotestore->libStatus($remotestore->dolistoreApiStatus, 2, $messagetoadd);
1473 }
1474 print '</td>';
1475 print '</tr>';
1476
1477 print "</table>\n";
1478 print '</div>';
1479
1480 print dol_get_fiche_end();
1481
1482 print '<br>';
1483
1484 if ($remotestore->numberOfProviders > 0) {
1485 // $options is array with filter criteria
1486 $nbmaxtoshow = $options['per_page'];
1487 $options['per_page']++;
1488
1489 //$remotestore->getRemoteCategories();
1490 //$remotestore->getRemoteProducts($options);
1491
1492 //print '<span class="opacitymedium hideonsmartphone">'.$langs->trans('DOLISTOREdescriptionLong').'</span><br><br>';
1493
1494 $categories_tree = $remotestore->getCategories($options['categorie']); // Call API to get the categories
1495
1496 $products_list = $remotestore->getProducts($options, $modules); // Get list of product from all sources
1497
1498 $previouslink = $remotestore->get_previous_link();
1499
1500 $nextlink = $remotestore->get_next_link();
1501
1502
1503 print '<div class="liste_titre liste_titre_bydiv centpercent"><div class="">';
1504
1505 print '<form method="POST" class="centpercent" id="searchFormList" action="'.$remotestore->url.'" spellcheck="false">'; ?>
1506 <input type="hidden" name="token" value="<?php echo newToken(); ?>">
1507 <input type="hidden" name="mode" value="marketplace">
1508 <input type="hidden" name="page_y" value="">
1509 <div class="divsearchfield">
1510 <input name="search_keyword" placeholder="<?php echo $langs->trans('Keyword') ?>" id="search_keyword" type="text" class="minwidth200" value="<?php echo dolPrintHTMLForAttribute($options['search']) ?>" spellcheck="false">
1511 </div>
1512 <div class="divsearchfield">
1513 <input name="buttonsubmit" class="button buttongen reposition" value="<?php echo $langs->trans('Search') ?>" type="submit">
1514 <?php
1515 if ($search_keyword !== '') {
1516 print '<a class="buttonreset reposition" href="'.$_SERVER["PHP_SELF"].'?mode=marketplace">'.$langs->trans('Reset').'</a>';
1517 } else {
1518 print $form->textwithpicto('', $langs->trans('DOLISTOREdescriptionLong'));
1519 }
1520 ?>
1521 &nbsp;
1522 </div>
1523 <?php
1524 $totalnboflines = '<span class="product-count opacitymedium paddingleft">';
1525 $totalnboflines .= $langs->trans("itemFound", $remotestore->numberTotalOfProducts);
1526 $totalnboflines .= '</span>';
1527
1528 print $totalnboflines;
1529 print $remotestore->getPagination();
1530 print '</form>';
1531
1532 print '</div>';
1533 print '<div class="clearboth"></div>';
1534 print '</div>';
1535 ?>
1536 <?php if (!empty($categories_tree)) { ?>
1537 <div id="category-tree-left" class="paddingtop">
1538 <ul class="tree">
1539 <?php
1540 print $categories_tree; ?>
1541 </ul>
1542 </div>
1543 <?php } ?>
1544
1545 <div id="listing-content" class="div-table-responsive" <?php if (empty($categories_tree)) { ?>style="width:100%;"<?php } ?>>
1546 <table summary="list_of_modules" id="list_of_modules" class="productlist centpercent">
1547 <tbody id="listOfModules">
1548 <!-- $product_list is $remotestore->getProducts($options) done previously -->
1549 <?php print $products_list; ?>
1550 </tbody>
1551 </table>
1552 </div>
1553 <div style="clear: both;"></div>
1554 <div><?php print $remotestore->getPagination(); ?></div>
1555 <?php
1556 }
1557}
1558
1559
1560// Form to install an external module
1561
1562if ($mode == 'deploy') {
1563 print dol_get_fiche_head($head, $mode, '', -1);
1564
1565 $fullurl = '<a href="'.$urldolibarrmodules.'" target="_blank" rel="noopener noreferrer">'.$urldolibarrmodules.'</a>';
1566 $message = '';
1567 if ($allowonlineinstall) {
1568 if (!in_array('/custom', explode(',', $dolibarr_main_url_root_alt))) {
1569 $message = info_admin($langs->trans("ConfFileMustContainCustom", DOL_DOCUMENT_ROOT.'/custom', DOL_DOCUMENT_ROOT));
1570 $allowfromweb = -1;
1571 } else {
1572 if ($dirins_ok) {
1573 if (!is_writable(dol_osencode($dirins))) {
1574 $langs->load("errors");
1575 $message = info_admin($langs->trans("ErrorFailedToWriteInDir", $dirins), 0, 0, '1', 'warning');
1576 $allowfromweb = 0;
1577 }
1578 } else {
1579 $message = info_admin($langs->trans("NotExistsDirect", $dirins).$langs->trans("InfDirAlt").$langs->trans("InfDirExample"));
1580 $allowfromweb = 0;
1581 }
1582 }
1583 } else {
1584 if (getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US')) {
1585 // Show clean message
1586 if (!is_numeric(getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US'))) {
1587 $message = info_admin($langs->trans(getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US')), 0, 0, 'warning');
1588 } else {
1589 $message = info_admin($langs->trans('InstallModuleFromWebHasBeenDisabledContactUs'), 0, 0, 'warning');
1590 }
1591 } else {
1592 // Show technical message
1593 $message = info_admin($langs->trans("InstallModuleFromWebHasBeenDisabledByFile", $dolibarrdataroot.'/installmodules.lock'), 0, 0, 'warning');
1594 }
1595 $allowfromweb = 0;
1596 }
1597
1598 print $deschelp;
1599
1600 if ($allowfromweb < 1) {
1601 print $langs->trans("SomethingMakeInstallFromWebNotPossible");
1602 print $message;
1603 //print $langs->trans("SomethingMakeInstallFromWebNotPossible2");
1604 print '<br>';
1605 }
1606
1607 // $allowfromweb = -1 if installation or setup not correct, 0 if not allowed, 1 if allowed
1608 if ($allowfromweb >= 0) {
1609 if ($allowfromweb == 1) {
1610 //print $langs->trans("ThisIsProcessToFollow").'<br>';
1611 } else {
1612 print '<br>';
1613
1614 print $langs->trans("ThisIsAlternativeProcessToFollow").'<br>';
1615 print '<b>'.$langs->trans("StepNb", 1).'</b>: ';
1616 print str_replace('{s1}', $fullurl, $langs->trans("FindPackageFromWebSite", '{s1}')).'<br>';
1617 print '<b>'.$langs->trans("StepNb", 2).'</b>: ';
1618 print str_replace('{s1}', $fullurl, $langs->trans("DownloadPackageFromWebSite", '{s1}')).'<br>';
1619 print '<b>'.$langs->trans("StepNb", 3).'</b>: ';
1620 }
1621
1622 if ($allowfromweb == 1) {
1623 print '<form enctype="multipart/form-data" method="POST" class="noborder" action="'.$_SERVER["PHP_SELF"].'" name="forminstall" spellcheck="false">';
1624 print '<input type="hidden" name="token" value="'.newToken().'">';
1625 print '<input type="hidden" name="action" value="install">';
1626 print '<input type="hidden" name="mode" value="deploy">';
1627
1628 print $langs->trans("YouCanSubmitFile").'<br><br><br>';
1629
1630 print '<span class="opacitymedium"><input class="paddingright" type="checkbox" name="checkforcompliance" id="checkforcompliance"'.(getDolGlobalString('DISABLE_CHECK_ON_MALWARE_MODULES') ? ' disabled="disabled"' : 'checked="checked"').'>';
1631 print '<label for="checkforcompliance">'.$form->textwithpicto($langs->trans("CheckIfModuleIsNotBlackListed"), $langs->trans("CheckIfModuleIsNotBlackListedHelp").'<br><br>'.DolibarrModules::URL_FOR_BLACKLISTED_MODULES).'</label>';
1632 print '</span><br><br>';
1633
1634 $max = getDolGlobalString('MAIN_UPLOAD_DOC'); // In Kb
1635 $maxphp = @ini_get('upload_max_filesize'); // In unknown
1636 if (preg_match('/k$/i', $maxphp)) {
1637 $maxphp = preg_replace('/k$/i', '', $maxphp);
1638 $maxphp *= 1;
1639 }
1640 if (preg_match('/m$/i', $maxphp)) {
1641 $maxphp = preg_replace('/m$/i', '', $maxphp);
1642 $maxphp *= 1024;
1643 }
1644 if (preg_match('/g$/i', $maxphp)) {
1645 $maxphp = preg_replace('/g$/i', '', $maxphp);
1646 $maxphp *= 1024 * 1024;
1647 }
1648 if (preg_match('/t$/i', $maxphp)) {
1649 $maxphp = preg_replace('/t$/i', '', $maxphp);
1650 $maxphp *= 1024 * 1024 * 1024;
1651 }
1652 $maxphp2 = @ini_get('post_max_size'); // In unknown
1653 if (preg_match('/k$/i', $maxphp2)) {
1654 $maxphp2 = preg_replace('/k$/i', '', $maxphp2);
1655 $maxphp2 *= 1;
1656 }
1657 if (preg_match('/m$/i', $maxphp2)) {
1658 $maxphp2 = preg_replace('/m$/i', '', $maxphp2);
1659 $maxphp2 *= 1024;
1660 }
1661 if (preg_match('/g$/i', $maxphp2)) {
1662 $maxphp2 = preg_replace('/g$/i', '', $maxphp2);
1663 $maxphp2 *= 1024 * 1024;
1664 }
1665 if (preg_match('/t$/i', $maxphp2)) {
1666 $maxphp2 = preg_replace('/t$/i', '', $maxphp2);
1667 $maxphp2 *= 1024 * 1024 * 1024;
1668 }
1669 // Now $max and $maxphp and $maxphp2 are in Kb
1670 $maxmin = $max;
1671 $maxphptoshow = $maxphptoshowparam = '';
1672 if ($maxphp > 0) {
1673 $maxmin = min($max, $maxphp);
1674 $maxphptoshow = $maxphp;
1675 $maxphptoshowparam = 'upload_max_filesize';
1676 }
1677 if ($maxphp2 > 0) {
1678 $maxmin = min($max, $maxphp2);
1679 if ($maxphp2 < $maxphp) {
1680 $maxphptoshow = $maxphp2;
1681 $maxphptoshowparam = 'post_max_size';
1682 }
1683 }
1684
1685 if ($maxmin > 0) {
1686 print '<script type="text/javascript">
1687 $(document).ready(function() {
1688 jQuery("#fileinstall").on("change", function() {
1689 if(this.files[0].size > '.($maxmin * 1024).') {
1690 alert(\''.dol_escape_js($langs->transnoentitiesnoconv("ErrorFileSizeTooLarge")).'\');
1691 this.value = "";
1692 }
1693 });
1694 });
1695 </script>'."\n";
1696 // MAX_FILE_SIZE must come before the file input field
1697 print '<input type="hidden" name="MAX_FILE_SIZE" value="'.($maxmin * 1024).'">';
1698 }
1699
1700 print '<input class="flat minwidth400" type="file" name="fileinstall" id="fileinstall">';
1701
1702 print '<input type="submit" name="send" value="'.dol_escape_htmltag($langs->trans("Upload")).'" class="button small">';
1703
1704 if (getDolGlobalString('MAIN_UPLOAD_DOC')) {
1705 if ($user->admin) {
1706 $langs->load('other');
1707 print ' ';
1708 print info_admin($langs->trans("ThisLimitIsDefinedInSetup", $max, $maxphptoshow, $maxphptoshowparam), 1);
1709 }
1710 } else {
1711 print ' ('.$langs->trans("UploadDisabled").')';
1712 }
1713
1714 print '</form>';
1715
1716 print '<br>';
1717 print '<br>';
1718
1719 print '<div class="center"><div class="logo_setup"></div></div>';
1720 } else {
1721 print $langs->trans("UnpackPackageInModulesRoot", $dirins).'<br>';
1722 print '<b>'.$langs->trans("StepNb", 4).'</b>: ';
1723 print $langs->trans("SetupIsReadyForUse", DOL_URL_ROOT.'/admin/modules.php?mainmenu=home', $langs->transnoentitiesnoconv("Home").' - '.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Modules")).'<br>';
1724 }
1725 }
1726
1727 print dol_get_fiche_end();
1728}
1729
1730if ($mode == 'develop') {
1731 print dol_get_fiche_head($head, $mode, '', -1);
1732
1733 print $deschelp;
1734
1735 print '<br>';
1736
1737 // Marketplace
1738 print '<div class="div-table-responsive-no-min">';
1739 print '<table summary="list_of_modules" class="noborder centpercent">'."\n";
1740 print '<tr class="liste_titre">'."\n";
1741 print '<td colspan="3">'.$langs->trans("DevelopYourModuleDesc").'</td>';
1742 print '</tr>';
1743
1744 print '<tr class="oddeven nohover" height="100">'."\n";
1745 print '<td class="center hideonsmartphone">';
1746 print '<div class="imgmaxheight50 logo_setup"></div>';
1747 print '</td>';
1748 print '<td class="minwidth500imp smallonsmartphone">'.$langs->trans("TryToUseTheModuleBuilder", $langs->transnoentitiesnoconv("ModuleBuilder")).'</td>';
1749 print '<td class="maxwidth300">';
1750 if (isModEnabled('modulebuilder')) {
1751 print $langs->trans("SeeTopRightMenu");
1752 } else {
1753 print '<span class="opacitymedium">'.$langs->trans("ModuleMustBeEnabledFirst", $langs->transnoentitiesnoconv("ModuleBuilder")).'</span>';
1754 }
1755 print '</td>';
1756 print '</tr>';
1757
1758 print '<tr class="oddeven nohover" height="100">'."\n";
1759 $url = 'https://partners.dolibarr.org';
1760 print '<td class="center hideonsmartphone">';
1761 print'<a href="'.$url.'" target="_blank" rel="noopener noreferrer external"><img border="0" class="imgautosize imgmaxwidth180" src="'.DOL_URL_ROOT.'/theme/dolibarr_preferred_partner.png"></a>';
1762 print '</td>';
1763 print '<td class="minwidth500imp smallonsmartphone">'.$langs->trans("DoliPartnersDesc").'</td>';
1764 print '<td><a href="'.$url.'" target="_blank" rel="noopener noreferrer external">';
1765 print img_picto('', 'url', 'class="pictofixedwidth"');
1766 print $url.'</a></td>';
1767 print '</tr>';
1768
1769 print "</table>\n";
1770 print '</div>';
1771
1772 print dol_get_fiche_end();
1773}
1774
1775// End of page
1776llxFooter();
1777$db->close();
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
dolibarr_set_const($db, $name, $value, $type='chaine', $visible=0, $note='', $entity=1)
Insert a parameter (key,value) into database (delete old key then insert it again).
modules_prepare_head($nbofactivatedmodules, $nboftotalmodules, $nbmodulesnotautoenabled)
Prepare array with list of tabs.
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class DolibarrModules.
Events class.
Class ExternalModules.
Class to manage generation of HTML components Only common components must be here.
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
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_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_uncompress($inputfile, $outputdir)
Uncompress a file.
dolCopyDir($srcfile, $destfile, $newmask, $overwriteifexists, $arrayreplacement=null, $excludesubdir=0, $excludefileext=null, $excludearchivefiles=0)
Copy a dir to another dir.
dol_is_file($pathoffile)
Return if path is a file.
dol_is_dir($folder)
Test if filename is a directory.
dol_move_uploaded_file($src_file, $dest_file, $allowoverwrite, $disablevirusscan=0, $uploaderrorcode=0, $nohook=0, $keyforsourcefile='addedfile', $upload_dir='', $mode=0)
Check validity of a file upload from an GUI page, and move it to its final destination.
dolGetModulesDirs($subdir='')
Return list of directories that contain modules.
dol_now($mode='gmt')
Return date for now.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
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)
multi select button
0 = Do not include form tag and submit button -1 = Do not include form tag but include submit button
treeview li table
No Email.
a disabled
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
Definition html.lib.php:540
dolGetButtonTitle($label, $helpText='', $iconClass='fa fa-file', $url='', $id='', $status=1, $params=array())
Function dolGetButtonTitle : this kind of buttons are used in title in list.
dol_set_focus($selector)
Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
dolGetButtonTitleSeparator($moreClass="")
Add space between dolGetButtonTitle.
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='', $textonpictotooltip='', $cssfordropdown='info_admin')
Show information in HTML for admin users or standard users.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
httponly_accessforbidden($message='1', $http_response_code=403, $stringalreadysanitized=0)
Show a message to say access is forbidden and stop program.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.