Campi obbligatori & fix

This commit is contained in:
2025-09-08 17:38:47 +02:00
parent cbcbcc6f8e
commit 5dec036e78
13 changed files with 711 additions and 114 deletions

View File

@ -3,8 +3,8 @@ namespace Pcrt\Component\Circolari\Administrator\Field;
\defined('_JEXEC') or die;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
class CalSafeField extends FormField
{
@ -16,11 +16,14 @@ class CalSafeField extends FormField
$id = $this->id;
$value = (string) $this->value;
if (!empty($value)) {
// Normalizza il valore DB in formato datetime-local (YYYY-MM-DDTHH:MM)
if ($value !== '' && $value !== '0000-00-00 00:00:00') {
$value = preg_replace('/\s+/', ' ', $value);
$value = str_replace('T', ' ', $value);
if (preg_match('/^(\d{4}-\d{2}-\d{2})\s(\d{2}:\d{2})(?::\d{2})?$/', $value, $m)) {
$value = $m[1] . 'T' . $m[2];
if (preg_match('/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}(:\d{2})?$/', $value)) {
// "YYYY-MM-DD HH:MM(:SS)" -> "YYYY-MM-DDTHH:MM"
$value = str_replace(' ', 'T', substr($value, 0, 16));
} else {
try {
$dt = Factory::getDate($value);
@ -29,20 +32,43 @@ class CalSafeField extends FormField
$value = '';
}
}
} else {
$value = '';
}
$attrs = [];
$attrs[] = 'type="datetime-local"';
$attrs[] = 'name="' . htmlspecialchars($name, ENT_COMPAT, 'UTF-8') . '"';
$attrs[] = 'id="' . htmlspecialchars($id, ENT_COMPAT, 'UTF-8') . '"';
$attrs[] = 'name="' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '"';
$attrs[] = 'id="' . htmlspecialchars($id, ENT_QUOTES, 'UTF-8') . '"';
if ($value !== '') {
$attrs[] = 'value="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"';
$attrs[] = 'value="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"';
}
if ($this->required) { $attrs[] = 'required'; }
// readonly/disabled da XML o proprietà
$readonly = (string) ($this->element['readonly'] ?? '');
if ($readonly === 'true' || $readonly === 'readonly' || $this->readonly) {
$attrs[] = 'readonly';
}
$disabled = (string) ($this->element['disabled'] ?? '');
if ($disabled === 'true' || $disabled === 'disabled' || $this->disabled) {
$attrs[] = 'disabled';
}
// Pass-through opzionali: min/max/step/placeholder
foreach (['min','max','step','placeholder'] as $p) {
if (isset($this->element[$p]) && (string)$this->element[$p] !== '') {
$attrs[] = $p . '="' . htmlspecialchars((string)$this->element[$p], ENT_QUOTES, 'UTF-8') . '"';
}
}
// Classe CSS
$class = trim((string) ($this->element['class'] ?? 'form-control'));
if ($class !== '') {
$attrs[] = 'class="' . htmlspecialchars($class, ENT_QUOTES, 'UTF-8') . '"';
}
if ($this->required) $attrs.append('required');
if ($this->readonly) $attrs.append('readonly');
if ($this->disabled) $attrs.append('disabled');
$class = $this->element['class'] ? (string) $this->element['class'] : 'form-control';
$attrs[] = 'class="' . htmlspecialchars($class, ENT_COMPAT, 'UTF-8') . '"';
return '<input ' . implode(' ', $attrs) . ' />';
}

View File

@ -149,6 +149,7 @@ class CircolareModel extends AdminModel
return $data;
}
/**
* Method to get a single record.
*
@ -282,6 +283,16 @@ class CircolareModel extends AdminModel
public function check()
{
// Se firma obbligatoria, scadenza deve esserci
$isObbl = (int) ($this->firma_obbligatoria ?? 0);
if ($isObbl === 1 && empty($this->scadenza)) {
$this->setError(\Joomla\CMS\Language\Text::_('COM_CIRCOLARI_ERR_SCADENZA_REQUIRED'));
return false;
}
// ordering per nuovi record
if (property_exists($this, 'ordering') && (int) $this->id === 0) {
$this->ordering = self::getNextOrder();

View File

@ -252,6 +252,36 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
}
}
$raw = isset($this->scadenza) ? trim((string) $this->scadenza) : '';
if ($raw === '' || $raw === '0000-00-00 00:00:00') {
// Se non è obbligatoria, salva NULL
if ((int) ($this->firma_obbligatoria ?? 0) === 1) {
$this->setError(\Joomla\CMS\Language\Text::_('Campo Data Scadenza Firma Mancante'));
return false;
}
$this->scadenza = null;
} else {
// Accetta vari formati e salva in SQL
$clean = str_replace('T', ' ', substr($raw, 0, 19));
$dt = \DateTime::createFromFormat('Y-m-d H:i', $clean)
?: \DateTime::createFromFormat('Y-m-d H:i:s', $clean)
?: \DateTime::createFromFormat('d-m-Y H:i', $clean)
?: \DateTime::createFromFormat('d-m-Y H:i:s', $clean);
if ($dt === false) {
$this->setError(\Joomla\CMS\Language\Text::_('Campo Data Scadenza Firma Mancante'));
return false;
}
$this->scadenza = $dt->format('Y-m-d H:i:s');
}
// (2) Se firma è obbligatoria, la scadenza deve esserci
if ((int) ($this->firma_obbligatoria ?? 0) === 1 && empty($this->scadenza)) {
$this->setError(\Joomla\CMS\Language\Text::_('Campo Data Scadenza Firma Mancante'));
return false;
}
return parent::check();
}

View File

@ -25,7 +25,7 @@ HTMLHelper::_('bootstrap.tooltip');
<form
action="<?php echo Route::_('index.php?option=com_circolari&layout=edit&id=' . (int) $this->item->id); ?>"
method="post" enctype="multipart/form-data" name="adminForm" id="adminForm" class="form-validate" class="form-validate form-horizontal">
method="post" enctype="multipart/form-data" name="adminForm" id="adminForm" class="form-validate form-horizontal">
<?php echo HTMLHelper::_('uitab.startTabSet', 'myTab', array('active' => 'Circolari')); ?>
@ -37,7 +37,6 @@ HTMLHelper::_('bootstrap.tooltip');
<?php echo $this->form->renderField('title'); ?>
<?php echo $this->form->renderField('alias'); ?>
<?php echo $this->form->renderField('categoria_id'); ?>
<?php echo $this->form->renderField('usergroup_id'); ?>
<?php echo $this->form->renderField('hits'); ?>
<?php echo $this->form->renderField('description'); ?>
<?php echo $this->form->renderField('attachment'); ?>
@ -57,3 +56,64 @@ HTMLHelper::_('bootstrap.tooltip');
<input type="hidden" name="task" value="" />
<?php echo HTMLHelper::_('form.token'); ?>
</form>
<?php
Factory::getDocument()->addScriptDeclaration(<<<'JS'
(function(){
// Supporta sia select che radio per jform_firma_obbligatoria
function getFirmaObbligatoriaValue(){
var radios = document.querySelectorAll('input[name="jform[firma_obbligatoria]"]');
if (radios.length){
var r = Array.prototype.find.call(radios, function(x){ return x.checked; });
return r ? r.value : '0';
}
var sel = document.getElementById('jform_firma_obbligatoria');
return sel ? sel.value : '0';
}
function toggleScadenzaRequired(){
var need = getFirmaObbligatoriaValue() === '1';
var input = document.getElementById('jform_scadenza');
var label = document.getElementById('jform_scadenza-lbl');
if (input){
if (need){
input.setAttribute('required','required');
input.setAttribute('aria-required','true');
} else {
input.removeAttribute('required');
input.removeAttribute('aria-required');
input.classList.remove('invalid');
}
}
if (label){
if (need){
label.classList.add('required');
if (!label.querySelector('.star')){
var s = document.createElement('span');
s.className = 'star';
s.setAttribute('aria-hidden','true');
s.innerHTML = '&nbsp;*';
label.appendChild(s);
}
} else {
label.classList.remove('required');
var star = label.querySelector('.star');
if (star){ star.remove(); }
}
}
}
// Bind change su select/radio
document.addEventListener('change', function(e){
if (e.target && (e.target.id === 'jform_firma_obbligatoria' || e.target.name === 'jform[firma_obbligatoria]')){
toggleScadenzaRequired();
}
});
// Init
document.addEventListener('DOMContentLoaded', toggleScadenzaRequired);
})();
JS);

View File

@ -33,6 +33,7 @@
<folder>src</folder>
<folder>services</folder>
<folder>tmpl</folder>
<folder>views</folder>
</files>
<media destination="com_circolari" folder="media">
<folder>css</folder>

View File

@ -1,4 +1,5 @@
<?php
namespace Pcrt\Component\Circolari\Site\Controller;
\defined('_JEXEC') or die;
@ -26,6 +27,10 @@ class DisplayController extends BaseController
}
}
if (!in_array($view, ['circolari', 'circolare', 'form'])) {
$this->input->set('view', $this->default_view);
}
return parent::display($cachable, $urlparams);
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace Pcrt\Component\Circolari\Site\Controller;
\defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
class FormController extends BaseController
{
public function display($cachable = false, $urlparams = [])
{
// Solo utenti autenticati + permesso admin/manage
$app = Factory::getApplication();
$user = $app->getIdentity();
if ($user->guest || (!$user->authorise('core.manage', 'com_circolari') && !$user->authorise('core.admin', 'com_circolari'))) {
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
$view = $this->input->getCmd('view', 'form');
$layout = $this->input->getCmd('layout', 'edit');
$id = $this->input->getInt('id');
$this->input->set('view', $view);
$this->input->set('layout', $layout);
$this->input->set('id', $id);
return parent::display();
}
public function save()
{
if (!Session::checkToken('post')) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$app = Factory::getApplication();
$user = $app->getIdentity();
if ($user->guest || (!$user->authorise('core.manage', 'com_circolari') && !$user->authorise('core.admin', 'com_circolari'))) {
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
$data = [
'id' => $this->input->getInt('id', 0),
'title' => trim($this->input->getString('title', '')),
'alias' => trim($this->input->getString('alias', '')),
'description' => $this->input->get('description', '', 'raw'),
'categoria_id' => $this->input->getInt('categoria_id', 0),
'tipologia_firma_id' => $this->input->getInt('tipologia_firma_id', 0),
'firma_obbligatoria' => $this->input->getInt('firma_obbligatoria', 0),
'scadenza' => $this->input->getString('scadenza', ''), // 'YYYY-MM-DD HH:MM'
'state' => $this->input->getInt('state', 1),
// nuovi campi per creare tipologia al volo
'nuova_tipologia_nome' => trim($this->input->getString('nuova_tipologia_nome', '')),
'nuovi_bottoni' => trim($this->input->getString('nuovi_bottoni', '')),
];
if ($data['title'] === '') {
$app->enqueueMessage(Text::_('COM_CIRCOLARI_ERR_TITLE_REQUIRED'), 'error');
$this->setRedirect(Route::_('index.php?option=com_circolari&view=form&layout=edit&id=' . (int)$data['id'], false));
return;
}
/** @var \Pcrt\Component\Circolari\Site\Model\FormModel $model */
$model = $this->getModel('Form', 'Site');
try {
$id = $model->saveData($data, $user->id);
$app->enqueueMessage(Text::_('COM_CIRCOLARI_MSG_SAVED_OK'), 'message');
$this->setRedirect(Route::_('index.php?option=com_circolari&view=circolare&id=' . (int)$id, false));
} catch (\Throwable $e) {
$app->enqueueMessage($e->getMessage(), 'error');
$this->setRedirect(Route::_('index.php?option=com_circolari&view=form&layout=edit&id=' . (int)$data['id'], false));
}
}
public function cancel()
{
$this->setRedirect(Route::_('index.php?option=com_circolari&view=circolari', false));
}
}

View File

@ -0,0 +1,148 @@
<?php
namespace Pcrt\Component\Circolari\Site\Model;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
class FormModel extends BaseDatabaseModel
{
public function getItem(int $id = 0): ?\stdClass
{
if ($id <= 0) return null;
$db = Factory::getContainer()->get('DatabaseDriver');
$q = $db->getQuery(true)
->select('*')
->from('#__circolari')
->where('id = ' . (int)$id);
$db->setQuery($q);
return $db->loadObject() ?: null;
}
public function getCategorie(): array
{
$db = Factory::getContainer()->get('DatabaseDriver');
$q = $db->getQuery(true)
->select('id, title, state')
->from('#__circolari_categorie')
->where('state = 1')
->order('title ASC');
$db->setQuery($q);
return $db->loadAssocList() ?: [];
}
public function getFirmetipi(): array
{
$db = \Joomla\CMS\Factory::getContainer()->get('DatabaseDriver');
$q = $db->getQuery(true)
->select('id, nome')
->from('#__circolari_firmetipi')
->where('state = 1')
->order('nome ASC');
$db->setQuery($q);
return $db->loadAssocList() ?: [];
}
public function getBottoniByFirmatipo(): array
{
$db = \Joomla\CMS\Factory::getContainer()->get('DatabaseDriver');
$q = $db->getQuery(true)
->select('f.id AS firmatipo_id, b.label')
->from('#__circolari_firmetipi AS f')
->leftJoin('#__circolari_firmetipi_bottoni AS b ON b.firmatipo_id = f.id')
->where('f.state = 1')
->order('f.id ASC, b.ordering ASC, b.id ASC');
$db->setQuery($q);
$rows = $db->loadAssocList() ?: [];
$map = [];
foreach ($rows as $r) {
$fid = (int) $r['firmatipo_id'];
if (!isset($map[$fid])) $map[$fid] = [];
if (!empty($r['label'])) $map[$fid][] = $r['label'];
}
return $map;
}
/**
* Salva/aggiorna la circolare; crea anche una nuova tipologia e bottoni se richiesto.
* Ritorna l'ID della circolare salvata.
*/
public function saveData(array $data, int $userId): int
{
$db = Factory::getContainer()->get('DatabaseDriver');
// 2) normalizza alias
if (empty($data['alias'])) {
$data['alias'] = $this->slugify($data['title']);
}
// 3) INSERT o UPDATE su #__circolari
$now = Factory::getDate()->toSql();
$row = (object) [
'id' => (int)($data['id'] ?? 0),
'title' => $data['title'],
'alias' => $data['alias'],
'description' => $data['description'],
'categoria_id' => (int)$data['categoria_id'],
'tipologia_firma_id' => (int)$data['tipologia_firma_id'],
'firma_obbligatoria' => (int)$data['firma_obbligatoria'],
'scadenza' => $data['scadenza'] ?: null,
'state' => (int)$data['state'],
];
if ($row->id > 0) {
$row->modified_by = $userId;
$row->checked_out = 0;
$row->checked_out_time = null;
$db->updateObject('#__circolari', $row, ['id']);
$id = $row->id;
} else {
$row->created_by = $userId;
$db->insertObject('#__circolari', $row);
$id = (int)$db->insertid();
}
return $id;
}
private function createNewFirmatipo(string $nome, string $bottoniRiga): int
{
$db = Factory::getContainer()->get('DatabaseDriver');
// crea tipologia
$tipo = (object) [
'nome' => $nome,
'descrizione' => '',
'state' => 1,
];
$db->insertObject('#__circolari_firmetipi', $tipo);
$id = (int) $db->insertid();
// crea bottoni (uno per riga)
$labels = array_filter(array_map('trim', preg_split('/\R+/', $bottoniRiga ?: '')));
$ordering = 1;
foreach ($labels as $label) {
$btn = (object) [
'firmatipo_id' => $id,
'label' => $label,
'ordering' => $ordering++,
];
$db->insertObject('#__circolari_firmetipi_bottoni', $btn);
}
return $id;
}
private function slugify(string $s): string
{
$t = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
$t = strtolower($t);
$t = preg_replace('~[^a-z0-9]+~', '-', $t);
$t = trim($t, '-');
return $t ?: 'circolare';
}
}

View File

@ -1,4 +1,5 @@
<?php
namespace Pcrt\Component\Circolari\Site\Service;
\defined('_JEXEC') or die;
@ -8,107 +9,79 @@ use Joomla\CMS\Menu\AbstractMenu;
use Joomla\CMS\Factory;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\RouterViewConfiguration;
use Joomla\CMS\Component\Router\Rules\StandardRules;
use Joomla\CMS\Component\Router\Rules\MenuRules;
use Joomla\CMS\Component\Router\Rules\StandardRules;
use Joomla\CMS\Component\Router\Rules\NomenuRules;
class Router extends RouterView
{
public function __construct(SiteApplication $app, AbstractMenu $menu)
{
// Parent: category (annidata → più segmenti)
// 1) CATEGORIA (se la usi nei percorsi)
$category = new RouterViewConfiguration('category');
$category->setKey('id')->setNestable();
$this->registerView($category);
// Child: circolare (lega la category tramite categoria_id)
$circolare = new RouterViewConfiguration('circolari');
$circolare->setKey('id')->setParent($category, 'categoria_id', 'id');
// 2) LISTA (nessuna key!)
$circolari = new RouterViewConfiguration('circolari');
// Se vuoi, puoi lasciarla senza parent, oppure solo setParent($category) SENZA variabili
// $circolari->setParent($category); // opzionale, ma senza 2° e 3° argomento
$this->registerView($circolari);
// DETTAGLIO (ha la key id)
$circolare = new RouterViewConfiguration('circolare');
$circolare->setKey('id')->setParent($circolari);
$this->registerView($circolare);
// 4) FORM (se presente)
$form = new RouterViewConfiguration('form');
$form->setParent($circolari);
$this->registerView($form);
parent::__construct($app, $menu);
// Regole standard di routing
$this->attachRule(new StandardRules($this));
// Regole: prima Menu, poi Standard, poi NoMenu
$this->attachRule(new MenuRules($this));
$this->attachRule(new StandardRules($this));
$this->attachRule(new NomenuRules($this));
}
/* ---------------- BUILD ---------------- */
// Segmenti categoria = path completo (parent1/parent2/figlia)
// Segmento categoria = alias (fallback id)
public function getCategorySegment($id, $query)
{
$id = (int) ($id ?: 0);
if ($id <= 0) {
return [];
}
$db = Factory::getContainer()->get('DatabaseDriver');
$path = (string) $db->setQuery(
$db->getQuery(true)
->select('path')
->from('#__circolari_categorie')
->where('id = ' . $id)
->where("extension = 'com_content'")
$alias = (string) $db->setQuery(
$db->getQuery(true)->select('alias')->from('#__circolari_categorie')->where('id=' . (int)$id)
)->loadResult();
return $path !== '' ? array_filter(explode('/', $path)) : [(string) $id];
return $alias !== '' ? [$alias] : [(string) (int) $id];
}
// Ultimo segmento = SOLO alias della circolare
// Segmento dettaglio = alias (fallback id se vuoto)
public function getCircolareSegment($id, $query)
{
$db = Factory::getContainer()->get('DatabaseDriver');
$alias = (string) $db->setQuery(
$db->getQuery(true)
->select('alias')
->from('#__circolari')
->where('id = ' . (int) $id)
$db->getQuery(true)->select('alias')->from('#__circolari')->where('id=' . (int)$id)
)->loadResult();
// Nessun fallback numerico: imponiamo l'alias
return [$alias];
return [$alias !== '' ? $alias : (string) (int) $id];
}
/* ---------------- PARSE ---------------- */
// Ricava categoria_id accumulando i segmenti e risolvendo per PATH completo
public function getCategoryId($segment, $query)
{
static $segments = [];
$segments[] = $segment;
$path = implode('/', $segments);
$db = Factory::getContainer()->get('DatabaseDriver');
// match per path (univoco in com_content)
$id = (int) $db->setQuery(
$db->getQuery(true)
->select('id')
->from('#__circolari_categorie')
->where('path = ' . $db->quote($path))
->where("extension = 'com_content'")
$db->getQuery(true)->select('id')->from('#__circolari_categorie')->where('alias=' . $db->quote($segment))
)->loadResult();
if ($id > 0) {
return $id;
return $id > 0 ? $id : (ctype_digit((string)$segment) ? (int)$segment : 0);
}
// fallback (singolo alias) se si visita direttamente un livello intermedio
$id = (int) $db->setQuery(
$db->getQuery(true)
->select('id')
->from('#__circolari_categorie')
->where('alias = ' . $db->quote($segment))
->where("extension = 'com_content'")
->order('level DESC')
)->loadResult();
return $id ?: 0;
}
// Alias circolare (+ categoria_id già risolto) → id
public function getCircolareId($segment, $query)
{
$categoria_id = (int) ($query['categoria_id'] ?? 0);
@ -124,7 +97,13 @@ class Router extends RouterView
}
$db->setQuery($q);
$id = (int) $db->loadResult();
return (int) $db->loadResult() ?: 0;
// fallback numerico sullultimo segmento
if ($id === 0 && ctype_digit((string)$segment)) {
$id = (int) $segment;
}
return $id;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Pcrt\Component\Circolari\Site\View\Form;
\defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Factory;
class HtmlView extends BaseHtmlView
{
public $item;
public $categorie = [];
public $firmetipi = [];
public $bottoniMap = [];
public function display($tpl = null)
{
$model = $this->getModel();
$this->item = $model->getItem((int) \Joomla\CMS\Factory::getApplication()->input->getInt('id', 0));
$this->categorie = $model->getCategorie();
$this->firmetipi = $model->getFirmetipi();
$this->bottoniMap = $model->getBottoniByFirmatipo();
$app = Factory::getApplication();
$user = $app->getIdentity();
if ($user->guest || (!$user->authorise('core.manage', 'com_circolari') && !$user->authorise('core.admin', 'com_circolari'))) {
throw new \RuntimeException(\Joomla\CMS\Language\Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
/** @var \Pcrt\Component\Circolari\Site\Model\FormModel $model */
$model = $this->getModel();
$id = (int) $app->input->getInt('id', 0);
$this->item = $model->getItem($id);
$this->categorie = $model->getCategorie();
$this->firmetipi = $model->getFirmetipi();
parent::display($tpl);
}
}

View File

@ -13,7 +13,9 @@ $items = $this->items ?? ($this->get('Items') ?? []);
$pagination = $this->pagination ?? ($this->get('Pagination') ?? null);
// Normalizzo items
if (!is_array($items)) { $items = (array) $items; }
if (!is_array($items)) {
$items = (array) $items;
}
$items = array_values(array_filter($items, static fn($it) => is_object($it) && !empty($it->id)));
$Itemid = (int) $input->getInt('Itemid', 0);
@ -39,12 +41,7 @@ $Itemid = (int) $input->getInt('Itemid', 0);
<tbody>
<?php foreach ($items as $i => $item) : ?>
<?php
$url = Route::_(
'index.php?option=com_circolari&view=circolare'
. '&id=' . (int) $item->id
. '&catid=' . (int) ($item->catid ?? 0)
. ($Itemid ? '&Itemid=' . $Itemid : '')
);
$url = Route::_('index.php?option=com_circolari&view=circolare&id=' . (int) $item->id);
$title = $item->title ?? ('#' . (int) $item->id);
$hits = isset($item->hits) ? (int) $item->hits : null;
?>

209
site/tmpl/form/default.php Normal file
View File

@ -0,0 +1,209 @@
<?php
\defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Factory;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
HTMLHelper::_('bootstrap.tooltip');
HTMLHelper::_('behavior.formvalidator');
$item = $this->item ?: (object) [];
$action = Route::_('index.php?option=com_circolari&task=form.save');
// Trova un Itemid valido per la lista (per il link "Annulla")
$menu = Factory::getApplication()->getMenu();
$component = ComponentHelper::getComponent('com_circolari');
$cancelItemId = 0;
foreach ((array) $menu->getItems('component_id', (int) $component->id) as $mi) {
$q = is_array($mi->query ?? null) ? $mi->query : [];
if (($q['option'] ?? '') === 'com_circolari' && ($q['view'] ?? '') === 'circolari') {
$cancelItemId = (int) $mi->id;
break;
}
}
$cancelUrl = $cancelItemId
? Route::_('index.php?Itemid=' . $cancelItemId)
: Uri::root() . 'index.php?option=com_circolari&view=circolari';
?>
<div class="container py-3">
<h2 class="mb-3"><?php echo Text::_('Nuova circolare'); ?></h2>
<form action="<?php echo $action; ?>" method="post" class="form-validate">
<div class="mb-3">
<label class="form-label" for="title"><?php echo Text::_('Titolo'); ?></label>
<input type="text" id="title" name="title" required
class="form-control"
value="<?php echo htmlspecialchars($item->title ?? '', ENT_QUOTES, 'UTF-8'); ?>">
</div>
<div class="mb-3">
<label class="form-label" for="alias"><?php echo Text::_('Alias (opzionale)'); ?></label>
<input type="text" id="alias" name="alias" class="form-control"
value="<?php echo htmlspecialchars($item->alias ?? '', ENT_QUOTES, 'UTF-8'); ?>">
</div>
<div class="mb-3">
<label class="form-label" for="categoria_id"><?php echo Text::_('Categoria'); ?></label>
<select id="categoria_id" name="categoria_id" class="form-select" required>
<option value="0">-- <?php echo Text::_('Seleziona'); ?> --</option>
<?php foreach ((array) $this->categorie as $c):
$cid = (int) ($c['id'] ?? 0);
$ctitle = (string) ($c['title'] ?? '');
$cstate = (int) ($c['state'] ?? 1);
if ($cstate !== 1) continue; // solo pubblicate
?>
<option value="<?php echo $cid; ?>"
<?php echo (!empty($item->categoria_id) && (int)$item->categoria_id === $cid) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($ctitle, ENT_QUOTES, 'UTF-8'); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label" for="description"><?php echo Text::_('Testo'); ?></label>
<textarea id="description" name="description" class="form-control" rows="8"><?php
echo htmlspecialchars($item->description ?? '', ENT_QUOTES, 'UTF-8');
?></textarea>
</div>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="state"><?php echo Text::_('Stato'); ?></label>
<select id="state" name="state" class="form-select">
<option value="1" <?php echo ((int)($item->state ?? 1) === 1 ? 'selected' : ''); ?>>
<?php echo Text::_('Pubblicata'); ?>
</option>
<option value="0" <?php echo ((int)($item->state ?? 1) === 0 ? 'selected' : ''); ?>>
<?php echo Text::_('Bozza/Non pubblicata'); ?>
</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="jform_firma_obbligatoria"><?php echo Text::_('Firma obbligatoria'); ?></label>
<?php $fo = (int) ($item->firma_obbligatoria ?? 0); ?>
<select name="firma_obbligatoria" id="jform_firma_obbligatoria" class="form-select">
<option value="0" <?php echo $fo===0?'selected':''; ?>><?php echo Text::_('No'); ?></option>
<option value="1" <?php echo $fo===1?'selected':''; ?>><?php echo Text::_('Sì'); ?></option>
</select>
</div>
<div class="col-md-4 firma-block" style="display:none;">
<label class="form-label" for="jform_tipologia_firma_id"><?php echo Text::_('Tipologia firma'); ?></label>
<select name="tipologia_firma_id" id="jform_tipologia_firma_id" class="form-select">
<option value="0">-- <?php echo Text::_('Seleziona'); ?> --</option>
<?php
$selTipo = (int) ($item->tipologia_firma_id ?? 0);
foreach ((array)$this->firmetipi as $t) {
$tid = (int) $t['id'];
$nm = (string) $t['nome'];
echo '<option value="'.$tid.'" '.($selTipo===$tid?'selected':'').'>'
. htmlspecialchars($nm, ENT_QUOTES, 'UTF-8') . '</option>';
}
?>
</select>
<div class="form-text"><?php echo Text::_('Anteprima bottoni:'); ?></div>
<div id="anteprima-bottoni" class="mt-1"></div>
</div>
</div>
<div class="row g-3 mt-2 firma-block" style="display:none;">
<div class="col-md-6">
<label class="form-label required" id="jform_scadenza-lbl" for="jform_scadenza">
<?php echo Text::_('Scadenza firma'); ?><span class="star" aria-hidden="true">&nbsp;*</span>
</label>
<input type="datetime-local" name="scadenza" id="jform_scadenza" class="form-control"
value="<?php
$sc = (string) ($item->scadenza ?? '');
if ($sc !== '') { echo htmlspecialchars(str_replace(' ', 'T', substr($sc, 0, 16)), ENT_QUOTES, 'UTF-8'); }
?>">
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button type="submit" class="btn btn-primary"><?php echo Text::_('Salva'); ?></button>
<a href="<?php echo $cancelUrl; ?>" class="btn btn-outline-secondary"><?php echo Text::_('Annulla'); ?></a>
</div>
<input type="hidden" name="id" value="<?php echo (int) ($item->id ?? 0); ?>">
<?php echo HTMLHelper::_('form.token'); ?>
<script type="application/json" id="bottoni-map"><?php
echo json_encode($this->bottoniMap ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?></script>
<script>
(function(){
function byId(id){ return document.getElementById(id); }
const obb = byId('jform_firma_obbligatoria');
const tipo = byId('jform_tipologia_firma_id');
const scad = byId('jform_scadenza');
const blocks= document.querySelectorAll('.firma-block');
const mapEl = byId('bottoni-map');
const prev = byId('anteprima-bottoni');
const map = mapEl ? JSON.parse(mapEl.textContent || '{}') : {};
function renderPreview(){
if (!prev || !tipo) return;
const fid = tipo.value;
prev.innerHTML = '';
if (!fid || !map[fid] || !map[fid].length) return;
map[fid].forEach(function(lbl){
const span = document.createElement('span');
span.className = 'badge bg-secondary me-1 mb-1';
span.textContent = lbl;
prev.appendChild(span);
});
}
function toggleFirma(){
const need = obb && obb.value === '1';
blocks.forEach(function(el){ el.style.display = need ? '' : 'none'; });
if (scad){
if (need){
scad.setAttribute('required','required');
scad.setAttribute('aria-required','true');
// aggiungi asterisco al label se manca
var lbl = document.getElementById('jform_scadenza-lbl');
if (lbl){
lbl.classList.add('required');
if (!lbl.querySelector('.star')){
var s = document.createElement('span');
s.className = 'star';
s.setAttribute('aria-hidden','true');
s.innerHTML = '&nbsp;*';
lbl.appendChild(s);
}
}
} else {
scad.removeAttribute('required');
scad.removeAttribute('aria-required');
var lbl = document.getElementById('jform_scadenza-lbl');
if (lbl){
lbl.classList.remove('required');
var st = lbl.querySelector('.star'); if (st) st.remove();
}
}
}
renderPreview();
}
document.addEventListener('change', function(e){
if (e.target === obb) toggleFirma();
if (e.target === tipo) renderPreview();
});
document.addEventListener('DOMContentLoaded', function(){
toggleFirma();
renderPreview();
});
})();
</script>
</form>
</div>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<metadata>
<layout title="Pubblica Circolare">
<message>Pubblica Circolare</message>
<layoutname>edit</layoutname> <!-- deve combaciare con tmpl/form/edit.php -->
</layout>
</metadata>