visualizzazione e gestione firme e lista firme con ACL
This commit is contained in:
15
administrator/sql/updates/1.1.8.sql
Normal file
15
administrator/sql/updates/1.1.8.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
DROP TABLE IF EXISTS `#__circolari_firme`;
|
||||||
|
|
||||||
|
CREATE TABLE `#__circolari_firme` (
|
||||||
|
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`circolare_id` INT(11) UNSIGNED NOT NULL,
|
||||||
|
`user_id` INT(11) UNSIGNED NOT NULL,
|
||||||
|
`firmatipo_bottone_id` INT(11) UNSIGNED NOT NULL, -- id del bottone scelto
|
||||||
|
`firma_label` VARCHAR(190) NOT NULL, -- copia dell’etichetta al momento della firma
|
||||||
|
`data_firma` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `firma_unica` (`circolare_id`, `user_id`), -- 1 firma per utente x circolare
|
||||||
|
KEY `idx_circolare` (`circolare_id`),
|
||||||
|
KEY `idx_user` (`user_id`),
|
||||||
|
KEY `idx_bottone` (`firmatipo_bottone_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT COLLATE=utf8mb4_unicode_ci;
|
||||||
243
site/src/Controller/CircolareController.php
Normal file
243
site/src/Controller/CircolareController.php
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
<?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;
|
||||||
|
use Joomla\CMS\Date\Date;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CircolareController extends BaseController
|
||||||
|
{
|
||||||
|
public function exportFirme()
|
||||||
|
{
|
||||||
|
// Token in GET per il download
|
||||||
|
if (!\Joomla\CMS\Session\Session::checkToken('get')) {
|
||||||
|
throw new \RuntimeException(\Joomla\CMS\Language\Text::_('JINVALID_TOKEN'), 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$app = \Joomla\CMS\Factory::getApplication();
|
||||||
|
$user = $app->getIdentity();
|
||||||
|
$id = $this->input->getInt('id');
|
||||||
|
|
||||||
|
// Permessi minimi (adatta come preferisci)
|
||||||
|
if (
|
||||||
|
!$user->authorise('core.admin', 'com_circolari')
|
||||||
|
&& !$user->authorise('core.manage', 'com_circolari')
|
||||||
|
) {
|
||||||
|
throw new \RuntimeException(\Joomla\CMS\Language\Text::_('JERROR_ALERTNOAUTHOR'), 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \Pcrt\Component\Circolari\Site\Model\CircolareModel $model */
|
||||||
|
$model = $this->getModel('Circolare', 'Site');
|
||||||
|
$rows = $model ? $model->getFirmeCircolare((int)$id) : [];
|
||||||
|
|
||||||
|
// === CLEAR OUTPUT BUFFERS ===
|
||||||
|
while (ob_get_level() > 0) {
|
||||||
|
@ob_end_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === HEADERS PER FORZARE DOWNLOAD ===
|
||||||
|
$filename = 'firme_circolare_' . (int)$id . '_' . date('Ymd_His') . '.csv';
|
||||||
|
|
||||||
|
// alcuni server riscrivono i content-type: duplichiamo la semantica "excel"
|
||||||
|
$app->clearHeaders();
|
||||||
|
$app->setHeader('Content-Description', 'File Transfer', true);
|
||||||
|
$app->setHeader('Content-Type', 'application/vnd.ms-excel; charset=utf-8', true);
|
||||||
|
$app->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"', true);
|
||||||
|
$app->setHeader('Content-Transfer-Encoding', 'binary', true);
|
||||||
|
$app->setHeader('Expires', '0', true);
|
||||||
|
$app->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true);
|
||||||
|
$app->setHeader('Pragma', 'public', true);
|
||||||
|
$app->sendHeaders();
|
||||||
|
|
||||||
|
// === OUTPUT CSV (separatore ;) con BOM UTF-8 ===
|
||||||
|
$out = fopen('php://output', 'w');
|
||||||
|
|
||||||
|
// BOM per Excel (non prima degli header!)
|
||||||
|
fwrite($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||||
|
|
||||||
|
// intestazione
|
||||||
|
fputcsv($out, ['ID Firma','ID Utente','Nome','Username','Email','Scelta','Data firma'], ';');
|
||||||
|
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$date = \Joomla\CMS\Factory::getDate($r['data_firma'])->format('d/m/Y H:i');
|
||||||
|
fputcsv($out, [
|
||||||
|
$r['id'],
|
||||||
|
$r['user_id'],
|
||||||
|
$r['user_name'],
|
||||||
|
$r['username'],
|
||||||
|
$r['email'],
|
||||||
|
$r['scelta_label'],
|
||||||
|
$date,
|
||||||
|
], ';');
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($out);
|
||||||
|
|
||||||
|
// Importantissimo: termina qui, niente layout/HTML
|
||||||
|
$app->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function sign()
|
||||||
|
{
|
||||||
|
if (!Session::checkToken('post')) {
|
||||||
|
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$app = Factory::getApplication();
|
||||||
|
$user = $app->getIdentity();
|
||||||
|
$in = $app->input;
|
||||||
|
|
||||||
|
$circolareId = $in->getInt('id');
|
||||||
|
$bottoneId = $in->getInt('bottone_id');
|
||||||
|
|
||||||
|
if ($user->guest) {
|
||||||
|
$app->enqueueMessage(Text::_('COM_CIRCOLARI_ERR_LOGIN_REQUIRED'), 'error');
|
||||||
|
$this->setRedirect(Route::_('index.php?option=com_users&view=login', false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($circolareId <= 0 || $bottoneId <= 0) {
|
||||||
|
$app->enqueueMessage(Text::_('COM_CIRCOLARI_ERR_INVALID_REQUEST'), 'error');
|
||||||
|
$this->back($circolareId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||||
|
|
||||||
|
// 1) il bottone appartiene alla tipologia della circolare?
|
||||||
|
$q = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('b.label'))
|
||||||
|
->from($db->quoteName('#__circolari_firmetipi_bottoni', 'b'))
|
||||||
|
->join('INNER', $db->quoteName('#__circolari', 'c') . ' ON ' .
|
||||||
|
$db->quoteName('c.tipologia_firma_id') . ' = ' . $db->quoteName('b.firmatipo_id'))
|
||||||
|
->where($db->quoteName('c.id') . ' = ' . (int)$circolareId)
|
||||||
|
->where($db->quoteName('b.id') . ' = ' . (int)$bottoneId);
|
||||||
|
$db->setQuery($q);
|
||||||
|
$label = (string) $db->loadResult();
|
||||||
|
|
||||||
|
if ($label === '') {
|
||||||
|
$app->enqueueMessage(Text::_('COM_CIRCOLARI_ERR_INVALID_BUTTON'), 'error');
|
||||||
|
$this->back($circolareId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) rileva lo schema della tabella firme
|
||||||
|
$cols = [];
|
||||||
|
foreach ($db->getTableColumns('#__circolari_firme', false) as $name => $def) {
|
||||||
|
$cols[strtolower($name)] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = Factory::getDate()->toSql();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isset($cols['firmatipo_bottone_id']) && isset($cols['firma_label'])) {
|
||||||
|
// Schema "nuovo"
|
||||||
|
$insert = $db->getQuery(true)
|
||||||
|
->insert($db->quoteName('#__circolari_firme'))
|
||||||
|
->columns($db->quoteName(['circolare_id', 'user_id', 'firmatipo_bottone_id', 'firma_label', 'data_firma']))
|
||||||
|
->values(implode(',', [
|
||||||
|
(int)$circolareId,
|
||||||
|
(int)$user->id,
|
||||||
|
(int)$bottoneId,
|
||||||
|
$db->quote($label),
|
||||||
|
$db->quote($now)
|
||||||
|
]));
|
||||||
|
$db->setQuery($insert)->execute();
|
||||||
|
} elseif (isset($cols['firma'])) {
|
||||||
|
// Schema "semplice" con ENUM firma
|
||||||
|
$enumValue = $this->mapLabelToEnum($label, $db, '#__circolari_firme', 'firma');
|
||||||
|
$insert = $db->getQuery(true)
|
||||||
|
->insert($db->quoteName('#__circolari_firme'))
|
||||||
|
->columns($db->quoteName(['circolare_id', 'user_id', 'firma', 'data_firma']))
|
||||||
|
->values(implode(',', [
|
||||||
|
(int)$circolareId,
|
||||||
|
(int)$user->id,
|
||||||
|
$db->quote($enumValue),
|
||||||
|
$db->quote($now),
|
||||||
|
]));
|
||||||
|
$db->setQuery($insert)->execute();
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException('Struttura tabella firme non supportata.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$app->enqueueMessage(Text::_('COM_CIRCOLARI_MSG_SIGNED_OK'), 'message');
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
// 1062 = UNIQUE (già firmata)
|
||||||
|
if ((int)$e->getCode() === 1062 || stripos($e->getMessage(), 'Duplicate') !== false) {
|
||||||
|
$app->enqueueMessage(Text::_('COM_CIRCOLARI_ERR_ALREADY_SIGNED'), 'warning');
|
||||||
|
} else {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->back($circolareId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function back(int $id)
|
||||||
|
{
|
||||||
|
$this->setRedirect(Route::_('index.php?option=com_circolari&view=circolare&id=' . (int)$id, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mappa la label del bottone ad un valore valido dell'ENUM `firma`.
|
||||||
|
* Se non combacia, prova a "sluggare" e, in ultima istanza, usa il primo valore dell'ENUM.
|
||||||
|
*/
|
||||||
|
private function mapLabelToEnum(string $label, $db, string $table, string $column): string
|
||||||
|
{
|
||||||
|
// Leggi i valori dell'ENUM dal DDL della colonna
|
||||||
|
$ddl = $db->getTableColumns($table);
|
||||||
|
$type = '';
|
||||||
|
foreach ($ddl as $name => $def) {
|
||||||
|
if (strtolower($name) === strtolower($column) && isset($def->Type)) {
|
||||||
|
$type = $def->Type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($type && preg_match("/^enum\\((.*)\\)$/i", $type, $m)) {
|
||||||
|
$raw = $m[1];
|
||||||
|
$vals = array_map(function ($v) {
|
||||||
|
return trim($v, " '\"");
|
||||||
|
}, explode(',', $raw));
|
||||||
|
} else {
|
||||||
|
// fallback ai valori noti
|
||||||
|
$vals = ['presa_visione', 'aderisco', 'non_aderisco', 'non_in_servizio'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$slug = $this->slugify($label);
|
||||||
|
if (in_array($slug, $vals, true)) {
|
||||||
|
return $slug;
|
||||||
|
}
|
||||||
|
// prova alcune normalizzazioni comuni
|
||||||
|
$map = [
|
||||||
|
'presa-visione' => 'presa_visione',
|
||||||
|
'presavisione' => 'presa_visione',
|
||||||
|
'non-in-servizio' => 'non_in_servizio',
|
||||||
|
'noninservizio' => 'non_in_servizio',
|
||||||
|
'non-aderisco' => 'non_aderisco',
|
||||||
|
];
|
||||||
|
if (isset($map[$slug]) && in_array($map[$slug], $vals, true)) {
|
||||||
|
return $map[$slug];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ultima spiaggia: primo valore dell'ENUM
|
||||||
|
return $vals[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function slugify(string $s): string
|
||||||
|
{
|
||||||
|
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||||
|
$s = strtolower($s);
|
||||||
|
$s = preg_replace('~[^a-z0-9]+~', '_', $s);
|
||||||
|
$s = trim($s, '_');
|
||||||
|
return $s ?: 'presa_visione';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ namespace Pcrt\Component\Circolari\Site\Model;
|
|||||||
use Joomla\CMS\MVC\Model\ItemModel;
|
use Joomla\CMS\MVC\Model\ItemModel;
|
||||||
use Joomla\CMS\Factory;
|
use Joomla\CMS\Factory;
|
||||||
use Joomla\CMS\User\UserHelper;
|
use Joomla\CMS\User\UserHelper;
|
||||||
|
use Joomla\CMS\Language\Text;
|
||||||
|
|
||||||
class CircolareModel extends ItemModel
|
class CircolareModel extends ItemModel
|
||||||
{
|
{
|
||||||
@ -31,7 +32,7 @@ class CircolareModel extends ItemModel
|
|||||||
|
|
||||||
$db = $this->getDatabase();
|
$db = $this->getDatabase();
|
||||||
$q = $db->getQuery(true)
|
$q = $db->getQuery(true)
|
||||||
->select('c.*') // <<< evita errori di colonne
|
->select('c.*')
|
||||||
->from($db->quoteName('#__circolari') . ' AS c')
|
->from($db->quoteName('#__circolari') . ' AS c')
|
||||||
->where('c.id = ' . (int) $pk);
|
->where('c.id = ' . (int) $pk);
|
||||||
|
|
||||||
@ -126,6 +127,127 @@ class CircolareModel extends ItemModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function bottoneValidoPerCircolare(int $circolareId, int $bottoneId): bool
|
||||||
|
{
|
||||||
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||||
|
$q = $db->getQuery(true)
|
||||||
|
->select('1')
|
||||||
|
->from($db->quoteName('#__circolari', 'c'))
|
||||||
|
->join('INNER', $db->quoteName('#__circolari_firmetipi_bottoni', 'b')
|
||||||
|
. ' ON ' . $db->quoteName('b.firmatipo_id') . ' = ' . $db->quoteName('c.tipologia_firma_id'))
|
||||||
|
->where($db->quoteName('c.id') . ' = ' . (int) $circolareId)
|
||||||
|
->where($db->quoteName('b.id') . ' = ' . (int) $bottoneId);
|
||||||
|
$db->setQuery($q, 0, 1);
|
||||||
|
return (bool) $db->loadResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insertFirma(int $circolareId, int $userId, int $bottoneId): bool
|
||||||
|
{
|
||||||
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||||
|
$now = Factory::getDate()->toSql();
|
||||||
|
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||||
|
|
||||||
|
$columns = ['circolare_id', 'user_id', 'firmatipo_bottone_id', 'signed_at', 'ip'];
|
||||||
|
$values = [
|
||||||
|
(int) $circolareId,
|
||||||
|
(int) $userId,
|
||||||
|
(int) $bottoneId,
|
||||||
|
$db->quote($now),
|
||||||
|
$db->quote($ip)
|
||||||
|
];
|
||||||
|
|
||||||
|
$q = $db->getQuery(true)
|
||||||
|
->insert($db->quoteName('#__circolari_firme'))
|
||||||
|
->columns($db->quoteName($columns))
|
||||||
|
->values(implode(',', $values));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->setQuery($q)->execute();
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
// 1062 = duplicate key (violazione UNIQUE circolare_id+user_id)
|
||||||
|
if ((int) $e->getCode() === 1062 || stripos($e->getMessage(), 'Duplicate') !== false) {
|
||||||
|
throw new \RuntimeException(Text::_('COM_CIRCOLARI_ERR_ALREADY_SIGNED'), 409);
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFirmaUtente(int $circolareId, int $userId): ?object
|
||||||
|
{
|
||||||
|
if ($circolareId <= 0 || $userId <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||||
|
|
||||||
|
// Schema compatibile con entrambe le versioni (ENUM "firma" oppure nuovo schema con firmatipo_bottone_id/firma_label)
|
||||||
|
$q = $db->getQuery(true)
|
||||||
|
->select($db->quoteName(['id', 'circolare_id', 'user_id']))
|
||||||
|
->select('' . $db->quoteName('firmatipo_bottone_id') . ' AS firmatipo_bottone_id')
|
||||||
|
->select('' . $db->quoteName('firma_label') . ' AS firma_label')
|
||||||
|
//->select('' . $db->quoteName('firma') . ' AS firma_enum')
|
||||||
|
->from($db->quoteName('#__circolari_firme'))
|
||||||
|
->where($db->quoteName('circolare_id') . ' = ' . (int)$circolareId)
|
||||||
|
->where($db->quoteName('user_id') . ' = ' . (int)$userId);
|
||||||
|
$db->setQuery($q, 0, 1);
|
||||||
|
$row = $db->loadObject();
|
||||||
|
|
||||||
|
return $row ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFirmeCircolare(int $circolareId): array
|
||||||
|
{
|
||||||
|
if ($circolareId <= 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||||
|
|
||||||
|
// Rileva le colonne della tabella firme (schema nuovo vs enum)
|
||||||
|
$cols = array_change_key_case($db->getTableColumns('#__circolari_firme', false));
|
||||||
|
$hasBtn = isset($cols['firmatipo_bottone_id']);
|
||||||
|
$hasLabel = isset($cols['firma_label']);
|
||||||
|
$hasEnum = isset($cols['firma']);
|
||||||
|
|
||||||
|
$q = $db->getQuery(true)
|
||||||
|
->select([
|
||||||
|
'f.id',
|
||||||
|
'f.circolare_id',
|
||||||
|
'f.user_id',
|
||||||
|
'f.data_firma',
|
||||||
|
($hasLabel ? 'f.firma_label' : 'NULL') . ' AS firma_label',
|
||||||
|
($hasEnum ? 'f.firma' : 'NULL') . ' AS firma_enum'
|
||||||
|
])
|
||||||
|
->from($db->quoteName('#__circolari_firme', 'f'))
|
||||||
|
->join('INNER', $db->quoteName('#__users', 'u') . ' ON u.id = f.user_id')
|
||||||
|
->select('u.name AS user_name, u.username, u.email')
|
||||||
|
->where('f.circolare_id = ' . (int) $circolareId)
|
||||||
|
->order('f.data_firma DESC');
|
||||||
|
|
||||||
|
if ($hasBtn) {
|
||||||
|
$q->select('b.label AS bottone_label')
|
||||||
|
->join('LEFT', $db->quoteName('#__circolari_firmetipi_bottoni', 'b') . ' ON b.id = f.firmatipo_bottone_id');
|
||||||
|
} else {
|
||||||
|
$q->select('NULL AS bottone_label');
|
||||||
|
}
|
||||||
|
|
||||||
|
$db->setQuery($q);
|
||||||
|
$rows = (array) $db->loadAssocList();
|
||||||
|
|
||||||
|
// Post-processing: normalizza la label scelta
|
||||||
|
foreach ($rows as &$r) {
|
||||||
|
$label = $r['firma_label'] ?: $r['bottone_label'] ?: $r['firma_enum'];
|
||||||
|
if ($label && $label === $r['firma_enum']) {
|
||||||
|
$label = ucwords(str_replace('_', ' ', $label)); // enum -> "Presa Visione"
|
||||||
|
}
|
||||||
|
$r['scelta_label'] = $label ?: '-';
|
||||||
|
}
|
||||||
|
unset($r);
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Incrementa gli hits della circolare.
|
* Incrementa gli hits della circolare.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,13 +1,49 @@
|
|||||||
<?php
|
<?php
|
||||||
\defined('_JEXEC') or die;
|
\defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Router\Route;
|
||||||
use Joomla\CMS\HTML\HTMLHelper;
|
use Joomla\CMS\HTML\HTMLHelper;
|
||||||
use Joomla\CMS\Language\Text;
|
use Joomla\CMS\Language\Text;
|
||||||
|
use Joomla\CMS\Session\Session;
|
||||||
|
|
||||||
/** @var \Pcrt\Component\Circolari\Site\View\Circolare\HtmlView $this */
|
$app = Factory::getApplication();
|
||||||
|
$user = $app->getIdentity();
|
||||||
|
$model = method_exists($this, 'getModel') ? $this->getModel('Circolare') : null;
|
||||||
$item = $this->item;
|
$item = $this->item;
|
||||||
|
|
||||||
|
$canAdmin = $user->authorise('core.admin', 'com_circolari') || $user->authorise('core.manage', 'com_circolari');
|
||||||
|
|
||||||
|
// Firma dell'utente (se esiste)
|
||||||
|
$firma = ($model && !$user->guest) ? $model->getFirmaUtente((int)$item->id, (int)$user->id) : null;
|
||||||
|
$hasVoted = (bool) $firma;
|
||||||
|
|
||||||
|
// Helper per confrontare anche in caso di schema ENUM
|
||||||
|
$slugify = function (string $s): string {
|
||||||
|
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||||
|
$s = strtolower($s);
|
||||||
|
$s = preg_replace('~[^a-z0-9]+~', '_', $s);
|
||||||
|
return trim($s, '_');
|
||||||
|
};
|
||||||
|
|
||||||
|
$selectedBtnId = isset($firma->firmatipo_bottone_id) ? (int)$firma->firmatipo_bottone_id : 0;
|
||||||
|
$selectedEnum = isset($firma->firma_enum) ? (string)$firma->firma_enum : '';
|
||||||
$buttons = $this->getModel()->getBottoniFirma((int)$item->tipologia_firma_id);
|
$buttons = $this->getModel()->getBottoniFirma((int)$item->tipologia_firma_id);
|
||||||
|
|
||||||
|
$firme = [];
|
||||||
|
if ($canAdmin) {
|
||||||
|
$model = method_exists($this, 'getModel') ? $this->getModel('Circolare') : null;
|
||||||
|
if (!$model) {
|
||||||
|
// fallback creazione model via MVCFactory se necessario
|
||||||
|
try {
|
||||||
|
$factory = $app->bootComponent('com_circolari')->getMVCFactory();
|
||||||
|
$model = $factory->createModel('Circolare', 'Site', ['ignore_request' => true]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$model = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$firme = $model ? $model->getFirmeCircolare((int)$item->id) : [];
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="container my-5 mega-container">
|
<div class="container my-5 mega-container">
|
||||||
@ -23,11 +59,42 @@ $buttons = $this->getModel()->getBottoniFirma((int)$item->tipologia_firma_id);
|
|||||||
|
|
||||||
<?php if ($item->firma_obbligatoria && $this->getModel()->userCanFirmare($item->id, $this->getModel()->currentUser->id) && !empty($buttons)) : ?>
|
<?php if ($item->firma_obbligatoria && $this->getModel()->userCanFirmare($item->id, $this->getModel()->currentUser->id) && !empty($buttons)) : ?>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2" data-sign-group="circolare-<?php echo (int)$item->id; ?>">
|
||||||
<?php foreach ($buttons as $btn) : ?>
|
<?php foreach ($buttons as $btn) :
|
||||||
<button type="button" class="btn btn-primary btn-sm">
|
$btnId = (int) $btn->id;
|
||||||
<?php echo htmlspecialchars($btn->label, ENT_QUOTES, 'UTF-8'); ?>
|
$isSelected =
|
||||||
</button>
|
($selectedBtnId > 0 && $selectedBtnId === $btnId)
|
||||||
|
|| ($selectedEnum !== '' && in_array($selectedEnum, [
|
||||||
|
$slugify($btn->label),
|
||||||
|
str_replace('-', '_', $slugify($btn->label)), // normalizza trattini→underscore
|
||||||
|
], true));
|
||||||
|
|
||||||
|
// Classi bootstrap: selezionato = "success", altri disattivi = outline-secondary
|
||||||
|
$btnClasses = 'btn btn-sm ' . ($isSelected ? 'btn-success' : ($hasVoted ? 'btn-outline-secondary' : 'btn-primary'));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($hasVoted): ?>
|
||||||
|
<button type="button"
|
||||||
|
class="<?php echo $btnClasses; ?> opacity-75"
|
||||||
|
disabled
|
||||||
|
aria-disabled="true"
|
||||||
|
title="<?php echo $isSelected ? $this->escape(Text::_('Hai già scelto questo')) : $this->escape(Text::_('Hai già firmato')); ?>">
|
||||||
|
<?php echo htmlspecialchars($btn->label, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</button>
|
||||||
|
<?php else: ?>
|
||||||
|
<form method="post"
|
||||||
|
action="<?=
|
||||||
|
Route::_('index.php?option=com_circolari&task=circolare.sign&id=' . (int)$item->id); ?>"
|
||||||
|
class="d-inline-block">
|
||||||
|
<input type="hidden" name="bottone_id" value="<?php echo $btnId; ?>">
|
||||||
|
<button type="submit"
|
||||||
|
class="<?php echo $btnClasses; ?> js-sign-btn">
|
||||||
|
<?php echo htmlspecialchars($btn->label, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</button>
|
||||||
|
<?= HTMLHelper::_('form.token'); ?>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -37,4 +104,83 @@ $buttons = $this->getModel()->getBottoniFirma((int)$item->tipologia_firma_id);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
<?php if ($canAdmin): ?>
|
||||||
|
<div class="card mt-4">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<strong>Firme ricevute</strong>
|
||||||
|
<a class="btn btn-outline-secondary btn-sm"
|
||||||
|
href="<?=
|
||||||
|
\Joomla\CMS\Router\Route::_(
|
||||||
|
'index.php?option=com_circolari&task=circolare.exportFirme&id='.(int)$item->id.'&'.\Joomla\CMS\Session\Session::getFormToken().'=1'
|
||||||
|
); ?>"
|
||||||
|
download
|
||||||
|
>
|
||||||
|
Scarica Excel
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm mb-0 align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th class="text-muted">Username</th>
|
||||||
|
<th>Scelta</th>
|
||||||
|
<th class="text-end">Data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($firme)) : ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center text-muted py-3">Nessuna firma registrata.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else : ?>
|
||||||
|
<?php foreach ($firme as $r) : ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($r['user_name'], ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
<td class="text-muted"><?php echo htmlspecialchars($r['username'], ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-secondary"><?php echo htmlspecialchars($r['scelta_label'], ENT_QUOTES, 'UTF-8'); ?></span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<?php echo htmlspecialchars(Factory::getDate($r['data_firma'])->format('d/m/Y H:i'), ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<?php if (!$hasVoted): ?>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var container = document.querySelector('[data-sign-group="circolare-<?php echo (int)$item->id; ?>"]');
|
||||||
|
if (!container) return;
|
||||||
|
container.addEventListener('submit', function(e) {
|
||||||
|
var form = e.target.closest('form');
|
||||||
|
if (!form) return;
|
||||||
|
var clickedBtn = form.querySelector('button');
|
||||||
|
var allBtns = container.querySelectorAll('button');
|
||||||
|
allBtns.forEach(function(b) {
|
||||||
|
b.classList.remove('btn-primary');
|
||||||
|
b.classList.add('btn-outline-secondary');
|
||||||
|
b.setAttribute('disabled', 'disabled');
|
||||||
|
b.setAttribute('aria-disabled', 'true');
|
||||||
|
b.classList.add('opacity-75');
|
||||||
|
});
|
||||||
|
if (clickedBtn) {
|
||||||
|
clickedBtn.classList.remove('btn-outline-secondary', 'opacity-75');
|
||||||
|
clickedBtn.classList.add('btn-success');
|
||||||
|
}
|
||||||
|
}, true); // usa capture per intercettare subito
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php endif; ?>
|
||||||
@ -1,44 +1,85 @@
|
|||||||
<?php
|
<?php
|
||||||
\defined('_JEXEC') or die;
|
\defined('_JEXEC') or die;
|
||||||
|
|
||||||
use Joomla\CMS\HTML\HTMLHelper;
|
use Joomla\CMS\Factory;
|
||||||
use Joomla\CMS\Language\Text;
|
use Joomla\CMS\Language\Text;
|
||||||
|
use Joomla\CMS\Router\Route;
|
||||||
|
|
||||||
/** @var \Pcrt\Component\Circolari\Site\View\Circolare\HtmlView $this */
|
$app = Factory::getApplication();
|
||||||
$item = $this->item;
|
$input = $app->getInput();
|
||||||
dump($item);
|
|
||||||
|
|
||||||
$buttons = $this->getModel()->getBottoniFirma((int)$item->tipologia_firma_id);
|
// Prendo dati dalla view con fallback ai getter
|
||||||
dump($this->getModel()->currentUser);
|
$items = $this->items ?? ($this->get('Items') ?? []);
|
||||||
|
$pagination = $this->pagination ?? ($this->get('Pagination') ?? null);
|
||||||
|
|
||||||
|
// Normalizzo 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);
|
||||||
?>
|
?>
|
||||||
|
<div class="circolari-list">
|
||||||
|
|
||||||
<div class="container my-5 mega-container">
|
<?php if (!count($items)) : ?>
|
||||||
|
<div class="alert alert-info">
|
||||||
<div class="row mb-4 align-items-end">
|
<span class="icon-info-circle" aria-hidden="true"></span>
|
||||||
<div class="col-md-9 col-12">
|
<span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
|
||||||
<h1 class="h2 mb-1"><?= $this->escape($item->title); ?></h1>
|
<?php echo Text::_('COM_CIRCOLARI_NO_ITEMS') ?: 'Nessuna circolare'; ?>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<?php if ( $item->firma_obbligatoria && !empty($buttons)) : ?>
|
<?php else : ?>
|
||||||
<div class="mt-4">
|
<table class="table table-striped table-bordered table-hover">
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<caption class="visually-hidden"><?php echo Text::_('COM_CIRCOLARI_ELENCO') ?: 'Elenco circolari'; ?></caption>
|
||||||
<?php foreach ($buttons as $btn) : ?>
|
<thead class="visually-hidden">
|
||||||
<button type="button" class="btn btn-primary btn-sm">
|
<tr>
|
||||||
<?php echo htmlspecialchars($btn->label, ENT_QUOTES, 'UTF-8'); ?>
|
<th scope="col">Titolo</th>
|
||||||
</button>
|
<th scope="col" class="text-end">Visite</th>
|
||||||
|
</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 : '')
|
||||||
|
);
|
||||||
|
$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; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</tbody>
|
||||||
</div>
|
</table>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="article-body mt-3">
|
|
||||||
<?php echo $item->description ?: $item->testo ?: $item->descrizione ?: '<em>(Nessun testo)</em>'; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<?php if ($pagination && (int) $pagination->pagesTotal > 1) : ?>
|
||||||
|
<div class="com-content-category__navigation w-100">
|
||||||
|
<div class="d-flex align-items-center mt-2">
|
||||||
|
<div class="flex-grow-1 d-flex justify-content-center">
|
||||||
|
<?php echo $pagination->getPagesLinks(); ?>
|
||||||
|
</div>
|
||||||
|
<p class="com-content-category__counter counter mb-0 ps-3">
|
||||||
|
<?php echo $pagination->getPagesCounter(); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user