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) && 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 $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 {
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 {
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) && substr($file, 0, 3) == 'mod' && substr($file, dol_strlen($file) - 10) == '.class.php') {
573 $modName = 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"]).'">';
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($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'));
808 $moreforfilter .= dolGetButtonTitleSeparator();
809 $moreforfilter .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.$param, '', ($mode == 'common' ? 2 : 1), array('morecss' => 'reposition'));
810 $moreforfilter .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=commonkanban'.$param, '', ($mode == 'commonkanban' ? 2 : 1), array('morecss' => 'reposition'));
811 $moreforfilter .= '</li></ul></div>';
812
813 $moreforfilter .= '<div class="divfilteralone colorbacktimesheet float valignmiddle nopaddingtopimp nopaddingbottomimp">';
814 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
815 $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')).'">';
816 $moreforfilter .= '</div>';
817 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
818 $moreforfilter .= $form->selectarray('search_nature', $arrayofnatures, dol_escape_htmltag($search_nature), $langs->trans('Origin'), 0, 0, '', 0, 0, 0, '', 'maxwidth250', 1);
819 $moreforfilter .= '</div>';
820
821 if (getDolGlobalInt('MAIN_FEATURES_LEVEL')) {
822 $array_version = array('stable' => $langs->transnoentitiesnoconv("Stable"));
823 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') < 0) {
824 $array_version['deprecated'] = $langs->trans("Deprecated");
825 }
826 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
827 $array_version['experimental'] = $langs->trans("Experimental");
828 }
829 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 1) {
830 $array_version['development'] = $langs->trans("Development");
831 }
832 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
833 $moreforfilter .= $form->selectarray('search_version', $array_version, $search_version, $langs->transnoentitiesnoconv('Version'), 0, 0, '', 0, 0, 0, '', 'maxwidth150', 1);
834 $moreforfilter .= '</div>';
835 }
836 $array_status = array('active' => $langs->transnoentitiesnoconv("Enabled"), 'disabled' => $langs->transnoentitiesnoconv("Disabled"));
837 $moreforfilter .= '<div class="divsearchfield paddingtop paddingbottom valignmiddle inline-block">';
838 $moreforfilter .= $form->selectarray('search_status', $array_status, $search_status, $langs->transnoentitiesnoconv('Status'), 0, 0, '', 0, 0, 0, '', 'maxwidth150', 1);
839 $moreforfilter .= '</div>';
840 $moreforfilter .= ' ';
841 $moreforfilter .= '<div class="divsearchfield valignmiddle inline-block">';
842 $moreforfilter .= '<input type="submit" name="buttonsubmit" class="button small nomarginleft" value="'.dolPrintHTMLForAttribute($langs->trans("Refresh")).'">';
843 if ($search_keyword || ($search_nature && $search_nature != '-1') || ($search_version && $search_version != '-1') || ($search_status && $search_status != '-1')) {
844 $moreforfilter .= ' ';
845 $moreforfilter .= '<input type="submit" name="buttonreset" class="buttonreset noborderall nomargintop nomarginbottom" value="'.dolPrintHTMLForAttribute($langs->trans("Reset")).'">';
846 }
847 $moreforfilter .= '</div>';
848 $moreforfilter .= '</div>';
849
850 $moreforfilter .= '</div>';
851
852 print $moreforfilter;
853 $parameters = array();
854 $reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
855 print $hookmanager->resPrint;
856
857 $moreforfilter = '';
858
859 print '<div class="clearboth"></div><br><br>';
860
861 $object = new stdClass();
862 $parameters = array();
863 $reshook = $hookmanager->executeHooks('insertExtraHeader', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
864 if ($reshook < 0) {
865 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
866 }
867
868 $disabled_modules = array();
869 if (!empty($_SESSION["disablemodules"])) {
870 $disabled_modules = explode(',', $_SESSION["disablemodules"]);
871 }
872
873 // Show list of modules
874 $oldfamily = '';
875 $foundoneexternalmodulewithupdate = 0;
876 $linenum = 0;
877 $atleastonequalified = 0;
878 $atleastoneforfamily = 0;
879
880 print '<script type="text/javascript">
881 jQuery(document).ready(function() {
882 jQuery(".modulefamilygroup").each(function() {
883 var $group = jQuery(this);
884 var $title = $group.find(".titre.inline-block").first();
885 var $nextContainer = $group.nextAll(".div-table-responsive, .box-flex-container").first();
886 if ($title.length && !$title.children(".modulefamilytoggleicon").length) {
887 $title.prepend("<i class=\"fa modulefamilytoggleicon paddingleft paddingleftright\"></i> ");
888 }
889 var $icon = $title.children(".modulefamilytoggleicon").first();
890 var isVisible = $nextContainer.is(":visible");
891 if ($icon.length && $nextContainer.length) {
892 $icon.toggleClass("fa-folder-open", isVisible);
893 $icon.toggleClass("fa-folder", !isVisible);
894 }
895 });
896
897 jQuery(document).on("click", ".modulefamilygroup", function() {
898 var $group = jQuery(this);
899 var $nextContainer = $group.nextAll(".div-table-responsive, .box-flex-container").first();
900 if ($nextContainer.length) {
901 var $icon = $group.find(".modulefamilytoggleicon").first();
902 var isVisible = $nextContainer.is(":visible");
903 $nextContainer.stop(true, true).slideToggle(150);
904 if ($icon.length) {
905 $icon.toggleClass("fa-folder-open", !isVisible);
906 $icon.toggleClass("fa-folder", isVisible);
907 }
908 }
909 });
910 });
911 </script>';
912
913 foreach ($orders as $key => $value) {
914 $linenum++;
915 $tab = explode('_', $value);
916 $familykey = $tab[1];
917 $module_position = $tab[2];
918
919 $modName = $filename[$key];
920
922 $objMod = $modules[$modName];
923
924 if (!is_object($objMod)) {
925 continue;
926 }
927
928 //print $objMod->name." - ".$key." - ".$objMod->version."<br>";
929 if ($mode == 'expdev' && $objMod->version != 'development' && $objMod->version != 'experimental') {
930 continue; // Discard if not for current tab
931 }
932
933 if (!$objMod->getName()) {
934 dol_syslog("Error for module ".$key." - Property name of module looks empty", LOG_WARNING);
935 continue;
936 }
937
938 $modulenameshort = strtolower(preg_replace('/^mod/i', '', get_class($objMod)));
939 $const_name = 'MAIN_MODULE_'.strtoupper(preg_replace('/^mod/i', '', get_class($objMod)));
940
941 // Check filters
942 $modulename = $objMod->getName();
943 $moduletechnicalname = $objMod->name;
944 $moduledesc = $objMod->getDesc();
945 $moduledesclong = $objMod->getDescLong();
946 $moduleauthor = $objMod->getPublisher();
947
948 // We discard showing according to filters
949 if ($search_keyword) {
950 $qualified = 0;
951 $search_keyword_array = explode(' ', $search_keyword);
952 $foundkeyword = 1;
953 foreach ($search_keyword_array as $word) {
954 if (!preg_match('/'.preg_quote($word, '/').'/i', $modulename)
955 && !preg_match('/'.preg_quote($word, '/').'/i', $moduletechnicalname)
956 && !($moduledesc && preg_match('/'.preg_quote($word, '/').'/i', $moduledesc))
957 && !($moduledesclong && preg_match('/'.preg_quote($word, '/').'/i', $moduledesclong))
958 && !($moduleauthor && preg_match('/'.preg_quote($word, '/').'/i', $moduleauthor))
959 ) {
960 $foundkeyword = 0;
961 }
962 }
963 if ($foundkeyword) {
964 $qualified = 1;
965 }
966 if (!$qualified) {
967 continue;
968 }
969 }
970 if ($search_status) {
971 if ($search_status == 'active' && !getDolGlobalString($const_name)) {
972 continue;
973 }
974 if ($search_status == 'disabled' && getDolGlobalString($const_name)) {
975 continue;
976 }
977 }
978 if ($search_nature) {
979 if (preg_match('/^external/', $search_nature) && $objMod->isCoreOrExternalModule() != 'external') {
980 continue;
981 }
982 $reg = array();
983 if (preg_match('/^external_(.*)$/', $search_nature, $reg)) {
984 //print $reg[1].'-'.dol_escape_htmltag($objMod->getPublisher());
985 $publisher = dol_escape_htmltag($objMod->getPublisher());
986 if ($reg[1] && dol_escape_htmltag($reg[1]) != $publisher) {
987 continue;
988 }
989 if (!$reg[1] && !empty($publisher)) {
990 continue;
991 }
992 }
993 if ($search_nature == 'core' && $objMod->isCoreOrExternalModule() == 'external') {
994 continue;
995 }
996 }
997 if ($search_version) {
998 if (($objMod->version == 'development' || $objMod->version == 'experimental' || preg_match('/deprecated/', $objMod->version)) && $search_version == 'stable') {
999 continue;
1000 }
1001 if ($objMod->version != 'development' && ($search_version == 'development')) {
1002 continue;
1003 }
1004 if ($objMod->version != 'experimental' && ($search_version == 'experimental')) {
1005 continue;
1006 }
1007 if (!preg_match('/deprecated/', $objMod->version) && ($search_version == 'deprecated')) {
1008 continue;
1009 }
1010 }
1011
1012 $atleastonequalified++;
1013
1014 // Load all language files of the qualified module
1015 if (isset($objMod->langfiles) && is_array($objMod->langfiles)) {
1016 foreach ($objMod->langfiles as $domain) {
1017 $langs->load($domain);
1018 }
1019 }
1020
1021 // Print a separator if we change family
1022 if ($familykey != $oldfamily) {
1023 if ($oldfamily) {
1024 print '</table></div><br>';
1025 }
1026
1027 $familytext = empty($familyinfo[$familykey]['label']) ? $familykey : $familyinfo[$familykey]['label'];
1028
1029 print load_fiche_titre($familytext, '', '', 0, '', 'modulefamilygroup');
1030
1031 if ($mode == 'commonkanban') {
1032 print '<div class="box-flex-container kanban">';
1033 } else {
1034 print '<div class="div-table-responsive">';
1035 print '<table class="tagtable liste" summary="list_of_modules">'."\n";
1036 }
1037
1038 $atleastoneforfamily = 0;
1039 }
1040
1041 $atleastoneforfamily++;
1042
1043 if ($familykey != $oldfamily) {
1044 $familytext = empty($familyinfo[$familykey]['label']) ? $familykey : $familyinfo[$familykey]['label'];
1045 $oldfamily = $familykey;
1046 }
1047
1048 // Version (with picto warning or not)
1049 $version = $objMod->getVersion(0);
1050 $versiontrans = '';
1051 $warningstring = '';
1052 if (preg_match('/development/i', $version)) {
1053 $warningstring = $langs->trans("Development");
1054 }
1055 if (preg_match('/experimental/i', $version)) {
1056 $warningstring = $langs->trans("Experimental");
1057 }
1058 if (preg_match('/deprecated/i', $version)) {
1059 $warningstring = $langs->trans("Deprecated");
1060 }
1061
1062 if ($objMod->isCoreOrExternalModule() == 'external' || preg_match('/development|experimental|deprecated/i', $version)) {
1063 $versiontrans .= $objMod->getVersion(1);
1064 }
1065
1066 if ($objMod->isCoreOrExternalModule() == 'external' && ($action == 'checklastversion' || getDolGlobalString('CHECKLASTVERSION_EXTERNALMODULE'))) {
1067 // Setting CHECKLASTVERSION_EXTERNALMODULE to on is a bad practice to activate a check on an external access during the building of the admin page.
1068 // 1 external module can hang the application.
1069 // Adding a cron job could be a good idea: see DolibarrModules::checkForUpdate()
1070 $checkRes = $objMod->checkForUpdate();
1071 if ($checkRes > 0) {
1072 setEventMessages($objMod->getName().' : '.preg_replace('/[^a-z0-9_\.\-\s]/i', '', $versiontrans).' -> '.preg_replace('/[^a-z0-9_\.\-\s]/i', '', $objMod->lastVersion), null, 'warnings');
1073 } elseif ($checkRes < 0) {
1074 setEventMessages($objMod->getName().' '.$langs->trans('CheckVersionFail'), null, 'errors');
1075 }
1076 }
1077
1078 if ($objMod->isCoreOrExternalModule() == 'external' && $action == 'checklastversion' && !getDolGlobalString('DISABLE_CHECK_ON_MALWARE_MODULES')) {
1079 $checkRes = $objMod->checkForCompliance(); // Check if module is reported as non compliant with Dolibarr rules and law
1080 if (!is_numeric($checkRes) && $checkRes != '') {
1081 $langs->load("errors");
1082 setEventMessages($objMod->getName().' : '.$langs->trans($checkRes), null, 'errors');
1083 }
1084 }
1085
1086 // Define imginfo
1087 $imginfo = "info";
1088 if ($objMod->isCoreOrExternalModule() == 'external') {
1089 $imginfo = "info_black";
1090 }
1091
1092 $codeenabledisable = '';
1093 $codetoconfig = '';
1094
1095 // Force disable of module disabled into session (for demo for example)
1096 if (in_array($modulenameshort, $disabled_modules)) {
1097 $objMod->disabled = true;
1098 }
1099
1100 // Activate/Disable and Setup (2 columns)
1101 if (getDolGlobalString($const_name)) { // If module is already activated
1102 // Set $codeenabledisable
1103 $disableSetup = 0;
1104 if (!empty($arrayofwarnings[$modName])) {
1105 $codeenabledisable .= '<!-- This module has a warning to show when we activate it (note: your country is '.$mysoc->country_code.') -->'."\n";
1106 }
1107
1108 if (!empty($objMod->disabled)) {
1109 $codeenabledisable .= $langs->trans("Disabled");
1110 } elseif (is_object($objMod)
1111 && (!empty($objMod->always_enabled) || ((isModEnabled('multicompany') && $objMod->core_enabled) && ($user->entity || $conf->entity != 1)))) {
1112 // @phan-suppress-next-line PhanUndeclaredMethod
1113 if (method_exists($objMod, 'alreadyUsed') && $objMod->alreadyUsed()) {
1114 $codeenabledisable .= $langs->trans("Used");
1115 } else {
1116 $codeenabledisable .= img_picto($langs->trans("Required"), 'switch_on', '', 0, 0, 0, '', 'opacitymedium valignmiddle');
1117 //print $langs->trans("Required");
1118 }
1119 if (isModEnabled('multicompany') && $user->entity) {
1120 $disableSetup++;
1121 }
1122 } else {
1123 // @phan-suppress-next-line PhanUndeclaredMethod
1124 if (is_object($objMod) && !empty($objMod->warnings_unactivation[$mysoc->country_code]) && method_exists($objMod, 'alreadyUsed') && $objMod->alreadyUsed()) {
1125 $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.'">';
1126 $codeenabledisable .= img_picto($langs->trans("Activated").($warningstring ? ' '.$warningstring : ''), 'switch_on');
1127 $codeenabledisable .= '</a>';
1128 if (getDolGlobalInt("MAIN_FEATURES_LEVEL") > 1) {
1129 $codeenabledisable .= '&nbsp;';
1130 $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.'">';
1131 $codeenabledisable .= img_picto($langs->trans("Reload"), 'refresh', 'class="opacitymedium"');
1132 $codeenabledisable .= '</a>';
1133 }
1134 } else {
1135 $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.'">';
1136 $codeenabledisable .= img_picto($langs->trans("Activated").($warningstring ? ' '.$warningstring : ''), 'switch_on');
1137 $codeenabledisable .= '</a>';
1138 if (getDolGlobalInt("MAIN_FEATURES_LEVEL") > 1) {
1139 $codeenabledisable .= '&nbsp;';
1140 $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.'">';
1141 $codeenabledisable .= img_picto($langs->trans("Reload"), 'refresh', 'class="opacitymedium"');
1142 $codeenabledisable .= '</a>';
1143 }
1144 }
1145 }
1146
1147 // Set $codetoconfig
1148 if (!empty($objMod->config_page_url) && !$disableSetup) {
1149 $backtourlquery = [];
1150 if ($search_keyword != '') {
1151 $backtourlquery += ['search_keyword' => $search_keyword]; // No urlencode here, done later
1152 }
1153 if ($search_nature > -1) {
1154 $backtourlquery += ['search_nature' => $search_nature]; // No urlencode here, done later
1155 }
1156 if ($search_version > -1) {
1157 $backtourlquery += ['search_version' => $search_version]; // No urlencode here, done later
1158 }
1159 if ($search_status > -1) {
1160 $backtourlquery += ['search_status' => $search_status]; // No urlencode here, done later
1161 }
1162 $backtourl = dolBuildUrl($_SERVER["PHP_SELF"], $backtourlquery);
1163
1164 $regs = array();
1165 $query = [
1166 'save_lastsearch_values' => 1,
1167 'backtopage' => $backtourl,
1168 ];
1169 if (is_array($objMod->config_page_url)) {
1170 $i = 0;
1171 foreach ($objMod->config_page_url as $page) {
1172 $urlpage = $page;
1173 if ($i++) {
1174 $codetoconfig .= '<a href="'.$urlpage.'" title="'.$langs->trans($page).'">'.img_picto(ucfirst($page), "setup").'</a>';
1175 // print '<a href="'.$page.'">'.ucfirst($page).'</a>&nbsp;';
1176 } else {
1177 if (preg_match('/^([^@]+)@([^@]+)$/i', $urlpage, $regs)) {
1178 $urltouse = dol_buildpath('/'.$regs[2].'/admin/'.$regs[1], 1);
1179 $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>';
1180 } else {
1181 $urltouse = $urlpage;
1182 $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>';
1183 }
1184 }
1185 }
1186 } elseif (preg_match('/^([^@]+)@([^@]+)$/i', (string) $objMod->config_page_url, $regs)) {
1187 $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>';
1188 } else {
1189 $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>';
1190 }
1191 } else {
1192 $codetoconfig .= img_picto($langs->trans("NothingToSetup"), "setup", 'class="opacitytransp" style="padding-right: 6px"', 0, 0, 0, '', 'fa-15');
1193 }
1194 } else { // Module not yet activated
1195 // Set $codeenabledisable
1196 if (!empty($objMod->always_enabled)) {
1197 // A 'always_enabled' module should not never be disabled. If this happen, we keep a link to re-enable it.
1198 $codeenabledisable .= '<!-- Message to show: an always_enabled module has been disabled -->'."\n";
1199 $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.'"';
1200 $codeenabledisable .= '>';
1201 $codeenabledisable .= img_picto($langs->trans("Disabled"), 'switch_off');
1202 $codeenabledisable .= "</a>\n";
1203 } elseif (!empty($objMod->disabled)) {
1204 $codeenabledisable .= $langs->trans("Disabled");
1205 } else {
1206 // Module qualified for activation
1207 $warningmessage = '';
1208 $disableCancel = 0;
1209
1210 if (!empty($arrayofwarnings[$modName])) {
1211 $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";
1212 foreach ($arrayofwarnings[$modName] as $keycountry => $cursorwarningmessage) {
1213 if (preg_match('/^always/', $keycountry) || ($mysoc->country_code && preg_match('/^'.$mysoc->country_code.'/', $keycountry))) {
1214 if (!is_array($cursorwarningmessage)) {
1215 $cursorwarningmessage = array($cursorwarningmessage);
1216 }
1217 foreach ($cursorwarningmessage as $messagetoshow) {
1218 if (preg_match('/:1$/', $messagetoshow)) {
1219 $disableCancel = 1;
1220 }
1221 $messagetoshow = preg_replace('/:1$/', '', $messagetoshow);
1222
1223 // TODO Use a replacement instead of always adding the module name and the country code to the string message ?
1224 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans($messagetoshow, $objMod->getName(), $mysoc->country_code);
1225 }
1226 }
1227 }
1228 }
1229 if ($objMod->isCoreOrExternalModule() == 'external' && !empty($arrayofwarningsext)) {
1230 $codeenabledisable .= '<!-- This module is an external module and it may have a warning to show (note: your country is '.$mysoc->country_code.') -->'."\n";
1231 foreach ($arrayofwarningsext as $keymodule => $arrayofwarningsextbycountry) {
1232 $keymodulelowercase = strtolower(preg_replace('/^mod/', '', $keymodule));
1233 if (preg_match('/^always/', $keymodulelowercase) || in_array($keymodulelowercase, $conf->modules)) { // If module that trigger the warning is on
1234 foreach ($arrayofwarningsextbycountry as $keycountry => $cursorwarningmessage) {
1235 if (preg_match('/^always/', $keycountry) || ($mysoc->country_code && preg_match('/^'.$mysoc->country_code.'/', $keycountry))) {
1236 if (!is_array($cursorwarningmessage)) {
1237 $cursorwarningmessage = array($cursorwarningmessage);
1238 }
1239 foreach ($cursorwarningmessage as $messagetoshow) {
1240 // TODO Use replacement instead of always adding param module name to enable and country code to the string message and triggering module
1241 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans($messagetoshow, $objMod->getName(), $mysoc->country_code, $modules[$keymodule]->getName());
1242 }
1243 $warningmessage .= ($warningmessage ? "\n" : "").($warningmessage ? "\n" : "").$langs->trans("Module").' : '.$objMod->getName();
1244 if (!empty($objMod->editor_name)) {
1245 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans("Publisher").' : '.$objMod->editor_name;
1246 }
1247 if ($keymodulelowercase != 'always') {
1248 $warningmessage .= ($warningmessage ? "\n" : "").$langs->trans("ModuleTriggeringThisWarning").' : '.$modules[$keymodule]->getName();
1249 }
1250 }
1251 }
1252 }
1253 }
1254 }
1255
1256 $urltogo = $_SERVER["PHP_SELF"].'?id='.$objMod->numero.'&token='.newToken().'&module_position='.$module_position.'&action=set&token='.newToken().'&value='.$modName.'&mode='.$mode.$param;
1257 $popupWidth = 600;
1258 $popupHeight = 300;
1259 $codeenabledisable .= '<!-- Message to show: '.$warningmessage.' -->'."\n";
1260 $codeenabledisable .= '<a class="reposition" id="idqualified'.$objMod->numero.'" data-alreadyclicked="0" href="'.$urltogo.'"';
1261 if ($warningmessage) {
1262 $codeenabledisable .= ' onclick="return confirmDolibarr(\''.dol_escape_js($warningmessage).'\', \'idqualified'.$objMod->numero.'\', '.$popupWidth.', '.$popupHeight.','.$disableCancel.');"';
1263 }
1264 $codeenabledisable .= '>';
1265 $codeenabledisable .= img_picto($langs->trans("Disabled"), 'switch_off');
1266 $codeenabledisable .= "</a>\n";
1267 }
1268
1269 // Set $codetoconfig
1270 $codetoconfig .= img_picto($langs->trans("NothingToSetup"), "setup", 'class="opacitytransp" style="padding-right: 6px"');
1271 }
1272
1273 if ($mode == 'commonkanban') {
1274 // Output Kanban
1275 print $objMod->getKanbanView($codeenabledisable, $codetoconfig);
1276 } else {
1277 print '<tr class="oddeven'.($warningstring ? ' info-box-content-warning' : '').'">'."\n";
1278 if (getDolGlobalString('MAIN_MODULES_SHOW_LINENUMBERS')) {
1279 print '<td class="width50">'.$linenum.'</td>';
1280 }
1281
1282 // Picto + Name of module
1283 print ' <td class="tdoverflowmax200 minwidth200imp" title="'.dol_escape_htmltag($objMod->getName()).'">';
1284 $alttext = '';
1285 //if (is_array($objMod->need_dolibarr_version)) $alttext.=($alttext?' - ':'').'Dolibarr >= '.join('.',$objMod->need_dolibarr_version);
1286 //if (is_array($objMod->phpmin)) $alttext.=($alttext?' - ':'').'PHP >= '.join('.',$objMod->phpmin);
1287 if (!empty($objMod->picto)) {
1288 if (preg_match('/^\//i', $objMod->picto)) {
1289 print img_picto($alttext, $objMod->picto, 'class="valignmiddle pictomodule paddingrightonly"', 1);
1290 } else {
1291 print img_object($alttext, $objMod->picto, 'class="valignmiddle pictomodule paddingrightonly"');
1292 }
1293 } else {
1294 print img_object($alttext, 'generic', 'class="valignmiddle paddingrightonly"');
1295 }
1296 print ' <span class="valignmiddle">'.$objMod->getName().'</span>';
1297 print "</td>\n";
1298
1299 // Desc
1300 print '<td class="valignmiddle tdoverflowmax300 minwidth200imp opacitylow">';
1301 print nl2br($objMod->getDesc());
1302 print "</td>\n";
1303
1304 // Help
1305 print '<td class="center nowrap" style="width: 82px;">';
1306 print '<a href="javascript:document_preview(\''.DOL_URL_ROOT.'/admin/modulehelp.php?id='.((int) $objMod->numero).'\',\'text/html\',\''.dol_escape_js($langs->trans("Module")).'\')">';
1307 print img_picto(($objMod->isCoreOrExternalModule() == 'external' ? $langs->trans("ExternalModule").' - ' : '').$langs->trans("ClickToShowDescription"), $imginfo, '', 0, 0, 0, '', 'purple');
1308 print '</a>';
1309 print($timestoinit[$modName] > 500 ? img_picto($langs->trans('InitModuleIsSlow'), 'fa-exclamation-circle') : '');
1310 print '</td>';
1311
1312 // Version
1313 print '<td class="center nowrap width150" title="'.dol_escape_htmltag(dol_string_nohtmltag($versiontrans)).'">';
1314 if ($objMod->needUpdate) {
1315 $versionTitle = $langs->trans('ModuleUpdateAvailable').' : '.$objMod->lastVersion;
1316 print '<span class="badge badge-warning classfortooltip" title="'.dol_escape_htmltag($versionTitle).'">'.$versiontrans.'</span>';
1317 } else {
1318 print $versiontrans;
1319 }
1320 print "</td>\n";
1321
1322 // Link enable/disable
1323 print '<td class="center valignmiddle left nowraponall" width="60px">';
1324 print $codeenabledisable;
1325 print "</td>\n";
1326
1327 // Link config
1328 print '<td class="tdsetuppicto right valignmiddle" width="60px">';
1329 print $codetoconfig;
1330 print '</td>';
1331
1332 print "</tr>\n";
1333 }
1334 if ($objMod->needUpdate) {
1335 $foundoneexternalmodulewithupdate++;
1336 }
1337 }
1338
1339 if ($action == 'checklastversion') {
1340 if ($foundoneexternalmodulewithupdate) {
1341 setEventMessages($langs->trans("ModuleUpdateAvailable"), null, 'warnings', '', 0, 1);
1342 } else {
1343 setEventMessages($langs->trans("NoExternalModuleWithUpdate"), null, 'mesgs');
1344 }
1345 }
1346
1347 if ($oldfamily) {
1348 if ($mode == 'commonkanban') {
1349 print '</div>';
1350 } else {
1351 print "</table>\n";
1352 print '</div>';
1353 }
1354 }
1355
1356 if (!$atleastonequalified) {
1357 print '<br><span class="opacitymedium">'.$langs->trans("NoDeployedModulesFoundWithThisSearchCriteria").'</span><br><br>';
1358 }
1359
1360 print dol_get_fiche_end();
1361
1362 print '<br>';
1363
1364 // Show warning about external users
1365 print info_admin(showModulesExludedForExternal($modules))."\n";
1366
1367 print '</form>';
1368}
1369
1370if ($mode == 'marketplace') {
1371 print dol_get_fiche_head($head, $mode, '', -1);
1372
1373 print $deschelp;
1374
1375 print '<br>';
1376
1377 print '<!-- summary of sources -->';
1378
1379 // Marketplace and community modules
1380 print '<div class="div-table-responsive-no-min">';
1381 print '<table summary="list_of_modules" class="noborder centpercent">'."\n";
1382 print '<tr class="liste_titre">'."\n";
1383 print '<td colspan="2">'.$form->textwithpicto($langs->trans("ModuleProviderSites"), $langs->trans("WebSiteDesc")).'</td>';
1384 print '<td class="hideonsmartphone">';
1385 print '</td>';
1386 print '<td></td>';
1387 print '</tr>';
1388
1389
1390 // Source Community github
1391 $url = 'https://github.com/Dolibarr/dolibarr-community-modules';
1392
1393 print '<tr class="oddeven nohover" height="100">'."\n";
1394 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>';
1395 print '<td class="minwidth500imp smallonsmartphone"><span class="opacitymedium">'.$langs->trans("CommunityModulesDesc").'</span><br>';
1396 print img_picto('', 'url', 'class="pictofixedwidth"').'<a href="'.$url.'" target="_blank" rel="noopener noreferrer external">'.$url.'</a></td>';
1397 print '<td>';
1398 print ajax_constantonoff('MAIN_ENABLE_EXTERNALMODULES_COMMUNITY', array(), null, 0, 0, 1);
1399 print '</td>';
1400 print '<td class="center">';
1401 if (!getDolGlobalString('MAIN_DISABLE_EXTERNALMODULES_COMMUNITY') && getDolGlobalInt('MAIN_ENABLE_EXTERNALMODULES_COMMUNITY')) {
1402 $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;
1403 $messagetoadd .= ' (Date: '.dol_print_date(dol_filemtime($remotestore->cache_file), 'dayhour', 'tzuserrel').')</span>';
1404 if ($remotestore->githubFileError) {
1405 $messagetoadd .= '<br><span class="error small">'.$remotestore->githubFileError.'</span>';
1406 }
1407 print $remotestore->libStatus($remotestore->githubFileStatus, 2, $messagetoadd);
1408 }
1409 print '</td>';
1410 print '</tr>';
1411
1412
1413 // Source Marketplace DoliStore
1414 $url = 'https://www.dolistore.com';
1415
1416 print '<tr class="oddeven nohover" height="100">'."\n";
1417 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>';
1418 print '<td class="minwidth500imp smallonsmartphone"><span class="opacitymedium">'.$langs->trans("DoliStoreDesc").'</span><br>';
1419 print img_picto('', 'url', 'class="pictofixedwidth"').'<a href="'.$url.'" target="_blank" rel="noopener noreferrer external">'.$url.'</a></td>';
1420 print '<td>';
1421 print ajax_constantonoff('MAIN_ENABLE_EXTERNALMODULES_DOLISTORE', array(), null, 0, 0, 1);
1422 print '</td>';
1423 print '<td class="center">';
1424 if (!getDolGlobalString('MAIN_DISABLE_EXTERNALMODULES_DOLISTORE') && getDolGlobalInt('MAIN_ENABLE_EXTERNALMODULES_DOLISTORE')) {
1425 $messagetoadd = '<br><span class="small">';
1426 if ($remotestore->dolistoreApiStatus <= 0) {
1427 $messagetoadd = '<br>'.$remotestore->dolistoreApiError.'<br>Failed to get answer of remote API server<br>';
1428 }
1429
1430 $messagetoadd .= '<br>Using Shop address MAIN_MODULE_DOLISTORE_SHOP_URL = '.$remotestore->shop_url;
1431 $messagetoadd .= '<br>Using Remote API address MAIN_MODULE_DOLISTORE_API_URL = '.$remotestore->dolistore_api_url;
1432 $messagetoadd .= '<br>Using API public key MAIN_MODULE_DOLISTORE_API_KEY = '.$remotestore->dolistore_api_key;
1433 // Add basic auth if needed
1434 $basicAuthLogin = getDolGlobalString('MAIN_MODULE_DOLISTORE_BASIC_LOGIN');
1435 $basicAuthPassword = getDolGlobalString('MAIN_MODULE_DOLISTORE_BASIC_PASSWORD');
1436 if ($basicAuthLogin) {
1437 $messagetoadd .= '<br>Using basic auth login: base64('.$basicAuthLogin.':'.$basicAuthPassword.')';
1438 }
1439 $messagetoadd .= '</span>';
1440
1441 print $remotestore->libStatus($remotestore->dolistoreApiStatus, 2, $messagetoadd);
1442 }
1443 print '</td>';
1444 print '</tr>';
1445
1446 print "</table>\n";
1447 print '</div>';
1448
1449 print dol_get_fiche_end();
1450
1451 print '<br>';
1452
1453 if ($remotestore->numberOfProviders > 0) {
1454 // $options is array with filter criteria
1455 $nbmaxtoshow = $options['per_page'];
1456 $options['per_page']++;
1457
1458 //$remotestore->getRemoteCategories();
1459 //$remotestore->getRemoteProducts($options);
1460
1461 //print '<span class="opacitymedium hideonsmartphone">'.$langs->trans('DOLISTOREdescriptionLong').'</span><br><br>';
1462
1463 $categories_tree = $remotestore->getCategories($options['categorie']); // Call API to get the categories
1464
1465 $products_list = $remotestore->getProducts($options); // Get list of product from all sources
1466
1467 $previouslink = $remotestore->get_previous_link();
1468
1469 $nextlink = $remotestore->get_next_link();
1470
1471
1472 print '<div class="liste_titre liste_titre_bydiv centpercent"><div class="">';
1473
1474 print '<form method="POST" class="centpercent" id="searchFormList" action="'.$remotestore->url.'">'; ?>
1475 <input type="hidden" name="token" value="<?php echo newToken(); ?>">
1476 <input type="hidden" name="mode" value="marketplace">
1477 <input type="hidden" name="page_y" value="">
1478 <div class="divsearchfield">
1479 <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">
1480 </div>
1481 <div class="divsearchfield">
1482 <input name="buttonsubmit" class="button buttongen reposition" value="<?php echo $langs->trans('Search') ?>" type="submit">
1483 <?php
1484 if ($search_keyword !== '') {
1485 print '<a class="buttonreset reposition" href="'.$_SERVER["PHP_SELF"].'?mode=marketplace">'.$langs->trans('Reset').'</a>';
1486 } else {
1487 print $form->textwithpicto('', $langs->trans('DOLISTOREdescriptionLong'));
1488 }
1489 ?>
1490 &nbsp;
1491 </div>
1492 <?php
1493 $totalnboflines = '<span class="product-count opacitymedium paddingleft">';
1494 $totalnboflines .= $langs->trans("itemFound", $remotestore->numberTotalOfProducts);
1495 $totalnboflines .= '</span>';
1496
1497 print $totalnboflines;
1498 print $remotestore->getPagination();
1499 print '</form>';
1500
1501 print '</div>';
1502 print '<div class="clearboth"></div>';
1503 print '</div>';
1504 ?>
1505 <?php if (!empty($categories_tree)) { ?>
1506 <div id="category-tree-left" class="paddingtop">
1507 <ul class="tree">
1508 <?php
1509 print $categories_tree; ?>
1510 </ul>
1511 </div>
1512 <?php } ?>
1513
1514 <div id="listing-content" class="div-table-responsive" <?php if (empty($categories_tree)) { ?>style="width:100%;"<?php } ?>>
1515 <table summary="list_of_modules" id="list_of_modules" class="productlist centpercent">
1516 <tbody id="listOfModules">
1517 <!-- $product_list is $remotestore->getProducts($options) done previously -->
1518 <?php print $products_list; ?>
1519 </tbody>
1520 </table>
1521 </div>
1522 <div style="clear: both;"></div>
1523 <div><?php print $remotestore->getPagination(); ?></div>
1524 <?php
1525 }
1526}
1527
1528
1529// Form to install an external module
1530
1531if ($mode == 'deploy') {
1532 print dol_get_fiche_head($head, $mode, '', -1);
1533
1534 $fullurl = '<a href="'.$urldolibarrmodules.'" target="_blank" rel="noopener noreferrer">'.$urldolibarrmodules.'</a>';
1535 $message = '';
1536 if ($allowonlineinstall) {
1537 if (!in_array('/custom', explode(',', $dolibarr_main_url_root_alt))) {
1538 $message = info_admin($langs->trans("ConfFileMustContainCustom", DOL_DOCUMENT_ROOT.'/custom', DOL_DOCUMENT_ROOT));
1539 $allowfromweb = -1;
1540 } else {
1541 if ($dirins_ok) {
1542 if (!is_writable(dol_osencode($dirins))) {
1543 $langs->load("errors");
1544 $message = info_admin($langs->trans("ErrorFailedToWriteInDir", $dirins), 0, 0, '1', 'warning');
1545 $allowfromweb = 0;
1546 }
1547 } else {
1548 $message = info_admin($langs->trans("NotExistsDirect", $dirins).$langs->trans("InfDirAlt").$langs->trans("InfDirExample"));
1549 $allowfromweb = 0;
1550 }
1551 }
1552 } else {
1553 if (getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US')) {
1554 // Show clean message
1555 if (!is_numeric(getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US'))) {
1556 $message = info_admin($langs->trans(getDolGlobalString('MAIN_MESSAGE_INSTALL_MODULES_DISABLED_CONTACT_US')), 0, 0, 'warning');
1557 } else {
1558 $message = info_admin($langs->trans('InstallModuleFromWebHasBeenDisabledContactUs'), 0, 0, 'warning');
1559 }
1560 } else {
1561 // Show technical message
1562 $message = info_admin($langs->trans("InstallModuleFromWebHasBeenDisabledByFile", $dolibarrdataroot.'/installmodules.lock'), 0, 0, 'warning');
1563 }
1564 $allowfromweb = 0;
1565 }
1566
1567 print $deschelp;
1568
1569 if ($allowfromweb < 1) {
1570 print $langs->trans("SomethingMakeInstallFromWebNotPossible");
1571 print $message;
1572 //print $langs->trans("SomethingMakeInstallFromWebNotPossible2");
1573 print '<br>';
1574 }
1575
1576 // $allowfromweb = -1 if installation or setup not correct, 0 if not allowed, 1 if allowed
1577 if ($allowfromweb >= 0) {
1578 if ($allowfromweb == 1) {
1579 //print $langs->trans("ThisIsProcessToFollow").'<br>';
1580 } else {
1581 print '<br>';
1582
1583 print $langs->trans("ThisIsAlternativeProcessToFollow").'<br>';
1584 print '<b>'.$langs->trans("StepNb", 1).'</b>: ';
1585 print str_replace('{s1}', $fullurl, $langs->trans("FindPackageFromWebSite", '{s1}')).'<br>';
1586 print '<b>'.$langs->trans("StepNb", 2).'</b>: ';
1587 print str_replace('{s1}', $fullurl, $langs->trans("DownloadPackageFromWebSite", '{s1}')).'<br>';
1588 print '<b>'.$langs->trans("StepNb", 3).'</b>: ';
1589 }
1590
1591 if ($allowfromweb == 1) {
1592 print '<form enctype="multipart/form-data" method="POST" class="noborder" action="'.$_SERVER["PHP_SELF"].'" name="forminstall">';
1593 print '<input type="hidden" name="token" value="'.newToken().'">';
1594 print '<input type="hidden" name="action" value="install">';
1595 print '<input type="hidden" name="mode" value="deploy">';
1596
1597 print $langs->trans("YouCanSubmitFile").'<br><br><br>';
1598
1599 print '<span class="opacitymedium"><input class="paddingright" type="checkbox" name="checkforcompliance" id="checkforcompliance"'.(getDolGlobalString('DISABLE_CHECK_ON_MALWARE_MODULES') ? ' disabled="disabled"' : 'checked="checked"').'>';
1600 print '<label for="checkforcompliance">'.$form->textwithpicto($langs->trans("CheckIfModuleIsNotBlackListed"), $langs->trans("CheckIfModuleIsNotBlackListedHelp").'<br><br>'.DolibarrModules::URL_FOR_BLACKLISTED_MODULES).'</label>';
1601 print '</span><br><br>';
1602
1603 $max = getDolGlobalString('MAIN_UPLOAD_DOC'); // In Kb
1604 $maxphp = @ini_get('upload_max_filesize'); // In unknown
1605 if (preg_match('/k$/i', $maxphp)) {
1606 $maxphp = preg_replace('/k$/i', '', $maxphp);
1607 $maxphp *= 1;
1608 }
1609 if (preg_match('/m$/i', $maxphp)) {
1610 $maxphp = preg_replace('/m$/i', '', $maxphp);
1611 $maxphp *= 1024;
1612 }
1613 if (preg_match('/g$/i', $maxphp)) {
1614 $maxphp = preg_replace('/g$/i', '', $maxphp);
1615 $maxphp *= 1024 * 1024;
1616 }
1617 if (preg_match('/t$/i', $maxphp)) {
1618 $maxphp = preg_replace('/t$/i', '', $maxphp);
1619 $maxphp *= 1024 * 1024 * 1024;
1620 }
1621 $maxphp2 = @ini_get('post_max_size'); // In unknown
1622 if (preg_match('/k$/i', $maxphp2)) {
1623 $maxphp2 = preg_replace('/k$/i', '', $maxphp2);
1624 $maxphp2 *= 1;
1625 }
1626 if (preg_match('/m$/i', $maxphp2)) {
1627 $maxphp2 = preg_replace('/m$/i', '', $maxphp2);
1628 $maxphp2 *= 1024;
1629 }
1630 if (preg_match('/g$/i', $maxphp2)) {
1631 $maxphp2 = preg_replace('/g$/i', '', $maxphp2);
1632 $maxphp2 *= 1024 * 1024;
1633 }
1634 if (preg_match('/t$/i', $maxphp2)) {
1635 $maxphp2 = preg_replace('/t$/i', '', $maxphp2);
1636 $maxphp2 *= 1024 * 1024 * 1024;
1637 }
1638 // Now $max and $maxphp and $maxphp2 are in Kb
1639 $maxmin = $max;
1640 $maxphptoshow = $maxphptoshowparam = '';
1641 if ($maxphp > 0) {
1642 $maxmin = min($max, $maxphp);
1643 $maxphptoshow = $maxphp;
1644 $maxphptoshowparam = 'upload_max_filesize';
1645 }
1646 if ($maxphp2 > 0) {
1647 $maxmin = min($max, $maxphp2);
1648 if ($maxphp2 < $maxphp) {
1649 $maxphptoshow = $maxphp2;
1650 $maxphptoshowparam = 'post_max_size';
1651 }
1652 }
1653
1654 if ($maxmin > 0) {
1655 print '<script type="text/javascript">
1656 $(document).ready(function() {
1657 jQuery("#fileinstall").on("change", function() {
1658 if(this.files[0].size > '.($maxmin * 1024).') {
1659 alert("'.dol_escape_js($langs->transnoentitiesnoconv("ErrorFileSizeTooLarge")).'");
1660 this.value = "";
1661 }
1662 });
1663 });
1664 </script>'."\n";
1665 // MAX_FILE_SIZE must come before the file input field
1666 print '<input type="hidden" name="MAX_FILE_SIZE" value="'.($maxmin * 1024).'">';
1667 }
1668
1669 print '<input class="flat minwidth400" type="file" name="fileinstall" id="fileinstall">';
1670
1671 print '<input type="submit" name="send" value="'.dol_escape_htmltag($langs->trans("Upload")).'" class="button small">';
1672
1673 if (getDolGlobalString('MAIN_UPLOAD_DOC')) {
1674 if ($user->admin) {
1675 $langs->load('other');
1676 print ' ';
1677 print info_admin($langs->trans("ThisLimitIsDefinedInSetup", $max, $maxphptoshow, $maxphptoshowparam), 1);
1678 }
1679 } else {
1680 print ' ('.$langs->trans("UploadDisabled").')';
1681 }
1682
1683 print '</form>';
1684
1685 print '<br>';
1686 print '<br>';
1687
1688 print '<div class="center"><div class="logo_setup"></div></div>';
1689 } else {
1690 print $langs->trans("UnpackPackageInModulesRoot", $dirins).'<br>';
1691 print '<b>'.$langs->trans("StepNb", 4).'</b>: ';
1692 print $langs->trans("SetupIsReadyForUse", DOL_URL_ROOT.'/admin/modules.php?mainmenu=home', $langs->transnoentitiesnoconv("Home").' - '.$langs->transnoentitiesnoconv("Setup").' - '.$langs->transnoentitiesnoconv("Modules")).'<br>';
1693 }
1694 }
1695
1696 print dol_get_fiche_end();
1697}
1698
1699if ($mode == 'develop') {
1700 print dol_get_fiche_head($head, $mode, '', -1);
1701
1702 print $deschelp;
1703
1704 print '<br>';
1705
1706 // Marketplace
1707 print '<div class="div-table-responsive-no-min">';
1708 print '<table summary="list_of_modules" class="noborder centpercent">'."\n";
1709 print '<tr class="liste_titre">'."\n";
1710 print '<td colspan="3">'.$langs->trans("DevelopYourModuleDesc").'</td>';
1711 print '</tr>';
1712
1713 print '<tr class="oddeven nohover" height="100">'."\n";
1714 print '<td class="center hideonsmartphone">';
1715 print '<div class="imgmaxheight50 logo_setup"></div>';
1716 print '</td>';
1717 print '<td class="minwidth500imp smallonsmartphone">'.$langs->trans("TryToUseTheModuleBuilder", $langs->transnoentitiesnoconv("ModuleBuilder")).'</td>';
1718 print '<td class="maxwidth300">';
1719 if (isModEnabled('modulebuilder')) {
1720 print $langs->trans("SeeTopRightMenu");
1721 } else {
1722 print '<span class="opacitymedium">'.$langs->trans("ModuleMustBeEnabledFirst", $langs->transnoentitiesnoconv("ModuleBuilder")).'</span>';
1723 }
1724 print '</td>';
1725 print '</tr>';
1726
1727 print '<tr class="oddeven nohover" height="100">'."\n";
1728 $url = 'https://partners.dolibarr.org';
1729 print '<td class="center hideonsmartphone">';
1730 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>';
1731 print '</td>';
1732 print '<td class="minwidth500imp smallonsmartphone">'.$langs->trans("DoliPartnersDesc").'</td>';
1733 print '<td><a href="'.$url.'" target="_blank" rel="noopener noreferrer external">';
1734 print img_picto('', 'url', 'class="pictofixedwidth"');
1735 print $url.'</a></td>';
1736 print '</tr>';
1737
1738 print "</table>\n";
1739 print '</div>';
1740
1741 print dol_get_fiche_end();
1742}
1743
1744// End of page
1745llxFooter();
1746$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.
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.
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.
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)
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.
dolPrintHTMLForAttribute($s, $escapeonlyhtmltags=0, $allowothertags=array())
Return a string ready to be output into an HTML attribute (alt, title, data-html, ....
dolGetButtonTitleSeparator($moreClass="")
Add space between dolGetButtonTitle.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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.
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.
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_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.