diff --git a/htdocs/admin/tools/ui/class/documentation.class.php b/htdocs/admin/tools/ui/class/documentation.class.php index c9381a9e8c740..388579726d324 100644 --- a/htdocs/admin/tools/ui/class/documentation.class.php +++ b/htdocs/admin/tools/ui/class/documentation.class.php @@ -234,6 +234,12 @@ private function setMenu() 'DocContributeStep3' => '#contributesection-step3', ), ), + 'HiddenConf' => array( + 'url' => dol_buildpath($this->baseUrl.'/resources/hidden-conf.php', 1), + 'icon' => 'fas fa-code', + 'submenu' => array(), + 'summary' => array(), + ), ) ); diff --git a/htdocs/admin/tools/ui/components/inputs.php b/htdocs/admin/tools/ui/components/inputs.php index e0307fae42700..a2309480b2e0c 100644 --- a/htdocs/admin/tools/ui/components/inputs.php +++ b/htdocs/admin/tools/ui/components/inputs.php @@ -383,6 +383,7 @@ 'print $form->getSearchFilterToolInput(', ' \'#demo-filter .search-item\',', ' \'search-tools-input\',', + ' \'\',', ' [\'attr\' => [', ' \'data-no-item-target\' => \'#demo-filter .search-tool-no-results\', ', ' \'data-counter-target\' => \'#demo-filter .counter\', ', diff --git a/htdocs/admin/tools/ui/css/hidden-conf.css b/htdocs/admin/tools/ui/css/hidden-conf.css new file mode 100644 index 0000000000000..465951b524ec8 --- /dev/null +++ b/htdocs/admin/tools/ui/css/hidden-conf.css @@ -0,0 +1,71 @@ +.conf-container { + display: flex; + flex-direction: column; + gap: 25px; +} + +.conf-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.conf-item { + border: 1px solid #eee; + border-radius: 6px; + padding: 12px; + background: #fafafa; +} + +.conf-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; +} + +.conf-name { + font-weight: bold; + font-family: monospace; +} + +.conf-body { + margin-top: 8px; +} + +.conf-desc { + margin-bottom: 10px; + color: #333; +} + +.conf-meta { + display: flex; + flex-wrap: wrap; + gap: 10px; + font-size: 12px; + color: #666; +} + +.conf-meta-line { + margin-right: 10px; +} + +#search-form-container { + position: sticky; + top:0; +} +#search-form-container .search-tool-container { + margin: 0; +} + +#hidden-conf-counter{ + display: block; + position: absolute; + right: 10px; + bottom: 0; + transform: translateY(50%); +} + +.conf-item summary.conf-title{ + cursor: pointer; +} diff --git a/htdocs/admin/tools/ui/index.php b/htdocs/admin/tools/ui/index.php index 66372a6354e6d..bf7c1df57fad2 100644 --- a/htdocs/admin/tools/ui/index.php +++ b/htdocs/admin/tools/ui/index.php @@ -64,6 +64,10 @@ + + + + diff --git a/htdocs/admin/tools/ui/resources/hidden-conf-list.lib.php b/htdocs/admin/tools/ui/resources/hidden-conf-list.lib.php new file mode 100644 index 0000000000000..7f1e9ef8d12a8 --- /dev/null +++ b/htdocs/admin/tools/ui/resources/hidden-conf-list.lib.php @@ -0,0 +1,585 @@ + + * } + * + * @phpstan-type HiddenConfList array + * + * @return array + * }> + */ +function getListOfHiddenConf() +{ + return [ + 'GLOBAL' => [ + 'label' => 'Global / System', + 'confs' => [ + 'ADD_UNSPLASH_LOGIN_BACKGROUND' => [ + 'name' => 'ADD_UNSPLASH_LOGIN_BACKGROUND', + 'title' => 'Use Unsplash random image as login background (external call)', + 'description' => 'The background image will be refreshed on every login page refresh. Background image is pulled from the popular open source image website Unsplash. If an already saved static background image exists, then this code will override it (but not delete the saved image). Warning: Using this may allow this external website to steel your login credentials (the value to be put is the URL of the service', + 'type' => 'string', + 'example' => 'https://source.unsplash.com/random', + 'added_in' => '10', + 'removed_in' => null, + 'deprecated' => false, + 'deprecated_since' => null, + 'module' => 'core', + 'tags' => ['ui', 'external'], + ], + + 'MAIN_HIDE_POWERED_BY' => [ + 'name' => 'MAIN_HIDE_POWERED_BY', + 'title' => 'Hide "Powered by Dolibarr" logo on public pages', + 'description' => '', + 'type' => 'bool', + 'example' => '1', + 'added_in' => null, + 'removed_in' => null, + 'deprecated' => false, + 'deprecated_since' => null, + 'module' => 'core', + 'tags' => ['ui'], + ], + + 'MAIN_APPLICATION_TITLE' => [ + 'name' => 'MAIN_APPLICATION_TITLE', + 'title' => 'Override application title (login page)', + 'description' => 'This will change the title of software (that appears on the login page by default). + Warning: changing this may make Dolibarr version detection to fail by smartphone applications like + Application Android - DoliDroid, + breaking some features when using Dolibarr from such application. If the text start with a "+", + the text will be added to the standard "Dolibarr" label instead of replacing it.', + 'type' => 'string', + 'example' => 'My ERP', + 'added_in' => null, + 'removed_in' => null, + 'deprecated' => false, + 'deprecated_since' => null, + 'module' => 'core', + 'tags' => ['ui'], + ], + + 'MAIN_AUTOFILL_DATE' => [ + 'name' => 'MAIN_AUTOFILL_DATE', + 'title' => 'Auto-fill document dates with current date (dangerous)', + 'description' => ' If this constant is defined (to something other than 0), the date of invoice, proposal, order or payment are auto-filled with the current date. It is highly recommended to NOT ENABLE this feature. This can create a lot of input errors with data not validated by users. This leads to incorrect values saved in the database causing confusion when you have to do your accountancy reports!', + 'type' => 'bool', + 'example' => '1', + 'added_in' => null, + 'removed_in' => null, + 'deprecated' => true, + 'deprecated_since' => null, + 'module' => 'core', + 'tags' => ['danger'], + ], + + 'MAIN_AUTOFILL_DATE_PROPOSAL' => [ + 'name' => 'MAIN_AUTOFILL_DATE_PROPOSAL', + 'title' => 'Auto-fill proposal date', + 'description' => 'If this constant is defined (to something other than 0), the date of proposal is auto-filled with the current date.', + 'type' => 'bool', + 'example' => '1', + 'added_in' => null, + 'removed_in' => null, + 'deprecated' => true, + 'deprecated_since' => null, + 'module' => 'core', + 'tags' => ['proposal'], + ], + + 'MAIN_DEFAULT_LANGUAGE_FILTER' => [ + 'name' => 'MAIN_DEFAULT_LANGUAGE_FILTER', + 'title' => 'Filter out en_US from language list', + 'description' => 'Useful if you do not want to show en_US language option in combo boxes (other languages can easily be removed in langs directory but not en_US)', + 'type' => 'bool', + 'example' => '1', + 'added_in' => '10.0', + 'removed_in' => null, + 'deprecated' => false, + 'deprecated_since' => null, + 'module' => 'core', + 'tags' => ['ui'], + ], + + // 'MAIN_LANGUAGES_ALLOWED' => [ + // 'name' => 'MAIN_LANGUAGES_ALLOWED', + // 'title' => 'Restrict allowed languages list', + // 'description' => '', + // 'type' => 'string', + // 'example' => 'fr_FR,en_US,de_DE', + // 'added_in' => '11.0', + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['ui'], + // ], + // + // 'MAIN_DISABLE_FULL_SCANLIST' => [ + // 'name' => 'MAIN_DISABLE_FULL_SCANLIST', + // 'title' => 'Disable full table scan for pagination performance', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['performance'], + // ], + // + // 'MAIN_DISABLE_JQUERY_JNOTIFY' => [ + // 'name' => 'MAIN_DISABLE_JQUERY_JNOTIFY', + // 'title' => 'Disable JNotify alerts', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['ui'], + // ], + // + // 'MAIN_DISABLE_AJAX_COMBOX' => [ + // 'name' => 'MAIN_DISABLE_AJAX_COMBOX', + // 'title' => 'Disable AJAX autocomplete in selects', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => '3.6', + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['ui'], + // ], + // + // 'MAIN_DISABLE_MULTIPLE_FILEUPLOAD' => [ + // 'name' => 'MAIN_DISABLE_MULTIPLE_FILEUPLOAD', + // 'title' => 'Disable multiple file upload', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['upload'], + // ], + // + // 'MAIN_DISABLE_TRUNC' => [ + // 'name' => 'MAIN_DISABLE_TRUNC', + // 'title' => 'Disable truncation in select lists', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => '7.0', + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['ui'], + // ], + ], + ], + + // 'SECURITY' => [ + // 'label' => 'Security', + // 'confs' => [ + // 'MAIN_SECURITY_CSRF_WITH_TOKEN' => [ + // 'name' => 'MAIN_SECURITY_CSRF_WITH_TOKEN', + // 'title' => 'CSRF protection level', + // 'description' => '', + // 'type' => 'int', + // 'example' => '2', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['security'], + // ], + // + // 'MAIN_RESTRICT_IPS' => [ + // 'name' => 'MAIN_RESTRICT_IPS', + // 'title' => 'Restrict access by IP range', + // 'description' => '', + // 'type' => 'string', + // 'example' => '192.168.0.0/24', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['security'], + // ], + // + // 'MAIN_ALLOW_SVG_FILES_AS_IMAGES' => [ + // 'name' => 'MAIN_ALLOW_SVG_FILES_AS_IMAGES', + // 'title' => 'Allow SVG upload (security risk)', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => true, + // 'deprecated_since' => null, + // 'module' => 'core', + // 'tags' => ['danger'], + // ], + // ], + // ], + // + // 'INVOICE' => [ + // 'label' => 'Invoices', + // 'confs' => [ + // 'INVOICE_CAN_BE_EDITED_EVEN_IF_PAYMENT_DONE' => [ + // 'name' => 'INVOICE_CAN_BE_EDITED_EVEN_IF_PAYMENT_DONE', + // 'title' => 'Allow invoice editing after payment started', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'invoice', + // 'tags' => ['danger'], + // ], + // + // 'INVOICE_CAN_ALWAYS_BE_REMOVED' => [ + // 'name' => 'INVOICE_CAN_ALWAYS_BE_REMOVED', + // 'title' => 'Allow invoice deletion even if not last', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'invoice', + // 'tags' => ['danger'], + // ], + // ], + // ], + // + // 'PRODUCT' => [ + // 'label' => 'Products', + // 'confs' => [ + // 'PRODUCT_DISABLE_SELLBY' => [ + // 'name' => 'PRODUCT_DISABLE_SELLBY', + // 'title' => 'Hide sell-by date field', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => '13', + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'product', + // 'tags' => ['ui'], + // ], + // + // 'PRODUCT_DISABLE_EATBY' => [ + // 'name' => 'PRODUCT_DISABLE_EATBY', + // 'title' => 'Hide eat-by date field', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => '13', + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'product', + // 'tags' => ['ui'], + // ], + // + // 'PRODUCT_DENY_CHANGE_PRODUCT_TYPE' => [ + // 'name' => 'PRODUCT_DENY_CHANGE_PRODUCT_TYPE', + // 'title' => 'Prevent changing product type', + // 'description' => '', + // 'type' => 'bool', + // 'example' => '1', + // 'added_in' => null, + // 'removed_in' => null, + // 'deprecated' => false, + // 'deprecated_since' => null, + // 'module' => 'product', + // 'tags' => ['security'], + // ], + // ], + // ], + ]; +} + + + + + +/** + * Render hidden Dolibarr configuration list + * + * @return void + */ +function renderHiddenConfList(): void +{ + $data = getListOfHiddenConf(); + + foreach ($data as $sectionKey => $section) { + renderSection($sectionKey, $section); + } +} + +/** + * Render one section + * + * @param string $sectionKey the selection + * @param array $section section array + * + * @return void + */ +function renderSection(string $sectionKey, array $section): void +{ + print '
'; + print '

' . htmlspecialchars($section['label']) . '

'; + + print '
'; + + foreach ($section['confs'] as $conf) { + renderConfItem($conf); + } + + print '
'; + print '
'; +} + +/** + * Render one configuration item + * + * @param array $conf conf item array + * + * @return void + */ +function renderConfItem(array $conf): void +{ + global $langs; + + $type = $conf['type'] ?? 'string'; + if ($type === 'bool') { + $type.= ' ( 0 | 1 )'; + } + + // TODO : compare current DOL_VERSION with 'removed_in' + // TODO : Add used badge if conf is currently Used, and add in search form a warning if deprecated conf are used + + print '
'; + + print '
'; + print ' ' . dolPrintHTML($conf['name']) . ''; + print ' ' . renderBadges($conf) . ''; + print '
'; + + print '
'; + + + $description = trim((string) ($conf['description'] ?? '')); + + if (!empty($description)) { + print '
'; + print ' ' . dolPrintHTML($conf['title'] ?? $conf['name']) . ''; + print '
' . dolPrintHTML($description, 0, ['code']) . '
'; + print '
'; + } else { + print '
' . ($conf['title']??'') . '
'; + } + + + print '
'; + + print renderMeta('Type', $type); + print renderMeta('Example', $conf['example']); + print renderMeta($langs->trans('AddedInVersion'), $conf['added_in']); + print renderMeta('Removed', $conf['removed_in']); + + print '
'; + + print '
'; + + print '
'; +} + +/** + * Render metadata line + * + * @param string $label meta label + * @param string|null $value meta value + * + * @return string + */ +function renderMeta(string $label, ?string $value): string +{ + if (empty($value)) { + return ''; + } + + return '
+ ' . dolPrintHTML($label) . ': + ' . dolPrintHTML($value) . ' +
'; +} + +/** + * Render badges from tags + deprecated state + * + * @param array $conf render tags for a conf item + * + * @return string + */ +function renderBadges(array $conf): string +{ + global $langs; + + $html = ''; + + $tags = $conf['tags'] ?? []; + + foreach ($tags as $tag) { + $class = getTagBadgeClass($tag); + $html .= '' . dol_escape_htmltag($tag) . ' '; + } + + if (!empty($conf['deprecated'])) { + $html .= ''. $langs->trans('deprecated') . ''; + } + + return $html; +} + +/** + * Map tag to bootstrap-like badge class + * + * @param string $tag the tag name + * + * @return string + */ +function getTagBadgeClass(string $tag): string +{ + return match ($tag) { + 'security', 'danger' => 'badge-danger', + 'ui' => 'badge-info', + 'performance' => 'badge-warning', + 'upload' => 'badge-secondary', + default => 'badge-light', + }; +} + +/** + * Normalize and validate hidden configuration list. + * + * - Ensures required fields + * - Applies defaults + * - Normalizes types + * + * @param array>}> $confList the conf list + * @return array>}> + */ +function normalizeHiddenConfList(array $confList): array +{ + foreach ($confList as $sectionKey => &$section) { + if (!isset($section['confs']) || !is_array($section['confs'])) { + $section['confs'] = []; + } + + foreach ($section['confs'] as $confKey => &$conf) { + $conf = normalizeHiddenConf($conf, $confKey); + } + } + + return $confList; +} + +/** + * Normalize a single configuration entry + * + * @param array $conf th conf array + * @param string $fallbackName a fallback name + * @return array + */ +function normalizeHiddenConf(array $conf, string $fallbackName): array +{ + // REQUIRED FIELDS + $name = $conf['name'] ?? $fallbackName; + $description = $conf['description'] ?? ''; + + if ($description === '') { + throw new InvalidArgumentException("Missing description for conf: " . $name); + } + + // TYPE NORMALIZATION + $type = $conf['type'] ?? 'string'; + + $allowedTypes = ['bool', 'int', 'float', 'string', 'array', 'json']; + if (!in_array($type, $allowedTypes, true)) { + $type = 'string'; + } + + // TAGS NORMALIZATION + $tags = $conf['tags'] ?? []; + if (!is_array($tags)) { + $tags = []; + } + + $tags = array_values(array_unique(array_map('strval', $tags))); + + // BOOLEAN NORMALIZATION + $deprecated = (bool) ($conf['deprecated'] ?? false); + + return [ + 'name' => (string) $name, + 'description' => (string) $description, + 'type' => $type, + 'example' => $conf['example'] ?? null, + 'added_in' => $conf['added_in'] ?? null, + 'removed_in' => $conf['removed_in'] ?? null, + 'deprecated' => $deprecated, + 'deprecated_since' => $conf['deprecated_since'] ?? null, + 'module' => $conf['module'] ?? 'core', + 'tags' => $tags, + ]; +} diff --git a/htdocs/admin/tools/ui/resources/hidden-conf.php b/htdocs/admin/tools/ui/resources/hidden-conf.php new file mode 100644 index 0000000000000..8e911a9d79d33 --- /dev/null +++ b/htdocs/admin/tools/ui/resources/hidden-conf.php @@ -0,0 +1,108 @@ + + * Copyright (C) 2026 MDW + * + * This program and files/directory inner it is free software: you can + * redistribute it and/or modify it under the terms of the + * GNU Affero General Public License (AGPL) as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AGPL for more details. + * + * You should have received a copy of the GNU AGPL + * along with this program. If not, see . + */ + +// Load Dolibarr environment +require '../../../../main.inc.php'; + +/** + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ + +// Protection if external user +if ($user->socid > 0) { + accessforbidden(); +} + +// Includes +require_once DOL_DOCUMENT_ROOT . '/admin/tools/ui/class/documentation.class.php'; +require_once __DIR__ . '/hidden-conf-list.lib.php'; + +// Load documentation translations +$langs->load('uxdocumentation'); + +// +$documentation = new Documentation($db); + +// Output html head + body - Param is Title +$documentation->docHeader('Hidden-confs', [], ['admin/tools/ui/css/hidden-conf.css'], GETPOST('hidenavmenu')); + +// Set view for menu and breadcrumb +// Menu must be set in constructor of documentation class +$documentation->view = array('Resources','Hidden-conf'); +$form = new Form($db); + +$mode = GETPOST('mode'); // ex : no-btn + +// Output sidebar +if (!GETPOST('hidenavmenu')) { + $documentation->showSidebar(); +} +?> + +
"> + + showBreadCrumb(); + } + ?> + +
+ +

trans('DocHiddenConfTitle'); ?>

+

trans('DocHiddenConfDescription'); ?>

+
trans('DocHiddenConfDisclaimer'); ?>
+ + + showSummary(); ?> + +
+ +
+ getSearchFilterToolInput( + '#hidden-conf-search-zone .conf-item', + 'search-tools-input', + '', + [ + 'attr' => [ + 'data-no-item-target' => '#search-form-container .search-tool-no-results', + 'data-counter-target' => '#search-form-container .counter', + ], + ] + ); + ?> + +
Counter : --
+
+
+
+ + +
+
+
+ +docFooter(); diff --git a/htdocs/langs/en_US/uxdocumentation.lang b/htdocs/langs/en_US/uxdocumentation.lang index 11348a16febfb..8fe73c0245d7d 100644 --- a/htdocs/langs/en_US/uxdocumentation.lang +++ b/htdocs/langs/en_US/uxdocumentation.lang @@ -202,3 +202,10 @@ LangsLocalChangedTo = Language locale changed to %s. Translations will now use t CacheCleared = Cache cleared JSDolibarrhooksAjaxSpecial = Dom reloaded Hook + +# Hidden conf doc +AddedInVersion = Added in version +DocHiddenConfTitle = Hidden conf list +DocHiddenConfDescription = Some options are not visible in the interface (often because they are deprecated or are beta features). In most cases you do not need these functions and their use may make the application unstable or may not work as expected. +DocHiddenConfDisclaimer = This page contains the list of some advanced development parameters. In most cases, they are parameters for internal usage, development, external modules, features in development not stable or that may lead to security risk. For standard usage of Dolibarr, such parameters SHOULD NOT be used. All experimental options are NOT supported. They may be removed in a future, modified at any version and break stability of your application. +HiddenConf = Hidden conf