diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 000000000..73254fb75
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,21 @@
+```sql
+ALTER TABLE zz_users ADD COLUMN remember_token VARCHAR (255) AFTER password;
+
+ALTER TABLE `zz_widgets` DROP `class`, ADD `class` varchar(255) NOT NULL;
+
+UPDATE `zz_widgets` SET `class` = 'App\\OSM\\Widgets\\Retro\\ModalWidget' WHERE `more_link_type` = 'popup';
+UPDATE `zz_widgets` SET `class` = 'App\\OSM\\Widgets\\Retro\\LinkWidget' WHERE `more_link_type` = 'link';
+UPDATE `zz_widgets` SET `class` = 'App\\OSM\\Widgets\\Retro\\StatsWidget' WHERE `more_link_type` = 'javascript';
+UPDATE `zz_widgets` SET `class` = 'App\\OSM\\Widgets\\Retro\\StatsWidget' WHERE `type` = 'print';
+UPDATE `zz_widgets` SET `class` = 'App\\OSM\\Widgets\\Retro\\StatsWidget' WHERE `class` = '';
+UPDATE `zz_widgets` SET `more_link` = `php_include` WHERE `more_link` = '';
+UPDATE `zz_widgets` SET `class` = 'App\\OSM\\Widgets\\Retro\\ModalWidget' WHERE `name` = 'Stampa calendario';
+
+UPDATE `zz_widgets` SET `more_link` = REPLACE(`more_link`, 'plugins/', 'modules/');
+
+ALTER TABLE `zz_widgets` DROP `print_link`, DROP `more_link_type`, DROP `php_include`;
+
+UPDATE `zz_widgets` SET `more_link` = REPLACE(`more_link`, './', '/');
+
+UPDATE `zz_widgets` SET `class` = 'Modules\\Dashboard\\NotificheWidget', `more_link` = '' WHERE `zz_widgets`.`name` = 'Note interne';
+```
diff --git a/app/Http/Controllers/LegacyController.php b/app/Http/Controllers/LegacyController.php
index 29068f9ed..081fb3c65 100644
--- a/app/Http/Controllers/LegacyController.php
+++ b/app/Http/Controllers/LegacyController.php
@@ -14,15 +14,46 @@ class LegacyController extends Controller
public function index(Request $request)
{
$path = substr($request->getPathInfo(), 1);
- $base_path = base_path('legacy');
+ // Gestione dell'output
+ $output = self::simulate($path);
+ $response = response($output);
+
+ // Fix content-type per contenuti non HTML
+ if (ends_with($path, '.js')) {
+ $response = $response->header('Content-Type', 'application/javascript');
+ } elseif (string_contains($path, 'pdfgen.php')) {
+ $response = $response->header('Content-Type', 'application/pdf');
+ }
+ // Correzione header per API
+ elseif (self::isApiRequest($path)) {
+ $response = $response->header('Content-Type', 'application/json');
+ }
+
+ return $response;
+ }
+
+ protected static function isApiRequest($path)
+ {
// Fix per redirect all'API
$api_request = false;
if (in_array($path, ['api', 'api/', 'api/index.php'])) {
- $path = 'api/index.php';
$api_request = true;
}
+ return $api_request;
+ }
+
+ public static function simulate($path)
+ {
+ $base_path = base_path('legacy');
+
+ // Fix per redirect all'API
+ $api_request = self::isApiRequest($path);
+ if ($api_request) {
+ $path = 'api/index.php';
+ }
+
// Ricerca del file interessato
$file = realpath($base_path.'/'.$path);
if (strpos($file, $base_path) === false) {
@@ -40,17 +71,7 @@ class LegacyController extends Controller
// Gestione dell'output
$output = ob_get_clean();
- $response = response($output);
- // Fix content-type per contenuti non HTML
- if (ends_with($path, '.js')) {
- $response = $response->header('Content-Type', 'application/javascript');
- } elseif (string_contains($path, 'pdfgen.php')) {
- $response = $response->header('Content-Type', 'application/pdf');
- } elseif ($api_request) {
- $response = $response->header('Content-Type', 'application/json');
- }
-
- return $response;
+ return $output;
}
}
diff --git a/app/Http/Controllers/PrintController.php b/app/Http/Controllers/PrintController.php
new file mode 100644
index 000000000..6178a7b9f
--- /dev/null
+++ b/app/Http/Controllers/PrintController.php
@@ -0,0 +1,33 @@
+ $args['print_id'],
+ 'record_id' => $args['record_id'],
+ ]);
+ $args['link'] = base_url().'/assets/pdfjs/web/viewer.html?file='.$link;
+
+ $response = $this->twig->render($response, '@resources/uploads/frame.twig', $args);
+
+ return $response;
+ }
+
+ public function open(Request $request)
+ {
+ $print = Template::find($args['print_id']);
+ $manager = $print->getManager();
+
+ $pdf = $manager->render();
+
+ return response()->setContent($pdf)
+ ->header('Content-Type', 'application/pdf');
+ }
+}
diff --git a/app/Http/Controllers/Test.php b/app/Http/Controllers/Test.php
deleted file mode 100644
index 53d7ca9e7..000000000
--- a/app/Http/Controllers/Test.php
+++ /dev/null
@@ -1,10 +0,0 @@
-getManager();
+
+ if (!($class instanceof ModalWidget)) {
+ throw new NotFoundHttpException();
+ }
+
+ return $class->getModal();
+ }
+}
diff --git a/app/OSM/ComponentManager.php b/app/OSM/ComponentManager.php
new file mode 100644
index 000000000..b65ca2a2b
--- /dev/null
+++ b/app/OSM/ComponentManager.php
@@ -0,0 +1,32 @@
+.
+ */
+
+namespace App\OSM;
+
+use Illuminate\Database\Eloquent\Model;
+
+abstract class ComponentManager
+{
+ protected $model;
+
+ public function __construct(Model $model)
+ {
+ $this->model = $model;
+ }
+}
diff --git a/app/OSM/ComponentManagerTrait.php b/app/OSM/ComponentManagerTrait.php
new file mode 100644
index 000000000..218dbc3b6
--- /dev/null
+++ b/app/OSM/ComponentManagerTrait.php
@@ -0,0 +1,41 @@
+.
+ */
+
+namespace App\OSM;
+
+/**
+ * Implementazione di base dell'interazione con i componenti a livello interno.
+ *
+ * @since 2.5
+ */
+trait ComponentManagerTrait
+{
+ protected $manager_object;
+
+ public function getManager(): ComponentManager
+ {
+ if (!isset($this->manager_object)) {
+ $class = $this->attributes['class'];
+
+ $this->manager_object = new $class($this);
+ }
+
+ return $this->manager_object;
+ }
+}
diff --git a/app/OSM/Prints/Autofill.php b/app/OSM/Prints/Autofill.php
new file mode 100644
index 000000000..181b22cef
--- /dev/null
+++ b/app/OSM/Prints/Autofill.php
@@ -0,0 +1,116 @@
+.
+ */
+
+namespace App\OSM\Prints;
+
+/**
+ * Classe dedicata alla gestione delle righe fantasma per il miglioramento grafico delle stampe tabellari.
+ *
+ * @since 2.3
+ */
+class Autofill
+{
+ protected $space = 0;
+ protected $current = 0;
+
+ protected $char_number;
+ protected $column_number;
+
+ protected $max_rows = 20;
+ protected $max_rows_first_page = 20;
+ protected $max_additional = 15;
+
+ public function __construct($column_number, $char_number = 70)
+ {
+ $this->column_number = $column_number;
+ $this->char_number = $char_number;
+ }
+
+ public function setRows($rows, $additional = null, $first_page = null)
+ {
+ $this->max_rows = $rows;
+
+ $this->max_additional = isset($additional) ? $additional : floor($this->max_rows - $this->max_rows / 4);
+ $this->max_rows_first_page = isset($first_page) ? $first_page : $rows;
+ }
+
+ public function count($text, $small = false)
+ {
+ $count = ceil(strlen($text) / $this->char_number);
+ $count += substr_count($text, PHP_EOL);
+ $count += substr_count($text, '
');
+
+ if ($small) {
+ $count = $count / 3;
+ }
+
+ $this->set($count);
+ }
+
+ public function set($count)
+ {
+ if ($count > $this->current) {
+ $this->current = $count;
+ }
+ }
+
+ public function next()
+ {
+ $this->space += $this->current;
+ $this->current = 0;
+ }
+
+ public function getAdditionalNumber()
+ {
+ $page = ceil($this->space / $this->max_rows_first_page);
+ if ($page > 1) {
+ $rows = floor($this->space) % $this->max_rows;
+ } else {
+ $rows = floor($this->space) % $this->max_rows_first_page;
+ }
+
+ $number = $this->max_additional - $rows;
+
+ return $number > 0 ? $number : 0;
+ }
+
+ public function generate()
+ {
+ $this->next();
+
+ $result = '';
+
+ $number = $this->getAdditionalNumber();
+
+ for ($i = 0; $i < $number; ++$i) {
+ $result .= '
+
';
+
+ for ($c = 0; $c < $this->column_number; ++$c) {
+ $result .= '
+ | ';
+ }
+
+ $result .= '
+
';
+ }
+
+ return $result;
+ }
+}
diff --git a/app/OSM/Prints/MPDFManager.php b/app/OSM/Prints/MPDFManager.php
new file mode 100644
index 000000000..35c3233ea
--- /dev/null
+++ b/app/OSM/Prints/MPDFManager.php
@@ -0,0 +1,150 @@
+.
+ */
+
+namespace App\OSM\Prints;
+
+use AppLegacy;
+use Mpdf\Mpdf;
+
+/**
+ * Classe per la gestione delle informazioni relative alle stampe installate.
+ *
+ * @since 2.5
+ */
+abstract class MPDFManager extends Manager
+{
+ public function getManager()
+ {
+ if (!isset($this->manager)) {
+ $settings = $this->getSettings();
+
+ // Instanziamento dell'oggetto mPDF
+ $manager = new Mpdf([
+ 'mode' => 'utf-8',
+ 'format' => $settings['format'],
+ 'orientation' => strtoupper($settings['orientation']) == 'L' ? 'L' : 'P',
+ 'font-size' => $settings['font-size'],
+ 'margin_left' => $settings['margins']['left'],
+ 'margin_right' => $settings['margins']['right'],
+ 'setAutoBottomMargin' => 'stretch',
+ 'setAutoTopMargin' => 'stretch',
+
+ // Abilitazione per lo standard PDF/A
+ //'PDFA' => true,
+ //'PDFAauto' => true,
+ ]);
+
+ // Inclusione dei fogli di stile CSS
+ $styles = [
+ AppLegacy::filepath('templates/base|custom|', 'bootstrap.css'),
+ AppLegacy::filepath('templates/base|custom|', 'style.css'),
+ ];
+
+ foreach ($styles as $value) {
+ $manager->WriteHTML(file_get_contents($value), 1);
+ }
+
+ // Impostazione del font-size
+ $manager->WriteHTML('body {font-size: '.$settings['font-size'].'pt;}', 1);
+
+ $this->manager = $manager;
+ }
+
+ return $this->manager;
+ }
+
+ /**
+ * Genera la stampa PDF richiesta.
+ */
+ public function generate(?string $directory = null): array
+ {
+ $info = $this->init();
+ $settings = $this->getSettings();
+ $manager = $this->getManager();
+
+ $replaces = $this->getReplaces($info['id_cliente'], $info['id_sede']);
+
+ $args = array_merge($info, $replaces);
+
+ // Impostazione header
+ $this->renderHeader($args);
+
+ // Impostazione footer
+ $this->renderFooter($args);
+
+ // Impostazione body
+ $this->renderBody($args);
+
+ // Impostazione footer per l'ultima pagina
+ if (!empty($options['last-page-footer'])) {
+ $args['is_last_page'] = true;
+ $footer = $this->getFooter($args);
+
+ $manager->WriteHTML('');
+ $manager->WriteHTML(''.$footer.'
');
+ }
+
+ $file = $this->getFileData($this->record_id, $directory, $replaces);
+ $title = $file['name'];
+
+ // Impostazione del titolo del PDF
+ $this->getManager()->SetTitle($title);
+
+ return $file;
+ }
+
+ /**
+ * Genera la stampa PDF richiesta e la visualizza nel browser.
+ */
+ public function render(): void
+ {
+ $this->generate();
+
+ // Creazione effettiva del PDF
+ $this->getManager()->Output(null, \Mpdf\Output\Destination::INLINE);
+ }
+
+ /**
+ * Genera la stampa PDF richiesta e la visualizza nel browser.
+ */
+ public function save(string $directory): void
+ {
+ parent::save($directory);
+ $file = $this->generate($directory);
+
+ // Creazione effettiva del PDF
+ $this->getManager()->Output($file['path'], \Mpdf\Output\Destination::FILE);
+ }
+
+ protected function renderHeader(array $args): void
+ {
+ $content = $this->getHeader($args);
+
+ // Impostazione di header
+ $this->getManager()->SetHTMLHeader($content);
+ }
+
+ protected function renderFooter(array $args): void
+ {
+ $content = $this->getFooter($args);
+
+ // Impostazione di footer
+ $this->getManager()->SetHTMLFooter($content);
+ }
+}
diff --git a/app/OSM/Prints/Manager.php b/app/OSM/Prints/Manager.php
new file mode 100644
index 000000000..c422c2b40
--- /dev/null
+++ b/app/OSM/Prints/Manager.php
@@ -0,0 +1,204 @@
+.
+ */
+
+namespace App\OSM\Prints;
+
+use App;
+use AppLegacy;
+
+abstract class Manager extends App\OSM\ComponentManager
+{
+ protected $record_id;
+
+ protected $manager;
+ protected $replaces;
+
+ public function setRecord(?int $record_id = null)
+ {
+ $this->record_id = $record_id;
+ }
+
+ /**
+ * Genera e salva la stampa PDF richiesta.
+ */
+ public function save(string $directory): void
+ {
+ if (empty($directory) || !directory($directory)) {
+ throw new \InvalidArgumentException();
+ }
+ }
+
+ /**
+ * Genera la stampa PDF richiesta e la visualizza nel browser.
+ */
+ abstract public function render(array $args = []): string;
+
+ /**
+ * Genera la stampa PDF richiesta.
+ */
+ abstract public function generate(?string $directory = null): array;
+
+ protected function getSettings(): array
+ {
+ // Impostazioni di default
+ $default = include AppLegacy::filepath('templates/base|custom|', 'settings.php');
+
+ // Impostazioni personalizzate della stampa
+ $custom = $this->getTemplateSettings();
+
+ // Individuazione delle impostazioni finali
+ $settings = array_merge($default, (array) $custom);
+
+ return $settings;
+ }
+
+ protected function getHeader(array $args): string
+ {
+ $content = $this->getTemplateHeader($args);
+
+ return !empty($content) ? $content : '$default_header$';
+ }
+
+ protected function getFooter(array $args): string
+ {
+ $content = $this->getTemplateFooter($args);
+
+ return !empty($content) ? $content : '$default_footer$';
+ }
+
+ protected function getReplaces(?int $id_cliente = null, ?int $id_sede = null): array
+ {
+ if (isset($this->replaces)) {
+ return $this->replaces;
+ }
+
+ $database = $this->database;
+ $id_record = $this->record_id;
+
+ // Informazioni cliente
+ $query = 'SELECT an_anagrafiche.*, an_sedi.*,
+ IF(an_sedi.codice_fiscale != "", an_sedi.codice_fiscale, sede_legale.codice_fiscale) AS codice_fiscale,
+ IF(an_sedi.piva != "", an_sedi.piva, sede_legale.piva) AS piva
+ FROM an_anagrafiche
+ INNER JOIN an_sedi ON an_anagrafiche.idanagrafica = an_sedi.idanagrafica
+ INNER JOIN an_sedi AS sede_legale ON an_anagrafiche.id_sede_legale = an_sedi.id
+ WHERE an_sedi.idanagrafica='.prepare($id_cliente);
+ if (empty($id_sede)) {
+ $query .= ' AND `an_sedi`.`id`=`an_anagrafiche`.`id_sede_legale`';
+ } else {
+ $query .= ' AND `an_sedi`.`id`='.prepare($id_sede);
+ }
+ $cliente = $database->fetchOne($query);
+
+ // Informazioni azienda
+ $id_azienda = setting('Azienda predefinita');
+ $azienda = $database->fetchOne('SELECT *, (SELECT iban FROM co_banche WHERE id IN (SELECT idbanca FROM co_documenti WHERE id = '.prepare($id_record).' ) ) AS codiceiban, (SELECT nome FROM co_banche WHERE id IN (SELECT idbanca FROM co_documenti WHERE id = '.prepare($id_record).' ) ) AS appoggiobancario, (SELECT bic FROM co_banche WHERE id IN (SELECT idbanca FROM co_documenti WHERE id = '.prepare($id_record).' ) ) AS bic FROM an_anagrafiche WHERE idanagrafica = '.prepare($id_azienda));
+
+ // Prefissi e contenuti del replace
+ $results = [
+ 'cliente' => $cliente,
+ 'azienda' => $azienda,
+ ];
+
+ foreach ($results as $prefix => $values) {
+ // Eventuali estensioni dei contenuti
+ $citta = '';
+ if (!empty($values['cap'])) {
+ $citta .= $values['cap'];
+ }
+ if (!empty($values['citta'])) {
+ $citta .= ' '.$values['citta'];
+ }
+ if (!empty($values['provincia'])) {
+ $citta .= ' ('.$values['provincia'].')';
+ }
+
+ $results[$prefix]['citta_full'] = $citta;
+ }
+
+ // Header di default
+ $header_file = AppLegacy::filepath('templates/base|custom|/header.php');
+ $default_header = include $header_file;
+ $default_header = !empty($options['hide-header']) ? '' : $default_header;
+
+ // Footer di default
+ $footer_file = AppLegacy::filepath('templates/base|custom|/footer.php');
+ $default_footer = include $footer_file;
+ $default_footer = !empty($options['hide-footer']) ? '' : $default_footer;
+
+ // Logo di default
+ $default_logo = AppLegacy::filepath('templates/base|custom|/logo_azienda.jpg');
+
+ // Logo generico
+ if (!empty(setting('Logo stampe'))) {
+ $default_logo = AppLegacy::filepath('files/anagrafiche/'.setting('Logo stampe'));
+ }
+
+ // Valori aggiuntivi per la sostituzione
+ $this->replaces = array_merge($results, [
+ 'default_header' => $default_header,
+ 'default_footer' => $default_footer,
+ 'default_logo' => $default_logo,
+ ]);
+
+ return $this->replaces;
+ }
+
+ protected function getFileData($directory, $original_replaces)
+ {
+ $id_record = $this->record_id;
+ $module = $this->print->module;
+
+ $name = $this->print->filename.'.pdf';
+ $name = $module->replacePlaceholders($id_record, $name);
+
+ $replaces = [];
+ foreach ($original_replaces as $key => $value) {
+ $key = str_replace('$', '', $key);
+
+ $replaces['{'.$key.'}'] = $value;
+ }
+
+ $name = replace($name, $replaces);
+
+ $filename = sanitizeFilename($name);
+ $file = rtrim($directory, '/').'/'.$filename;
+
+ return [
+ 'name' => $name,
+ 'path' => $file,
+ ];
+ }
+
+ abstract protected function init(): array;
+
+ abstract protected function getManager();
+
+ abstract protected function renderHeader(array $args): void;
+
+ abstract protected function renderFooter(array $args): void;
+
+ abstract protected function renderBody(array $args): void;
+
+ abstract protected function getTemplateSettings(): array;
+
+ abstract protected function getTemplateHeader(array $args): string;
+
+ abstract protected function getTemplateFooter(array $args): string;
+}
diff --git a/app/OSM/Prints/Retro/Manager.php b/app/OSM/Prints/Retro/Manager.php
new file mode 100644
index 000000000..bbbf2addd
--- /dev/null
+++ b/app/OSM/Prints/Retro/Manager.php
@@ -0,0 +1,176 @@
+.
+ */
+
+namespace App\OSM\Prints\Retro;
+
+use App;
+use AppLegacy;
+use App\OSM\Prints\MPDFManager;
+
+class Manager extends MPDFManager
+{
+ protected $body;
+ protected $header;
+ protected $footer;
+
+ protected function renderBody(array $args): void
+ {
+ $this->load($args);
+
+ $this->getManager()->WriteHTML($this->body);
+ }
+
+ protected function load(array $args): void
+ {
+ if (isset($this->body)) {
+ return;
+ }
+
+ // Fix per le variabili in PHP
+ foreach ($args['cliente'] as $key => $value) {
+ $args['c_'.$key] = $value;
+ }
+
+ foreach ($args['azienda'] as $key => $value) {
+ $args['f_'.$key] = $value;
+ }
+
+ extract($args);
+ $dbo = $database = database();
+
+ ob_start();
+ include $this->filepath('header.php');
+ $content = ob_get_clean();
+
+ $this->header = $this->replace($content);
+
+ ob_start();
+ include $this->filepath('body.php');
+ $content = ob_get_clean();
+
+ if (!empty($autofill)) {
+ $result = $autofill->generate();
+
+ $content = str_replace('|autofill|', $result, $content);
+ }
+
+ $this->body = $this->replace($content);
+
+ ob_start();
+ include $this->filepath('footer.php');
+ $content = ob_get_clean();
+
+ $this->footer = $this->replace($content);
+ }
+
+ protected function getReplaces(?int $id_cliente = null, ?int $id_sede = null): array
+ {
+ $replaces = parent::getReplaces($id_cliente, $id_sede);
+
+ // Logo specifico della stampa
+ $logo = \Prints::filepath($this->print->id, 'logo_azienda.jpg');
+ $logo = $logo ?: $replaces['default_logo'];
+
+ // Valori aggiuntivi per la sostituzione
+ $this->replaces = array_merge($replaces, [
+ 'logo' => $logo,
+ ]);
+
+ return $this->replaces;
+ }
+
+ protected function replace($content): string
+ {
+ $info = $this->init();
+ $replaces = $this->getReplaces($info['id_cliente'], $info['id_sede']);
+
+ $replaces = array_merge($replaces, (array) $info['custom']);
+
+ $list = [];
+ foreach ($replaces as $key => $value) {
+ if (!is_array($value)) {
+ $list[$key] = $value;
+ }
+ }
+
+ foreach ($replaces['cliente'] as $key => $value) {
+ $list['c_'.$key] = $value;
+ }
+
+ foreach ($replaces['azienda'] as $key => $value) {
+ $list['f_'.$key] = $value;
+ }
+
+ $results = [];
+ foreach ($list as $key => $value) {
+ $results['$'.$key.'$'] = $value;
+ }
+
+ return replace($content, $results);
+ }
+
+ protected function getPath(): string
+ {
+ return base_path().'/templates/'.$this->print->directory;
+ }
+
+ protected function filepath($file): ?string
+ {
+ return AppLegacy::filepath($this->getPath().'|custom|', $file);
+ }
+
+ protected function init(): array
+ {
+ $record_id = $id_record = $this->record_id;
+ $module_id = $id_module = $this->print->module->id;
+ $print_id = $id_print = $this->print->id;
+
+ $dbo = $database = database();
+
+ // Individuazione delle variabili fondamentali per la sostituzione dei contenuti
+ include $this->filepath('init.php');
+
+ return get_defined_vars();
+ }
+
+ protected function getTemplateSettings(): array
+ {
+ $file = $this->filepath('settings.php');
+
+ if (file_exists($file)) {
+ return include $file;
+ }
+
+ return [];
+ }
+
+ protected function getTemplateHeader(array $args): string
+ {
+ $this->load($args);
+
+ return $this->header;
+ }
+
+ protected function getTemplateFooter(array $args): string
+ {
+ $this->load($args);
+
+ return $this->footer;
+ }
+}
diff --git a/app/OSM/Prints/Template.php b/app/OSM/Prints/Template.php
new file mode 100644
index 000000000..50a1fbe1d
--- /dev/null
+++ b/app/OSM/Prints/Template.php
@@ -0,0 +1,83 @@
+.
+ */
+
+namespace App\OSM\Prints;
+
+use App\OSM\ComponentManagerTrait;
+use Common\SimpleModelTrait;
+use Models\Group;
+use Common\Model;
+use Illuminate\Database\Eloquent\Builder;
+use Models\Module;
+use Traits\LocalPoolTrait;
+
+class Template extends Model
+{
+ use SimpleModelTrait;
+ use LocalPoolTrait;
+ use ComponentManagerTrait;
+
+ protected $table = 'zz_prints';
+ protected $main_folder = 'templates';
+
+ // Attributi Eloquent
+
+ /**
+ * Restituisce un array associativo dalla codifica JSON delle opzioni di stampa.
+ *
+ * @param string $string
+ *
+ * @return array
+ */
+ public function getOptionsAttribute()
+ {
+ // Fix per contenuti con newline integrate
+ $string = str_replace(["\n", "\r"], ['\\n', '\\r'], $this->options);
+
+ $result = (array) json_decode($string, true);
+
+ return $result;
+ }
+
+ /* Relazioni Eloquent */
+
+ public function module()
+ {
+ return $this->belongsTo(Module::class, 'id_module');
+ }
+
+ /*
+ public function groups()
+ {
+ return $this->morphToMany(Group::class, 'permission', 'zz_permissions', 'external_id', 'group_id')->where('permission_level', '!=', '-')->withPivot('permission_level');
+ }*/
+
+ protected static function boot()
+ {
+ parent::boot();
+
+ static::addGlobalScope('enabled', function (Builder $builder) {
+ $builder->where('enabled', true);
+ });
+
+ static::addGlobalScope('permission', function (Builder $builder) {
+ //$builder->with('groups');
+ });
+ }
+}
diff --git a/app/OSM/Widgets/LinkWidget.php b/app/OSM/Widgets/LinkWidget.php
new file mode 100644
index 000000000..4532052e4
--- /dev/null
+++ b/app/OSM/Widgets/LinkWidget.php
@@ -0,0 +1,36 @@
+.
+ */
+
+namespace App\OSM\Widgets;
+
+/**
+ * Tipologia di widget indirizzato alla presentazione di un link ausiliario per l'utente finale.
+ * Presenta esclusivamente un tiolo e al click prevede il reindirizzamento a un indirizzo specifico.
+ *
+ * @since 2.5
+ */
+abstract class LinkWidget extends Manager
+{
+ abstract public function getLink(): string;
+
+ public function getAttributes(): string
+ {
+ return 'href="'.$this->getLink().'"';
+ }
+}
diff --git a/app/OSM/Widgets/Manager.php b/app/OSM/Widgets/Manager.php
new file mode 100644
index 000000000..a99361b38
--- /dev/null
+++ b/app/OSM/Widgets/Manager.php
@@ -0,0 +1,68 @@
+.
+ */
+
+namespace App\OSM\Widgets;
+
+use App\OSM\ComponentManager;
+
+/**
+ * Classe dedicata alla gestione di base dei widget del gestionale.
+ * Introduce un rendering di base e definisce i comportamenti standard da estendere per un utilizzo piĆ¹ completo.
+ *
+ * @since 2.5
+ */
+abstract class Manager extends ComponentManager
+{
+ protected $record_id;
+
+ public function setRecord(?int $record_id = null)
+ {
+ $this->record_id = $record_id;
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return \Illuminate\Contracts\View\View|string
+ */
+ public function render()
+ {
+ $widget = $this->model;
+
+ $title = $this->getTitle();
+ $content = $this->getContent();
+ $attributes = $this->getAttributes();
+
+ return view('components.widget', [
+ 'widget' => $widget,
+ 'title' => $title,
+ 'content' => $content,
+ 'attrs' => $attributes,
+ ]);
+ }
+
+ abstract public function getTitle(): string;
+
+ abstract public function getContent(): string;
+
+ public function getAttributes(): string
+ {
+ return 'href="#"';
+ }
+}
diff --git a/app/OSM/Widgets/ModalWidget.php b/app/OSM/Widgets/ModalWidget.php
new file mode 100644
index 000000000..4cb4dc2c2
--- /dev/null
+++ b/app/OSM/Widgets/ModalWidget.php
@@ -0,0 +1,40 @@
+.
+ */
+
+namespace App\OSM\Widgets;
+
+/**
+ * Tipologia di widget dedicato alla gestione di un modal aperto al click
+ * Presenta un titolo e una valore personalizzato; al click produce l'apertura del modal specificato.
+ *
+ * @since 2.5
+ */
+abstract class ModalWidget extends Manager
+{
+ abstract public function getModal(): string;
+
+ abstract public function getLink(): string;
+
+ public function getAttributes(): string
+ {
+ $title = $this->getTitle();
+
+ return 'data-href="'.$this->getLink().'" data-toggle="modal" data-title="'.$title.'"';
+ }
+}
diff --git a/app/OSM/Widgets/Retro/LinkWidget.php b/app/OSM/Widgets/Retro/LinkWidget.php
new file mode 100644
index 000000000..576e680ba
--- /dev/null
+++ b/app/OSM/Widgets/Retro/LinkWidget.php
@@ -0,0 +1,40 @@
+.
+ */
+
+namespace App\OSM\Widgets\Retro;
+
+use App\OSM\Widgets\LinkWidget as Original;
+
+class LinkWidget extends Original
+{
+ public function getLink(): string
+ {
+ return base_url().$this->model['more_link'];
+ }
+
+ public function getTitle(): string
+ {
+ return $this->model['text'];
+ }
+
+ public function getContent(): string
+ {
+ return '';
+ }
+}
diff --git a/app/OSM/Widgets/Retro/ModalWidget.php b/app/OSM/Widgets/Retro/ModalWidget.php
new file mode 100644
index 000000000..5d9bf3d23
--- /dev/null
+++ b/app/OSM/Widgets/Retro/ModalWidget.php
@@ -0,0 +1,84 @@
+.
+ */
+
+namespace App\OSM\Widgets\Retro;
+
+use App\Http\Controllers\LegacyController;
+use Models\Module;
+use Util\Query;
+use App\OSM\Widgets\ModalWidget as Original;
+
+class ModalWidget extends Original
+{
+ public function getModal(): string
+ {
+ $content = '';
+
+ $widget = $this->model;
+ if (!empty($widget['more_link'])) {
+ $content = LegacyController::simulate($widget['more_link']);
+ }
+
+ return $content;
+ }
+
+ public function getLink(): string
+ {
+ $id = $this->model->id;
+
+ return route('widget-modal', [
+ 'id' => $id,
+ ]);
+ }
+
+ public function getTitle(): string
+ {
+ return $this->model['text'] ?: '';
+ }
+
+ public function getContent(): string
+ {
+ $content = '';
+
+ $widget = $this->model;
+ if (!empty($widget['query'])) {
+ $query = $widget['query'];
+ $module = Module::pool($widget['id_module']);
+
+ $additionals = \Modules::getAdditionalsQuery($widget['id_module']);
+ //$additionals = $module->getAdditionalsQuery();
+ if (!empty($additionals)) {
+ $query = str_replace('1=1', '1=1 '.$additionals, $query);
+ }
+
+ $query = Query::replacePlaceholder($query);
+
+ // Individuazione del risultato della query
+ $database = database();
+ $value = '-';
+ if (!empty($query)) {
+ $value = $database->fetchArray($query)[0]['dato'];
+ }
+
+ $content = preg_match('/\\d/', $value) ? $value : '-';
+ }
+
+ return $content;
+ }
+}
diff --git a/app/OSM/Widgets/Retro/StatsWidget.php b/app/OSM/Widgets/Retro/StatsWidget.php
new file mode 100644
index 000000000..4ccb8a320
--- /dev/null
+++ b/app/OSM/Widgets/Retro/StatsWidget.php
@@ -0,0 +1,47 @@
+.
+ */
+
+namespace App\OSM\Widgets\Retro;
+
+use App\OSM\Widgets\StatsWidget as Original;
+
+class StatsWidget extends Original
+{
+ public function getQuery(): string
+ {
+ return $this->model['query'] ?: 'SELECT 0 AS dato';
+ }
+
+ public function getAttributes(): string
+ {
+ $attributes = parent::getAttributes();
+ $js = $this->model['more_link'];
+
+ if (!empty($js)) {
+ return $attributes.' onclick="'.$js.'"';
+ }
+
+ return $attributes;
+ }
+
+ public function getTitle(): string
+ {
+ return $this->model['text'];
+ }
+}
diff --git a/app/OSM/Widgets/StatsWidget.php b/app/OSM/Widgets/StatsWidget.php
new file mode 100644
index 000000000..4d9117ebd
--- /dev/null
+++ b/app/OSM/Widgets/StatsWidget.php
@@ -0,0 +1,64 @@
+.
+ */
+
+namespace App\OSM\Widgets;
+
+use Models\Module;
+use Util\Query;
+
+/**
+ * Tipologia di widget indirizzato alla visualizzazione di una statistica informativa per l'utente finale.
+ * Presenta un titolo e una valore personalizzato; al click non prevede particolari operazioni.
+ *
+ * @since 2.5
+ */
+abstract class StatsWidget extends Manager
+{
+ abstract public function getQuery(): string;
+
+ public function getContent(): string
+ {
+ $widget = $this->model;
+
+ // Individuazione della query relativa
+ $query = $this->getQuery();
+
+ $module = Module::pool($widget['id_module']);
+
+ $additionals = \Modules::getAdditionalsQuery($widget['id_module']);
+ //$additionals = $module->getAdditionalsQuery();
+ if (!empty($additionals)) {
+ $query = str_replace('1=1', '1=1 '.$additionals, $query);
+ }
+
+ $query = Query::replacePlaceholder($query);
+
+ // Individuazione del risultato della query
+ $database = database();
+ $value = null;
+ if (!empty($query)) {
+ $value = $database->fetchArray($query)[0]['dato'];
+ if (!preg_match('/\\d/', $value)) {
+ $value = '-';
+ }
+ }
+
+ return $value;
+ }
+}
diff --git a/app/OSM/Widgets/Widget.php b/app/OSM/Widgets/Widget.php
new file mode 100644
index 000000000..536a5b221
--- /dev/null
+++ b/app/OSM/Widgets/Widget.php
@@ -0,0 +1,70 @@
+.
+ */
+
+namespace App\OSM\Widgets;
+
+use App\OSM\ComponentManagerTrait;
+use Common\SimpleModelTrait;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Model;
+use Models\Module;
+
+/**
+ * Modello Eloquent per i widget del gestionale.
+ *
+ * @since 2.5
+ */
+class Widget extends Model
+{
+ use SimpleModelTrait;
+ use ComponentManagerTrait;
+
+ protected $table = 'zz_widgets';
+
+ protected $appends = [
+ 'permission',
+ ];
+
+ /* Relazioni Eloquent */
+
+ public function module()
+ {
+ return $this->belongsTo(Module::class, 'id_module');
+ }
+
+ /*
+ public function groups()
+ {
+ return $this->morphToMany(Group::class, 'permission', 'zz_permissions', 'external_id', 'group_id')->where('permission_level', '!=', '-')->withPivot('permission_level');
+ }
+ */
+
+ protected static function boot()
+ {
+ parent::boot();
+
+ static::addGlobalScope('enabled', function (Builder $builder) {
+ $builder->where('enabled', true);
+ });
+
+ static::addGlobalScope('permission', function (Builder $builder) {
+ //$builder->with('groups');
+ });
+ }
+}
diff --git a/app/View/Components/Widget.php b/app/View/Components/Widget.php
new file mode 100644
index 000000000..1d70845c6
--- /dev/null
+++ b/app/View/Components/Widget.php
@@ -0,0 +1,40 @@
+widget = Model::find($id);
+ $this->manager = $this->widget->getManager();
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return \Illuminate\Contracts\View\View|string
+ */
+ public function render()
+ {
+ $manager = $this->manager;
+
+ return $manager->render();
+ }
+}
diff --git a/legacy b/legacy
index 5730434cc..767e211f4 160000
--- a/legacy
+++ b/legacy
@@ -1 +1 @@
-Subproject commit 5730434cc63c67880049f6adb116791dc8af8a5b
+Subproject commit 767e211f4c616315618e222b6d55338f92d247a5
diff --git a/resources/views/components/widget.blade.php b/resources/views/components/widget.blade.php
new file mode 100644
index 000000000..58557df7a
--- /dev/null
+++ b/resources/views/components/widget.blade.php
@@ -0,0 +1,25 @@
+
+
+
+
+
+ @if(!empty($widget['icon']))
+
+ @endif
+
+
+
+
+ {{ $title }}
+
+ {{ !empty($widget['help']) ? '' : '' }}
+
+
+ @if(isset($content))
+ {{ $content }}
+ @endif
+
+
+
diff --git a/resources/views/modules/base.blade.php b/resources/views/modules/base.blade.php
new file mode 100644
index 000000000..0eeada437
--- /dev/null
+++ b/resources/views/modules/base.blade.php
@@ -0,0 +1,62 @@
+@extends('layouts.app')
+
+@section('title', $module->title)
+
+@section('content')
+ @yield('top_content')
+
+
+
+
+
+ @php($hide_sidebar = auth()->check() && setting('Nascondere la barra dei plugin di default'))
+
+
+
+
+
+
+
+
+
+ @yield('module_content')
+
+
+
+ @yield('bottom_content')
+
+
+@endsection
+
+@section('top_content')
+
+ {( "name": "widgets", "id_module": "{{ $module->id }}", "id_record": "{{ 1 }}", "position": "top", "place": "controller" )}
+@endsection
+
+@section('bottom_content')
+
+ {( "name": "widgets", "id_module": "{{ $module->id }}", "id_record": "{{ 1 }}", "position": "right", "place": "controller" )}
+@endsection
diff --git a/resources/views/modules/title.blade.php b/resources/views/modules/title.blade.php
new file mode 100644
index 000000000..9c85b4aae
--- /dev/null
+++ b/resources/views/modules/title.blade.php
@@ -0,0 +1,17 @@
+@if(!empty($module->help))
+
+ {{ $module->title }}
+
+
+
+@else
+ {{ $module->title }}
+@endif
+
+{{-- $module->hasAddFile() and --}}
+@if($module->permission == 'rw')
+
+
+@endif
diff --git a/routes/web.php b/routes/web.php
index add4c0fdc..4bbf41065 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -9,6 +9,7 @@ use App\Http\Controllers\MessageController;
use App\Http\Controllers\RequirementsController;
use App\Http\Controllers\Test;
use App\Http\Controllers\UserController;
+use App\Http\Controllers\WidgetModalController;
use Illuminate\Support\Facades\Route;
/*
@@ -119,6 +120,13 @@ Route::get('/logs', [UserController::class, 'logs'])
->middleware(['auth'])
->name('logs');
+
+// Log di accesso
+Route::get('/widget/modal/{id}', [WidgetModalController::class, 'modal'])
+ ->whereNumber('id')
+ ->middleware(['auth'])
+ ->name('widget-modal');
+
// Informazioni sull'utente
Route::prefix('user')
->middleware(['auth'])