Files
dolibarr/htdocs/admin/tools/ui/class/documentation.class.php

591 lines
18 KiB
PHP

<?php
/* Copyright (C) 2024 Anthony Damhet <a.damhet@progiseize.fr>
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file htdocs/admint/tools/ui/class/documentation.class.php
* \ingroup ui
* \brief File of class to manage UI documentation
*/
/**
* Class to manage UI documentation
*/
class Documentation
{
/**
* Views
*
* @var array|array<int,string>
*/
public $view = array();
/**
* Menu - Set in setMenu in order to use dol_buildpath and called in constructor
*
* @var array<array{url?: string, summary?: array<string,string>, submenu?: array<string,mixed>}>
*/
public $menu = array();
/**
* Summary - Set in setSummary and called in constructor
*
* @var array<int,string>
*/
public $summary = array();
/**
* @var DoliDB Database handler.
*/
public $db;
/**
* @var string
*/
public $baseUrl = 'admin/tools/ui';
/**
* Constructor
*
* @param DoliDB $db Database handler
* @return void
*/
public function __construct(DoliDB $db)
{
$this->db = $db;
// https://www.figma.com/community/file/1393171578760389765/dolibarr-ui-ux-kit
// Menu Constructor
$this->setMenu();
}
/**
* Set Documentation Menu
*
* @return mixed false if error, void if no errors
*/
private function setMenu()
{
global $hookmanager;
$hookmanager->initHooks(array('uidocumentation'));
// Go back to Dolibarr
$this->menu['BackToDolibarr'] = array(
'url' => dol_buildpath('modulebuilder/index.php', 1),
'icon' => 'fas fa-arrow-left',
'submenu' => array(),
);
// Home for Ui documentation
$this->menu['DocumentationHome'] = array(
'url' => dol_buildpath($this->baseUrl.'/index.php', 1),
'icon' => 'fas fa-book',
'submenu' => array(),
);
// Components
$this->menu['Components'] = array(
'url' => dol_buildpath($this->baseUrl.'/components/index.php', 1),
'icon' => 'fas fa-th-large',
'submenu' => array(
'Badges' => array(
'url' => dol_buildpath($this->baseUrl.'/components/badges.php', 1),
'icon' => 'fas fa-certificate',
'submenu' => array(),
'summary' => array(
'DocBasicUsage' => '#badgesection-basicusage',
'DocBadgeContextualVariations' => '#badgesection-contextvariations',
'DocBadgeDefaultStatus' => '#badgesection-defaultstatus',
'DocBadgePillBadges' => '#badgesection-pill',
'DocBadgeDotBadges' => '#badgesection-dot',
'DocBadgeLinks' => '#badgesection-links',
'DocBadgeHelper' => '#badgesection-dolgetbadge'
),
),
'Buttons' => array(
'url' => dol_buildpath($this->baseUrl.'/components/buttons.php', 1),
'icon' => 'fas fa-mouse',
'submenu' => array(),
'summary' => array(
'DocBasicUsage' => '#buttonsection-basicusage',
'DocButtonModal' => '#buttonsection-modals',
'DocButtonSubmenu' => '#buttonsection-submenu',
),
),
'Icons' => array(
'url' => dol_buildpath($this->baseUrl.'/components/icons.php', 1),
'icon' => 'far fa-flag',
'submenu' => array(),
'summary' => array(
'DocIconsList' => '#img-picto-section-list',
'DocIconsFontAwesomeList' => '#icon-section-list',
),
),
'Progress' => array(
'url' => dol_buildpath($this->baseUrl.'/components/progress-bars.php', 1),
'icon' => 'fas fa-battery-half',
'submenu' => array(),
'summary' => array(
'DocBasicUsage' => '#progresse-section-basic-usage',
'DocColorVariants' => '#progress-section-color',
'DocStripedVariants' => '#progresse-section-stripped',
),
),
'Event Message' => array(
'url' => dol_buildpath($this->baseUrl.'/components/event-message.php', 1),
'icon' => 'fas fa-comments',
'submenu' => array(),
'summary' => array(
'DocBasicUsage' => '#seteventmessagesection-basicusage',
'DocSetEventMessageContextualVariations' => '#seteventmessagesection-contextvariations',
'DocSetEventMessageJsContext' => '#titlesection-tool-seteventmessage',
)
),
'Inputs' => array(
'url' => dol_buildpath($this->baseUrl.'/components/inputs.php', 1),
'icon' => 'far fa-edit',
'submenu' => array(),
'summary' => array(
'DocBasicUsage' => '#setinputssection-basicusage',
'DocHelperFunctionsInputUsage' => '#setinputssection-helperfunctions',
'DocHelperFunctionsGetSearchFilterToolInput' => '#setinputssection-getSearchFilterToolInput',
)
),
'ExperimentalUxInputAjaxFeedback' => array(
'url' => dol_buildpath($this->baseUrl.'/content/input-feedback.php', 1),
'icon' => 'far fa-share-square',
'submenu' => array(),
'summary' => array(),
),
),
);
// Elements
$this->menu['Content'] = array(
'url' => dol_buildpath($this->baseUrl.'/content/index.php', 1),
'icon' => 'far fa-file-alt',
'submenu' => array(
'Titles' => array(
'url' => dol_buildpath('admin/tools/ui/content/titles.php', 1),
'icon' => 'fas fa-heading',
'submenu' => array(),
'summary' => array(
'DocBasicUsage' => '#titlesection-basicusage',
'DocTitleWithFilters' => '#titlesection-withfilters',
),
),
'Tables' => array(
'url' => dol_buildpath('admin/tools/ui/content/tables.php', 1),
'icon' => 'fas fa-table',
'submenu' => array(),
'summary' => array(
'DocBasicUsage' => '#tablesection-basicusage',
'DocTableWithFilters' => '#tablesection-withfilters',
'DocTableBeforeFilters' => '#tablesection-beforefilters',
'DocTableCSSClass' => '#tablesection-cssclasses',
),
),
'TableRowIntuitiveSelect' => array(
'url' => dol_buildpath($this->baseUrl.'/content/intuitive-table-row-select.php', 1),
'icon' => 'far fa-check-square',
'submenu' => array(),
'summary' => array(),
),
'FreezeTooltip' => array(
'url' => dol_buildpath($this->baseUrl.'/content/freeze-tooltip.php', 1),
'icon' => 'far fa-comment',
'submenu' => array(),
'summary' => array(),
),
)
);
// Elements
$this->menu['Resources'] = array(
'url' => dol_buildpath($this->baseUrl.'/resources/index.php', 1),
'icon' => 'fas fa-wrench',
'submenu' => array(
'Contributing' => array(
'url' => dol_buildpath($this->baseUrl.'/resources/contributing.php', 1),
'icon' => 'fas fa-code',
'submenu' => array(),
'summary' => array(
'DocContributeStep1' => '#contributesection-step1',
'DocContributeStep2' => '#contributesection-step2',
'DocContributeStep3' => '#contributesection-step3',
),
),
)
);
// Elements
$this->menu['UxDolibarrContext'] = array(
'url' => dol_buildpath($this->baseUrl.'/dolibarr-context/index.php', 1),
'icon' => 'fab fa-fort-awesome',
'submenu' => array(
'UxDolibarrContextHowItWork' => array(
'url' => dol_buildpath($this->baseUrl.'/dolibarr-context/index.php', 1),
'icon' => 'fab fa-fort-awesome',
'submenu' => array(),
'summary' => array(
'Introduction' => '#titlesection-basicusage',
'ConsoleHelp' => '#titlesection-console-help',
'JSDolibarrhooks' => '#titlesection-hooks',
'JSDolibarrhooksReadyVsInit' => '#titlesection-event-init-vs-ready',
'JSDolibarrAwaitHooks' => '#titlesection-await-hooks',
'JSDolibarrhooksAjaxSpecial' => '#titlesection-dom-initnewcontent',
'ExampleOfCreatingNewContextTool' => '#titlesection-create-tool-example',
'SetEventMessageTool' => '#titlesection-tool-seteventmessage',
'SetAndUseContextVars' => '#titlesection-contextvars',
),
),
'UxDolibarrContextLangsTool' => array(
'url' => dol_buildpath($this->baseUrl.'/dolibarr-context/langs-tool.php', 1),
'icon' => 'far fa-flag',
'submenu' => array(),
'summary' => array(),
),
)
);
// Elements
$this->menu['ExperimentalUx'] = array(
'url' => dol_buildpath($this->baseUrl.'/experimental/index.php', 1),
'icon' => 'fas fa-flask',
'submenu' => array(
'ExperimentalUxIntroductionMenu' => array(
'url' => dol_buildpath($this->baseUrl.'/experimental/index.php', 1),
'icon' => 'fas fa-flask',
'submenu' => array(),
'summary' => array(
'Index' => '#top',
'ExperimentalUxIntroductionTitle' => '#experimental-ux-introduction',
'ExperimentalUxContributionTitle' => '#experimental-ux-contribution',
),
),
'UxMenuTooltipTheme' => array(
'url' => dol_buildpath($this->baseUrl.'/experimental/tooltip-themes/index.php', 1),
'icon' => 'fas fa-comment',
'submenu' => array(),
'summary' => array(
'Introduction' => '#ux-introduction',
'TooltipThemesAndOrientation' => '#tooltip-themes',),
),
)
);
$parameters = array(
'baseUrl' => $this->baseUrl,
);
$action = '';
$reshook = $hookmanager->executeHooks('setMenu', $parameters, $this, $action);
if ($reshook < 0) {
return false;
}
}
/**
* Output header + body
*
* @param string $title Title of page
* @param string[] $arrayofjs Array of complementary js files
* @param string[] $arrayofcss Array of complementary css files
* @param string $hidenavmenu Hide nav menu
* @return void
*/
public function docHeader($title = '', $arrayofjs = [], $arrayofcss = [], $hidenavmenu = '')
{
global $langs;
$title = (!empty($title)) ? dol_escape_htmltag($title) : $langs->trans('Documentation');
$arrayofcss[] = 'admin/tools/ui/css/documentation.css';
top_htmlhead('', $title, 0, 0, $arrayofjs, $arrayofcss);
print '<body class="dolibarr-doc'.($hidenavmenu ? "-bis" : "").'">';
}
/**
* Output close body + html
* @return void
*/
public function docFooter()
{
global $langs;
// DIV FOR SCROLL ANIMATION
print '<div id="documentation-scrollwrapper">';
print '<div id="documentation-scroll"></div>';
print '</div>';
// JS
print '<script src="'.dol_buildpath('admin/tools/ui/js/documentation.js', 1).'"></script>';
print '<script src="'.DOL_URL_ROOT.'/core/js/lib_foot.js.php?lang='.$langs->defaultlang.'"></script>';
print '</body>';
print '</html>';
dol_htmloutput_events(0);
}
/**
* Output sidebar
*
* @return void
*/
public function showSidebar()
{
print '<div class="doc-sidebar">';
// LOGO
print '<div class="sidebar-logo">';
if (is_readable(DOL_DOCUMENT_ROOT.'/theme/dolibarr_logo.svg')) {
$urllogo = DOL_URL_ROOT.'/theme/dolibarr_logo.svg';
print '<img src="'.$urllogo.'" />';
}
print '</div>';
// NAVIGATION
print '<nav>';
if (!empty($this->menu)) {
$this->displayMenu($this->menu);
}
print '</nav>';
print '</div>';
}
/**
* Recursive function to set Menu
*
* @param array<string, array{url?: string, icon?: string, summary?: array<string,string>, submenu?: array<string,array>}> $menu Menu entry or submenu
* @param int $level level of menu
* @return void
*/
private function displayMenu($menu, $level = 0)
{
global $langs;
$level++;
print '<ul>';
foreach ($menu as $key => $item) {
$levelclass = (!empty($item['submenu'])) ? 'li-withsubmenu' : '';
$levelclass .= (in_array($key, $this->view)) ? ' active' : '';
$levelclass .= ($key == 'BackToDolibarr') ? ' li-withseparator' : '';
print '<li class="'.trim($levelclass).' level-'.$level.'">';
print '<a href="'.$item['url'].'" class="'.((!empty($item['submenu'])) ? 'link-withsubmenu' : '').'">';
print ((!empty($item['icon'])) ? '<i class="menu-icon '.$item['icon'].' pictofixedwidth" aria-hidden="true"></i>' : '');
print '<span class="label">'.$langs->transnoentities($key).'</span>';
print ((!empty($item['submenu'])) ? '<i class="submenu-toggle fas fa-chevron-right" aria-hidden="true"></i>' : '');
print '</a>';
if (!empty($item['submenu'])) {
$this->displayMenu($item['submenu'], $level); // Appel récursif pour afficher les sous-menus
}
echo '</li>';
}
print '</ul>';
}
/**
* Output breadcrumb
* @return void
*/
public function showBreadcrumb()
{
global $langs;
print '<nav class="doc-breadcrumbs">';
print '<ul>';
print '<li class="breadcrumb-item"><a href="'.$this->menu['DocumentationHome']['url'].'"><i class="'.$this->menu['DocumentationHome']['icon'].'" aria-hidden="true"></i></a></li>';
if (!empty($this->view)) {
$nb_entries = count($this->view);
$i = 0;
$menu_entry = $this->menu;
foreach ($this->view as $page) {
$i++;
if ($i < $nb_entries && isset($menu_entry[$page])) {
print '<li class="breadcrumb-item"><a href="'.$menu_entry[$page]['url'].'">'.$langs->transnoentities($page).'</a></li>';
$menu_entry = $menu_entry[$page]['submenu'];
} else {
print '<li class="breadcrumb-item">'.$langs->transnoentities($page).'</li>';
}
}
} else {
print '<li class="breadcrumb-item">'.$langs->trans('Documentation').'</li>';
}
print '</ul>';
print '</nav>';
}
/**
* Output summary
*
* @param int $showsubmenu Show Sub menus: 0 = No, 1 = Yes
* @param int $showsubmenu_summary Show summary of sub menus: 0 = No, 1 = Yes
* @return void
*/
public function showSummary($showsubmenu = 1, $showsubmenu_summary = 1)
{
$i = 0;
$menu_entry = [];
if (!empty($this->view)) {
// Set the correct menu depth (level)
foreach ($this->view as $view) {
$i++;
if ($i == 1) {
$menu_entry = $this->menu[$view] ?? [];
} else {
$menu_entry = $menu_entry['submenu'][$view] ?? [];
}
}
}
if (!empty($menu_entry['summary']) || (!empty($menu_entry['submenu']) && $showsubmenu)) {
print '<div class="summary-wrapper">';
$this->displaySummary($menu_entry);
print '</div>';
}
}
/**
* Recursive function for Automatic Summary
*
* @param array{summary?: array<string,string>, submenu?: array<string,array>} $menu $this->menu or submenus
* @param int $level level of menu
* @param int $showsubmenu Show Sub menus: 0 = No, 1 = Yes
* @param int $showsubmenu_summary Show summary of sub menus: 0 = No, 1 = Yes
* @return void
*/
public function displaySummary($menu, $level = 0, $showsubmenu = 1, $showsubmenu_summary = 1)
{
global $langs;
$level++;
print '<ul class="documentation-summary level-'.$level.'"">';
if (!empty($menu['summary'])) {
foreach ($menu['summary'] as $summary_label => $summary_link) {
/*
if ($summary_link[0] == '#') {
$tmp_summary_link = $menu['url'];
if (GETPOSTINT('hidenavmenu')) {
$tmp_summary_link .= (strpos($tmp_summary_link, '?') === false ? '?' : '&').'hidenavmenu=1';
}
if (GETPOSTINT('displayMode')) {
$tmp_summary_link .= (strpos($tmp_summary_link, '?') === false ? '?' : '&').'displayMode=1';
}
$summary_link = $tmp_summary_link;
}
*/
print '<li><a href="'.$summary_link.'">'.$langs->trans($summary_label).'</a></li>';
}
}
if ($showsubmenu && !empty($menu['submenu'])) {
foreach ($menu['submenu'] as $key => $item) {
print '<li class="summary-title ">';
if (!empty($item['url'])) {
print '<h3 class="level-'.$level.'"><a href="'.dolBuildUrl($item['url']).'" >'.$langs->trans($key).'</a></h3>';
} else {
print '<h3 class="level-'.$level.'">'.$langs->trans($key).'</h3>';
}
if ($showsubmenu_summary) {
$this->displaySummary($item, $level);
}
print '</li>';
}
}
print '</ul>';
}
/**
* Output a View Code area
*
* @param array<int,string> $lines Lines of code to show
* @param string $option Source code language ('html', 'php' etc)
* @return void
*/
public function showCode($lines = array(), $option = 'html')
{
require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
print '<div class="documentation-code">';
if (isset($lines[0])) {
if ($option === 'html' && strpos(strtolower($lines[0]), '<!doctype') === false) {
array_unshift($lines, '<!DOCTYPE html>', '');
}
}
$content = implode("\n", $lines) . "\n";
$doleditor = new DolEditor(md5($content), $content, '', 0, 'Basic', 'In', true, false, 'ace', 0, '99%', 1);
print $doleditor->Create(1, '', false, '', $option);
print '</div>';
}
/**
* Generate lorem ipsum
*
* @param int $paragraphCount nb paragraph you need
* @param int $wordsPerParagraph nb words per paragraph you need
* @param bool $html return html formatted paragraph
*
* @return string
*/
static public function generateLoremIpsum($paragraphCount = 3, $wordsPerParagraph = 50, $html = true)
{
$baseText = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur Excepteur sint occaecat cupidatat non proident sunt in culpa qui officia deserunt mollit anim id est laborum";
$words = explode(" ", $baseText);
$paragraphs = [];
for ($p = 0; $p < $paragraphCount; $p++) {
$sentence = [];
for ($i = 0; $i < $wordsPerParagraph; $i++) {
$word = $words[array_rand($words)];
// Randomly add a comma
if ($i > 2 && rand(0, 10) > 8) {
$word .= ",";
}
$sentence[] = $word;
}
$paragraphText = ucfirst(implode(" ", $sentence)) . ".";
if ($html) {
$paragraphText = "<p>$paragraphText</p>";
}
$paragraphs[] = $paragraphText;
}
return implode($html ? "\n" : "\n\n", $paragraphs);
}
}