butterfly/src/Config.php
Claude 39f3f3f2a2 Modernização GLPI 11: namespace GlpiPlugin\Butterfly, Monaco, assets em public/
- Classes migradas de inc/*.class.php (PluginButterfly*) para src/ com
  namespace GlpiPlugin\Butterfly (PSR-4 registrado pelo core; KB-033).
  Referências de core qualificadas (\Config, \Html, \Session, ...).
- Editor de CSS custom agora usa o Monaco do core (GLPI.Monaco.createEditor
  + sync via formdata, mesmo padrão de custom_ui do core) com fallback
  para textarea se a lib não carregar.
- pics/ movido para public/pics: no GLPI 11 só recursos em public/ são
  servidos estaticamente em /plugins/<key>/... (RequestRouterTrait).
  URLs do select2 de temas agora usam root_doc + encodeURIComponent.
- MIN_GLPI_VERSION 11.0.0; removido bloco morto de GLPI <9.5 e
  css/login.base.css órfão.
- config_page com glpi_tab urlencoded para a classe namespaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 18:04:13 -03:00

626 lines
21 KiB
PHP

<?php
namespace GlpiPlugin\Butterfly;
class Config extends \CommonDBTM {
private static $_cache = null;
static function getConfig($name, $defaultValue = null) {
if (self::$_cache === null) {
$config = new self();
$config->getEmpty();
$config->fields = array_merge($config->fields, \Config::getConfigurationValues('butterfly'));
self::$_cache = $config->fields;
}
if (isset(self::$_cache[$name]) && self::$_cache[$name] !== '') {
return self::$_cache[$name];
}
return $defaultValue;
}
function getTabNameForItem(\CommonGLPI $item, $withtemplate = 0) {
switch (get_class($item)) {
case 'Config':
return [1 => t_butterfly('Butterfly')];
default:
return '';
}
}
static function displayTabContentForItem(\CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
switch (get_class($item)) {
case 'Config':
$config = new self();
$config->showFormDisplay();
break;
}
return true;
}
protected static function checkPicture($name, $input, $old, $width = 0, $height = 0, $max_size = 500) {
$blank = "_blank_$name";
$new = "_$name";
if (isset($input[$blank]) && $input[$blank]) {
unset($input[$blank]);
if (isset($old[$name]) && $old[$name]) {
Toolbox::deletePicture($old[$name]);
}
$input[$name] = '';
} else if (isset($input[$new]) && !empty($input[$new]) && !empty($input[$new][0])) {
$picName = array_shift($input[$new]);
// Use GLPI temp directory if available, fallback to system temp
$glpiTmpConstant = 'GLPI_TMP_DIR';
$tmpDir = (defined($glpiTmpConstant) && constant($glpiTmpConstant)) ? constant($glpiTmpConstant) : sys_get_temp_dir();
$picPath = $tmpDir . '/' . $picName;
$picResizedPath = $tmpDir . '/resized_' . $picName;
if ($width || $height) {
if (Toolbox::resizePicture($picPath, $picResizedPath, $width, $height, 0, 0, 0, 0, $max_size)) {
$picPath = $picResizedPath;
}
}
if ($dest = Toolbox::savePicture($picPath)) {
$input[$name] = $dest;
} else {
\Session::addMessageAfterRedirect(__('Unable to save picture file.'), true, ERROR);
}
if (isset($old[$name]) && $old[$name]) {
Toolbox::deletePicture($old[$name]);
}
}
unset($input["_$name"]);
unset($input["_prefix_$name"]);
unset($input["_prefix_new_$name"]);
unset($input["_tag_$name"]);
unset($input["_tag_new_$name"]);
unset($input["_uploader_$name"]);
unset($input["new_$name"]);
unset($input[$blank]);
unset($input[$new]);
return $input;
}
protected static function checkCSS($name, $label, $input, $old) {
$fullName = "{$name}_css_custom";
$type = "{$name}_css_type";
if (!isset($input[$type])) {
$input[$type] = 'css';
}
if (isset($input[$fullName])) {
$input[$fullName] = html_entity_decode($input[$fullName]);
$input[$fullName] = preg_replace('/\\\\r\\\\n/', "\n", $input[$fullName]);
$input[$fullName] = preg_replace('/\\\\n/', "\n", $input[$fullName]);
if ($input[$type] === 'scss' && Scss::hasScssSuport()) {
try {
$variables = [];
$variables['butterfly_timestamp'] = Toolbox::getTimestamp();
Scss::compileScss($input[$fullName], $variables);
} catch (\Throwable $th) {
$message = sprintf(t_butterfly('Unable to compile the SCSS (%1$s). Message: '), $label);
\Session::addMessageAfterRedirect($message . $th->getMessage(), true, ERROR);
}
}
}
return $input;
}
static function configUpdate($input) {
$old = \Config::getConfigurationValues('butterfly');
unset($input['_no_history']);
$input = self::checkPicture('favicon_picture', $input, $old, 192, 192, 192);
$input = self::checkPicture('login_picture', $input, $old, 145, 80, 300);
$input = self::checkPicture('internal_picture', $input, $old, 100, 55, 300);
$input = self::checkPicture('login_background_picture', $input, $old);
$input = self::checkCSS('login', t_butterfly('Login Page'), $input, $old);
$input = self::checkCSS('internal', t_butterfly('Internal Page'), $input, $old);
foreach ($input as $key => $value) {
if ($value && strpos($key, '_blank_') === 0) {
$name = substr($key, 7);
$input[$name] = '';
}
}
$input['timestamp'] = time();
Toolbox::setTimestamp($input['timestamp']);
\Session::addMessageAfterRedirect(__('Item successfully updated'), false, INFO);
return $input;
}
function getEmpty() {
$defaultCss = 'css';
if (Scss::hasScssSuport()) {
$defaultCss = 'scss';
}
$this->fields = [
'favicon_picture' => '',
'page_title' => '',
'page_footer_display' => 'original',
'page_footer_text' => '',
'login_picture' => '',
'login_picture_max_width' => '240px',
'login_picture_max_height' => '130px',
'login_css_custom' => '',
'login_css_type' => $defaultCss,
'login_theme' => '',
'internal_picture' => '',
'internal_picture_width' => '100px',
'internal_picture_height' => '55px',
'internal_css_custom' => '',
'internal_css_type' => $defaultCss,
];
}
protected function buildPictureLine($name, $recommendedSize = null) {
if (!empty($this->fields[$name])) {
echo '<td>';
echo \Html::image(Toolbox::getPictureUrl($this->fields[$name]), [
'style' => '
max-width: 100px;
max-height: 100px;
background-image: linear-gradient(45deg, #b0b0b0 25%, transparent 25%), linear-gradient(-45deg, #b0b0b0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #b0b0b0 75%), linear-gradient(-45deg, transparent 75%, #b0b0b0 75%);
background-size: 10px 10px;
background-position: 0 0, 0 5px, 5px -5px, -5px 0px;',
'class' => 'picture_square'
]);
echo "&nbsp;";
echo \Html::getCheckbox([
'title' => t_butterfly('Reset'),
'name' => "_blank_$name"
]);
echo "&nbsp;" . t_butterfly('Reset');
echo '</td>';
echo '<td colspan="2">';
} else {
echo '<td colspan="3">';
}
\Html::file([
'name' => $name,
'onlyimages' => true,
'min_file_size' => 1,
]);
if ($recommendedSize) {
echo '<small>';
echo sprintf(t_butterfly('Recommended size: %1$s'), $recommendedSize);
echo '</small>';
}
echo '</td>';
}
protected function buildCssLine($name, $label) {
$fullName = "{$name}_css_custom";
$type = "{$name}_css_type";
echo "<tr><th colspan='4'>" . sprintf(t_butterfly('Custom CSS for %1$s'), $label) . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>";
echo t_butterfly('CSS Type') . ':';
echo "</td>";
echo "<td colspan='3'>";
$css_type = [
'off' => __('Disabled'),
'css' => 'CSS',
];
if (Scss::hasScssSuport()) {
$css_type['scss'] = 'SCSS';
}
\Dropdown::showFromArray($type, $css_type, ['value' => $this->fields[$type]]);
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
echo "<td colspan='4' style='max-width: 1000px'>";
$rand = mt_rand();
$containerId = $fullName . '_editor_' . $rand;
$value = rtrim($this->fields[$fullName]);
// Editor Monaco do core (GLPI 11) com fallback: o textarea escondido
// mantém o name/valor e é sincronizado no submit via evento formdata
echo sprintf(
'<textarea %1$s>',
\Html::parseAttributes([
'id' => $fullName . '_' . $rand,
'name' => $fullName,
'class' => 'butterfly-css-editor d-none',
])
);
echo $value;
echo '</textarea>';
echo '<div id="' . $containerId . '" style="height: 300px"></div>';
echo \Html::scriptBlock('
$(function() {
if (window.GLPI === undefined || window.GLPI.Monaco === undefined) {
$("#' . $fullName . '_' . $rand . '").removeClass("d-none").attr({rows: 15, spellcheck: false});
$("#' . $containerId . '").remove();
return;
}
window.GLPI.Monaco.createEditor(
' . json_encode($containerId, JSON_HEX_TAG | JSON_HEX_AMP) . ',
"scss",
' . json_encode($value, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) . ',
[]
).then(() => {
$("#' . $containerId . '").closest("form").on("formdata", (e) => {
const editors = window.monaco.editor.getEditors().filter(
(editor) => editor._domElement.id === ' . json_encode($containerId, JSON_HEX_TAG | JSON_HEX_AMP) . '
);
if (editors.length) {
e.originalEvent.formData.delete(' . json_encode($fullName, JSON_HEX_TAG | JSON_HEX_AMP) . ');
e.originalEvent.formData.append(' . json_encode($fullName, JSON_HEX_TAG | JSON_HEX_AMP) . ', editors[0].getValue());
}
});
});
});
');
echo "</td>";
echo "</tr>\n";
}
/**
* Print the config form for display
*
* @return Nothing (display)
* */
function showFormDisplay() {
global $CFG_GLPI;
if (!\Config::canView()) {
return false;
}
$this->getEmpty();
$this->fields = array_merge($this->fields, \Config::getConfigurationValues('butterfly'));
$canedit = \Session::haveRight(\Config::$rightname, UPDATE);
if ($canedit) {
echo "<form name='form' action=\"" . \Toolbox::getItemTypeFormURL('Config') . "\" method='post'>";
}
echo \Html::hidden('config_context', ['value' => 'butterfly']);
echo \Html::hidden('config_class', ['value' => __CLASS__]);
echo "<div class='center' id='tabsbody'>";
$rand = mt_rand();
echo "<style>
#tabs$rand .ui-tabs-nav {
width: auto;
}
#tabs$rand .ui-tabs-nav li {
clear: none;
width: auto;
margin-right: 5px;
}
#tabs$rand .ui-tabs-nav li a {
width: auto;
}
#tabs$rand .ui-tabs-panel {
margin-left: 0;
}
.butterfly-css-editor {
width: 100%;
border: 1px solid #eee;
font-family: monospace;
}
.butterfly-themeselect-container .select2-selection__rendered,
.butterfly-themeselect-dropdown .select2-results__option {
height: 80px !important;
line-height: 80px !important;
}
.butterfly-themeselect-container .select2-selection__rendered img,
.butterfly-themeselect-dropdown .select2-results__option img {
width: 170px;
height: 80px;
object-fit:cover;
float: right;
}
.butterfly-themeselect-container.select2-selection--single {
min-width: 350px;
max-width: 350px;
height: 80px !important;
}
.butterfly-themeselect-container.select2-selection--single:before {
line-height: 80px !important;
}
.butterfly-themeselect-container .select2-selection__arrow {
height: 79px !important;
}
</style>";
// echo "<div id='tabs$rand' class='center tab_cadre_fixe horizontal butterfly ui-tabs ui-corner-all ui-widget ui-widget-content'>";
// echo "<ul role='tablist' class='ui-tabs-nav ui-corner-all ui-helper-reset ui-helper-clearfix ui-widget-header'>";
// echo "<li><a href='#tab_butterfly_favicon'>" . t_butterfly('Favicon and Title') . "</a></li>";
// echo "<li><a href='#tab_butterfly_login'>" . t_butterfly('Login Page') . "</a></li>";
// echo "<li><a href='#tab_butterfly_internal'>" . t_butterfly('Internal Page') . "</a></li>";
// echo "</ul>";
// General
echo "<div id='tab_butterfly_favicon'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='noHover'><th colspan='4' >" . t_butterfly('Favicon and Title') . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Picture') . "</td>";
$this->buildPictureLine('favicon_picture', '192px x 192px');
echo "</tr>\n";
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Title') . "</td>";
echo "<td colspan='3'>";
echo sprintf(
'<input type="text" %1$s />',
\Html::parseAttributes([
'name' => 'page_title',
'value' => $this->fields['page_title'],
'style' => 'width: 98%',
])
);
echo "</td>";
echo "</tr>\n";
echo "<tr class='tab_bg_1'>";
echo "<td>" . t_butterfly('Footer') . "</td>";
echo "<td colspan='3'>";
\Dropdown::showFromArray('page_footer_display', [
'original' => __('Original'),
'hide' => t_butterfly('Hide'),
'custom' => t_butterfly('Custom'),
], ['value' => $this->fields['page_footer_display']]);
echo "</td>";
echo "</tr>\n";
echo "<tr class='tab_bg_1'>";
echo "<td>" . t_butterfly('Footer text') . "</td>";
echo "<td colspan='3'>";
\Html::textarea([
'rand' => $rand,
'editor_id' => 'text' . $rand,
'name' => 'page_footer_text',
'value' => $this->fields['page_footer_text'],
'style' => 'width: 98%',
'rows' => 1,
'enable_richtext' => true,
'enable_fileupload' => false,
'enable_images' => false,
]);
?>
<script type="text/javascript">
$(document).ready(function() {
var editor = tinyMCE.get(<?php echo json_encode('text' . $rand) ?>);
editor.settings.force_br_newlines = true;
editor.settings.force_p_newlines = false;
editor.settings.forced_root_block = '';
});
</script>
<?php
echo "</td>";
echo "</tr>\n";
echo "</table>";
echo "</div>";
// Login
echo "<div id='tab_butterfly_login'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='noHover'><th colspan='4' >" . t_butterfly('Login Page') . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . t_butterfly('Theme') . "</td>";
echo "<td colspan='2'>";
echo sprintf(
'<select %1$s>',
\Html::parseAttributes([
'name' => 'login_theme',
])
);
echo '<option value="">GLPI (' . __('Original') . ')</option>';
foreach (Theme::getLoginThemes() as $id => $theme) {
$attrs = [
'name' => 'login_theme',
'value' => $id,
];
if (isset($theme['login-preview']) && $theme['login-preview']) {
$attrs['data-preview'] = $theme['login-preview'];
}
if ($id === $this->fields['login_theme']) {
$attrs['selected'] = 'selected';
}
echo sprintf(
'<option %1$s>%2$s</option>',
\Html::parseAttributes($attrs),
htmlentities($theme['name'])
);
}
echo '</select>';
?>
<script type="text/javascript">
var butterflyPluginBase = <?php echo json_encode($CFG_GLPI['root_doc'] . '/plugins/butterfly', JSON_HEX_TAG | JSON_HEX_AMP) ?>;
function butterflyFormatThemes(theme) {
var data = theme && theme.element && theme.element.dataset || {};
if (!theme.id || !data.preview) {
return $('<span></span>', {
html: '<img src="' + butterflyPluginBase + '/pics/login.preview.png"/>&nbsp;' + theme.text
});
}
return $('<span></span>', {
html: '<img src="' + butterflyPluginBase + '/front/picture.send.php?theme=' + encodeURIComponent(theme.id) + '&path=' + encodeURIComponent(data.preview) + '"/>&nbsp;' + theme.text
});
}
$("select[name=login_theme]").select2({
templateResult: butterflyFormatThemes,
templateSelection: butterflyFormatThemes,
width: '100%',
containerCssClass: 'butterfly-themeselect-container',
dropdownCssClass: 'butterfly-themeselect-dropdown',
escapeMarkup: function(m) {
return m;
}
});
</script>
<?php
echo "</td>";
echo "<td>";
echo '<a id="butterfly-preview" href="#">' . __('Preview') . '</a>';
?>
<script type="text/javascript">
$('#butterfly-preview').on('click', function() {
var url = <?php echo json_encode($CFG_GLPI['root_doc'] . "/index.php") ?>;
url += '?noAUTO=1';
var theme = $("select[name=login_theme]").val() || 'original';
url += '&theme=' + theme;
window.open(url, 'butterfly_preview', 'titlebar=0&status=0');
});
</script>
<?php
echo "</td>";
echo "</tr>\n";
$themeInfo = Theme::getThemeInfo($this->fields['login_theme']);
if ($themeInfo && isset($themeInfo['variables'])) {
foreach ($themeInfo['variables'] as $k => $v) {
$themeId = $themeInfo['id'];
$fieldName = "login_theme-$themeId-$k";
$fieldValue = $v['default'];
if (isset($this->fields[$fieldName]) && $this->fields[$fieldName]) {
$fieldValue = $this->fields[$fieldName];
}
echo "<tr class='tab_bg_1'>";
echo "<td>" . t_butterfly($v['name']) . "</td>";
echo "<td>";
\Html::showColorField($fieldName, [
'value' => $fieldValue
]);
echo "&nbsp;";
echo \Html::getCheckbox([
'title' => t_butterfly('Reset'),
'name' => "_blank_$fieldName"
]);
echo "&nbsp;" . t_butterfly('Reset');
echo "</td>";
echo "<td></td>";
echo "<td></td>";
echo "</tr>\n";
}
}
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Picture') . "</td>";
$this->buildPictureLine('login_picture', '145px x 80px');
echo "</tr>\n";
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Width') . "</td>";
echo "<td>";
echo sprintf(
'<input type="text" %1$s />',
\Html::parseAttributes([
'name' => 'login_picture_max_width',
'value' => $this->fields['login_picture_max_width'],
])
);
echo "</td>";
echo "<td>" . __('Height') . "</td>";
echo "<td>";
echo sprintf(
'<input type="text" %1$s />',
\Html::parseAttributes([
'name' => 'login_picture_max_height',
'value' => $this->fields['login_picture_max_height'],
])
);
echo "</td>";
echo "</tr>\n";
echo "<tr class='tab_bg_1'>";
echo "<td>" . t_butterfly('Background picture') . "</td>";
$this->buildPictureLine('login_background_picture', '1920px x 1080px');
echo "</tr>\n";
// Custom CSS Login
$this->buildCssLine('login', t_butterfly('Login Page'));
echo "</table>";
echo "</div>";
// Internal Page
echo "<div id='tab_butterfly_internal'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='noHover'><th colspan='4' >" . t_butterfly('Internal Page') . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Picture') . "</td>";
$this->buildPictureLine('internal_picture', '100px x 55px');
echo "</tr>\n";
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Width') . "</td>";
echo "<td>";
echo sprintf(
'<input type="text" %1$s />',
\Html::parseAttributes([
'name' => 'internal_picture_width',
'value' => $this->fields['internal_picture_width'],
])
);
echo "</td>";
echo "<td>" . __('Height') . "</td>";
echo "<td>";
echo sprintf(
'<input type="text" %1$s />',
\Html::parseAttributes([
'name' => 'internal_picture_height',
'value' => $this->fields['internal_picture_height'],
])
);
echo "</td>";
echo "</tr>\n";
// Custom CSS Internal
$this->buildCssLine('internal', t_butterfly('Internal Page'));
echo "</table>";
echo "</div>";
// echo "</div>";
if ($canedit) {
echo "<table class='tab_cadre_fixe'>";
echo "<tr class='tab_bg_2'>";
echo "<td colspan='4' class='center'>";
echo "<input type='submit' name='update' class='submit' value=\"" . _sx('button', 'Save') . "\">";
echo "</td></tr>";
echo "</table>";
}
\Html::closeForm();
}
}