aggiunta firma circolari

This commit is contained in:
2025-09-05 12:26:43 +02:00
parent c206ce7e4b
commit 835e123444
12 changed files with 250 additions and 92 deletions

View File

@ -18,9 +18,7 @@
<field name="alias" type="text" label="JFIELD_ALIAS_LABEL" description="JFIELD_ALIAS_DESC" />
<field name="categoria_id" type="Categoria" label="Categoria" required="true" />
<field name="usergroup_id" type="usergrouplist"
label="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_USERGROUP"
description="COM_CIRCOLARI_FORM_DESC_CIRCOLARE_USERGROUP" />
<field name="description" type="editor" label="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_DESCRIPTION"
filter="safehtml" buttons="true" />
<field name="attachment" type="media" label="COM_CIRCOLARI_FORM_LBL_CIRCOLARE_ATTACHMENT"
@ -38,6 +36,17 @@
</field>
<field
name="usergroup_ids"
type="usergrouplist"
label="Gruppi abilitati alla firma"
description="Seleziona i gruppi che possono firmare la circolare"
multiple="true"
required="false"
showon="firma_obbligatoria:1"
/>
<field name="tipologia_firma_id" type="Tipologiafirma" label="Tipologia firma"
showon="firma_obbligatoria:1" />
<field name="scadenza" type="CalSafe" label="Data Scadenza Firma"

View File

@ -0,0 +1,12 @@
-- tabella ponte: quali gruppi possono firmare quale circolare
CREATE TABLE IF NOT EXISTS `#__circolari_usergroups` (
`circolare_id` INT NOT NULL,
`usergroup_id` INT NOT NULL,
`required` TINYINT(1) NOT NULL DEFAULT 0, -- opzionale: firma obbligatoria del gruppo
`can_firmare` TINYINT(1) NOT NULL DEFAULT 1, -- opzionale: futura granularità
PRIMARY KEY (`circolare_id`, `usergroup_id`),
KEY `idx_group` (`usergroup_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `#__circolari` DROP COLUMN `usergroup_id`;

View File

@ -135,6 +135,17 @@ class CircolareModel extends AdminModel
}
}
$id = (int) ($data->id ?? $this->getState($this->getName() . '.id'));
if ($id) {
$db = Factory::getContainer()->get('DatabaseDriver');
$q = $db->getQuery(true)
->select($db->quoteName('usergroup_id'))
->from($db->quoteName('#__circolari_usergroups'))
->where($db->quoteName('circolare_id') . ' = ' . (int) $id);
$db->setQuery($q);
$data->usergroup_ids = $db->loadColumn() ?: [];
}
return $data;
}
@ -324,4 +335,47 @@ class CircolareModel extends AdminModel
$this->_db->setQuery($q);
return ((int) $this->_db->loadResult()) > 0;
}
public function save($data)
{
$ok = parent::save($data);
if (!$ok) {
return false;
}
$id = (int) $this->getState($this->getName() . '.id');
$groups = array_map('intval', $data['usergroup_ids'] ?? []);
$db = Factory::getContainer()->get('DatabaseDriver');
$db->transactionStart();
try {
// ripulisci associazioni esistenti
$db->setQuery(
$db->getQuery(true)
->delete($db->quoteName('#__circolari_usergroups'))
->where($db->quoteName('circolare_id') . ' = ' . (int) $id)
)->execute();
// reinserisci le nuove
if (!empty($groups)) {
$q = $db->getQuery(true)
->insert($db->quoteName('#__circolari_usergroups'))
->columns([$db->quoteName('circolare_id'), $db->quoteName('usergroup_id')]);
foreach ($groups as $gid) {
$q->values((int) $id . ',' . (int) $gid);
}
$db->setQuery($q)->execute();
}
$db->transactionCommit();
} catch (\Throwable $e) {
$db->transactionRollback();
$this->setError($e->getMessage());
return false;
}
return true;
}
}

View File

@ -43,7 +43,7 @@ HTMLHelper::_('bootstrap.tooltip');
<?php echo $this->form->renderField('attachment'); ?>
<?php echo $this->form->renderField('image'); ?>
<?php echo $this->form->renderField('firma_obbligatoria'); ?>
<?php echo $this->form->renderField('tipologia_firma_id'); ?>
<?php echo $this->form->renderField('usergroup_ids'); ?>
<?php echo $this->form->renderField('scadenza'); ?>
</fieldset>
</div>

View File

@ -82,7 +82,7 @@ if (!empty($saveOrder)) {
<th scope="col" class="w-3 d-none d-lg-table-cell">
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?> </th>
<?php echo HTMLHelper::_('searchtools.sort', 'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?> </th>
</tr>
</thead>
<tfoot>

View File

@ -7,7 +7,7 @@
<author>Tommaso Cippitelli</author>
<authorEmail>tommaso.cippitelli@protocollicreativi.it</authorEmail>
<authorUrl>http://</authorUrl>
<version>1.1.6</version>
<version>1.1.7</version>
<description></description>
<namespace path="src">Pcrt\Component\Circolari</namespace>

View File

@ -1,10 +1,12 @@
<?php
namespace Pcrt\Component\Circolari\Site\Model;
\defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ItemModel;
use Joomla\CMS\Factory;
use Joomla\CMS\User\UserHelper;
class CircolareModel extends ItemModel
{
@ -55,23 +57,90 @@ class CircolareModel extends ItemModel
return $row ?: null;
}
}
public function getBottoniFirma(int $firmatipoId): array
{
$buttons = []; // <- sempre inizializzato
/**
* Incrementa gli hits della circolare.
*/
public function hit($pk = null)
{
$pk = $pk ?: (int) $this->getState($this->getName() . '.id');
if (!$pk) {
return false;
if ($firmatipoId <= 0) {
return $buttons;
}
$db = Factory::getContainer()->get('DatabaseDriver');
// 1) Tabella relazionale
$q = $db->getQuery(true)
->select($db->quoteName(['id', 'label']))
->from($db->quoteName('#__circolari_firmetipi_bottoni'))
->where($db->quoteName('firmatipo_id') . ' = ' . (int) $firmatipoId)
->order($db->escape('ordering ASC, id ASC'));
$db->setQuery($q);
$buttons = (array) $db->loadObjectList();
// 2) Fallback JSON
if (!$buttons) {
$q2 = $db->getQuery(true)
->select($db->quoteName('bottoni_firma'))
->from($db->quoteName('#__circolari_firmetipi'))
->where($db->quoteName('id') . ' = ' . (int) $firmatipoId);
$db->setQuery($q2);
$json = (string) $db->loadResult();
if ($json) {
$rows = json_decode($json, true) ?: [];
foreach ($rows as $r) {
$label = trim((string) ($r['etichetta'] ?? ''));
if ($label !== '') {
$buttons[] = (object) ['id' => 0, 'label' => $label];
}
}
}
}
return $buttons;
}
public function userCanFirmare(int $circolareId, ?int $userId = null): bool
{
$user = Factory::getUser($userId);
if ($user->guest) {
return false;
}
// Gruppi (inclusa ereditarietà). In J4/J5 puoi usare UserHelper::getUserGroups
$userGroups = array_map('intval', UserHelper::getUserGroups($user->id));
if (!$userGroups) {
return false;
}
$db = Factory::getContainer()->get('DatabaseDriver');
$q = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__circolari_usergroups'))
->where($db->quoteName('circolare_id') . ' = ' . (int) $circolareId)
->where($db->quoteName('usergroup_id') . ' IN (' . implode(',', $userGroups) . ')');
$db->setQuery($q);
return ((int) $db->loadResult()) > 0;
}
/**
* Incrementa gli hits della circolare.
*/
public function hit($pk = null)
{
$pk = $pk ?: (int) $this->getState($this->getName() . '.id');
if (!$pk) {
return false;
}
$db = $this->getDatabase();
$query = $db->getQuery(true)
->update($db->quoteName('#__circolari'))
->set($db->quoteName('hits') . ' = ' . $db->quoteName('hits') . ' + 1')
->where($db->quoteName('id') . ' = ' . (int)$pk);
$db->setQuery($query)->execute();
return true;
}
$db = $this->getDatabase();
$query = $db->getQuery(true)
->update($db->quoteName('#__circolari'))
->set($db->quoteName('hits') . ' = ' . $db->quoteName('hits') . ' + 1')
->where($db->quoteName('id') . ' = ' . (int)$pk);
$db->setQuery($query)->execute();
return true;
}

View File

@ -14,14 +14,13 @@ class CircolariModel extends ListModel
protected $filter_fields = [
'id', 'c.id',
'title', 'c.title',
'created', 'c.created',
'hits', 'c.hits',
'categoria_id', 'c.categoria_id',
'state', 'c.state',
];
protected function populateState($ordering = 'c.created', $direction = 'DESC')
{
{
$app = Factory::getApplication();
// paging
@ -80,9 +79,6 @@ class CircolariModel extends ListModel
}
// ordinamento
$orderCol = $this->getState('list.ordering', 'c.created');
$orderDir = $this->getState('list.direction', 'DESC');
$q->order($db->escape($orderCol . ' ' . $orderDir));
return $q;
}

View File

@ -22,7 +22,7 @@ class Router extends RouterView
$this->registerView($category);
// Child: circolare (lega la category tramite categoria_id)
$circolare = new RouterViewConfiguration('circolare');
$circolare = new RouterViewConfiguration('circolari');
$circolare->setKey('id')->setParent($category, 'categoria_id', 'id');
$this->registerView($circolare);

View File

@ -6,6 +6,8 @@ use Joomla\CMS\Language\Text;
/** @var \Pcrt\Component\Circolari\Site\View\Circolare\HtmlView $this */
$item = $this->item;
$buttons = $this->getModel()->getBottoniFirma((int)$item->tipologia_firma_id);
?>
<div class="container my-5 mega-container">
@ -18,6 +20,18 @@ $item = $this->item;
<div class="col-md-3 col-12 text-md-end mt-2 mt-md-0">
<a href="#" class="small text-decoration-none text-uppercase fw-bold">Condividi</a>
</div>
<?php if ($item->firma_obbligatoria && $this->getModel()->userCanFirmare($item->id, $this->getModel()->currentUser->id) && !empty($buttons)) : ?>
<div class="mt-4">
<div class="d-flex flex-wrap gap-2">
<?php foreach ($buttons as $btn) : ?>
<button type="button" class="btn btn-primary btn-sm">
<?php echo htmlspecialchars($btn->label, ENT_QUOTES, 'UTF-8'); ?>
</button>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<div class="article-body mt-3">
<?php echo $item->description ?: $item->testo ?: $item->descrizione ?: '<em>(Nessun testo)</em>'; ?>
</div>

View File

@ -1,67 +1,44 @@
<?php
// ... intestazione invariata ...
\defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
/** @var \Pcrt\Component\Circolari\Site\View\Circolare\HtmlView $this */
$item = $this->item;
dump($item);
$buttons = $this->getModel()->getBottoniFirma((int)$item->tipologia_firma_id);
dump($this->getModel()->currentUser);
// $listOrder / $listDirn / $filter già come li hai
$Itemid = (int) ($input->getInt('Itemid') ?: 0);
?>
<form action="<?php echo htmlspecialchars(Uri::getInstance()->toString(), ENT_QUOTES, 'UTF-8'); ?>"
method="post"
name="adminForm"
id="adminForm"
class="com-content-category__articles">
<!-- filtro -->
<!-- (invariato) -->
<div class="container my-5 mega-container">
<!-- Limite per pagina -->
<?php if ($pagination && \method_exists($pagination, 'getLimitBox')) : ?>
<div class="com-content-category__pagination btn-group float-end">
<label for="limit" class="visually-hidden"><?php echo Text::_('JGLOBAL_DISPLAY_NUM'); ?></label>
<?php echo $pagination->getLimitBox(); ?>
</div>
<?php endif; ?>
<!-- tabella -->
<table class="com-content-category__table category table table-striped table-bordered table-hover mt-3">
<thead>
<tr>
<th scope="col" id="circolari_header_title">
<?php echo HTMLHelper::_('grid.sort', 'JGLOBAL_TITLE', 'c.title', $listDirn, $listOrder, null, 'asc', '', 'adminForm'); ?>
</th>
<th scope="col" id="circolari_header_date" class="small">
<?php echo HTMLHelper::_('grid.sort', Text::_('JDATE'), 'c.created', $listDirn, $listOrder, null, 'asc', '', 'adminForm'); ?>
</th>
<th scope="col" id="circolari_header_attachment" class="text-center">
<?php echo Text::_('COM_CIRCOLARI_ATTACHMENT') ?: 'Allegato'; ?>
</th>
<th scope="col" id="circolari_header_hits" class="text-end">
<?php echo HTMLHelper::_('grid.sort', 'JGLOBAL_HITS', 'c.hits', $listDirn, $listOrder, null, 'asc', '', 'adminForm'); ?>
</th>
</tr>
</thead>
<!-- tbody invariato -->
</table>
<!-- paginazione -->
<?php if ($pagination && $pagination->pagesTotal > 1) : ?>
<div class="com-content-category__navigation w-100">
<p class="com-content-category__counter counter float-end pt-3 pe-2">
<?php echo $pagination->getPagesCounter(); ?>
</p>
<div class="com-content-category__pagination">
<?php echo $pagination->getPagesLinks(); ?>
</div>
</div>
<?php endif; ?>
<!-- hidden -->
<div>
<input type="hidden" name="option" value="com_circolari">
<input type="hidden" name="view" value="circolari">
<?php if ($Itemid) : ?><input type="hidden" name="Itemid" value="<?php echo $Itemid; ?>"><?php endif; ?>
<input type="hidden" name="filter_order" value="<?php echo $listOrder; ?>">
<input type="hidden" name="filter_order_Dir" value="<?php echo $listDirn; ?>">
<input type="hidden" name="limitstart" value="<?php echo (int) ($pagination?->limitstart ?? 0); ?>">
<?php echo HTMLHelper::_('form.token'); ?>
<div class="row mb-4 align-items-end">
<div class="col-md-9 col-12">
<h1 class="h2 mb-1"><?= $this->escape($item->title); ?></h1>
</div>
</form>
<div class="col-md-3 col-12 text-md-end mt-2 mt-md-0">
<a href="#" class="small text-decoration-none text-uppercase fw-bold">Condividi</a>
</div>
<?php if ( $item->firma_obbligatoria && !empty($buttons)) : ?>
<div class="mt-4">
<div class="d-flex flex-wrap gap-2">
<?php foreach ($buttons as $btn) : ?>
<button type="button" class="btn btn-primary btn-sm">
<?php echo htmlspecialchars($btn->label, ENT_QUOTES, 'UTF-8'); ?>
</button>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<div class="article-body mt-3">
<?php echo $item->description ?: $item->testo ?: $item->descrizione ?: '<em>(Nessun testo)</em>'; ?>
</div>
</div>
</div>

View File

@ -1,10 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<metadata>
<layout title="COM_CIRCOLARI_MENU_LIST" option="com_circolari" />
<layout title="Lista di categorie Circolari" option="com_circolari">
<help key="JHELP_MENUS_MENU_ITEM_GENERIC" />
</layout>
<!-- Parametri che finiscono direttamente nella URL (request) -->
<fields name="request">
<fieldset name="request">
<!-- Selezione della categoria delle circolari -->
<field
name="categoria_id"
type="sql"
label="Categoria"
required="true"
query="SELECT id AS value, title AS text FROM #__circolari_categorie WHERE state = 1 ORDER BY title"
key_field="value"
value_field="text"
translate="false"
/>
<!-- Forza la view corretta -->
<field name="view" type="hidden" default="circolari" />
<field name="categoria_id" type="category" extension="com_content" label="JCATEGORY" required="false" />
</fieldset>
</fields>
<!-- Opzioni pagina (facoltative) -->
<fields name="params">
<fieldset name="advanced" label="JGLOBAL_FIELDSET_ADVANCED">
<field name="page_title" type="text" label="JGLOBAL_TITLE" />
<field name="show_page_heading" type="radio" default="1" label="JGLOBAL_SHOW_PAGE_HEADING">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
</fields>
</metadata>