dolibarr 24.0.0-beta
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-2025 Frédéric France <frederic.france@free.fr>
12 * Copyright (C) 2024 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) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') {
319 $modName = 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 = 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 $queryString = http_build_query($searchParams);
436 $redirectUrl = DOL_URL_ROOT . '/admin/modules.php?' . $queryString;
437
438 $message = $langs->trans("SetupIsReadyForUse", $redirectUrl, $langs->transnoentitiesnoconv("Home").' - '.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Modules"));
439
440 setEventMessages($message, null, 'warnings');
441 }
442} elseif ($action == 'install' && !$allowonlineinstall) {
443 httponly_accessforbidden("You try to bypass the protection to disallow deployment of an external module. Hack attempt ?");
444}
445
446if ($action == 'set' && $user->admin) {
447 // We made some check against evil eternal modules that try to low security options.
448 $checkOldValue = getDolGlobalInt('CHECKLASTVERSION_EXTERNALMODULE');
449 $csrfCheckOldValue = getDolGlobalInt('MAIN_SECURITY_CSRF_WITH_TOKEN');
450
451 $resarray = activateModule($value, 1, 0, 'acceptredirect');
452
453 if ($checkOldValue != getDolGlobalInt('CHECKLASTVERSION_EXTERNALMODULE')) {
454 setEventMessage($langs->trans('WarningModuleHasChangedLastVersionCheckParameter', $value), 'warnings');
455 }
456 if ($csrfCheckOldValue != getDolGlobalInt('MAIN_SECURITY_CSRF_WITH_TOKEN')) {
457 setEventMessage($langs->trans('WarningModuleHasChangedSecurityCsrfParameter', $value), 'warnings');
458 }
459
460 dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1, 'chaine', 0, '', $conf->entity);
461 if (!empty($resarray['errors'])) {
462 setEventMessages('', $resarray['errors'], 'errors');
463 } else {
464 //var_dump($resarray);exit;
465 if ($resarray['nbperms'] > 0) {
466 $tmpsql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."user WHERE admin <> 1";
467 $resqltmp = $db->query($tmpsql);
468 if ($resqltmp) {
469 $obj = $db->fetch_object($resqltmp);
470 //var_dump($obj->nb);exit;
471 if ($obj && $obj->nb > 1) {
472 $msg = $langs->trans('ModuleEnabledAdminMustCheckRights');
473 setEventMessages($msg, null, 'warnings');
474 }
475 } else {
477 }
478 }
479 }
480 header("Location: ".$_SERVER["PHP_SELF"]."?mode=".$mode.$param.($page_y ? '&page_y='.$page_y : ''));
481 exit;
482} elseif ($action == 'reset' && $user->admin && GETPOST('confirm') == 'yes') {
483 $result = unActivateModule($value);
484 dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1, 'chaine', 0, '', $conf->entity);
485 if ($result) {
486 setEventMessages($result, null, 'errors');
487 }
488 header("Location: ".$_SERVER["PHP_SELF"]."?mode=".$mode.$param.($page_y ? '&page_y='.$page_y : ''));
489 exit;
490} elseif (getDolGlobalInt("MAIN_FEATURES_LEVEL") > 1 && $action == 'reload' && $user->admin && GETPOST('confirm') == 'yes') {
491 $result = unActivateModule($value, 0, 'newboxdefonly'); // unactivate all module features but for widget, we reload only definition and we do not change position or setup
492 dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1, 'chaine', 0, '', $conf->entity);
493 if ($result) {
494 setEventMessages($result, null, 'errors');
495 header("Location: ".$_SERVER["PHP_SELF"]."?mode=".$mode.$param.($page_y ? '&page_y='.$page_y : ''));
496 exit;
497 }
498
499 $resarray = activateModule($value, 0, 1, 'acceptredirect');
500
501 dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", (getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1), 'chaine', 0, '', $conf->entity);
502 if (!empty($resarray['errors'])) {
503 setEventMessages('', $resarray['errors'], 'errors');
504 } else {
505 if ($resarray['nbperms'] > 0) {
506 $tmpsql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX."user WHERE admin <> 1";
507 $resqltmp = $db->query($tmpsql);
508 if ($resqltmp) {
509 $obj = $db->fetch_object($resqltmp);
510 if ($obj && $obj->nb > 1) {
511 $msg = $langs->trans('ModuleEnabledAdminMustCheckRights');
512 setEventMessages($msg, null, 'warnings');
513 }
514 } else {
516 }
517 }
518 }
519 header("Location: ".$_SERVER["PHP_SELF"]."?mode=".$mode.$param.($page_y ? '&page_y='.$page_y : ''));
520 exit;
521}
522
523
524/*
525 * View
526 */
527
528$form = new Form($db);
529
530$morejs = array();
531$morecss = array("/admin/remotestore/css/store.css");
532
533// Set dir where external modules are installed
534if (!dol_is_dir($dirins)) {
535 dol_mkdir($dirins);
536}
537$dirins_ok = (dol_is_dir($dirins));
538
539$help_url = 'EN:First_setup|FR:Premiers_paramétrages|ES:Primeras_configuraciones';
540llxHeader('', $langs->trans("Setup"), $help_url, '', 0, 0, $morejs, $morecss, '', 'mod-admin page-modules');
541
542
543// Search modules dirs
544$modulesdir = dolGetModulesDirs();
545
546$arrayofnatures = array(
547 'core' => array('label' => $langs->transnoentitiesnoconv("NativeModules")),
548 'external' => array('label' => $langs->transnoentitiesnoconv("External").' - ['.$langs->trans("AllPublishers").']')
549);
550$arrayofwarnings = array(); // Array of warning each module want to show when activated
551$arrayofwarningsext = array(); // Array of warning each module want to show when we activate an external module
552$filename = array();
553$modules = array();
554$orders = array();
555$categ = array();
556$timestoinit = [];
557//$publisherlogoarray = array();
558
559$i = 0; // is a sequencer of modules found
560$j = 0; // j is module number. Automatically affected if module number not defined.
561$modNameLoaded = array();
562
563// Load $modules (required for the badge count)
564foreach ($modulesdir as $dir) {
565 // Load modules attributes in arrays (name, numero, orders) from dir directory
566 //print $dir."\n<br>";
567 dol_syslog("Scan directory ".$dir." for module descriptor files (modXXX.class.php)");
568 $handle = @opendir($dir);
569 $timestart = microtime(true);
570 if (is_resource($handle)) {
571 while (($file = readdir($handle)) !== false) {
572 //print "$i ".$file."\n<br>";
573 if (is_readable($dir.$file) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') {
574 $modName = substr($file, 0, dol_strlen($file) - 10);
575
576 if ($modName) {
577 if (!empty($modNameLoaded[$modName])) { // In cache of already loaded modules ?
578 $mesg = "Error: Module ".$modName." was found twice: Into ".$modNameLoaded[$modName]." and ".$dir.". You probably have an old file on your disk.<br>";
579 setEventMessages($mesg, null, 'warnings');
580 dol_syslog($mesg, LOG_ERR);
581 continue;
582 }
583
584 try {
585 $res = include_once $dir.$file; // A class already exists in a different file will send a non catchable fatal error.
586 if (class_exists($modName)) {
587 $objMod = new $modName($db);
588 '@phan-var-force DolibarrModules $objMod';
590 $modNameLoaded[$modName] = $dir;
591 if (!$objMod->numero > 0 && $modName != 'modUser') {
592 dol_syslog('The module descriptor '.$modName.' must have a numero property', LOG_ERR);
593 }
594 $j = $objMod->numero;
595
596 $modulequalified = 1;
597
598 // We discard modules according to features level (PS: if module is activated we always show it)
599 $const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($objMod)));
600 if ($objMod->version == 'development' && (!getDolGlobalString($const_name) && (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 2))) {
601 $modulequalified = 0;
602 }
603 if ($objMod->version == 'experimental' && (!getDolGlobalString($const_name) && (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 1))) {
604 $modulequalified = 0;
605 }
606 if (preg_match('/deprecated/', $objMod->version) && (!getDolGlobalString($const_name) && (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 0))) {
607 $modulequalified = 0;
608 }
609
610 // We discard modules according to property ->hidden
611 if (!empty($objMod->hidden)) {
612 $modulequalified = 0;
613 }
614
615 if ($modulequalified > 0) {
616 $publisher = dol_escape_htmltag($objMod->getPublisher());
617 $external = ($objMod->isCoreOrExternalModule() == 'external');
618 if ($external) {
619 if ($publisher) {
620 // Check if there is a logo forpublisher
621 /* Do not show the company logo in combo. Make combo list dirty.
622 if (!empty($objMod->editor_squarred_logo)) {
623 $publisherlogoarray['external_'.$publisher] = img_picto('', $objMod->editor_squarred_logo, 'class="publisherlogoinline"');
624 }
625 $publisherlogo = empty($publisherlogoarray['external_'.$publisher]) ? '' : $publisherlogoarray['external_'.$publisher];
626 */
627 $arrayofnatures['external_'.$publisher] = array('label' => $langs->trans("External").' - '.$publisher, 'data-html' => $langs->trans("External").' - <span class="opacitymedium inine-block valignmiddle">'.$publisher.'</span>');
628 } else {
629 $arrayofnatures['external_'] = array('label' => $langs->trans("External").' - ['.$langs->trans("UnknownPublishers").']');
630 }
631 }
632 ksort($arrayofnatures);
633
634 // Define an array $categ with categ with at least one qualified module
635 $filename[$i] = $modName;
636 $modules[$modName] = $objMod;
637 $timestoinit[$modName] = round((microtime(true) - $timestart) * 1000, 3);
638
639 // Gives the possibility to the module, to provide his own family info and position of this family
640 if (is_array($objMod->familyinfo) && !empty($objMod->familyinfo)) {
641 $familyinfo = array_merge($familyinfo, $objMod->familyinfo);
642 $familykey = key($objMod->familyinfo);
643 } else {
644 $familykey = $objMod->family;
645 }
646 '@phan-var-force string $familykey'; // if not, phan considers $familykey may be null
647
648 $moduleposition = ($objMod->module_position ? $objMod->module_position : '50');
649 if ($objMod->isCoreOrExternalModule() == 'external' && $moduleposition < 100000) {
650 // an external module should never return a value lower than '80'.
651 $moduleposition = '80'; // External modules at end by default
652 }
653
654 // Add list of warnings to show into arrayofwarnings and arrayofwarningsext
655 if (!empty($objMod->warnings_activation)) {
656 $arrayofwarnings[$modName] = $objMod->warnings_activation;
657 }
658 if (!empty($objMod->warnings_activation_ext)) {
659 $arrayofwarningsext[$modName] = $objMod->warnings_activation_ext;
660 }
661
662 $familyposition = (empty($familyinfo[$familykey]['position']) ? '0' : $familyinfo[$familykey]['position']);
663 if ($external && !in_array($familykey, array_keys($familyinfo))) {
664 // 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).
665 if (is_numeric($familyposition)) {
666 $familyposition = sprintf("%03d", (int) $familyposition + 100);
667 }
668 }
669
670 $orders[$i] = $familyposition."_".$familykey."_".$moduleposition."_".$j; // Sort by family, then by module position then number
671
672 // Set categ[$i]
673 $specialstring = 'unknown';
674 if ($objMod->version == 'development' || $objMod->version == 'experimental') {
675 $specialstring = 'expdev';
676 }
677 if (isset($categ[$specialstring])) {
678 $categ[$specialstring]++; // Array of all different modules categories
679 } else {
680 $categ[$specialstring] = 1;
681 }
682 $j++;
683 $i++;
684 } else {
685 dol_syslog("Module ".get_class($objMod)." not qualified");
686 }
687 } else {
688 // Skip warning for modules being refactored (class split in progress)
689 $silentModules = array('modSupplierOrder', 'modSupplierInvoice', 'modFournisseur');
690 if (!in_array($modName, $silentModules)) {
691 print info_admin("admin/modules.php Warning bad descriptor file : ".$dir.$file." (Class ".$modName." not found into file)", 0, 0, '1', 'warning');
692 }
693 }
694 } catch (Exception $e) {
695 dol_syslog("Failed to load ".$dir.$file." ".$e->getMessage(), LOG_ERR);
696 }
697 }
698 }
699 }
700 closedir($handle);
701 } else {
702 dol_syslog("htdocs/admin/modules.php: Failed to open directory ".$dir.". See permission and open_basedir option.", LOG_WARNING);
703 }
704}
705
706'@phan-var-force array<string,DolibarrModules> $modules';
709if ($action == 'reset_confirm' && $user->admin) {
710 if (!empty($modules[$value])) {
711 $objMod = $modules[$value];
712
713 if (!empty($objMod->langfiles)) {
714 $langs->loadLangs($objMod->langfiles);
715 }
716
717 $form = new Form($db);
718 $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);
719 }
720}
721
722if ($action == 'reload_confirm' && $user->admin) {
723 if (!empty($modules[$value])) {
724 $objMod = $modules[$value];
725
726 if (!empty($objMod->langfiles)) {
727 $langs->loadLangs($objMod->langfiles);
728 }
729
730 $form = new Form($db);
731 $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?value='.$value.'&mode='.$mode.$param, $langs->trans('ConfirmReload'), $langs->trans(GETPOST('confirm_message_code')), 'reload', '', 'no', 1);
732 }
733}
734
735print $formconfirm;
736
737asort($orders);
738//var_dump($orders);
739//var_dump($categ);
740//var_dump($modules);
741
742$nbofactivatedmodules = count($conf->modules);
743
744// Define $nbmodulesnotautoenabled - TODO This code is at different places
745$nbmodulesnotautoenabled = count($conf->modules);
746$listofmodulesautoenabled = array('user', 'agenda', 'fckeditor', 'export', 'import');
747foreach ($listofmodulesautoenabled as $moduleautoenable) {
748 if (in_array($moduleautoenable, $conf->modules)) {
749 $nbmodulesnotautoenabled--;
750 }
751}
752
753print load_fiche_titre($langs->trans("ModulesSetup"), '', 'title_setup');
754
755// Start to show page
756$deschelp = '';
757if ($mode == 'common' || $mode == 'commonkanban') {
758 $desc = $langs->trans("ModulesDesc", '{picto}');
759 $desc .= ' '.$langs->trans("ModulesDesc2", '{picto2}');
760 $desc = str_replace('{picto}', img_picto('', 'switch_off', 'class="size15x"'), $desc);
761 $desc = str_replace('{picto2}', img_picto('', 'setup', 'class="size15x"'), $desc);
762 if (getDolGlobalInt('MAIN_SETUP_MODULES_DESC') || $nbmodulesnotautoenabled < getDolGlobalInt('MAIN_MIN_NB_ENABLED_MODULE_FOR_WARNING', 1)) { // If only minimal initial modules enabled
763 $deschelp .= '<div class="info hideonsmartphone">'.$desc."<br></div>\n";
764 }
765 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.
766 $deschelp .= '<div class="info">'.$langs->trans(getDolGlobalString('MAIN_SETUP_MODULES_INFO'))."<br></div>\n";
767 }
768 if ($deschelp) {
769 $deschelp .= '<br>';
770 }
771}
772if ($mode == 'deploy') {
773 $deschelp = '<div class="info hideonsmartphone">'.$langs->trans("ModulesDeployDesc", $langs->transnoentitiesnoconv("AvailableModules"))."<br></div><br>\n";
774}
775if ($mode == 'develop') {
776 $deschelp = '<div class="info hideonsmartphone">'.$langs->trans("ModulesDevelopDesc")."<br></div><br>\n";
777}
778
779$head = modules_prepare_head($nbofactivatedmodules, count($modules), $nbmodulesnotautoenabled);
780
781
782if ($mode == 'common' || $mode == 'commonkanban') {
783 dol_set_focus('#search_keyword');
784
785 print '<form method="POST" id="searchFormList" action="'.dolBuildUrl($_SERVER["PHP_SELF"]).'">';
786 print '<input type="hidden" name="token" value="'.newToken().'">';
787 if (isset($optioncss) && $optioncss != '') {
788 print '<input type="hidden" name="optioncss" value="'.$optioncss.'">';
789 }
790 if (isset($sortfield) && $sortfield != '') {
791 print '<input type="hidden" name="sortfield" value="'.$sortfield.'">';
792 }
793 if (isset($sortorder) && $sortorder != '') {
794 print '<input type="hidden" name="sortorder" value="'.$sortorder.'">';
795 }
796 if (isset($page) && $page != '') {
797 print '<input type="hidden" name="page" value="'.$page.'">';
798 }
799 print '<input type="hidden" name="mode" value="'.$mode.'">';
800
801 print dol_get_fiche_head($head, 'modules', '', -1);
802
803 print $deschelp;
804
805 $moreforfilter = '<div class="valignmiddle">';
806
807 $moreforfilter .= '<div class="floatright right pagination paddingtop --module-list"><ul><li>';
808 $moreforfilter .= dolGetButtonTitle($langs->trans('CheckForModuleUpdate'), $langs->trans('CheckForModuleUpdate').'<br><br>'.img_warning('', '', 'paddingright').$langs->trans('CheckForModuleUpdateHelp').' '.$langs->trans('CheckForModuleUpdateHelp2', DolibarrModules::URL_FOR_BLACKLISTED_MODULES).'<br>'.$langs->trans("YourIPWillBeRevealedToThisExternalProviders"), 'fa fa-sync', $_SERVER["PHP_SELF"].'?action=checklastversion&token='.newToken().'&mode='.$mode.$param, '', 1, array('morecss' => 'reposition'));
809 $moreforfilter .= dolGetButtonTitleSeparator();
810 $moreforfilter .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.$param, '', ($mode == 'common' ? 2 : 1), array('morecss' => 'reposition'));
811 $moreforfilter .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=commonkanban'.$param, '', ($mode == 'commonkanban' ? 2 : 1), array('morecss' => 'reposition'));
812 $moreforfilter .= '</li></ul></div>';
813
814 $moreforfilter .= '<div class="divfilteralone colorbacktimesheet float valignmiddle nopaddingtopimp nopaddingbottomimp">';
815 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
816 $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')).'">';
817 $moreforfilter .= '</div>';
818 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
819 $moreforfilter .= $form->selectarray('search_nature', $arrayofnatures, dol_escape_htmltag($search_nature), $langs->trans('Origin'), 0, 0, '', 0, 0, 0, '', 'maxwidth250', 1);
820 $moreforfilter .= '</div>';
821
822 if (getDolGlobalInt('MAIN_FEATURES_LEVEL')) {
823 $array_version = array('stable' => $langs->transnoentitiesnoconv("Stable"));
824 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 0) {
825 $array_version['deprecated'] = $langs->trans("Deprecated");
826 }
827 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
828 $array_version['experimental'] = $langs->trans("Experimental");
829 }
830 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 1) {
831 $array_version['development'] = $langs->trans("Development");
832 }
833 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
834 $moreforfilter .= $form->selectarray('search_version', $array_version, $search_version, $langs->transnoentitiesnoconv('Version'), 0, 0, '', 0, 0, 0, '', 'maxwidth150', 1);
835 $moreforfilter .= '</div>';
836 }
837 $array_status = array('active' => $langs->transnoentitiesnoconv("Enabled"), 'disabled' => $langs->transnoentitiesnoconv("Disabled"));
838 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
839 $moreforfilter .= $form->selectarray('search_status', $array_status, $search_status, $langs->transnoentitiesnoconv('Status'), 0, 0, '', 0, 0, 0, '', 'maxwidth150', 1);
840 $moreforfilter .= '</div>';
841 $moreforfilter .= ' ';
842 $moreforfilter .= '<div class="divsearchfield valignmiddle inline-block">';
843 $moreforfilter .= '<input type="submit" name="buttonsubmit" class="button small nomarginleft" value="'.dolPrintHTMLForAttribute($langs->trans("Refresh")).'">';
844 if ($search_keyword || ($search_nature && $search_nature != '-1') || ($search_version && $search_version != '-1') || ($search_status && $search_status != '-1')) {
845 $moreforfilter .= ' ';
846 $moreforfilter .= '<input type="submit" name="buttonreset" class="buttonreset noborderall nomargintop nomarginbottom" value="'.dolPrintHTMLForAttribute($langs->trans("Reset")).'">';
847 }
848 $moreforfilter .= '</div>';
849 $moreforfilter .= '</div>';
850
851 $moreforfilter .= '</div>';
852
853 print $moreforfilter;
854 $parameters = array();
855 $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
856 print $hookmanager->resPrint;
857
858 $moreforfilter = '';
859
860 print '<div class="clearboth"></div><br><br>';
861
862 $object = new stdClass();
863 $parameters = array();
864 $reshook = $hookmanager->executeHooks('insertExtraHeader', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
865 if ($reshook < 0) {
866 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
867 }
868
869 $disabled_modules = array();
870 if (!empty($_SESSION["disablemodules"])) {
871 $disabled_modules = explode(',', $_SESSION["disablemodules"]);
872 }
873
874 // Show list of modules
875 $oldfamily = '';
876 $foundoneexternalmodulewithupdate = 0;
877 $linenum = 0;
878 $atleastonequalified = 0;
879 $atleastoneforfamily = 0;
880
881 print '<script type="text/javascript">
882 jQuery(document).ready(function() {
883 jQuery(".modulefamilygroup").each(function() {
884 var $group = jQuery(this);
885 var $title = $group.find(".titre.inline-block").first();
886 var $nextContainer = $group.nextAll(".div-table-responsive, .box-flex-container").first();
887 if ($title.length && !$title.children(".modulefamilytoggleicon").length) {
888 $title.prepend("<i class=\"fa modulefamilytoggleicon paddingleft paddingleftright\"></i> ");
889 }
890 var $icon = $title.children(".modulefamilytoggleicon").first();
891 var isVisible = $nextContainer.is(":visible");
892 if ($icon.length && $nextContainer.length) {
893 $icon.toggleClass("fa-folder-open", isVisible);
894 $icon.toggleClass("fa-folder", !isVisible);
895 }
896 });
897
898 jQuery(document).on("click", ".modulefamilygroup", function() {
899 var $group = jQuery(this);
900 var $nextContainer = $group.nextAll(".div-table-responsive, .box-flex-container").first();
901 if ($nextContainer.length) {
902 var $icon = $group.find(".modulefamilytoggleicon").first();
903 var isVisible = $nextContainer.is(":visible");
904 $nextContainer.stop(true, true).slideToggle(150);
905 if ($icon.length) {
906 $icon.toggleClass("fa-folder-open", !isVisible);
907 $icon.toggleClass("fa-folder", isVisible);
908 }
909 }
910 });
911 });
912 </script>';
913
914 foreach ($orders as $key => $value) {
915 $linenum++;
916 $tab = explode('_', $value);
917 $familykey = $tab[1];
918 $module_position = $tab[2];
919
920 $modName = $filename[$key];
921
923 $objMod = $modules[$modName];
924
925 if (!is_object($objMod)) {
926 continue;
927 }
928
929 //print $objMod->name." - ".$key." - ".$objMod->version."<br>";
930 if ($mode == 'expdev' && $objMod->version != 'development' && $objMod->version != 'experimental') {
931 continue; // Discard if not for current tab
932 }
933
934 if (!$objMod->getName()) {
935 dol_syslog("Error for module ".$key." - Property name of module looks empty", LOG_WARNING);
936 continue;
937 }
938
939 $modulenameshort = strtolower(preg_replace('/^mod/i', '', get_class($objMod)));
940 $const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($objMod)));
941
942 // Check filters
943 $modulename = $objMod->getName();
944 $moduletechnicalname = $objMod->name;
945 $moduledesc = $objMod->getDesc();
946 $moduledesclong = $objMod->getDescLong();
947 $moduleauthor = $objMod->getPublisher();
948
949 // We discard showing according to filters
950 if ($search_keyword) {
951 $qualified = 0;
952 $search_keyword_array = explode(' ', $search_keyword);
953 $foundkeyword = 1;
954 foreach ($search_keyword_array as $word) {
955 if (!preg_match('/'.preg_quote($word, '/').'/i', $modulename)
956 && !preg_match('/'.preg_quote($word, '/').'/i', $moduletechnicalname)
957 && !($moduledesc && preg_match('/'.preg_quote($word, '/').'/i', $moduledesc))
958 && !($moduledesclong && preg_match('/'.preg_quote($word, '/').'/i', $moduledesclong))
959 && !($moduleauthor && preg_match('/'.preg_quote($word, '/').'/i', $moduleauthor))
960 ) {
961 $foundkeyword = 0;
962 }
963 }
964 if ($foundkeyword) {
965 $qualified = 1;
966 }
967 if (!$qualified) {
968 continue;
969 }
970 }
971 if ($search_status) {
972 if ($search_status == 'active' && !getDolGlobalString($const_name)) {
973 continue;
974 }
975 if ($search_status == 'disabled' && getDolGlobalString($const_name)) {
976 continue;
977 }
978 }
979 if ($search_nature) {
980 if (preg_match('/^external/', $search_nature) && $objMod->isCoreOrExternalModule() != 'external') {
981 continue;
982 }
983 $reg = array();
984 if (preg_match('/^external_(.*)$/', $search_nature, $reg)) {
985 //print $reg[1].'-'.dol_escape_htmltag($objMod->getPublisher());
986 $publisher = dol_escape_htmltag($objMod->getPublisher());
987 if ($reg[1] && dol_escape_htmltag($reg[1]) != $publisher) {
988 continue;
989 }
990 if (!$reg[1] && !empty($publisher)) {
991 continue;
992 }
993 }
994 if ($search_nature == 'core' && $objMod->isCoreOrExternalModule() == 'external') {
995 continue;
996 }
997 }
998 if ($search_version) {
999 if (($objMod->version == 'development' || $objMod->version == 'experimental' || preg_match('/deprecated/', $objMod->version)) && $search_version == 'stable') {
1000 continue;
1001 }
1002 if ($objMod->version != 'development' && ($search_version == 'development')) {
1003 continue;
1004 }
1005 if ($objMod->version != 'experimental' && ($search_version == 'experimental')) {
1006 continue;
1007 }
1008 if (!preg_match('/deprecated/', $objMod->version) && ($search_version == 'deprecated')) {
1009 continue;
1010 }
1011 }
1012
1013 $atleastonequalified++;
1014
1015 // Load all language files of the qualified module
1016 if (isset($objMod->langfiles) && is_array($objMod->langfiles)) {
1017 foreach ($objMod->langfiles as $domain) {
1018 $langs->load($domain);
1019 }
1020 }
1021
1022 // Print a separator if we change family
1023 if ($familykey != $oldfamily) {
1024 if ($oldfamily) {
1025 print '</table></div><br>';
1026 }
1027
1028 $familytext = empty($familyinfo[$familykey]['label']) ? $familykey : $familyinfo[$familykey]['label'];
1029
1030 print load_fiche_titre($familytext, '', '', 0, '', 'modulefamilygroup');
1031
1032 if ($mode == 'commonkanban') {
1033 print '<div class="box-flex-container kanban">';
1034 } else {
1035 print '<div class="div-table-responsive">';
1036 print '<table class="tagtable liste" summary="list_of_modules">'."\n";
1037 }
1038
1039 $atleastoneforfamily = 0;
1040 }
1041
1042 $atleastoneforfamily++;
1043
1044 if ($familykey != $oldfamily) {
1045 $familytext = empty($familyinfo[$familykey]['label']) ? $familykey : $familyinfo[$familykey]['label'];
1046 $oldfamily = $familykey;
1047 }
1048
1049 // Version (with picto warning or not)
1050 $version = $objMod->getVersion(0);
1051 $versiontrans = '';
1052 $warningstring = '';
1053 if (preg_match('/development/i', $version)) {
1054 $warningstring = $langs->trans("Development");
1055 }
1056 if (preg_match('/experimental/i', $version)) {
1057 $warningstring = $langs->trans("Experimental");
1058 }
1059 if (preg_match('/deprecated/i', $version)) {
1060 $warningstring = $langs->trans("Deprecated");
1061 }
1062
1063 if ($objMod->isCoreOrExternalModule() == 'external' || preg_match('/development|experimental|deprecated/i', $version)) {
1064 $versiontrans .= $objMod->getVersion(1);
1065 }
1066
1067 if ($objMod->isCoreOrExternalModule() == 'external' && ($action == 'checklastversion' || getDolGlobalString('CHECKLASTVERSION_EXTERNALMODULE'))) {
1068 // Setting CHECKLASTVERSION_EXTERNALMODULE to on is a bad practice to activate a check on an external access during the building of the admin page.
1069 // 1 external module can hang the application.
1070 // Adding a cron job could be a good idea: see DolibarrModules::checkForUpdate()
1071 $checkRes = $objMod->checkForUpdate();
1072 if ($checkRes > 0) {
1073 setEventMessages($objMod->getName().' : '.preg_replace('/[^a-z0-9_\.\-\s]/i', '', $versiontrans).' -> '.preg_replace('/[^a-z0-9_\.\-\s]/i', '', $objMod->lastVersion), null, 'warnings');
1074 } elseif ($checkRes < 0) {
1075 setEventMessages($objMod->getName().' '.$langs->trans('CheckVersionFail'), null, 'errors');
1076 }
1077 }
1078
1079 if ($objMod->isCoreOrExternalModule() == 'external' && $action == 'checklastversion' && !getDolGlobalString('DISABLE_CHECK_ON_MALWARE_MODULES')) {
1080 $checkRes = $objMod->checkForCompliance(); // Check if module is reported as non compliant with Dolibarr rules and law
1081 if (!is_numeric($checkRes) && $checkRes != '') {
1082 $langs->load("errors");
1083 setEventMessages($objMod->getName().' : '.$langs->trans($checkRes), null, 'errors');
1084 }
1085 }
1086
1087 // Define imginfo
1088 $imginfo = "info";
1089 if ($objMod->isCoreOrExternalModule() == 'external') {
1090 $imginfo = "info_black";
1091 }
1092
1093 $codeenabledisable = '';
1094 $codetoconfig = '';
1095
1096 // Force disable of module disabled into session (for demo for example)
1097 if (in_array($modulenameshort, $disabled_modules)) {
1098 $objMod->disabled = true;
1099 }
1100
1101 // Activate/Disable and Setup (2 columns)
1102 if (getDolGlobalString($const_name)) { // If module is already activated
1103 // Set $codeenabledisable
1104 $disableSetup = 0;
1105 if (!empty($arrayofwarnings[$modName])) {
1106 $codeenabledisable .= '<!-- This module has a warning to show when we activate it (note: your country is '.$mysoc->country_code.') -->'."\n";
1107 }
1108
1109 if (!empty($objMod->disabled)) {
1110 $codeenabledisable .= $langs->trans("Disabled");
1111 } elseif (is_object($objMod)
1112 && (!empty($objMod->always_enabled) || ((isModEnabled('multicompany') && $objMod->core_enabled) && ($user->entity || $conf->entity != 1)))) {
1113 // @phan-suppress-next-line PhanUndeclaredMethod
1114 if (method_exists($objMod, 'alreadyUsed') && $objMod->alreadyUsed()) {
1115 $codeenabledisable .= $langs->trans("Used");
1116 } else {
1117 $codeenabledisable .= img_picto($langs->trans("Required"), 'switch_on', '', 0, 0, 0, '', 'opacitymedium valignmiddle');
1118 //print $langs->trans("Required");
1119 }
1120 if (isModEnabled('multicompany') && $user->entity) {
1121 $disableSetup++;
1122 }
1123 } else {
1124 // @phan-suppress-next-line PhanUndeclaredMethod
1125 if (is_object($objMod) && !empty($objMod->warnings_unactivation[$mysoc->country_code]) && method_exists($objMod, 'alreadyUsed') && $objMod->alreadyUsed()) {
1126 $codeenabledisable .= '<a class="reposition valignmiddle" href="'.$_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&amp;token='.newToken().'&amp;module_position='.$module_position.'&amp;action=reset_confirm&amp;confirm_message_code='.urlencode($objMod->warnings_unactivation[$mysoc->country_code]).'&amp;value='.$modName.'&amp;mode='.$mode.$param.'">';
1127 $codeenabledisable .= img_picto($langs->trans("Activated").($warningstring ? ' '.$warningstring : ''), 'switch_on');
1128 $codeenabledisable .= '</a>';
1129 if (getDolGlobalInt("MAIN_FEATURES_LEVEL") > 1) {
1130 $codeenabledisable .= '&nbsp;';
1131 $codeenabledisable .= '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&amp;token='.newToken().'&amp;module_position='.$module_position.'&amp;action=reload_confirm&amp;value='.$modName.'&amp;mode='.$mode.'&amp;confirm=yes'.$param.'">';
1132 $codeenabledisable .= img_picto($langs->trans("Reload"), 'refresh', 'class="opacitymedium"');
1133 $codeenabledisable .= '</a>';
1134 }
1135 } else {
1136 $codeenabledisable .= '<a class="reposition valignmiddle" href="'.$_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&amp;token='.newToken().'&amp;module_position='.$module_position.'&amp;action=reset&amp;value='.$modName.'&amp;mode='.$mode.'&amp;confirm=yes'.$param.'">';
1137 $codeenabledisable .= img_picto($langs->trans("Activated").($warningstring ? ' '.$warningstring : ''), 'switch_on');
1138 $codeenabledisable .= '</a>';
1139 if (getDolGlobalInt("MAIN_FEATURES_LEVEL") > 1) {
1140 $codeenabledisable .= '&nbsp;';
1141 $codeenabledisable .= '<a class="reposition" href="'.$_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&amp;token='.newToken().'&amp;module_position='.$module_position.'&amp;action=reload&amp;value='.$modName.'&amp;mode='.$mode.'&amp;confirm=yes'.$param.'">';
1142 $codeenabledisable .= img_picto($langs->trans("Reload"), 'refresh', 'class="opacitymedium"');
1143 $codeenabledisable .= '</a>';
1144 }
1145 }
1146 }
1147
1148 // Set $codetoconfig
1149 if (!empty($objMod->config_page_url) && !$disableSetup) {
1150 $backtourlquery = [];
1151 if ($search_keyword != '') {
1152 $backtourlquery += ['search_keyword' => $search_keyword]; // No urlencode here, done later
1153 }
1154 if ($search_nature > -1) {
1155 $backtourlquery += ['search_nature' => $search_nature]; // No urlencode here, done later
1156 }
1157 if ($search_version > -1) {
1158 $backtourlquery += ['search_version' => $search_version]; // No urlencode here, done later
1159 }
1160 if ($search_status > -1) {
1161 $backtourlquery += ['search_status' => $search_status]; // No urlencode here, done later
1162 }
1163 $backtourl = dolBuildUrl($_SERVER["PHP_SELF"], $backtourlquery);
1164
1165 $regs = array();
1166 $query = [
1167 'save_lastsearch_values' => 1,
1168 'backtopage' => $backtourl,
1169 ];
1170 if (is_array($objMod->config_page_url)) {
1171 $i = 0;
1172 foreach ($objMod->config_page_url as $page) {
1173 $urlpage = $page;
1174 if ($i++) {
1175 $codetoconfig .= '<a href="'.$urlpage.'" title="'.$langs->trans($page).'">'.img_picto(ucfirst($page), "setup").'</a>';
1176 // print '<a href="'.$page.'">'.ucfirst($page).'</a>&nbsp;';
1177 } else {
1178 if (preg_match('/^([^@]+)@([^@]+)$/i', $urlpage, $regs)) {
1179 $urltouse = dol_buildpath('/'.$regs[2].'/admin/'.$regs[1], 1);
1180 $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>';
1181 } else {
1182 $urltouse = $urlpage;
1183 $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>';
1184 }
1185 }
1186 }
1187 } elseif (preg_match('/^([^@]+)@([^@]+)$/i', (string) $objMod->config_page_url, $regs)) {
1188 $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>';
1189 } else {
1190 $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>';
1191 }
1192 } else {
1193 $codetoconfig .= img_picto($langs->trans("NothingToSetup"), "setup", 'class="opacitytransp" style="padding-right: 6px"', 0, 0, 0, '', 'fa-15');
1194 }
1195 } else { // Module not yet activated
1196 // Set $codeenabledisable
1197 if (!empty($objMod->always_enabled)) {
1198 // A 'always_enabled' module should not never be disabled. If this happen, we keep a link to re-enable it.
1199 $codeenabledisable .= '<!-- Message to show: an always_enabled module has been disabled -->'."\n";
1200 $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.'"';
1201 $codeenabledisable .= '>';
1202 $codeenabledisable .= img_picto($langs->trans("Disabled"), 'switch_off');
1203 $codeenabledisable .= "</a>\n";
1204 } elseif (!empty($objMod->disabled)) {
1205 $codeenabledisable .= $langs->trans("Disabled");
1206 } else {
1207 // Module qualified for activation
1208 $warningmessage = '';
1209 $disableCancel = 0;
1210
1211 if (!empty($arrayofwarnings[$modName])) {
1212 $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";
1213 foreach ($arrayofwarnings[$modName] as $keycountry => $cursorwarningmessage) {
1214 if (preg_match('/^always/', $keycountry) || ($mysoc->country_code && preg_match('/^'.$mysoc->country_code.'/', $keycountry))) {
1215 if (!is_array($cursorwarningmessage)) {
1216 $cursorwarningmessage = array($cursorwarningmessage);
1217 }
1218 foreach ($cursorwarningmessage as $messagetoshow) {
1219 if (preg_match('/:1$/', $messagetoshow)) {
1220 $disableCancel = 1;
1221 }
1222 $messagetoshow = preg_replace('/:1$/', '', $messagetoshow);
1223
1224 // TODO Use a replacement instead of always adding the module name and the country code to the string message ?
1225 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans($messagetoshow, $objMod->getName(), $mysoc->country_code);
1226 }
1227 }
1228 }
1229 }
1230 if ($objMod->isCoreOrExternalModule() == 'external' && !empty($arrayofwarningsext)) {
1231 $codeenabledisable .= '<!-- This module is an external module and it may have a warning to show (note: your country is '.$mysoc->country_code.') -->'."\n";
1232 foreach ($arrayofwarningsext as $keymodule => $arrayofwarningsextbycountry) {
1233 $keymodulelowercase = strtolower(preg_replace('/^mod/', '', $keymodule));
1234 if (preg_match('/^always/', $keymodulelowercase) || in_array($keymodulelowercase, $conf->modules)) { // If module that trigger the warning is on
1235 foreach ($arrayofwarningsextbycountry as $keycountry => $cursorwarningmessage) {
1236 if (preg_match('/^always/', $keycountry) || ($mysoc->country_code && preg_match('/^'.$mysoc->country_code.'/', $keycountry))) {
1237 if (!is_array($cursorwarningmessage)) {
1238 $cursorwarningmessage = array($cursorwarningmessage);
1239 }
1240 foreach ($cursorwarningmessage as $messagetoshow) {
1241 // TODO Use replacement instead of always adding param module name to enable and country code to the string message and triggering module
1242 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans($messagetoshow, $objMod->getName(), $mysoc->country_code, $modules[$keymodule]->getName());
1243 }
1244 $warningmessage .= ($warningmessage ? "\n" : "").($warningmessage ? "\n" : "").$langs->trans("Module").' : '.$objMod->getName();
1245 if (!empty($objMod->editor_name)) {
1246 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans("Publisher").' : '.$objMod->editor_name;
1247 }
1248 if ($keymodulelowercase != 'always') {
1249 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans("ModuleTriggeringThisWarning").' : '.$modules[$keymodule]->getName();
1250 }
1251 }
1252 }
1253 }
1254 }
1255 }
1256
1257 $urltogo = $_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&token='.newToken().'&module_position='.$module_position.'&action=set&token='.newToken().'&value='.$modName.'&mode='.$mode.$param;
1258 $popupWidth = 500;
1259 $popupHeight = 300;
1260 $codeenabledisable .= '<!-- Message to show: '.$warningmessage.' -->'."\n";
1261 $codeenabledisable .= '<a class="reposition" id="idqualified'.$objMod->numero.'" data-alreadyclicked="0" href="'.$urltogo.'"';
1262 if ($warningmessage) {
1263 $codeenabledisable .= ' onclick="return confirmDolibarr(\''.dol_escape_js($warningmessage).'\', \'idqualified'.$objMod->numero.'\', '.$popupWidth.', '.$popupHeight.','.$disableCancel.');"';
1264 }
1265 $codeenabledisable .= '>';
1266 $codeenabledisable .= img_picto($langs->trans("Disabled"), 'switch_off');
1267 $codeenabledisable .= "</a>\n";
1268 }
1269
1270 // Set $codetoconfig
1271 $codetoconfig .= img_picto($langs->trans("NothingToSetup"), "setup", 'class="opacitytransp" style="padding-right: 6px"');
1272 }
1273
1274 if ($mode == 'commonkanban') {
1275 // Output Kanban
1276 print $objMod->getKanbanView($codeenabledisable, $codetoconfig);
1277 } else {
1278 print '<tr class="oddeven'.($warningstring ? ' info-box-content-warning' : '').'">'."\n";
1279 if (getDolGlobalString('MAIN_MODULES_SHOW_LINENUMBERS')) {
1280 print '<td class="width50">'.$linenum.'</td>';
1281 }
1282
1283 // Picto + Name of module
1284 print ' <td class="tdoverflowmax200 minwidth200imp" title="'.dol_escape_htmltag($objMod->getName()).'">';
1285 $alttext = '';
1286 //if (is_array($objMod->need_dolibarr_version)) $alttext.=($alttext?' - ':'').'Dolibarr >= '.join('.',$objMod->need_dolibarr_version);
1287 //if (is_array($objMod->phpmin)) $alttext.=($alttext?' - ':'').'PHP >= '.join('.',$objMod->phpmin);
1288 if (!empty($objMod->picto)) {
1289 if (preg_match('/^\//i', $objMod->picto)) {
1290 print img_picto($alttext, $objMod->picto, 'class="valignmiddle pictomodule paddingrightonly"', 1);
1291 } else {
1292 print img_object($alttext, $objMod->picto, 'class="valignmiddle pictomodule paddingrightonly"');
1293 }
1294 } else {
1295 print img_object($alttext, 'generic', 'class="valignmiddle paddingrightonly"');
1296 }
1297 print ' <span class="valignmiddle">'.$objMod->getName().'</span>';
1298 print "</td>\n";
1299
1300 // Desc
1301 print '<td class="valignmiddle tdoverflowmax300 minwidth200imp opacitylow">';
1302 print nl2br($objMod->getDesc());
1303 print "</td>\n";
1304
1305 // Help
1306 print '<td class="center nowrap" style="width: 82px;">';
1307 print '<a href="javascript:document_preview(\''.DOL_URL_ROOT.'/admin/modulehelp.php?id='.((int) $objMod->numero).'\',\'text/html\',\''.dol_escape_js($langs->trans("Module")).'\')">';
1308 print img_picto(($objMod->isCoreOrExternalModule() == 'external' ? $langs->trans("ExternalModule").' - ' : '').$langs->trans("ClickToShowDescription"), $imginfo, '', 0, 0, 0, '', 'purple');
1309 print '</a>';
1310 print ($timestoinit[$modName] > 500 ? img_picto($langs->trans('InitModuleIsSlow'), 'fa-exclamation-circle') : '');
1311 print '</td>';
1312
1313 // Version
1314 print '<td class="center nowrap width150" title="'.dol_escape_htmltag(dol_string_nohtmltag($versiontrans)).'">';
1315 if ($objMod->needUpdate) {
1316 $versionTitle = $langs->trans('ModuleUpdateAvailable').' : '.$objMod->lastVersion;
1317 print '<span class="badge badge-warning classfortooltip" title="'.dol_escape_htmltag($versionTitle).'">'.$versiontrans.'</span>';
1318 } else {
1319 print $versiontrans;
1320 }
1321 print "</td>\n";
1322
1323 // Link enable/disable
1324 print '<td class="center valignmiddle left nowraponall" width="60px">';
1325 print $codeenabledisable;
1326 print "</td>\n";
1327
1328 // Link config
1329 print '<td class="tdsetuppicto right valignmiddle" width="60px">';
1330 print $codetoconfig;
1331 print '</td>';
1332
1333 print "</tr>\n";
1334 }
1335 if ($objMod->needUpdate) {
1336 $foundoneexternalmodulewithupdate++;
1337 }
1338 }
1339
1340 if ($action == 'checklastversion') {
1341 if ($foundoneexternalmodulewithupdate) {
1342 setEventMessages($langs->trans("ModuleUpdateAvailable"), null, 'warnings', '', 0, 1);
1343 } else {
1344 setEventMessages($langs->trans("NoExternalModuleWithUpdate"), null, 'mesgs');
1345 }
1346 }
1347
1348 if ($oldfamily) {
1349 if ($mode == 'commonkanban') {
1350 print '</div>';
1351 } else {
1352 print "</table>\n";
1353 print '</div>';
1354 }
1355 }
1356
1357 if (!$atleastonequalified) {
1358 print '<br><span class="opacitymedium">'.$langs->trans("NoDeployedModulesFoundWithThisSearchCriteria").'</span><br><br>';
1359 }
1360
1361 print dol_get_fiche_end();
1362
1363 print '<br>';
1364
1365 // Show warning about external users
1366 print info_admin(showModulesExludedForExternal($modules))."\n";
1367
1368 print '</form>';
1369}
1370
1371if ($mode == 'marketplace') {
1372 print dol_get_fiche_head($head, $mode, '', -1);
1373
1374 print $deschelp;
1375
1376 print '<br>';
1377
1378 print '<!-- summary of sources -->';
1379
1380 // Marketplace and community modules
1381 print '<div class="div-table-responsive-no-min">';
1382 print '<table summary="list_of_modules" class="noborder centpercent">'."\n";
1383 print '<tr class="liste_titre">'."\n";
1384 print '<td colspan="2">'.$form->textwithpicto($langs->trans("ModuleProviderSites"), $langs->trans("WebSiteDesc")).'</td>';
1385 print '<td class="hideonsmartphone">';
1386 print '</td>';
1387 print '<td></td>';
1388 print '</tr>';
1389
1390
1391 // Source Community github
1392 $url = 'https://github.com/Dolibarr/dolibarr-community-modules';
1393
1394 print '<tr class="oddeven nohover" height="100">'."\n";
1395 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>';
1396 print '<td class="minwidth500imp smallonsmartphone"><span class="opacitymedium">'.$langs->trans("CommunityModulesDesc").'</span><br>';
1397 print img_picto('', 'url', 'class="pictofixedwidth"').'<a href="'.$url.'" target="_blank" rel="noopener noreferrer external">'.$url.'</a></td>';
1398 print '<td>';
1399 print ajax_constantonoff('MAIN_ENABLE_EXTERNALMODULES_COMMUNITY', array(), null, 0, 0, 1);
1400 print '</td>';
1401 print '<td class="center">';
1402 if (!getDolGlobalString('MAIN_DISABLE_EXTERNALMODULES_COMMUNITY') && getDolGlobalInt('MAIN_ENABLE_EXTERNALMODULES_COMMUNITY')) {
1403 $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;
1404 $messagetoadd .= ' (Date: '.dol_print_date(dol_filemtime($remotestore->cache_file), 'dayhour', 'tzuserrel').')</span>';
1405 if ($remotestore->githubFileError) {
1406 $messagetoadd .= '<br><span class="error small">'.$remotestore->githubFileError.'</span>';
1407 }
1408 print $remotestore->libStatus($remotestore->githubFileStatus, 2, $messagetoadd);
1409 }
1410 print '</td>';
1411 print '</tr>';
1412
1413
1414 // Source Marketplace DoliStore
1415 $url = 'https://www.dolistore.com';
1416
1417 print '<tr class="oddeven nohover" height="100">'."\n";
1418 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>';
1419 print '<td class="minwidth500imp smallonsmartphone"><span class="opacitymedium">'.$langs->trans("DoliStoreDesc").'</span><br>';
1420 print img_picto('', 'url', 'class="pictofixedwidth"').'<a href="'.$url.'" target="_blank" rel="noopener noreferrer external">'.$url.'</a></td>';
1421 print '<td>';
1422 print ajax_constantonoff('MAIN_ENABLE_EXTERNALMODULES_DOLISTORE', array(), null, 0, 0, 1);
1423 print '</td>';
1424 print '<td class="center">';
1425 if (!getDolGlobalString('MAIN_DISABLE_EXTERNALMODULES_DOLISTORE') && getDolGlobalInt('MAIN_ENABLE_EXTERNALMODULES_DOLISTORE')) {
1426 $messagetoadd = '<br><span class="small">';
1427 if ($remotestore->dolistoreApiStatus <= 0) {
1428 $messagetoadd = '<br>'.$remotestore->dolistoreApiError.'<br>Failed to get answer of remote API server<br>';
1429 }
1430
1431 $messagetoadd .= '<br>Using Shop address MAIN_MODULE_DOLISTORE_SHOP_URL = '.$remotestore->shop_url;
1432 $messagetoadd .= '<br>Using Remote API address MAIN_MODULE_DOLISTORE_API_URL = '.$remotestore->dolistore_api_url;
1433 $messagetoadd .= '<br>Using API public key MAIN_MODULE_DOLISTORE_API_KEY = '.$remotestore->dolistore_api_key;
1434 // Add basic auth if needed
1435 $basicAuthLogin = getDolGlobalString('MAIN_MODULE_DOLISTORE_BASIC_LOGIN');
1436 $basicAuthPassword = getDolGlobalString('MAIN_MODULE_DOLISTORE_BASIC_PASSWORD');
1437 if ($basicAuthLogin) {
1438 $messagetoadd .= '<br>Using basic auth login: base64('.$basicAuthLogin.':'.$basicAuthPassword.')';
1439 }
1440 $messagetoadd .= '</span>';
1441
1442 print $remotestore->libStatus($remotestore->dolistoreApiStatus, 2, $messagetoadd);
1443 }
1444 print '</td>';
1445 print '</tr>';
1446
1447 print "</table>\n";
1448 print '</div>';
1449
1450 print dol_get_fiche_end();
1451
1452 print '<br>';
1453
1454 if ($remotestore->numberOfProviders > 0) {
1455 // $options is array with filter criteria
1456 $nbmaxtoshow = $options['per_page'];
1457 $options['per_page']++;
1458
1459 //$remotestore->getRemoteCategories();
1460 //$remotestore->getRemoteProducts($options);
1461
1462 //print '<span class="opacitymedium hideonsmartphone">'.$langs->trans('DOLISTOREdescriptionLong').'</span><br><br>';
1463
1464 $categories_tree = $remotestore->getCategories($options['categorie']); // Call API to get the categories
1465
1466 $products_list = $remotestore->getProducts($options); // Get list of product from all sources
1467
1468 $previouslink = $remotestore->get_previous_link();
1469
1470 $nextlink = $remotestore->get_next_link();
1471
1472
1473 print '<div class="liste_titre liste_titre_bydiv centpercent"><div class="">';
1474
1475 print '<form method="POST" class="centpercent" id="searchFormList" action="'.$remotestore->url.'">'; ?>
1476 <input type="hidden" name="token" value="<?php echo newToken(); ?>">
1477 <input type="hidden" name="mode" value="marketplace">
1478 <input type="hidden" name="page_y" value="">
1479 <div class="divsearchfield">
1480 <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">
1481 </div>
1482 <div class="divsearchfield">
1483 <input name="buttonsubmit" class="button buttongen reposition" value="<?php echo $langs->trans('Search') ?>" type="submit">
1484 <?php
1485 if ($search_keyword !== '') {
1486 print '<a class="buttonreset reposition" href="'.$_SERVER["PHP_SELF"].'?mode=marketplace">'.$langs->trans('Reset').'</a>';
1487 } else {
1488 print $form->textwithpicto('', $langs->trans('DOLISTOREdescriptionLong'));
1489 }
1490 ?>
1491 &nbsp;
1492 </div>
1493 <?php
1494 $totalnboflines = '<span class="product-count opacitymedium paddingleft">';
1495 $totalnboflines .= $langs->trans("itemFound", $remotestore->numberTotalOfProducts);
1496 $totalnboflines .= '</span>';
1497
1498 print $totalnboflines;
1499 print $remotestore->getPagination();
1500 print '</form>';
1501
1502 print '</div>';
1503 print '<div class="clearboth"></div>';
1504 print '</div>';
1505 ?>
1506 <?php if (!empty($categories_tree)) { ?>
1507 <div id="category-tree-left" class="paddingtop">
1508 <ul class="tree">
1509 <?php
1510 print $categories_tree; ?>
1511 </ul>
1512 </div>
1513 <?php } ?>
1514
1515 <div id="listing-content" class="div-table-responsive" <?php if (empty($categories_tree)) { ?>style="width:100%;"<?php } ?>>
1516 <table summary="list_of_modules" id="list_of_modules" class="productlist centpercent">
1517 <tbody id="listOfModules">
1518 <!-- $product_list is $remotestore->getProducts($options) done previously -->
1519 <?php print $products_list; ?>
1520 </tbody>
1521 </table>
1522 </div>
1523 <div style="clear: both;"></div>
1524 <div><?php print $remotestore->getPagination(); ?></div>
1525 <?php
1526 }
1527}
1528
1529
1530// Form to install an external module
1531
1532if ($mode == 'deploy') {
1533 print dol_get_fiche_head($head, $mode, '', -1);
1534
1535 $fullurl = '<a href="'.$urldolibarrmodules.'" target="_blank" rel="noopener noreferrer">'.$urldolibarrmodules.'</a>';
1536 $message = '';
1537 if ($allowonlineinstall) {
1538 if (!in_array('/custom', explode(',', $dolibarr_main_url_root_alt))) {
1539 $message = info_admin($langs->trans("ConfFileMustContainCustom", DOL_DOCUMENT_ROOT.'/custom', DOL_DOCUMENT_ROOT));
1540 $allowfromweb = -1;
1541 } else {
1542 if ($dirins_ok) {
1543 if (!is_writable(dol_osencode($dirins))) {
1544 $langs->load("errors");
1545 $message = info_admin($langs->trans("ErrorFailedToWriteInDir", $dirins), 0, 0, '1', 'warning');
1546 $allowfromweb = 0;
1547 }
1548 } else {
1549 $message = info_admin($langs->trans("NotExistsDirect", $dirins).$langs->trans("InfDirAlt").$langs->trans("InfDirExample"));
1550 $allowfromweb = 0;
1551 }
1552 }
1553 } else {
1554 if (getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US')) {
1555 // Show clean message
1556 if (!is_numeric(getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US'))) {
1557 $message = info_admin($langs->trans(getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US')), 0, 0, 'warning');
1558 } else {
1559 $message = info_admin($langs->trans('InstallModuleFromWebHasBeenDisabledContactUs'), 0, 0, 'warning');
1560 }
1561 } else {
1562 // Show technical message
1563 $message = info_admin($langs->trans("InstallModuleFromWebHasBeenDisabledByFile", $dolibarrdataroot.'/installmodules.lock'), 0, 0, 'warning');
1564 }
1565 $allowfromweb = 0;
1566 }
1567
1568 print $deschelp;
1569
1570 if ($allowfromweb < 1) {
1571 print $langs->trans("SomethingMakeInstallFromWebNotPossible");
1572 print $message;
1573 //print $langs->trans("SomethingMakeInstallFromWebNotPossible2");
1574 print '<br>';
1575 }
1576
1577 // $allowfromweb = -1 if installation or setup not correct, 0 if not allowed, 1 if allowed
1578 if ($allowfromweb >= 0) {
1579 if ($allowfromweb == 1) {
1580 //print $langs->trans("ThisIsProcessToFollow").'<br>';
1581 } else {
1582 print '<br>';
1583
1584 print $langs->trans("ThisIsAlternativeProcessToFollow").'<br>';
1585 print '<b>'.$langs->trans("StepNb", 1).'</b>: ';
1586 print str_replace('{s1}', $fullurl, $langs->trans("FindPackageFromWebSite", '{s1}')).'<br>';
1587 print '<b>'.$langs->trans("StepNb", 2).'</b>: ';
1588 print str_replace('{s1}', $fullurl, $langs->trans("DownloadPackageFromWebSite", '{s1}')).'<br>';
1589 print '<b>'.$langs->trans("StepNb", 3).'</b>: ';
1590 }
1591
1592 if ($allowfromweb == 1) {
1593 print '<form enctype="multipart/form-data" method="POST" class="noborder" action="'.$_SERVER["PHP_SELF"].'" name="forminstall">';
1594 print '<input type="hidden" name="token" value="'.newToken().'">';
1595 print '<input type="hidden" name="action" value="install">';
1596 print '<input type="hidden" name="mode" value="deploy">';
1597
1598 print $langs->trans("YouCanSubmitFile").'<br><br><br>';
1599
1600 print '<span class="opacitymedium"><input class="paddingright" type="checkbox" name="checkforcompliance" id="checkforcompliance"'.(getDolGlobalString('DISABLE_CHECK_ON_MALWARE_MODULES') ? ' disabled="disabled"' : 'checked="checked"').'>';
1601 print '<label for="checkforcompliance">'.$form->textwithpicto($langs->trans("CheckIfModuleIsNotBlackListed"), $langs->trans("CheckIfModuleIsNotBlackListedHelp").'<br><br>'.DolibarrModules::URL_FOR_BLACKLISTED_MODULES).'</label>';
1602 print '</span><br><br>';
1603
1604 $max = getDolGlobalString('MAIN_UPLOAD_DOC'); // In Kb
1605 $maxphp = @ini_get('upload_max_filesize'); // In unknown
1606 if (preg_match('/k$/i', $maxphp)) {
1607 $maxphp = preg_replace('/k$/i', '', $maxphp);
1608 $maxphp *= 1;
1609 }
1610 if (preg_match('/m$/i', $maxphp)) {
1611 $maxphp = preg_replace('/m$/i', '', $maxphp);
1612 $maxphp *= 1024;
1613 }
1614 if (preg_match('/g$/i', $maxphp)) {
1615 $maxphp = preg_replace('/g$/i', '', $maxphp);
1616 $maxphp *= 1024 * 1024;
1617 }
1618 if (preg_match('/t$/i', $maxphp)) {
1619 $maxphp = preg_replace('/t$/i', '', $maxphp);
1620 $maxphp *= 1024 * 1024 * 1024;
1621 }
1622 $maxphp2 = @ini_get('post_max_size'); // In unknown
1623 if (preg_match('/k$/i', $maxphp2)) {
1624 $maxphp2 = preg_replace('/k$/i', '', $maxphp2);
1625 $maxphp2 *= 1;
1626 }
1627 if (preg_match('/m$/i', $maxphp2)) {
1628 $maxphp2 = preg_replace('/m$/i', '', $maxphp2);
1629 $maxphp2 *= 1024;
1630 }
1631 if (preg_match('/g$/i', $maxphp2)) {
1632 $maxphp2 = preg_replace('/g$/i', '', $maxphp2);
1633 $maxphp2 *= 1024 * 1024;
1634 }
1635 if (preg_match('/t$/i', $maxphp2)) {
1636 $maxphp2 = preg_replace('/t$/i', '', $maxphp2);
1637 $maxphp2 *= 1024 * 1024 * 1024;
1638 }
1639 // Now $max and $maxphp and $maxphp2 are in Kb
1640 $maxmin = $max;
1641 $maxphptoshow = $maxphptoshowparam = '';
1642 if ($maxphp > 0) {
1643 $maxmin = min($max, $maxphp);
1644 $maxphptoshow = $maxphp;
1645 $maxphptoshowparam = 'upload_max_filesize';
1646 }
1647 if ($maxphp2 > 0) {
1648 $maxmin = min($max, $maxphp2);
1649 if ($maxphp2 < $maxphp) {
1650 $maxphptoshow = $maxphp2;
1651 $maxphptoshowparam = 'post_max_size';
1652 }
1653 }
1654
1655 if ($maxmin > 0) {
1656 print '<script type="text/javascript">
1657 $(document).ready(function() {
1658 jQuery("#fileinstall").on("change", function() {
1659 if(this.files[0].size > '.($maxmin * 1024).') {
1660 alert("'.dol_escape_js($langs->transnoentitiesnoconv("ErrorFileSizeTooLarge")).'");
1661 this.value = "";
1662 }
1663 });
1664 });
1665 </script>'."\n";
1666 // MAX_FILE_SIZE doit précéder le champ input de type file
1667 print '<input type="hidden" name="MAX_FILE_SIZE" value="'.($maxmin * 1024).'">';
1668 }
1669
1670 print '<input class="flat minwidth400" type="file" name="fileinstall" id="fileinstall">';
1671
1672 print '<input type="submit" name="send" value="'.dol_escape_htmltag($langs->trans("Upload")).'" class="button small">';
1673
1674 if (getDolGlobalString('MAIN_UPLOAD_DOC')) {
1675 if ($user->admin) {
1676 $langs->load('other');
1677 print ' ';
1678 print info_admin($langs->trans("ThisLimitIsDefinedInSetup", $max, $maxphptoshow, $maxphptoshowparam), 1);
1679 }
1680 } else {
1681 print ' ('.$langs->trans("UploadDisabled").')';
1682 }
1683
1684 print '</form>';
1685
1686 print '<br>';
1687 print '<br>';
1688
1689 print '<div class="center"><div class="logo_setup"></div></div>';
1690 } else {
1691 print $langs->trans("UnpackPackageInModulesRoot", $dirins).'<br>';
1692 print '<b>'.$langs->trans("StepNb", 4).'</b>: ';
1693 print $langs->trans("SetupIsReadyForUse", DOL_URL_ROOT.'/admin/modules.php?mainmenu=home', $langs->transnoentitiesnoconv("Home").' - '.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Modules")).'<br>';
1694 }
1695 }
1696
1697 print dol_get_fiche_end();
1698}
1699
1700if ($mode == 'develop') {
1701 print dol_get_fiche_head($head, $mode, '', -1);
1702
1703 print $deschelp;
1704
1705 print '<br>';
1706
1707 // Marketplace
1708 print '<div class="div-table-responsive-no-min">';
1709 print '<table summary="list_of_modules" class="noborder centpercent">'."\n";
1710 print '<tr class="liste_titre">'."\n";
1711 print '<td colspan="3">'.$langs->trans("DevelopYourModuleDesc").'</td>';
1712 print '</tr>';
1713
1714 print '<tr class="oddeven nohover" height="100">'."\n";
1715 print '<td class="center hideonsmartphone">';
1716 print '<div class="imgmaxheight50 logo_setup"></div>';
1717 print '</td>';
1718 print '<td class="minwidth500imp smallonsmartphone">'.$langs->trans("TryToUseTheModuleBuilder", $langs->transnoentitiesnoconv("ModuleBuilder")).'</td>';
1719 print '<td class="maxwidth300">';
1720 if (isModEnabled('modulebuilder')) {
1721 print $langs->trans("SeeTopRightMenu");
1722 } else {
1723 print '<span class="opacitymedium">'.$langs->trans("ModuleMustBeEnabledFirst", $langs->transnoentitiesnoconv("ModuleBuilder")).'</span>';
1724 }
1725 print '</td>';
1726 print '</tr>';
1727
1728 print '<tr class="oddeven nohover" height="100">'."\n";
1729 $url = 'https://partners.dolibarr.org';
1730 print '<td class="center hideonsmartphone">';
1731 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>';
1732 print '</td>';
1733 print '<td class="minwidth500imp smallonsmartphone">'.$langs->trans("DoliPartnersDesc").'</td>';
1734 print '<td><a href="'.$url.'" target="_blank" rel="noopener noreferrer external">';
1735 print img_picto('', 'url', 'class="pictofixedwidth"');
1736 print $url.'</a></td>';
1737 print '</tr>';
1738
1739 print "</table>\n";
1740 print '</div>';
1741
1742 print dol_get_fiche_end();
1743}
1744
1745// End of page
1746llxFooter();
1747$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
document_preview(file, type, title)
Function to show a document preview popup.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dol_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.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_warning($titlealt='default', $moreatt='', $morecss='pictowarning')
Show warning logo.
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='', $textonpictotooltip='')
Show information in HTML for admin users or standard users.
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
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_set_focus($selector)
Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
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)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
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
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
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.