| Current Path : /var/www/html/libraries/noboss/src/Form/Field/ |
| Current File : /var/www/html/libraries/noboss/src/Form/Field/NbFileListField.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\Field\FilelistField;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* NbFileListField
*
* Field que lista arquivos de áudio e vídeo do servidor. Permite definir o diretório
* diretamente no XML do field ou via parâmetro do componente, restringir extensões
* permitidas e criar o diretório automaticamente quando necessário. Em caso de erro
* exibe mensagens amigáveis abaixo do campo.
*
* Utilizado inicialmente em: No Boss FAQ e No Boss Video Gallery.
*
* Documentacao: https://wiki.nobossextensions.com/private/nb/plataforma-e-extensoes/no-boss-file-list-field
*/
class NbFileListField extends FilelistField {
protected $type = "NbFileList";
/**
* Mensagem de erro ao tentar criar o diretório (exibida abaixo do campo)
* @var string
*/
protected $directoryCreationError = '';
protected function getOptions() {
$options = parent::getOptions();
array_unshift($options, HTMLHelper::_('select.option', '', Text::_('LIB_NOBOSS_FIELD_NBFILELIST_SELECT_FILE')));
return $options;
}
public function setup(\SimpleXMLElement $element, $value, $group = null){
// Obtém o componente que estamos editando dinamicamente
$input = Factory::getApplication()->input;
$option = $input->getCmd('option', '');
$componentParams = null;
if(!empty($option)) {
$componentParams = ComponentHelper::getParams($option);
}
// Armazena extensoes de arquivos permitidas
$extList = '';
// Extensoes de arquivos permitidos - se o field XML indicar um nome de parâmetro do componente (config_field_name_file_granted) e o parâmetro estiver definido no componente, usamos esse valor. Caso contrário, usamos o valor definido diretamente no XML do field (file_upload_extensions_granted) se presente.
if (isset($element['config_field_name_file_granted']) && (string) $element['config_field_name_file_granted'] !== '' && $componentParams) {
$paramNameFileGranted = (string) $element['config_field_name_file_granted'];
$paramValueFileGranted = $componentParams->get($paramNameFileGranted, '');
if ($paramValueFileGranted !== '') {
// If the param is a multiple select (list with multiple), it may return an array.
// Convert array to CSV so later processing (explode) works as expected.
if (is_array($paramValueFileGranted)) {
$extList = implode(',', $paramValueFileGranted);
} else {
$extList = (string) $paramValueFileGranted;
}
}
}
// Extensoes de arquivos permitidos - Se não obtemos a lista via config do componente, usa o valor direto no XML do field
if ($extList === '' && isset($element['file_upload_extensions_granted']) && (string) $element['file_upload_extensions_granted'] !== '') {
$extList = (string) $element['file_upload_extensions_granted'];
}
// Extensoes de arquivos permitidos - Se agora temos uma lista de extensões, converte para regex e define no elemento
if ($extList !== '') {
// Normaliza: divide por vírgula, trim, remove pontos iniciais e itens vazios
$parts = array_filter(array_map(function ($p) {
$p = trim($p);
$p = ltrim($p, '.');
return $p === '' ? null : $p;
}, explode(',', $extList)));
if (!empty($parts)) {
// Escapa cada parte para uso em regex e constrói um padrão que case as extensões (case-insensitive)
$escaped = array_map(function ($p) {
return preg_quote($p, '/');
}, $parts);
$pattern = '(?i)\.(' . implode('|', $escaped) . ')$';
// Define ambos os nomes de atributo possíveis para que o FilelistField os reconheça
$element['filter'] = $pattern;
$element['fileFilter'] = $pattern;
}
}
// Inicializa variável que conterá o diretório a ser listado
$directoryList = '';
// Diretório a ser listado - se o field XML indicar um nome de parâmetro do componente (config_field_name_directory) e o parâmetro estiver definido no componente, usamos esse valor. Caso contrário, usamos o valor definido diretamente no XML do field (directory) se presente.
if (isset($element['config_field_name_directory']) && (string) $element['config_field_name_directory'] !== '' && $componentParams) {
$paramNameDirectory = (string) $element['config_field_name_directory'];
$paramValueDirectory = $componentParams->get($paramNameDirectory, '');
if ($paramValueDirectory !== '') {
$directoryList = (string) $paramValueDirectory;
}
}
// Diretório a ser listado - Se não obtemos o diretório via config do componente, usa o valor direto no XML do field
if ($directoryList === '' && isset($element['directory']) && (string) $element['directory'] !== '') {
$directoryList = (string) $element['directory'];
}
// Diretório a ser listado - Se ainda não temos diretório, informa erro
if ($directoryList === '') {
$this->directoryCreationError = '<div class="nbfilelist__error">' .
'No directory was provided in the field (attribute "directory" or "config_field_name_directory" is missing).' .
'</div>';
}
// Diretório a ser listado - Se temos um valor em $directoryList, realiza verificação/ criação do diretório
if ($directoryList !== '') {
// Sanitiza: remove bytes nulos e espaços
$directoryList = trim(str_replace("\0", '', $directoryList));
// Rejeita caminhos com segmentos de subida (..)
if (strpos($directoryList, '..') !== false) {
$this->directoryCreationError = '<div class="nbfilelist__error">' .
'The provided path contains invalid segments (..).' .
'</div>';
$element['directory'] = '';
return parent::setup($element, $value, $group);
}
// Substitui %s no label pelo diretório, se presente
if (isset($element['label']) && strpos(Text::_($element['label']), '%s') !== false) {
$element['label'] = sprintf(Text::_($element['label']), $directoryList);
}
// Substitui %s no description pelo diretório, se presente
if (isset($element['description']) && strpos(Text::_($element['description']), '%s') !== false) {
$element['description'] = sprintf(Text::_($element['description']), $directoryList);
}
// Monta caminho completo com JPATH_ROOT
$trimmed = ltrim($directoryList, DIRECTORY_SEPARATOR);
$fullPath = rtrim(JPATH_ROOT, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $trimmed;
// Se o diretório não existir, tenta criar (recursivo)
if (!is_dir($fullPath)) {
// Tenta criar e captura eventual erro
$created = @mkdir($fullPath, 0755, true);
if (!$created) {
$lastError = error_get_last();
$message = isset($lastError['message']) ? $lastError['message'] : 'Error creating directory.';
// Armazena a mensagem para ser exibida no getInput()
$this->directoryCreationError = '<div class="nbfilelist__error">' .
'Error creating directory <strong>' . htmlspecialchars($fullPath, ENT_QUOTES, 'UTF-8') . '</strong>: ' .
htmlspecialchars($message, ENT_QUOTES, 'UTF-8') .
'</div>';
$element['directory'] = '';
}
} else {
// Diretório existe — verifica se é legível. Se não for legível, evita que o core do Joomla tente listar e gere erro global.
if (!is_readable($fullPath)) {
$this->directoryCreationError = '<div class="nbfilelist__error">' .
'The directory <strong>' . htmlspecialchars($fullPath, ENT_QUOTES, 'UTF-8') . '</strong> exists but is not readable by the webserver. Please check permissions.' .
'</div>';
$element['directory'] = '';
}
}
}
return parent::setup($element, $value, $group);
}
protected function getInput(){
// Obtém o document de forma robusta e pega o WebAssetManager quando disponível
$doc = Factory::getApplication()->getDocument();
$wa = (is_object($doc) && method_exists($doc, 'getWebAssetManager')) ? $doc->getWebAssetManager() : null;
// JS e CSS do field — registra o estilo apenas se o WebAssetManager estiver disponível
if ($wa) {
// $wa->registerAndUseScript('nobossfilelist', Uri::root()."libraries/noboss/src/Form/Field/assets/js/min/nobossfilelist.min.js");
$wa->registerAndUseStyle('nobossfilelist', Uri::root()."libraries/noboss/src/Form/Field/assets/stylesheets/css/nobossfilelist.min.css");
}
$html = parent::getInput();
// Botão para abrir o Gerenciador de Mídias em nova aba
$mediaUrl = 'index.php?option=com_media&path=local-files:/';
$label = Text::_('LIB_NOBOSS_FIELD_NBFILELIST_OPEN_MEDIA_MANAGER');
$button = '<a href="' . htmlspecialchars($mediaUrl, ENT_QUOTES, 'UTF-8') . '" '
. 'target="_blank" rel="noopener" '
. 'class="btn btn-secondary btn-sm nbfilelist__media-btn" '
. 'style="display:inline-block; margin-top:6px">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</a>';
// Botão para abrir a documentação externa (wiki)
$docUrl = 'https://wiki.nobossextensions.com/no-boss-library/como-subir-um-arquivo-audio-video-para-o-servidor-do-seu-site';
$docLabel = Text::_('LIB_NOBOSS_FIELD_NBFILELIST_OPEN_DOC');
// Exibe a documentação como um link simples (visual de link, não botão)
$docButton = '<a href="' . htmlspecialchars($docUrl, ENT_QUOTES, 'UTF-8') . '" '
. 'target="_blank" rel="noopener" '
. 'class="nbfilelist__doc-link">' . htmlspecialchars($docLabel, ENT_QUOTES, 'UTF-8') . '</a>';
// Insere os botões logo após o input do field (mídias e documentação)
$html .= $button . $docButton;
// Se houve erro ao criar diretório, exibe mensagem em vermelho abaixo do campo
if (!empty($this->directoryCreationError)) {
$html .= $this->directoryCreationError;
}
return $html;
}
}