Campi obbligatori & fix
This commit is contained in:
@ -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 = [];
|
||||
$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) . ' />';
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ class CircolareModel extends AdminModel
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function loadFormData()
|
||||
protected function loadFormData()
|
||||
{
|
||||
// Check the session for previously entered form data.
|
||||
$data = Factory::getApplication()->getUserState('com_circolari.edit.circolare.data', array());
|
||||
@ -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();
|
||||
|
||||
@ -240,19 +240,49 @@ class CircolareTable extends Table implements VersionableTableInterface, Taggabl
|
||||
|
||||
|
||||
|
||||
// Default gruppo utenti
|
||||
// Default gruppo utenti
|
||||
$this->usergroup_id = (int)($this->usergroup_id ?? 0);
|
||||
$this->hits = max(0, (int)($this->hits ?? 0));
|
||||
|
||||
// Normalize scadenza from datetime-local (YYYY-MM-DDTHH:MM)
|
||||
if (!empty($this->scadenza)) {
|
||||
$this->scadenza = str_replace('T', ' ', (string) $this->scadenza);
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', (string) $this->scadenza)) {
|
||||
$this->scadenza .= ':00';
|
||||
}
|
||||
}
|
||||
// Normalize scadenza from datetime-local (YYYY-MM-DDTHH:MM)
|
||||
if (!empty($this->scadenza)) {
|
||||
$this->scadenza = str_replace('T', ' ', (string) $this->scadenza);
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', (string) $this->scadenza)) {
|
||||
$this->scadenza .= ':00';
|
||||
}
|
||||
}
|
||||
|
||||
return parent::check();
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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 = ' *';
|
||||
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);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
84
site/src/Controller/FormController.php
Normal file
84
site/src/Controller/FormController.php
Normal 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));
|
||||
}
|
||||
}
|
||||
148
site/src/Model/FormModel.php
Normal file
148
site/src/Model/FormModel.php
Normal 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';
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Pcrt\Component\Circolari\Site\Service;
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
@ -8,123 +9,101 @@ 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'")
|
||||
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||
$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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return $id > 0 ? $id : (ctype_digit((string)$segment) ? (int)$segment : 0);
|
||||
}
|
||||
|
||||
// Alias circolare (+ categoria_id già risolto) → id
|
||||
public function getCircolareId($segment, $query)
|
||||
{
|
||||
$categoria_id = (int) ($query['categoria_id'] ?? 0);
|
||||
|
||||
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||
$q = $db->getQuery(true)
|
||||
->select('id')
|
||||
->from('#__circolari')
|
||||
->where('alias = ' . $db->quote($segment));
|
||||
->select('id')
|
||||
->from('#__circolari')
|
||||
->where('alias = ' . $db->quote($segment));
|
||||
|
||||
if ($categoria_id > 0) {
|
||||
$q->where('categoria_id = ' . $categoria_id);
|
||||
}
|
||||
|
||||
$db->setQuery($q);
|
||||
$id = (int) $db->loadResult();
|
||||
|
||||
return (int) $db->loadResult() ?: 0;
|
||||
// fallback numerico sull’ultimo segmento
|
||||
if ($id === 0 && ctype_digit((string)$segment)) {
|
||||
$id = (int) $segment;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
||||
40
site/src/View/Form/HtmlView.php
Normal file
40
site/src/View/Form/HtmlView.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
@ -37,34 +39,29 @@ $Itemid = (int) $input->getInt('Itemid', 0);
|
||||
</tr>
|
||||
</thead>
|
||||
<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 : '')
|
||||
);
|
||||
<?php foreach ($items as $i => $item) : ?>
|
||||
<?php
|
||||
$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;
|
||||
?>
|
||||
<tr class="cat-list-row<?php echo $i % 2; ?>">
|
||||
<th class="list-title" scope="row">
|
||||
<a href="<?php echo $url; ?>">
|
||||
<?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</a>
|
||||
</th>
|
||||
<td class="text-end">
|
||||
<?php if ($hits !== null) : ?>
|
||||
<span class="btn btn-secondary btn-sm disabled" aria-disabled="true">
|
||||
<?php echo Text::_('JGLOBAL_HITS') ?: 'Visite'; ?>: <?php echo $hits; ?>
|
||||
</span>
|
||||
<?php else : ?>
|
||||
<span class="text-muted">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
?>
|
||||
<tr class="cat-list-row<?php echo $i % 2; ?>">
|
||||
<th class="list-title" scope="row">
|
||||
<a href="<?php echo $url; ?>">
|
||||
<?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</a>
|
||||
</th>
|
||||
<td class="text-end">
|
||||
<?php if ($hits !== null) : ?>
|
||||
<span class="btn btn-secondary btn-sm disabled" aria-disabled="true">
|
||||
<?php echo Text::_('JGLOBAL_HITS') ?: 'Visite'; ?>: <?php echo $hits; ?>
|
||||
</span>
|
||||
<?php else : ?>
|
||||
<span class="text-muted">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
209
site/tmpl/form/default.php
Normal file
209
site/tmpl/form/default.php
Normal 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"> *</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 = ' *';
|
||||
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>
|
||||
7
site/views/form/metadata.xml
Normal file
7
site/views/form/metadata.xml
Normal 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>
|
||||
Reference in New Issue
Block a user