| Current Path : /var/www/html/libraries/noboss/src/Form/Field/ |
| Current File : /var/www/html/libraries/noboss/src/Form/Field/NbTransferToolsField.php |
<?php
/**
* @package No Boss Extensions
* @subpackage No Boss Library
* @author No Boss Technology <contact@nobosstechnology.com>
* @copyright Copyright (C) 2026 No Boss Technology. All rights reserved.
* @license GNU Lesser General Public License version 3 or later; see <https://www.gnu.org/licenses/lgpl-3.0.en.html>
*/
namespace Noboss\Library\Form\Field;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Field de ferramentas para exportar e importar os parâmetros (params) do módulo em formato JSON.
*
* @since 1.0.0
*/
class NbTransferToolsField extends FormField
{
/**
* Tipo do field no XML do formulário.
*
* @var string
* @since 1.0.0
*/
protected $type = 'NbTransferTools';
/**
* Omite a label padrão do Joomla — o título é renderizado dentro do markup do field.
*
* @return string
*
* @since 1.0.0
*/
protected function getLabel(): string
{
return '';
}
/**
* Renderiza a seção de copiar configurações com botões de exportar/importar params do módulo.
*
* Botões ficam desabilitados no site demo ou quando o módulo ainda não foi salvo (sem ID).
*
* @return string HTML da seção de ferramentas
*
* @since 1.0.0
*/
protected function getInput(): string
{
$editModuleId = $this->getEditModuleId();
$moduleIdForDom = $editModuleId > 0 ? $editModuleId : 0;
$buttonsDisabled = $this->isSiteDemo() || $editModuleId === 0;
$exportUrl = $this->buildNobossAjaxUrl('noboss.src.Util.NbModuleParamsUtil', 'exportModuleParams', ['id_module' => max($editModuleId, 1)]);
$importUrl = $this->buildNobossAjaxUrl('noboss.src.Util.NbModuleParamsUtil', 'importModuleParams');
$inputFileId = 'nb-layout-import-file-' . $moduleIdForDom;
$statusId = 'nb-layout-import-status-' . $moduleIdForDom;
if ($buttonsDisabled) {
$exportButton = $this->renderDisabledButton('icon-download', 'LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_EXPORT_BUTTON');
$importButton = $this->renderDisabledButton('icon-upload', 'LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_IMPORT_BUTTON');
} else {
$exportButton = $this->renderLinkButton($exportUrl, 'icon-download', 'LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_EXPORT_BUTTON');
$importButton = '<input type="file" id="' . $inputFileId . '" accept=".json,application/json" style="display:none;" />';
$importButton .= '<label for="' . $inputFileId . '" class="btn btn-secondary hasTooltip" style="cursor:pointer; margin-bottom:0;">';
$importButton .= '<span class="icon-upload" aria-hidden="true"></span> ';
$importButton .= '<span>' . Text::_('LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_IMPORT_BUTTON') . '</span>';
$importButton .= '</label>';
}
$statusDiv = '<div id="' . $statusId . '" role="alert" aria-live="polite" style="display:none; margin-top: 0.75rem;"></div>';
Factory::getApplication()->getDocument()->getWebAssetManager()->addInline(
'style',
'.nb-tools-section .btn::before{display:none!important;content:none!important}',
['name' => 'nb-tools-section-btn-icons']
);
$html = '<div class="nb-transfer-tools nb-tools-section" style="margin-top: 20px;">';
$html .= '<h4><span class="icon-copy" aria-hidden="true"></span> ' . Text::_('LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_TITLE') . '</h4>';
$html .= '<hr>';
$html .= '<p class="mb-2 small text-muted"><span class="icon-info-circle" aria-hidden="true"></span> '
. Text::_('LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_INFO_NOTE') . '</p>';
$html .= '<div class="btn-toolbar" role="toolbar" style="gap: 0.5rem; flex-wrap: wrap;">' . $exportButton . $importButton . '</div>';
$html .= $statusDiv;
$html .= '</div>';
if (!$buttonsDisabled) {
Factory::getApplication()->getDocument()->getWebAssetManager()->addInline(
'script',
$this->buildImportScript($inputFileId, $statusId, $importUrl, $editModuleId),
['name' => 'nb-layout-import-' . $editModuleId]
);
}
return $html;
}
/**
* Verifica se a extensão está sendo executada no site de demonstração No Boss.
*
* @return bool
*
* @since 1.0.0
*/
private function isSiteDemo(): bool
{
return strpos(Uri::root(), 'nobossextensions.com/demos') !== false;
}
/**
* Monta URL para requisição AJAX via com_nobossajax.
*
* @param string $library Caminho da library (ex.: noboss.src.Util.NbModuleParamsUtil)
* @param string $method Método a invocar
* @param array $params Parâmetros adicionais da query string
*
* @return string
*
* @since 1.0.0
*/
private function buildNobossAjaxUrl(string $library, string $method, array $params = []): string
{
$query = http_build_query(array_merge([
'option' => 'com_nobossajax',
'library' => $library,
'method' => $method,
'format' => 'raw',
], $params));
return Uri::root() . 'index.php?' . $query;
}
/**
* Retorna o ID do módulo em edição no com_modules, ou 0 quando não aplicável.
*
* @return int
*
* @since 1.0.0
*/
private function getEditModuleId(): int
{
$input = Factory::getApplication()->input;
if ($input->getCmd('option') === 'com_modules') {
return $input->getInt('id', 0);
}
return 0;
}
/**
* Renderiza um link estilizado como botão secundário do Joomla.
*
* @param string $url URL completa do href
* @param string $icon Classe do ícone Joomla
* @param string $labelKey Chave de tradução do rótulo e tooltip
*
* @return string
*
* @since 1.0.0
*/
private function renderLinkButton(string $url, string $icon, string $labelKey): string
{
$label = Text::_($labelKey);
return '<a href="' . $url . '" target="_blank" rel="noopener noreferrer" class="btn btn-secondary hasTooltip" title="' . $label . '">'
. '<span class="' . $icon . '" aria-hidden="true"></span> '
. '<span>' . $label . '</span>'
. '</a>';
}
/**
* Renderiza um botão visualmente idêntico ao ativo, porém sem ação (estado disabled).
*
* @param string $icon Classe do ícone Joomla
* @param string $labelKey Chave de tradução do rótulo e tooltip
*
* @return string
*
* @since 1.0.0
*/
private function renderDisabledButton(string $icon, string $labelKey): string
{
$label = Text::_($labelKey);
return '<span class="btn btn-secondary disabled" aria-disabled="true" title="' . $label . '">'
. '<span class="' . $icon . '" aria-hidden="true"></span> '
. '<span>' . $label . '</span>'
. '</span>';
}
/**
* Gera o JavaScript do fluxo de importação de params via upload de arquivo JSON.
*
* @param string $inputFileId ID do input file oculto
* @param string $statusId ID do elemento de feedback de status
* @param string $importUrl URL do endpoint de importação
* @param int $editModuleId ID do módulo enviado no POST
*
* @return string
*
* @since 1.0.0
*/
private function buildImportScript(string $inputFileId, string $statusId, string $importUrl, int $editModuleId): string
{
$importUrl = addslashes($importUrl);
$editModuleId = (int) $editModuleId;
$msgProcessing = addslashes(Text::_('LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_IMPORT_PROCESSING'));
$msgInvalidJson = addslashes(Text::_('LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_IMPORT_ERROR_INVALID_JSON'));
$msgInvalidObj = addslashes(Text::_('LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_IMPORT_ERROR_INVALID_STRUCTURE'));
$msgNetworkErr = addslashes(Text::_('LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_IMPORT_ERROR_NETWORK'));
$msgConfirm = addslashes(Text::_('LIB_NOBOSS_FIELD_NBTRANSFERTOOLS_IMPORT_CONFIRM'));
return <<<JS
document.addEventListener('DOMContentLoaded', function () {
var fileInput = document.getElementById('{$inputFileId}');
var statusEl = document.getElementById('{$statusId}');
if (!fileInput || !statusEl) { return; }
function showStatus(message, type) {
statusEl.className = 'alert alert-' + type;
statusEl.innerHTML = message;
statusEl.style.display = 'block';
}
fileInput.addEventListener('change', function () {
var file = fileInput.files[0];
if (!file) { return; }
statusEl.style.display = 'none';
fileInput.value = '';
var reader = new FileReader();
reader.onload = function (e) {
var raw = e.target.result;
var parsed;
try {
parsed = JSON.parse(raw);
} catch (err) {
showStatus('<span class="icon-warning" aria-hidden="true"></span> {$msgInvalidJson}', 'warning');
return;
}
if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {
showStatus('<span class="icon-warning" aria-hidden="true"></span> {$msgInvalidObj}', 'warning');
return;
}
if (!confirm('{$msgConfirm}')) { return; }
showStatus('<span class="icon-spinner icon-spin" aria-hidden="true"></span> {$msgProcessing}', 'info');
var formData = new FormData();
formData.append('id_module', '{$editModuleId}');
formData.append('json_content', raw);
fetch('{$importUrl}', {
method: 'POST',
credentials: 'same-origin',
body: formData
})
.then(function (response) { return response.json(); })
.then(function (data) {
if (data && data.success) {
showStatus('<span class="icon-checkmark" aria-hidden="true"></span> ' + data.message, 'success');
} else {
showStatus('<span class="icon-warning" aria-hidden="true"></span> ' + (data ? data.message : '{$msgNetworkErr}'), 'warning');
}
})
.catch(function () {
showStatus('<span class="icon-warning" aria-hidden="true"></span> {$msgNetworkErr}', 'warning');
});
};
reader.readAsText(file, 'UTF-8');
});
});
JS;
}
}