Compare commits

..

1 Commits

Author SHA1 Message Date
solocla 955b663322 added end column for XLS in template and import 2026-06-18 16:50:38 +02:00
93 changed files with 1257 additions and 12157 deletions
-4
View File
@@ -62,7 +62,3 @@ public/userarea/schemi_base_response.json
public/userarea/cache/
public/userarea/error_log.txt
# File PDF di esempio e importati
/public/userarea/pdftemplates/*.pdf
/public/userarea/pdftemplates/imported/*.pdf
-1
View File
@@ -44,7 +44,6 @@
"phpmailer/phpmailer": "^6.9",
"phpoffice/phpspreadsheet": "^4.1",
"proengsoft/laravel-jsvalidation": "^4.0.0",
"robmorgan/phinx": "^0.16.12",
"socialiteproviders/microsoft": "^4.7",
"spatie/laravel-query-builder": "^5.0",
"vanguardapp/activity-log": "^6.0",
Generated
+2 -646
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "557df76fdde549210349be369d479f5d",
"content-hash": "9c4f1e3bc3ee2180211c055e70635aef",
"packages": [
{
"name": "akaunting/laravel-setting",
@@ -251,330 +251,6 @@
],
"time": "2023-11-29T23:19:16+00:00"
},
{
"name": "cakephp/chronos",
"version": "3.5.0",
"source": {
"type": "git",
"url": "https://github.com/cakephp/chronos.git",
"reference": "e6e777b534244911566face8a5dbdbd7f7bda5a6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/chronos/zipball/e6e777b534244911566face8a5dbdbd7f7bda5a6",
"reference": "e6e777b534244911566face8a5dbdbd7f7bda5a6",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/clock": "^1.0"
},
"provide": {
"psr/clock-implementation": "1.0"
},
"require-dev": {
"cakephp/cakephp-codesniffer": "^5.0",
"phpunit/phpunit": "^10.5.58 || ^11.5.3 || ^12.1.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Cake\\Chronos\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Nesbitt",
"email": "brian@nesbot.com",
"homepage": "http://nesbot.com"
},
{
"name": "The CakePHP Team",
"homepage": "https://cakephp.org"
}
],
"description": "A simple API extension for DateTime.",
"homepage": "https://cakephp.org",
"keywords": [
"date",
"datetime",
"time"
],
"support": {
"issues": "https://github.com/cakephp/chronos/issues",
"source": "https://github.com/cakephp/chronos"
},
"time": "2026-04-10T02:50:39+00:00"
},
{
"name": "cakephp/core",
"version": "5.3.6",
"source": {
"type": "git",
"url": "https://github.com/cakephp/core.git",
"reference": "9c458b0e9322ec88bc4c758b33cde6a0abf49d12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/core/zipball/9c458b0e9322ec88bc4c758b33cde6a0abf49d12",
"reference": "9c458b0e9322ec88bc4c758b33cde6a0abf49d12",
"shasum": ""
},
"require": {
"cakephp/utility": "^5.3.0",
"league/container": "^5.1",
"php": ">=8.2",
"psr/container": "^1.1 || ^2.0"
},
"provide": {
"psr/container-implementation": "^2.0"
},
"suggest": {
"cakephp/cache": "To use Configure::store() and restore().",
"cakephp/event": "To use PluginApplicationInterface or plugin applications.",
"league/container": "To use Container and ServiceProvider classes"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-5.next": "5.4.x-dev"
}
},
"autoload": {
"files": [
"functions.php"
],
"psr-4": {
"Cake\\Core\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/core/graphs/contributors"
}
],
"description": "CakePHP Framework Core classes",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"core",
"framework"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/core"
},
"time": "2026-05-15T03:31:14+00:00"
},
{
"name": "cakephp/database",
"version": "5.3.6",
"source": {
"type": "git",
"url": "https://github.com/cakephp/database.git",
"reference": "442d12bf0a1edeffc555a59de9f955cb0619c7ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/database/zipball/442d12bf0a1edeffc555a59de9f955cb0619c7ca",
"reference": "442d12bf0a1edeffc555a59de9f955cb0619c7ca",
"shasum": ""
},
"require": {
"cakephp/chronos": "^3.3",
"cakephp/core": "^5.3.0",
"cakephp/datasource": "^5.3.0",
"php": ">=8.2",
"psr/log": "^3.0"
},
"require-dev": {
"cakephp/i18n": "^5.3.0",
"cakephp/log": "^5.3.0"
},
"suggest": {
"cakephp/i18n": "If you are using locale-aware datetime formats.",
"cakephp/log": "If you want to use query logging without providing a logger yourself."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-5.next": "5.4.x-dev"
}
},
"autoload": {
"psr-4": {
"Cake\\Database\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/database/graphs/contributors"
}
],
"description": "Flexible and powerful Database abstraction library with a familiar PDO-like API",
"homepage": "https://cakephp.org",
"keywords": [
"abstraction",
"cakephp",
"database",
"database abstraction",
"pdo"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/database"
},
"time": "2026-05-21T19:38:13+00:00"
},
{
"name": "cakephp/datasource",
"version": "5.3.6",
"source": {
"type": "git",
"url": "https://github.com/cakephp/datasource.git",
"reference": "b768f0c0bf0fca815f83c4caef14e7fbb2409bf5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/datasource/zipball/b768f0c0bf0fca815f83c4caef14e7fbb2409bf5",
"reference": "b768f0c0bf0fca815f83c4caef14e7fbb2409bf5",
"shasum": ""
},
"require": {
"cakephp/core": "^5.3.0",
"php": ">=8.2",
"psr/simple-cache": "^2.0 || ^3.0"
},
"require-dev": {
"cakephp/cache": "^5.3.0",
"cakephp/collection": "^5.3.0",
"cakephp/utility": "^5.3.0"
},
"suggest": {
"cakephp/cache": "If you decide to use Query caching.",
"cakephp/collection": "If you decide to use ResultSetInterface.",
"cakephp/utility": "If you decide to use EntityTrait."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-5.next": "5.4.x-dev"
}
},
"autoload": {
"psr-4": {
"Cake\\Datasource\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/datasource/graphs/contributors"
}
],
"description": "Provides connection managing and traits for Entities and Queries that can be reused for different datastores",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"connection management",
"datasource",
"entity",
"query"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/datasource"
},
"time": "2026-05-20T10:27:33+00:00"
},
{
"name": "cakephp/utility",
"version": "5.3.6",
"source": {
"type": "git",
"url": "https://github.com/cakephp/utility.git",
"reference": "4c703a010b9d955fed44731669e35d3043425cc7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/utility/zipball/4c703a010b9d955fed44731669e35d3043425cc7",
"reference": "4c703a010b9d955fed44731669e35d3043425cc7",
"shasum": ""
},
"require": {
"cakephp/core": "^5.3.0",
"php": ">=8.2"
},
"suggest": {
"ext-intl": "To use Text::transliterate() or Text::slug()",
"lib-ICU": "To use Text::transliterate() or Text::slug()"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-5.next": "5.4.x-dev"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Cake\\Utility\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/utility/graphs/contributors"
}
],
"description": "CakePHP Utility classes such as Inflector, String, Hash, and Security",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"hash",
"inflector",
"security",
"string",
"utility"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/utility"
},
"time": "2026-05-21T19:38:13+00:00"
},
{
"name": "carbonphp/carbon-doctrine-types",
"version": "3.2.0",
@@ -2951,90 +2627,6 @@
],
"time": "2022-12-11T20:36:23+00:00"
},
{
"name": "league/container",
"version": "5.2.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/container.git",
"reference": "58accbc032f0090a9bd08326f93062c5a658b2c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/container/zipball/58accbc032f0090a9bd08326f93062c5a658b2c5",
"reference": "58accbc032f0090a9bd08326f93062c5a658b2c5",
"shasum": ""
},
"require": {
"php": "^8.1",
"psr/container": "^2.0.2",
"psr/event-dispatcher": "^1.0"
},
"provide": {
"psr/container-implementation": "^1.0"
},
"replace": {
"orno/di": "~2.0"
},
"require-dev": {
"nette/php-generator": "^4.1",
"nikic/php-parser": "^5.0",
"phpstan/phpstan": "^2.1.11",
"phpunit/phpunit": "^10.5.45|^11.5.15|^12.0",
"roave/security-advisories": "dev-latest",
"scrutinizer/ocular": "^1.9",
"squizlabs/php_codesniffer": "^3.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev",
"dev-2.x": "2.x-dev",
"dev-3.x": "3.x-dev",
"dev-4.x": "4.x-dev",
"dev-5.x": "5.x-dev",
"dev-master": "5.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Container\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Phil Bennett",
"email": "mail@philbennett.co.uk",
"role": "Developer"
}
],
"description": "A fast and intuitive dependency injection container.",
"homepage": "https://github.com/thephpleague/container",
"keywords": [
"container",
"dependency",
"di",
"injection",
"league",
"provider",
"service"
],
"support": {
"issues": "https://github.com/thephpleague/container/issues",
"source": "https://github.com/thephpleague/container/tree/5.2.0"
},
"funding": [
{
"url": "https://github.com/philipobenito",
"type": "github"
}
],
"time": "2026-03-19T18:52:39+00:00"
},
{
"name": "league/flysystem",
"version": "3.28.0",
@@ -5388,93 +4980,6 @@
],
"time": "2024-04-27T21:32:50+00:00"
},
{
"name": "robmorgan/phinx",
"version": "0.16.12",
"source": {
"type": "git",
"url": "https://github.com/cakephp/phinx.git",
"reference": "1cce44387a9146dc04c312e6a231b17fb30385cd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/phinx/zipball/1cce44387a9146dc04c312e6a231b17fb30385cd",
"reference": "1cce44387a9146dc04c312e6a231b17fb30385cd",
"shasum": ""
},
"require": {
"cakephp/database": "^5.0.2",
"composer-runtime-api": "^2.0",
"php-64bit": ">=8.1",
"psr/container": "^1.1|^2.0",
"symfony/config": "^4.0|^5.0|^6.0|^7.0|^8.0",
"symfony/console": "^6.0|^7.0|^8.0"
},
"require-dev": {
"cakephp/cakephp-codesniffer": "^5.0",
"cakephp/i18n": "^5.0",
"ext-json": "*",
"ext-pdo": "*",
"phpunit/phpunit": "^10.5",
"symfony/yaml": "^4.0|^5.0|^6.0|^7.0|^8.0"
},
"suggest": {
"ext-json": "Install if using JSON configuration format",
"ext-pdo": "PDO extension is needed",
"symfony/yaml": "Install if using YAML configuration format"
},
"bin": [
"bin/phinx"
],
"type": "library",
"autoload": {
"psr-4": {
"Phinx\\": "src/Phinx/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Morgan",
"email": "robbym@gmail.com",
"homepage": "https://robmorgan.id.au",
"role": "Lead Developer"
},
{
"name": "Woody Gilk",
"email": "woody.gilk@gmail.com",
"homepage": "https://shadowhand.me",
"role": "Developer"
},
{
"name": "Richard Quadling",
"email": "rquadling@gmail.com",
"role": "Developer"
},
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/phinx/graphs/contributors",
"role": "Developer"
}
],
"description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
"homepage": "https://phinx.org",
"keywords": [
"database",
"database migrations",
"db",
"migrations",
"phinx"
],
"support": {
"issues": "https://github.com/cakephp/phinx/issues",
"source": "https://github.com/cakephp/phinx/tree/0.16.12"
},
"time": "2026-07-03T07:27:17+00:00"
},
{
"name": "socialiteproviders/manager",
"version": "v4.8.1",
@@ -5807,85 +5312,6 @@
],
"time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/config",
"version": "v7.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "7b665e443381ea7c4db03eb03b4bf79ea2b020eb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/7b665e443381ea7c4db03eb03b4bf79ea2b020eb",
"reference": "7b665e443381ea7c4db03eb03b4bf79ea2b020eb",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/filesystem": "^7.1|^8.0",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/finder": "<6.4",
"symfony/service-contracts": "<2.5"
},
"require-dev": {
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",
"symfony/finder": "^6.4|^7.0|^8.0",
"symfony/messenger": "^6.4|^7.0|^8.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/yaml": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Config\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/config/tree/v7.4.14"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-06-09T07:51:57+00:00"
},
{
"name": "symfony/console",
"version": "v7.1.3",
@@ -6342,76 +5768,6 @@
],
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/filesystem",
"version": "v7.4.11",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/d721ea61b4a5fba8c5b6e7c1feda19efea144b50",
"reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"require-dev": {
"symfony/process": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v7.4.11"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-05-11T16:38:44+00:00"
},
{
"name": "symfony/finder",
"version": "v7.1.3",
@@ -11999,6 +11355,6 @@
"php": "^8.2.0",
"ext-json": "*"
},
"platform-dev": {},
"platform-dev": [],
"plugin-api-version": "2.6.0"
}
@@ -1,15 +0,0 @@
<?php
use Phinx\Migration\AbstractMigration;
final class CreatePhinxTestTable extends AbstractMigration
{
public function change(): void
{
$table = $this->table('phinx_test');
$table
->addColumn('name', 'string', ['limit' => 100, 'null' => false])
->addColumn('created_at', 'datetime', ['default' => 'CURRENT_TIMESTAMP'])
->create();
}
}
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddAddnoteToTemplateMapping extends AbstractMigration
{
public function change(): void
{
$table = $this->table('template_mapping');
$table->addColumn('is_addnote', 'boolean', [
'default' => 0,
'null' => false,
'after' => 'is_visible_parts',
'comment' => 'Campo utilizzabile come addnote (multi-selezione)',
])
->update();
}
}
@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateOptionsTable extends AbstractMigration
{
public function change(): void
{
$table = $this->table('options');
$table
->addColumn('opt_key', 'string', ['limit' => 100])
->addColumn('opt_value', 'text', ['null' => true])
->addColumn('created_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
->addColumn('updated_at', 'timestamp', [
'default' => 'CURRENT_TIMESTAMP',
'update' => 'CURRENT_TIMESTAMP',
])
->addIndex(['opt_key'], ['unique' => true])
->create();
// Righe iniziali del popup
$this->table('options')->insert([
[
'opt_key' => 'popup_enabled',
'opt_value' => '1',
],
[
'opt_key' => 'popup_version',
'opt_value' => '2026-07-migrazione',
],
[
'opt_key' => 'popup',
'opt_value' =>
'<h2 style="margin-top:0;">Nuovo portale disponibile</h2>'
. '<p>Ci siamo spostati. Da ora usa il nuovo indirizzo:</p>'
. '<p style="text-align:center;">'
. '<a href="https://trfsmart-bv.cesoft.io/" '
. 'style="display:inline-block;padding:12px 24px;background:#2563eb;'
. 'color:#fff;text-decoration:none;border-radius:6px;">Vai al nuovo portale</a>'
. '</p>'
. '<p style="font-size:13px;color:#666;">Salva il nuovo indirizzo nei preferiti. '
. 'Se riscontri problemi puoi continuare a usare questo portale per ora.</p>',
],
])->saveData();
}
}
@@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddExportDateToDatadb extends AbstractMigration
{
/**
* Aggiunge export_date su datadb: data/ora di esportazione verso il LIMS,
* valorizzata insieme al numero commessa quando status passa a 'l'.
*/
public function change(): void
{
$this->table('datadb')
->addColumn('export_date', 'datetime', [
'null' => true,
'default' => null,
'after' => 'commessaweb',
'comment' => 'Data/ora esportazione verso LIMS (valorizzata quando status = l)',
])
->addIndex(['export_date'], ['name' => 'idx_datadb_export_date'])
->update();
}
}
@@ -1,57 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateLimsDailySamplesTable extends AbstractMigration
{
/**
* Tabella con un dato al giorno: numero totale di campioni inseriti
* nel LIMS (loro software) in quella giornata. Serve per calcolare
* la % di campioni esportati sul totale inserito nel LIMS.
*/
public function change(): void
{
$table = $this->table('lims_daily_samples', [
'id' => false,
'primary_key' => ['id'],
'comment' => 'Totale campioni inseriti nel LIMS per giornata',
]);
$table
->addColumn('id', 'integer', [
'identity' => true,
'signed' => false,
])
->addColumn('sample_date', 'date', [
'null' => false,
'comment' => 'Giornata di riferimento',
])
->addColumn('total_samples', 'integer', [
'signed' => false,
'null' => false,
'default' => 0,
'comment' => 'Totale campioni inseriti nel LIMS in quella giornata',
])
->addColumn('note', 'string', [
'limit' => 255,
'null' => true,
'default' => null,
])
->addColumn('created_at', 'timestamp', [
'null' => true,
'default' => 'CURRENT_TIMESTAMP',
])
->addColumn('updated_at', 'timestamp', [
'null' => true,
'default' => null,
'update' => 'CURRENT_TIMESTAMP',
])
->addIndex(['sample_date'], [
'unique' => true,
'name' => 'uq_lims_daily_samples_date',
])
->create();
}
}
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddTemplateNoteToExcelTemplates extends AbstractMigration
{
public function change(): void
{
$this->table('excel_templates')
->addColumn('template_note', 'text', [
'null' => true,
'default' => null,
'after' => 'idroutine',
'comment' => 'Nota opzionale del template, aggiunta in fondo alle note in esportazione',
])
->update();
}
}
@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class DropUniqPartAnalysisIndex extends AbstractMigration
{
public function up(): void
{
$table = $this->table('identification_parts_analyses');
if ($table->hasIndexByName('uniq_part_analysis')) {
$table->removeIndexByName('uniq_part_analysis')->update();
}
}
public function down(): void
{
$table = $this->table('identification_parts_analyses');
if (!$table->hasIndexByName('uniq_part_analysis')) {
$table->addIndex(['part_id', 'analysis_recordkey'], [
'unique' => true,
'name' => 'uniq_part_analysis',
])->update();
}
}
}
@@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class ChangeExcelrowToVarchar extends AbstractMigration
{
public function up(): void
{
$this->table('datadb')
->changeColumn('excelrow', 'string', [
'limit' => 100,
'null' => true,
])
->update();
}
public function down(): void
{
$this->table('datadb')
->changeColumn('excelrow', 'integer', [
'null' => true,
])
->update();
}
}
@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddPdfSupportToTemplates extends AbstractMigration
{
public function change(): void
{
// 1) PDF di esempio + metadati pagine sul template
$templates = $this->table('excel_templates');
if (!$templates->hasColumn('sample_pdf')) {
$templates->addColumn('sample_pdf', 'string', [
'limit' => 255,
'null' => true,
'default' => null,
'after' => 'sample_xlsx',
'comment' => 'Filename of the sample PDF used to draw regions',
]);
}
if (!$templates->hasColumn('pdf_page_meta')) {
$templates->addColumn('pdf_page_meta', 'text', [
'null' => true,
'default' => null,
'comment' => 'JSON: pages count and rendered dimensions per page',
]);
}
$templates->update();
// 2) Riquadri (etichetta + dato) per ogni campo mappato
$mapping = $this->table('template_mapping');
if (!$mapping->hasColumn('pdf_regions')) {
$mapping->addColumn('pdf_regions', 'text', [
'null' => true,
'default' => null,
'after' => 'json_node',
'comment' => 'JSON: {page, label:{x,y,w,h}, value:{x,y,w,h}}',
]);
}
$mapping->update();
}
}
@@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddPdfToSourceTypeEnum extends AbstractMigration
{
public function up(): void
{
$this->execute(
"ALTER TABLE `excel_templates`
MODIFY `source_type` ENUM('XLS','API','PDF') NOT NULL DEFAULT 'XLS'"
);
}
public function down(): void
{
$this->execute(
"ALTER TABLE `excel_templates`
MODIFY `source_type` ENUM('XLS','API') NOT NULL DEFAULT 'XLS'"
);
}
}
-27
View File
@@ -1,27 +0,0 @@
<?php
require_once __DIR__ . '/vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();
return [
'paths' => [
'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds',
],
'environments' => [
'default_migration_table' => 'phinxlog',
'default_environment' => 'production',
'production' => [
'adapter' => 'mysql',
'host' => $_ENV['DB_HOST'] ?? 'localhost',
'name' => $_ENV['DB_DATABASE'] ?? '',
'user' => $_ENV['DB_USERNAME'] ?? '',
'pass' => $_ENV['DB_PASSWORD'] ?? '',
'port' => $_ENV['DB_PORT'] ?? 3306,
'charset' => 'utf8mb4',
],
],
'version_order' => 'creation',
];
+39 -63
View File
@@ -7,7 +7,7 @@
function loadAnalysisMatrixNames() {
return $.ajax({
url: "search_matrici.php?full_list=1",
url: "get_matrici_db.php",
method: "GET",
dataType: "json",
})
@@ -106,9 +106,8 @@
}
container.innerHTML = items
.map(function (item, index) {
.map(function (item) {
const recordKey = item.analysis_recordkey || "";
const rowId = item.id != null ? item.id : "";
const title = item.analysis_name || "Unnamed analysis";
const method = item.analysis_method || "";
@@ -122,8 +121,6 @@
class="analysis-remove-btn"
data-part-id="${escapeHtml(partId)}"
data-recordkey="${escapeHtml(recordKey)}"
data-row-id="${escapeHtml(rowId)}"
data-index="${index}"
title="Remove analysis">×</button>
</div>
`;
@@ -169,21 +166,19 @@
};
}
function addAnalysisToLocalState(
partId,
payload,
iddatadb,
idmatrice,
newId,
) {
function addAnalysisToLocalState(partId, payload, iddatadb, idmatrice) {
const key = String(partId);
if (!Array.isArray(analysisAssignedState[key])) {
analysisAssignedState[key] = [];
}
{
const exists = analysisAssignedState[key].some(function (item) {
return item.analysis_recordkey === payload.analysis_recordkey;
});
if (!exists) {
analysisAssignedState[key].push({
id: newId || null,
id: null,
part_id: parseInt(partId, 10),
iddatadb: iddatadb || null,
idmatrice: idmatrice || null,
@@ -200,24 +195,17 @@
}
}
function removeAnalysisFromLocalState(partId, recordKey, index) {
function removeAnalysisFromLocalState(partId, recordKey) {
const key = String(partId);
if (!Array.isArray(analysisAssignedState[key])) {
return;
}
const i = parseInt(index, 10);
if (!isNaN(i) && i >= 0 && i < analysisAssignedState[key].length) {
analysisAssignedState[key].splice(i, 1);
return;
}
const found = analysisAssignedState[key].findIndex(function (item) {
return item.analysis_recordkey === recordKey;
});
if (found !== -1) {
analysisAssignedState[key].splice(found, 1);
}
analysisAssignedState[key] = analysisAssignedState[key].filter(
function (item) {
return item.analysis_recordkey !== recordKey;
},
);
}
function saveAnalysisAssociation(partId, payload, callback) {
@@ -249,13 +237,12 @@
is_accredited: payload.is_accredited,
},
})
.done(function (resp) {
.done(function () {
addAnalysisToLocalState(
partId,
payload,
iddatadb,
idmatrice !== "NO_MATRIX" ? idmatrice : null,
resp && resp.id ? resp.id : null,
);
renderAssignedAnalysesForPart(partId);
if (typeof callback === "function") callback(true);
@@ -270,13 +257,7 @@
});
}
function deleteAnalysisAssociation(
partId,
recordKey,
rowId,
index,
callback,
) {
function deleteAnalysisAssociation(partId, recordKey, callback) {
$.ajax({
url: "delete_part_analysis.php",
method: "POST",
@@ -284,11 +265,10 @@
data: {
part_id: partId,
analysis_recordkey: recordKey,
row_id: rowId || "",
},
})
.done(function () {
removeAnalysisFromLocalState(partId, recordKey, index);
removeAnalysisFromLocalState(partId, recordKey);
renderAssignedAnalysesForPart(partId);
if (typeof callback === "function") callback(true);
})
@@ -537,13 +517,6 @@
.done(function (response) {
const analyses = Array.isArray(response.value)
? response.value.filter(function (item) {
const name =
item.NomeAnalisiTraduzione ||
item.NomeAnalisi ||
"";
if (name.trim().startsWith("*")) {
return false;
}
return (
item.SelezionabileSuWeb === true ||
item.SelezionabileSuWeb === 1 ||
@@ -727,18 +700,10 @@
const partId = removeBtn.getAttribute("data-part-id");
const recordKey = removeBtn.getAttribute("data-recordkey");
const rowId = removeBtn.getAttribute("data-row-id");
const index = removeBtn.getAttribute("data-index");
deleteAnalysisAssociation(
partId,
recordKey,
rowId,
index,
function () {
const stillUsed = Object.keys(
analysisAssignedState,
).some(function (pid) {
deleteAnalysisAssociation(partId, recordKey, function () {
const stillUsed = Object.keys(analysisAssignedState).some(
function (pid) {
return (
Array.isArray(analysisAssignedState[pid]) &&
analysisAssignedState[pid].some(
@@ -750,15 +715,15 @@
},
)
);
});
},
);
if (!stillUsed) {
delete analysisSelectedState[recordKey];
}
if (!stillUsed) {
delete analysisSelectedState[recordKey];
}
syncSelectedAnalysisRows();
},
);
syncSelectedAnalysisRows();
});
return;
}
@@ -779,6 +744,17 @@
}
const recordKey = payload.analysis_recordkey;
const alreadySelected = !!analysisSelectedState[recordKey];
if (alreadySelected) {
selectedPartIds.forEach(function (partId) {
deleteAnalysisAssociation(partId, recordKey);
});
delete analysisSelectedState[recordKey];
syncSelectedAnalysisRows();
return;
}
let pending = selectedPartIds.length;
let atLeastOneSaved = false;
+2 -234
View File
@@ -19,9 +19,6 @@ $(document).ready(function () {
let descriptionTextbox = null;
let markerObjects = {};
let nextMarkerId = 1;
let freeTextMode = false;
let freeTextObjects = {};
let nextFreeTextId = 1;
let partsListData = [];
// DIMENSIONE GLOBALE MARKER
let globalMarkerSize = 16;
@@ -58,7 +55,7 @@ $(document).ready(function () {
.data("iddatadb", iddatadb || null)
.data("idquotations", idquotations || null);
// Parti (non legate al canvas) possono partire subito
loadPhoto(iddatadb, idquotations);
loadExistingParts(iddatadb, idquotations);
const modalElement = document.getElementById("annotationsModal");
@@ -77,18 +74,6 @@ $(document).ready(function () {
focus: true,
});
}
// IMPORTANTE: carica la foto SOLO dopo che il modale è completamente
// visibile (animazione fade terminata). Se l'immagine è già in cache
// del browser, l'evento "load" può scattare quasi subito, PRIMA che
// il contenitore del canvas abbia dimensioni reali (parent ancora
// display:none durante il fade) — questo rompeva il canvas Fabric.js
// per la prima foto mostrata (bug riprodotto solo con foto già in cache,
// tipico del server di test dove le foto erano già state visualizzate).
$(modalElement).one("shown.bs.modal", function () {
loadPhoto(iddatadb, idquotations);
});
modal.show();
// Inizializza slider dimensione marker
$("#markerSizeSlider").val(globalMarkerSize);
@@ -147,9 +132,6 @@ $(document).ready(function () {
selectedPartNumber = null;
unsavedChanges = false;
partsListData = [];
freeTextMode = false; // ← aggiungi
freeTextObjects = {}; // ← aggiungi
$("#addFreeTextBtnAnnotations").removeClass("active"); // ← aggiungi
if (fabricCanvas) {
fabricCanvas.off();
fabricCanvas.dispose();
@@ -198,29 +180,6 @@ $(document).ready(function () {
updateMarkers();
markUnsaved();
});
// ===================
// TESTO LIBERO — TOGGLE MODALITÀ
// ===================
$(document)
.off("click.freeTextToggle", "#addFreeTextBtnAnnotations")
.on("click.freeTextToggle", "#addFreeTextBtnAnnotations", function (e) {
e.preventDefault();
e.stopPropagation();
freeTextMode = !freeTextMode;
$(this).toggleClass("active", freeTextMode);
// Disattiva la selezione parte/marker mentre si aggiunge testo libero
if (freeTextMode) {
selectedPartNumber = null;
$("#partsListAnnotations .list-group-item").removeClass(
"active",
);
}
console.log("Free text mode:", freeTextMode);
});
// ===================
// COLORE LISTA DESCRIZIONI
// ===================
@@ -234,22 +193,9 @@ $(document).ready(function () {
if (descriptionTextbox && fabricCanvas) {
descriptionTextbox.set("fill", globalDescriptionColor);
fabricCanvas.renderAll();
}
// Aggiorna anche i testi liberi già piazzati sulla foto corrente
const currentPhoto = $("#samplePhotoAnnotations").attr("src");
const annotations = photoAnnotations[currentPhoto];
if (annotations && annotations.freeTexts) {
annotations.freeTexts.forEach((entry) => {
entry.color = globalDescriptionColor;
});
}
Object.values(freeTextObjects).forEach((obj) => {
obj.set("fill", globalDescriptionColor);
});
if (fabricCanvas) fabricCanvas.renderAll();
markUnsaved();
},
);
@@ -424,29 +370,7 @@ $(document).ready(function () {
console.log(
"Evento mouse:down su canvas, selectedPartNumber:",
selectedPartNumber,
"freeTextMode:",
freeTextMode,
);
// Modalità testo libero: ha priorità, un solo click e si disattiva da sola
if (freeTextMode) {
if (options.target) {
console.log(
"Click su un oggetto esistente, ignoro.",
);
return;
}
const pointer = fabricCanvas.getPointer(options.e);
const x = pointer.x / photoData.scale;
const y = pointer.y / photoData.scale;
addFreeText(x, y);
freeTextMode = false;
$("#addFreeTextBtnAnnotations").removeClass("active");
return;
}
if (selectedPartNumber === null) {
console.log(
"Nessuna parte selezionata, ignoro il click.",
@@ -501,7 +425,6 @@ $(document).ready(function () {
updateMarkers();
updateDescriptions();
updateFreeTexts();
}, 10);
});
}
@@ -1062,128 +985,7 @@ $(document).ready(function () {
},
});
}
// ===================
// TESTO LIBERO — CREAZIONE E RENDER
// ===================
function addFreeText(x, y) {
const currentPhoto = $("#samplePhotoAnnotations").attr("src");
if (!photoAnnotations[currentPhoto]) {
photoAnnotations[currentPhoto] = {
markers: [],
freeTexts: [],
hasDescriptions: false,
descriptionPosition: { x: 10, y: 10 },
descriptionSize: {
width: photoData.displayWidth * 0.3,
height: photoData.displayHeight * 0.3,
},
};
}
if (!photoAnnotations[currentPhoto].freeTexts) {
photoAnnotations[currentPhoto].freeTexts = [];
}
const entry = {
id: nextFreeTextId++,
x,
y,
text: "Testo",
color: globalDescriptionColor,
fontSize: Math.max(16, Math.round(globalMarkerSize * 0.8)),
};
photoAnnotations[currentPhoto].freeTexts.push(entry);
renderFreeText(entry, true);
markUnsaved();
}
function renderFreeText(entry, startEditing = false) {
const itext = new fabric.IText(entry.text, {
left: entry.x * photoData.scale,
top: entry.y * photoData.scale,
fontFamily: "Arial",
fontSize: entry.fontSize,
fill: entry.color,
selectable: true,
hasControls: true,
editable: true,
borderColor: "#333",
cornerColor: "#000",
cornerSize: 12,
transparentCorners: false,
cornerStyle: "rect",
borderScaleFactor: 2,
});
itext.freeTextId = entry.id;
// FIX: Fabric.js appende la <textarea> nascosta (usata per catturare la
// tastiera durante l'editing) a document.body, FUORI dal modale.
// Il focus trap di Bootstrap la considera "esterna" e ruba il focus
// ogni volta che Fabric lo assegna a quella textarea — così i tasti
// digitati non arrivano mai al testo. Spostiamo la textarea dentro
// il modale non appena l'editing viene avviato.
itext.on("editing:entered", function () {
const modalEl = document.getElementById("annotationsModal");
if (
this.hiddenTextarea &&
modalEl &&
this.hiddenTextarea.parentNode !== modalEl
) {
modalEl.appendChild(this.hiddenTextarea);
}
});
itext.on("moving", function () {
entry.x = this.left / photoData.scale;
entry.y = this.top / photoData.scale;
markUnsaved();
});
itext.on("editing:exited", function () {
entry.text = this.text;
markUnsaved();
});
itext.on("scaling", function () {
entry.fontSize = Math.round(this.fontSize * this.scaleY);
this.set({ fontSize: entry.fontSize, scaleX: 1, scaleY: 1 });
markUnsaved();
});
fabricCanvas.add(itext);
freeTextObjects[entry.id] = itext;
// IMPORTANTE: non attivare l'editing nello stesso evento mouse:down che
// ha creato l'oggetto. Fabric processa quel click come "click su canvas
// vuoto" (il target non esisteva ancora al momento del mousedown) e al
// successivo mouseup deseleziona tutto, interrompendo l'editing appena
// avviato — l'utente non riesce a scrivere né a spostare il testo.
// Rimandiamo l'attivazione al prossimo giro di event loop, così avviene
// DOPO che Fabric ha concluso la gestione nativa di quel click.
if (startEditing) {
setTimeout(() => {
fabricCanvas.setActiveObject(itext);
itext.enterEditing();
itext.selectAll();
fabricCanvas.renderAll();
}, 0);
}
fabricCanvas.renderAll();
}
function updateFreeTexts() {
for (let id in freeTextObjects) {
fabricCanvas.remove(freeTextObjects[id]);
delete freeTextObjects[id];
}
freeTextObjects = {};
const currentPhoto = $("#samplePhotoAnnotations").attr("src");
const annotations = photoAnnotations[currentPhoto];
if (!annotations || !annotations.freeTexts) return;
annotations.freeTexts.forEach((entry) => renderFreeText(entry, false));
}
// ===================
// MARKERS & DESCRIPTIONS
// ===================
@@ -1489,40 +1291,6 @@ $(document).ready(function () {
}
}
// ===================
// TESTO LIBERO — RIMOZIONE CON CANC/DELETE
// ===================
$(document).on("keydown.freeTextDelete", function (e) {
// Solo se il modale annotazioni è aperto
if (!$("#annotationsModal").hasClass("show")) return;
if (!fabricCanvas) return;
// Ignora se si sta scrivendo dentro un campo di editing testo
// (es. l'utente sta modificando il contenuto dell'IText stesso)
const active = fabricCanvas.getActiveObject();
if (active && active.isEditing) return;
if (e.key !== "Delete" && e.key !== "Backspace") return;
if (!active || active.freeTextId === undefined) return;
e.preventDefault();
const currentPhoto = $("#samplePhotoAnnotations").attr("src");
const annotations = photoAnnotations[currentPhoto];
if (annotations && annotations.freeTexts) {
annotations.freeTexts = annotations.freeTexts.filter(
(entry) => entry.id !== active.freeTextId,
);
}
fabricCanvas.remove(active);
delete freeTextObjects[active.freeTextId];
fabricCanvas.renderAll();
markUnsaved();
});
// Delegazione evento per il pulsante "Descrizioni"
$(document)
.off("click.addDescriptions", "#addDescriptionsBtnAnnotations")
@@ -1,152 +0,0 @@
<?php
/**
* debug_odata_metadata.php TEMPORANEO, da rimuovere dopo il test.
* Scopo: leggere $metadata (XML standard OData) per vedere:
* - tutti gli EntitySet disponibili (non solo "Cliente")
* - tutti i campi (Property) definiti per l'EntityType Cliente
* Questo bypassa qualunque vista/proiezione ristretta lato server
* applicata sull'endpoint "Cliente" normale.
*/
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
include dirname(__DIR__) . '/../extra/auth.php';
if (!Auth::check()) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
use Dotenv\Dotenv;
header('Content-Type: text/plain; charset=utf-8');
ini_set('display_errors', '1');
error_reporting(E_ALL);
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 2));
$dotenv->load();
$baseUrl = $_ENV['API_BASE_URL'];
$username = $_ENV['API_USERNAME'];
$password = $_ENV['API_PASSWORD'];
$scriptStart = microtime(true);
// ── 1. Autenticazione (stessa logica della classe originale) ──────────────
$ch = curl_init("{$baseUrl}/api/authentication/authenticate");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'Username' => $username,
'Password' => $password
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$authResponse = curl_exec($ch);
$authHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($authHttpCode !== 200) {
echo "AUTH FAILED: HTTP {$authHttpCode}\n{$authResponse}\n";
exit;
}
$tokenData = json_decode($authResponse, true);
$token = null;
if (is_array($tokenData) && isset($tokenData['token'])) {
$token = $tokenData['token'];
} elseif (is_string($tokenData)) {
$token = trim($tokenData, '"');
} else {
$token = trim($authResponse, '"');
}
if (empty($token)) {
echo "TOKEN NOT RECEIVED\n{$authResponse}\n";
exit;
}
$authElapsed = microtime(true) - $scriptStart;
// ── 2. Richiesta $metadata (XML, non JSON) ─────────────────────────────────
// Cache-buster nell'URL: alcuni server/reverse-proxy potrebbero cachare per URL esatto.
$cacheBuster = uniqid('cb_', true);
$metadataUrl = "{$baseUrl}/api/odata/\$metadata?_cb={$cacheBuster}";
$metadataStart = microtime(true);
$ch = curl_init($metadataUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
"Accept: application/xml"
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HEADER, true); // includi header risposta per ispezione
$rawResponse = curl_exec($ch);
$curlInfo = curl_getinfo($ch);
$httpCode = $curlInfo['http_code'];
$curlTotalTime = $curlInfo['total_time'];
$curlError = curl_error($ch);
$headerSize = $curlInfo['header_size'];
curl_close($ch);
$metadataElapsed = microtime(true) - $metadataStart;
if ($rawResponse === false || $httpCode !== 200) {
echo "METADATA REQUEST FAILED: HTTP {$httpCode}\nError: {$curlError}\n{$rawResponse}\n";
exit;
}
$responseHeaders = substr($rawResponse, 0, $headerSize);
$xml = substr($rawResponse, $headerSize);
echo "==================== TIMING (prova che NON è cache) ====================\n";
echo "Tempo autenticazione: " . round($authElapsed * 1000, 1) . " ms\n";
echo "Tempo chiamata \$metadata (curl total_time): " . round($curlTotalTime * 1000, 1) . " ms\n";
echo "Tempo chiamata \$metadata (wall clock PHP): " . round($metadataElapsed * 1000, 1) . " ms\n";
echo "Cache-buster usato: {$cacheBuster}\n";
echo "Dimensione XML ricevuto: " . strlen($xml) . " bytes\n\n";
echo "==================== HEADER RISPOSTA (controlla se c'è Cache-Control/Age/X-Cache) ====================\n";
echo $responseHeaders . "\n";
// ── 3. Estrai tutti gli EntitySet disponibili ──────────────────────────────
echo "==================== ENTITY SETS DISPONIBILI ====================\n";
if (preg_match_all('/<EntitySet Name="([^"]+)"/', $xml, $matches)) {
foreach ($matches[1] as $name) {
echo "- {$name}\n";
}
} else {
echo "(nessun EntitySet trovato — controlla il formato XML grezzo sotto)\n";
}
// ── 4. Estrai tutte le Property dell'EntityType "Cliente" (e simili) ──────
echo "\n==================== CAMPI PER ENTITYTYPE CHE CONTENGONO 'Client' ====================\n";
if (preg_match_all('/<EntityType Name="([^"]*[Cc]lient[^"]*)"[^>]*>(.*?)<\/EntityType>/s', $xml, $entityMatches, PREG_SET_ORDER)) {
foreach ($entityMatches as $entity) {
$entityName = $entity[1];
$entityBody = $entity[2];
echo "\n--- EntityType: {$entityName} ---\n";
if (preg_match_all('/<Property Name="([^"]+)" Type="([^"]+)"/', $entityBody, $propMatches, PREG_SET_ORDER)) {
foreach ($propMatches as $prop) {
echo " {$prop[1]} ({$prop[2]})\n";
}
}
}
} else {
echo "(nessun EntityType con 'Client' nel nome trovato)\n";
}
// ── 5. Salva l'XML grezzo completo per ispezione manuale, se serve ────────
file_put_contents(__DIR__ . '/metadata_debug.xml', $xml);
echo "\n\nXML completo salvato in: metadata_debug.xml (rimuovilo dopo il test)\n";
+1 -68
View File
@@ -10,11 +10,6 @@ $data = json_decode(file_get_contents('php://input'), true);
$sourceIddatadb = isset($data['source_iddatadb']) ? (int)$data['source_iddatadb'] : 0;
$targetList = $data['target_iddatadb_list'] ?? [];
// Default true: se il client non manda il parametro, cloniamo comunque le note
// (comportamento di default richiesto, coerente con la checkbox pre-spuntata)
$cloneNotes = array_key_exists('clone_notes', $data) ? !empty($data['clone_notes']) : true;
$cloneAnalyses = !empty($data['clone_analyses']);
$overwrite = !empty($data['overwrite']);
$targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) {
return $v > 0 && $v !== $sourceIddatadb;
@@ -67,23 +62,6 @@ try {
ORDER BY id ASC
");
$stmtLoadAnalyses = $pdo->prepare("
SELECT idmatrice, analysis_recordkey, analysis_name, analysis_method,
analysis_level, is_web_selectable, is_accredited
FROM identification_parts_analyses
WHERE part_id = ?
ORDER BY id ASC
");
$stmtInsertAnalysis = $pdo->prepare("
INSERT INTO identification_parts_analyses
(part_id, iddatadb, idmatrice, analysis_recordkey, analysis_name, analysis_method,
analysis_level, is_web_selectable, is_accredited, created_at, updated_at)
VALUES
(:part_id, :iddatadb, :idmatrice, :analysis_recordkey, :analysis_name, :analysis_method,
:analysis_level, :is_web_selectable, :is_accredited, NOW(), NOW())
");
$stmtInsertCF = $pdo->prepare("
INSERT INTO identification_parts_customfields
(part_id, field_id, value_id, value_text, created_at, updated_at)
@@ -93,29 +71,6 @@ try {
$details = [];
$totalClonedParts = 0;
$totalClonedAnalyses = 0;
$totalDeletedParts = 0;
// 2b. Se overwrite: elimina le parti esistenti dei target (e i loro figli)
if ($overwrite) {
$stmtSelectExisting = $pdo->prepare("SELECT id FROM identification_parts WHERE iddatadb = ?");
$stmtDeleteCFByPart = $pdo->prepare("DELETE FROM identification_parts_customfields WHERE part_id = ?");
$stmtDeleteAnByPart = $pdo->prepare("DELETE FROM identification_parts_analyses WHERE part_id = ?");
$stmtDeletePart = $pdo->prepare("DELETE FROM identification_parts WHERE id = ?");
foreach ($targetIds as $targetIddatadb) {
$stmtSelectExisting->execute([$targetIddatadb]);
$existingIds = $stmtSelectExisting->fetchAll(PDO::FETCH_COLUMN);
foreach ($existingIds as $existingPartId) {
$existingPartId = (int)$existingPartId;
$stmtDeleteCFByPart->execute([$existingPartId]);
$stmtDeleteAnByPart->execute([$existingPartId]);
$stmtDeletePart->execute([$existingPartId]);
$totalDeletedParts++;
}
}
}
// 3. Clone source parts to each target record
foreach ($targetIds as $targetIddatadb) {
@@ -128,7 +83,7 @@ try {
':part_description' => $part['part_description'],
':mix' => $part['mix'] ?? 'N',
':idmatrice' => $part['idmatrice'] !== '' ? $part['idmatrice'] : null,
':note' => $cloneNotes ? ($part['note'] ?? null) : null,
':note' => $part['note'] ?? null,
':dateexpiry' => $part['dateexpiry'] ?? null,
]);
@@ -147,27 +102,6 @@ try {
]);
}
// Clone linked analyses, if requested
if ($cloneAnalyses) {
$stmtLoadAnalyses->execute([(int)$part['id']]);
$analyses = $stmtLoadAnalyses->fetchAll(PDO::FETCH_ASSOC);
foreach ($analyses as $an) {
$stmtInsertAnalysis->execute([
':part_id' => $newPartId,
':iddatadb' => $targetIddatadb,
':idmatrice' => ($an['idmatrice'] !== null && $an['idmatrice'] !== '') ? (int)$an['idmatrice'] : null,
':analysis_recordkey' => $an['analysis_recordkey'],
':analysis_name' => $an['analysis_name'] ?? null,
':analysis_method' => $an['analysis_method'] ?? null,
':analysis_level' => ($an['analysis_level'] !== null && $an['analysis_level'] !== '') ? (int)$an['analysis_level'] : null,
':is_web_selectable' => (int)($an['is_web_selectable'] ?? 0),
':is_accredited' => (int)($an['is_accredited'] ?? 0),
]);
$totalClonedAnalyses++;
}
}
$clonedCountForTarget++;
$totalClonedParts++;
}
@@ -185,7 +119,6 @@ try {
'source_iddatadb' => $sourceIddatadb,
'cloned_targets' => count($targetIds),
'total_cloned_parts' => $totalClonedParts,
'total_cloned_analyses' => $totalClonedAnalyses,
'details' => $details
]);
} catch (Throwable $e) {
+22 -18
View File
@@ -23,17 +23,19 @@ if ((int)$stmt->fetchColumn() > 0) {
}
/**
* FIXED FIELDS STANDARD
* FIXED FIELDS STANDARD (no UI selection)
* is_manual always 1
* is_visible_import default 1
*/
$fixedFields = [
['ClienteResponsabile', 'INT'],
['ClienteFornitore', 'INT'],
['ClienteAnalisi', 'INT'],
['ClienteFatturazione', 'INT'], // ← NUOVO
['MoltiplicatorePrezzo', 'INT'],
['AnagraficaCertestObject', 'INT'],
// fixed_field_key, data_type
['ClienteResponsabile', 'INT'],
['ClienteFornitore', 'INT'],
['ClienteAnalisi', 'INT'],
['MoltiplicatorePrezzo', 'INT'],
['AnagraficaCertestObject', 'INT'],
['AnagraficaCertestService', 'INT'],
['ConsegnaRichiesta', 'DATE'],
['ConsegnaRichiesta', 'DATE'],
];
@@ -41,11 +43,12 @@ try {
$pdo->beginTransaction();
$ins = $pdo->prepare("
INSERT INTO template_fixed_mapping
(template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import)
VALUES
(:template_id, :fixed_field_key, 1, :data_type, 1, NULL, 1)
");
INSERT INTO template_fixed_mapping
(template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import)
VALUES
(:template_id, :fixed_field_key, 1, :data_type, 1, NULL, 1)
");
foreach ($fixedFields as $f) {
$ins->execute([
@@ -55,15 +58,16 @@ try {
]);
}
$pdo->commit();
// Return rows (for client render)
$stmt = $pdo->prepare("
SELECT id, template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
FROM template_fixed_mapping
WHERE template_id = ?
ORDER BY id ASC
");
SELECT id, template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
FROM template_fixed_mapping
WHERE template_id = ?
ORDER BY id ASC
");
$stmt->execute([$templateId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-152
View File
@@ -1,152 +0,0 @@
<?php
/**
* debug_odata_metadata.php TEMPORANEO, da rimuovere dopo il test.
* Scopo: leggere $metadata (XML standard OData) per vedere:
* - tutti gli EntitySet disponibili (non solo "Cliente")
* - tutti i campi (Property) definiti per l'EntityType Cliente
* Questo bypassa qualunque vista/proiezione ristretta lato server
* applicata sull'endpoint "Cliente" normale.
*/
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
include dirname(__DIR__) . '/../extra/auth.php';
if (!Auth::check()) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
use Dotenv\Dotenv;
header('Content-Type: text/plain; charset=utf-8');
ini_set('display_errors', '1');
error_reporting(E_ALL);
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 2));
$dotenv->load();
$baseUrl = $_ENV['API_BASE_URL'];
$username = $_ENV['API_USERNAME'];
$password = $_ENV['API_PASSWORD'];
$scriptStart = microtime(true);
// ── 1. Autenticazione (stessa logica della classe originale) ──────────────
$ch = curl_init("{$baseUrl}/api/authentication/authenticate");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'Username' => $username,
'Password' => $password
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$authResponse = curl_exec($ch);
$authHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($authHttpCode !== 200) {
echo "AUTH FAILED: HTTP {$authHttpCode}\n{$authResponse}\n";
exit;
}
$tokenData = json_decode($authResponse, true);
$token = null;
if (is_array($tokenData) && isset($tokenData['token'])) {
$token = $tokenData['token'];
} elseif (is_string($tokenData)) {
$token = trim($tokenData, '"');
} else {
$token = trim($authResponse, '"');
}
if (empty($token)) {
echo "TOKEN NOT RECEIVED\n{$authResponse}\n";
exit;
}
$authElapsed = microtime(true) - $scriptStart;
// ── 2. Richiesta $metadata (XML, non JSON) ─────────────────────────────────
// Cache-buster nell'URL: alcuni server/reverse-proxy potrebbero cachare per URL esatto.
$cacheBuster = uniqid('cb_', true);
$metadataUrl = "{$baseUrl}/api/odata/\$metadata?_cb={$cacheBuster}";
$metadataStart = microtime(true);
$ch = curl_init($metadataUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
"Accept: application/xml"
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HEADER, true); // includi header risposta per ispezione
$rawResponse = curl_exec($ch);
$curlInfo = curl_getinfo($ch);
$httpCode = $curlInfo['http_code'];
$curlTotalTime = $curlInfo['total_time'];
$curlError = curl_error($ch);
$headerSize = $curlInfo['header_size'];
curl_close($ch);
$metadataElapsed = microtime(true) - $metadataStart;
if ($rawResponse === false || $httpCode !== 200) {
echo "METADATA REQUEST FAILED: HTTP {$httpCode}\nError: {$curlError}\n{$rawResponse}\n";
exit;
}
$responseHeaders = substr($rawResponse, 0, $headerSize);
$xml = substr($rawResponse, $headerSize);
echo "==================== TIMING (prova che NON è cache) ====================\n";
echo "Tempo autenticazione: " . round($authElapsed * 1000, 1) . " ms\n";
echo "Tempo chiamata \$metadata (curl total_time): " . round($curlTotalTime * 1000, 1) . " ms\n";
echo "Tempo chiamata \$metadata (wall clock PHP): " . round($metadataElapsed * 1000, 1) . " ms\n";
echo "Cache-buster usato: {$cacheBuster}\n";
echo "Dimensione XML ricevuto: " . strlen($xml) . " bytes\n\n";
echo "==================== HEADER RISPOSTA (controlla se c'è Cache-Control/Age/X-Cache) ====================\n";
echo $responseHeaders . "\n";
// ── 3. Estrai tutti gli EntitySet disponibili ──────────────────────────────
echo "==================== ENTITY SETS DISPONIBILI ====================\n";
if (preg_match_all('/<EntitySet Name="([^"]+)"/', $xml, $matches)) {
foreach ($matches[1] as $name) {
echo "- {$name}\n";
}
} else {
echo "(nessun EntitySet trovato — controlla il formato XML grezzo sotto)\n";
}
// ── 4. Estrai tutte le Property dell'EntityType "Cliente" (e simili) ──────
echo "\n==================== CAMPI PER ENTITYTYPE CHE CONTENGONO 'Client' ====================\n";
if (preg_match_all('/<EntityType Name="([^"]*[Cc]lient[^"]*)"[^>]*>(.*?)<\/EntityType>/s', $xml, $entityMatches, PREG_SET_ORDER)) {
foreach ($entityMatches as $entity) {
$entityName = $entity[1];
$entityBody = $entity[2];
echo "\n--- EntityType: {$entityName} ---\n";
if (preg_match_all('/<Property Name="([^"]+)" Type="([^"]+)"/', $entityBody, $propMatches, PREG_SET_ORDER)) {
foreach ($propMatches as $prop) {
echo " {$prop[1]} ({$prop[2]})\n";
}
}
}
} else {
echo "(nessun EntityType con 'Client' nel nome trovato)\n";
}
// ── 5. Salva l'XML grezzo completo per ispezione manuale, se serve ────────
file_put_contents(__DIR__ . '/metadata_debug.xml', $xml);
echo "\n\nXML completo salvato in: metadata_debug.xml (rimuovilo dopo il test)\n";
+10 -25
View File
@@ -15,9 +15,8 @@ try {
$partId = isset($_POST['part_id']) ? (int)$_POST['part_id'] : 0;
$analysisRecordkey = trim($_POST['analysis_recordkey'] ?? '');
$rowId = isset($_POST['row_id']) && $_POST['row_id'] !== '' ? (int)$_POST['row_id'] : 0;
if ($partId <= 0 || ($analysisRecordkey === '' && $rowId <= 0)) {
if ($partId <= 0 || $analysisRecordkey === '') {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Missing required data']);
exit;
@@ -26,29 +25,15 @@ try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
if ($rowId > 0) {
$stmt = $pdo->prepare("
DELETE FROM identification_parts_analyses
WHERE id = :id
AND part_id = :part_id
LIMIT 1
");
$stmt->execute([
':id' => $rowId,
':part_id' => $partId,
]);
} else {
$stmt = $pdo->prepare("
DELETE FROM identification_parts_analyses
WHERE part_id = :part_id
AND analysis_recordkey = :analysis_recordkey
LIMIT 1
");
$stmt->execute([
':part_id' => $partId,
':analysis_recordkey' => $analysisRecordkey,
]);
}
$stmt = $pdo->prepare("
DELETE FROM identification_parts_analyses
WHERE part_id = :part_id
AND analysis_recordkey = :analysis_recordkey
");
$stmt->execute([
':part_id' => $partId,
':analysis_recordkey' => $analysisRecordkey,
]);
echo json_encode([
'success' => true,
+40 -5
View File
@@ -207,6 +207,21 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
<input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
</div>
<div class="mb-3" id="xlsEndColumnWrapper">
<label class="form-label">Last Column included</label>
<input
type="text"
name="xls_end_column"
id="xlsEndColumn"
class="form-control"
value="<?php echo htmlspecialchars($template['xls_end_column'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
placeholder="Example: AN">
<small class="text-danger fw-semibold">
Attention: if left empty, the system will read the entire XLS/XLSX sheet.
Dirty Excel files may cause memory errors or timeout.
</small>
</div>
<div class="mb-3" id="xlsSheetNumberWrapper">
<label class="form-label">XLS Sheet Number</label>
<input
@@ -265,11 +280,7 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
<textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description'] ?? ''); ?></textarea>
</div>
<div class="mb-3">
<label class="form-label">Template Note</label>
<textarea name="template_note" class="form-control" rows="3"><?php echo htmlspecialchars($template['template_note'] ?? ''); ?></textarea>
<small class="text-muted">If set, this note will be appended at the end of the notes during export.</small>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table'] ?? 'datadb'); ?>" readonly required>
@@ -408,11 +419,13 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
const headerRowWrapper = document.getElementById("headerRowWrapper");
const startColumnWrapper = document.getElementById("startColumnWrapper");
const xlsEndColumnWrapper = document.getElementById("xlsEndColumnWrapper");
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
const headerRow = document.getElementById("headerRow");
const startColumn = document.getElementById("startColumn");
const xlsEndColumn = document.getElementById("xlsEndColumn");
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
const apiConfigSelect = document.getElementById("apiConfigSelect");
@@ -448,13 +461,16 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
if (isXls) {
headerRowWrapper.style.display = 'block';
startColumnWrapper.style.display = 'block';
xlsEndColumnWrapper.style.display = 'block';
xlsSheetNumberWrapper.style.display = 'block';
headerRow.required = true;
startColumn.required = true;
xlsEndColumn.required = false;
headerRow.disabled = false;
startColumn.disabled = false;
xlsEndColumn.disabled = false;
xlsSheetIndex.disabled = false;
apiConfigWrapper.style.display = 'none';
@@ -464,13 +480,16 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
} else {
headerRowWrapper.style.display = 'none';
startColumnWrapper.style.display = 'none';
xlsEndColumnWrapper.style.display = 'none';
xlsSheetNumberWrapper.style.display = 'none';
headerRow.required = false;
startColumn.required = false;
xlsEndColumn.required = false;
headerRow.disabled = true;
startColumn.disabled = true;
xlsEndColumn.disabled = true;
xlsSheetIndex.disabled = true;
if (isApiOrJson) {
@@ -730,6 +749,22 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
return;
}
if (selectedSource === 'XLS' && xlsEndColumn.value.trim() !== '') {
const lastColumnValue = xlsEndColumn.value.trim().toUpperCase();
if (!/^[A-Z]+$|^[1-9][0-9]*$/.test(lastColumnValue)) {
Swal.fire({
title: "Error!",
text: "Last Column must be an Excel column like AN or a positive number like 40.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
xlsEndColumn.value = lastColumnValue;
}
fetch("process_edit_template_xls.php", {
method: "POST",
body: formData
-11
View File
@@ -363,14 +363,3 @@
2026-06-16 17:14:25 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21065 ms: Couldn't connect to server, Risposta:
2026-06-16 17:14:46 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21071 ms: Couldn't connect to server, Risposta:
2026-06-16 17:14:46 - Autenticazione fallita: HTTP 0, Errore cURL: Failed to connect to bvcpsitaly-elims.com port 443 after 21078 ms: Couldn't connect to server, Risposta:
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:33 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-09 08:06:41 - Autenticazione fallita: HTTP 0, Errore cURL: Could not resolve host: bvcpsitaly-elims.com, Risposta:
2026-07-10 08:12:24 - Errore nella richiesta: SSL connection timeout
+116 -301
View File
@@ -5,7 +5,7 @@
* Single export + batch export (Export All) with validation.
*/
(function () {
"use strict";
'use strict';
let pendingConfirmHandler = null;
let batchRunning = false;
@@ -17,7 +17,7 @@
// ── Helpers ──────────────────────────────────────────────────────────
function cleanupBackdrop() {
document.querySelectorAll(".modal-backdrop").forEach((b) => b.remove());
document.querySelectorAll(".modal-backdrop").forEach(b => b.remove());
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
const overlay = document.querySelector(".overlay.toggle-icon");
@@ -29,9 +29,7 @@
}
function getRowIndexByIddatadb(iddatadb) {
return (window.gridData || []).findIndex(
(r) => String(r.iddatadb) === String(iddatadb),
);
return (window.gridData || []).findIndex(r => String(r.iddatadb) === String(iddatadb));
}
// ── Validation ──────────────────────────────────────────────────────
@@ -42,31 +40,21 @@
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rows: rowsToValidate }),
});
if (!response.ok)
throw new Error(`Validation HTTP error: ${response.status}`);
if (!response.ok) throw new Error(`Validation HTTP error: ${response.status}`);
return response.json();
}
function clearValidationErrors() {
// Clear from gridData
(window.gridData || []).forEach((row) => {
delete row._validationErrors;
delete row._exportError;
});
(window.gridData || []).forEach(row => { delete row._validationErrors; delete row._exportError; });
document
.querySelectorAll(".grid-cell.validation-error")
.forEach((cell) => {
cell.classList.remove("validation-error");
cell.querySelectorAll(".input-validation-error").forEach((el) =>
el.classList.remove("input-validation-error"),
);
const tooltip = cell.querySelector(".validation-tooltip");
if (tooltip) tooltip.remove();
});
document
.querySelectorAll(".grid-row.validation-row-error")
.forEach((row) => row.classList.remove("validation-row-error"));
document.querySelectorAll(".grid-cell.validation-error").forEach(cell => {
cell.classList.remove("validation-error");
cell.querySelectorAll(".input-validation-error").forEach(el => el.classList.remove("input-validation-error"));
const tooltip = cell.querySelector(".validation-tooltip");
if (tooltip) tooltip.remove();
});
document.querySelectorAll(".grid-row.validation-row-error").forEach(row => row.classList.remove("validation-row-error"));
clearAllRowErrors();
}
@@ -79,7 +67,7 @@
gridRow.classList.add("validation-row-error");
const messages = [];
errors.forEach((err) => {
errors.forEach(err => {
messages.push(err.message);
if (!err.field) return;
@@ -88,26 +76,19 @@
const label = err.field.substring("field_label:".length);
const headers = document.querySelectorAll(".grid-header");
let targetIndex = null;
headers.forEach((h) => {
if (h.textContent.trim() === label)
targetIndex = h.getAttribute("data-index");
headers.forEach(h => {
if (h.textContent.trim() === label) targetIndex = h.getAttribute("data-index");
});
if (targetIndex) {
cell = gridRow.querySelector(
`.grid-cell[data-index="${targetIndex}"]`,
);
cell = gridRow.querySelector(`.grid-cell[data-index="${targetIndex}"]`);
}
} else {
cell = gridRow.querySelector(
`.grid-cell[data-col="${err.field}"]`,
);
cell = gridRow.querySelector(`.grid-cell[data-col="${err.field}"]`);
}
if (cell) {
cell.classList.add("validation-error");
cell.querySelectorAll("input, select").forEach((el) =>
el.classList.add("input-validation-error"),
);
cell.querySelectorAll("input, select").forEach(el => el.classList.add("input-validation-error"));
let tooltip = cell.querySelector(".validation-tooltip");
if (!tooltip) {
tooltip = document.createElement("div");
@@ -128,43 +109,34 @@
formData.append("iddatadb", iddatadb);
if (batchUuid) formData.append("batch_uuid", batchUuid);
const response = await fetch("export_to_lims.php", {
method: "POST",
body: formData,
});
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
const response = await fetch("export_to_lims.php", { method: "POST", body: formData });
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
if (data.success) {
// Update gridData
const idx = getRowIndexByIddatadb(iddatadb);
if (idx >= 0) {
window.gridData[idx].status = "l";
window.gridData[idx].status = 'l';
window.gridData[idx].commessaweb = data.commessaweb;
}
// Update visible DOM row
const gridRow = getGridRow(iddatadb);
if (gridRow) {
const statusBadge = gridRow.querySelector(
'.grid-cell[data-col="status"] .status-badge',
);
const statusBadge = gridRow.querySelector('.grid-cell[data-col="status"] .status-badge');
if (statusBadge) {
statusBadge.classList.remove("status-i", "status-P");
statusBadge.classList.add("status-l");
statusBadge.textContent = "To LIMS";
}
const statusCell = gridRow.querySelector(
'.grid-cell[data-col="status"]',
);
const statusCell = gridRow.querySelector('.grid-cell[data-col="status"]');
if (statusCell && data.commessaweb) {
let cwSpan = statusCell.querySelector(".commessaweb-code");
if (!cwSpan) {
cwSpan = document.createElement("span");
cwSpan.className = "commessaweb-code";
cwSpan.style.cssText =
"display:block; font-size:0.75em; color:#555; margin-top:2px;";
cwSpan.style.cssText = "display:block; font-size:0.75em; color:#555; margin-top:2px;";
statusCell.appendChild(cwSpan);
}
cwSpan.textContent = data.commessaweb;
@@ -192,8 +164,7 @@
const label = document.getElementById("exportResponseModalLabel");
if (data.success) {
msg.innerHTML =
`${data.message.replace(/\n/g, "<br>")}` +
msg.innerHTML = `${data.message.replace(/\n/g, "<br>")}` +
`<br>ID CommessaWeb: ${data.idcommessaweb}` +
`<br>Codice CommessaWeb: ${data.commessaweb}` +
(data.totalPhotos > 0 ? `<br>Foto: ${data.totalPhotos}` : "");
@@ -215,14 +186,10 @@
row.classList.remove("batch-disabled");
row.classList.add("batch-exporting");
if (btnCell) {
btnCell.querySelectorAll(".action-btn").forEach((b) => {
b.dataset.prevDisplay = b.style.display;
b.style.display = "none";
});
btnCell.querySelectorAll(".action-btn").forEach(b => { b.dataset.prevDisplay = b.style.display; b.style.display = "none"; });
const spinner = document.createElement("span");
spinner.className = "batch-row-spinner";
spinner.innerHTML =
'<i class="fas fa-spinner fa-spin"></i> Exporting...';
spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Exporting...';
btnCell.appendChild(spinner);
}
} else {
@@ -230,10 +197,7 @@
if (btnCell) {
const spinner = btnCell.querySelector(".batch-row-spinner");
if (spinner) spinner.remove();
btnCell.querySelectorAll(".action-btn").forEach((b) => {
b.style.display = b.dataset.prevDisplay || "";
delete b.dataset.prevDisplay;
});
btnCell.querySelectorAll(".action-btn").forEach(b => { b.style.display = b.dataset.prevDisplay || ""; delete b.dataset.prevDisplay; });
}
}
}
@@ -249,56 +213,32 @@
errorEl.className = "batch-error-msg";
errorEl.textContent = "Warning — click for details";
errorEl.addEventListener("click", () => {
document.getElementById("exportResponseMessage").innerHTML =
message.replace(/\n/g, "<br>");
document.getElementById(
"exportResponseModalLabel",
).textContent = "Error (id: " + iddatadb + ")";
new bootstrap.Modal(
document.getElementById("exportResponseModal"),
{ keyboard: false },
).show();
document.getElementById("exportResponseMessage").innerHTML = message.replace(/\n/g, "<br>");
document.getElementById("exportResponseModalLabel").textContent = "Error (id: " + iddatadb + ")";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
});
btnCell.appendChild(errorEl);
}
}
function clearAllRowErrors() {
document
.querySelectorAll(".grid-row.batch-row-error")
.forEach((row) => {
row.classList.remove("batch-row-error");
const msg = row.querySelector(".batch-error-msg");
if (msg) msg.remove();
});
document.querySelectorAll(".grid-row.batch-row-error").forEach(row => {
row.classList.remove("batch-row-error");
const msg = row.querySelector(".batch-error-msg");
if (msg) msg.remove();
});
}
function disableAllRowButtons() {
document
.querySelectorAll(".grid-row[data-id]")
.forEach((row) => row.classList.add("batch-disabled"));
const toggle = document.querySelector(
".actions-dropdown .dropdown-toggle",
);
if (toggle) {
toggle.disabled = true;
toggle.style.opacity = "0.5";
toggle.style.pointerEvents = "none";
}
document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.add("batch-disabled"));
const toggle = document.querySelector(".actions-dropdown .dropdown-toggle");
if (toggle) { toggle.disabled = true; toggle.style.opacity = "0.5"; toggle.style.pointerEvents = "none"; }
}
function enableAllRowButtons() {
document
.querySelectorAll(".grid-row[data-id]")
.forEach((row) => row.classList.remove("batch-disabled"));
const toggle = document.querySelector(
".actions-dropdown .dropdown-toggle",
);
if (toggle) {
toggle.disabled = false;
toggle.style.opacity = "";
toggle.style.pointerEvents = "";
}
document.querySelectorAll(".grid-row[data-id]").forEach(row => row.classList.remove("batch-disabled"));
const toggle = document.querySelector(".actions-dropdown .dropdown-toggle");
if (toggle) { toggle.disabled = false; toggle.style.opacity = ""; toggle.style.pointerEvents = ""; }
}
// ── Single row export: validate → confirm → send ────────────────────
@@ -310,61 +250,40 @@
if (gridRow) {
setRowExporting(gridRow, true);
const spinner = gridRow.querySelector(".batch-row-spinner");
if (spinner)
spinner.innerHTML =
'<i class="fas fa-spinner fa-spin"></i> Validating...';
if (spinner) spinner.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Validating...';
}
validateRows([{ iddatadb: parseInt(iddatadb), index: rowIndex }])
.then((validationData) => {
if (gridRow) {
setRowExporting(gridRow, false);
gridRow.classList.remove("batch-disabled");
}
.then(validationData => {
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
if (!validationData.success) {
showExportResult({
success: false,
message: validationData.message || "Validation error",
});
showExportResult({ success: false, message: validationData.message || "Validation error" });
return;
}
const result = validationData.results[rowIndex];
if (result && !result.valid) {
if (gridRow)
showValidationErrors(gridRow, iddatadb, result.errors);
if (gridRow) showValidationErrors(gridRow, iddatadb, result.errors);
return;
}
showConfirmAndExport(iddatadb, rowIndex);
})
.catch((error) => {
if (gridRow) {
setRowExporting(gridRow, false);
gridRow.classList.remove("batch-disabled");
}
showExportResult({
success: false,
message: "Validation error: " + error.message,
});
.catch(error => {
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
showExportResult({ success: false, message: "Validation error: " + error.message });
});
}
function showConfirmAndExport(iddatadb, rowIndex) {
const confirmModalElement =
document.getElementById("exportConfirmModal");
const confirmModalElement = document.getElementById("exportConfirmModal");
if (!confirmModalElement) return;
const confirmModal = new bootstrap.Modal(confirmModalElement, {
keyboard: false,
});
const confirmModal = new bootstrap.Modal(confirmModalElement, { keyboard: false });
document.getElementById("exportIddatadb").textContent = iddatadb;
confirmModal.show();
const confirmBtn = document.getElementById("exportConfirmBtn");
if (!confirmBtn) {
confirmModal.hide();
return;
}
if (!confirmBtn) { confirmModal.hide(); return; }
const confirmHandler = async () => {
pendingConfirmHandler = null;
@@ -375,36 +294,24 @@
try {
const data = await sendExport(iddatadb);
if (gridRow) {
setRowExporting(gridRow, false);
gridRow.classList.remove("batch-disabled");
}
if (!data.success)
showRowError(
gridRow,
iddatadb,
data.message || "Unknown error",
);
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
if (!data.success) showRowError(gridRow, iddatadb, data.message || "Unknown error");
showExportResult(data);
} catch (error) {
if (gridRow) {
setRowExporting(gridRow, false);
gridRow.classList.remove("batch-disabled");
}
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
showRowError(gridRow, iddatadb, error.message);
showExportResult({ success: false, message: error.message });
}
};
if (pendingConfirmHandler)
confirmBtn.removeEventListener("click", pendingConfirmHandler);
if (pendingConfirmHandler) confirmBtn.removeEventListener("click", pendingConfirmHandler);
pendingConfirmHandler = confirmHandler;
confirmBtn.addEventListener("click", confirmHandler, { once: true });
}
// ── Single row click (event delegation) ─────────────────────────────
$(document).on("click", ".export-lims-btn", function (e) {
$(document).on('click', '.export-lims-btn', function (e) {
e.preventDefault();
if (batchRunning) return;
@@ -415,34 +322,24 @@
// Check unsaved changes for this row
const dataRow = window.gridData?.[rowIndex];
if (dataRow && dataRow._dirty) {
const unsavedModal = new bootstrap.Modal(
document.getElementById("exportUnsavedModal"),
{ keyboard: false },
);
const unsavedModal = new bootstrap.Modal(document.getElementById("exportUnsavedModal"), { keyboard: false });
unsavedModal.show();
document.getElementById("saveAndExportBtn")?.addEventListener(
"click",
() => {
unsavedModal.hide();
// Save first, then export
const formData = window.buildSavePayload(rowIndex);
fetch("save_edited_row.php", {
method: "POST",
body: formData,
})
.then((r) => r.json())
.then((result) => {
if (result.success) {
dataRow._dirty = false;
startExportConfirmFlow(iddatadb, rowIndex);
} else {
alert("Save failed: " + result.message);
}
});
},
{ once: true },
);
document.getElementById("saveAndExportBtn")?.addEventListener("click", () => {
unsavedModal.hide();
// Save first, then export
const formData = window.buildSavePayload(rowIndex);
fetch('save_edited_row.php', { method: 'POST', body: formData })
.then(r => r.json())
.then(result => {
if (result.success) {
dataRow._dirty = false;
startExportConfirmFlow(iddatadb, rowIndex);
} else {
alert('Save failed: ' + result.message);
}
});
}, { once: true });
return;
}
@@ -452,33 +349,18 @@
// ── Batch export (Export All) ───────────────────────────────────────
function collectEligibleRows() {
// Read from gridData, not DOM. Se i filtri sono attivi, limita al target set.
const target =
typeof window.getTargetGridIds === "function"
? window.getTargetGridIds()
: null;
// Read from gridData, not DOM
const eligible = [];
(window.gridData || []).forEach((row, index) => {
if (row.status === "l") return;
if (target && !target.has(parseInt(row.iddatadb, 10))) return;
eligible.push({
iddatadb: row.iddatadb,
index,
row: getGridRow(row.iddatadb),
});
if (row.status !== 'l') {
eligible.push({ iddatadb: row.iddatadb, index, row: getGridRow(row.iddatadb) });
}
});
return eligible;
}
function hasUnsavedChanges() {
const target =
typeof window.getTargetGridIds === "function"
? window.getTargetGridIds()
: null;
return (window.gridData || []).some(
(r) =>
r._dirty && (!target || target.has(parseInt(r.iddatadb, 10))),
);
return (window.gridData || []).some(r => r._dirty);
}
async function validateAndFilter(eligibleRows) {
@@ -487,8 +369,7 @@
index,
}));
const validationData = await validateRows(rowsToValidate);
if (!validationData.success)
throw new Error(validationData.message || "Validation error");
if (!validationData.success) throw new Error(validationData.message || "Validation error");
const validRows = [];
let invalidCount = 0;
@@ -496,12 +377,7 @@
for (const item of eligibleRows) {
const result = validationData.results[item.index];
if (result && !result.valid) {
if (item.row)
showValidationErrors(
item.row,
item.iddatadb,
result.errors,
);
if (item.row) showValidationErrors(item.row, item.iddatadb, result.errors);
invalidCount++;
} else {
validRows.push(item);
@@ -534,92 +410,54 @@
if (validRows.length === 0) {
document.getElementById("exportResponseMessage").innerHTML =
`No valid rows for export.<br><strong>${invalidCount}</strong> rows with validation errors.`;
document.getElementById(
"exportResponseModalLabel",
).textContent = "Validation Failed";
new bootstrap.Modal(
document.getElementById("exportResponseModal"),
{ keyboard: false },
).show();
document.getElementById("exportResponseModalLabel").textContent = "Validation Failed";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
return;
}
const confirmModal = new bootstrap.Modal(
document.getElementById("exportBatchConfirmModal"),
{ keyboard: false },
);
const confirmModal = new bootstrap.Modal(document.getElementById("exportBatchConfirmModal"), { keyboard: false });
let countText = String(validRows.length);
if (invalidCount > 0)
countText += ` (${invalidCount} excluded due to errors)`;
document.getElementById("exportBatchCount").textContent =
countText;
if (invalidCount > 0) countText += ` (${invalidCount} excluded due to errors)`;
document.getElementById("exportBatchCount").textContent = countText;
confirmModal.show();
const confirmBtn = document.getElementById(
"exportBatchConfirmBtn",
);
if (pendingBatchConfirmHandler)
confirmBtn.removeEventListener(
"click",
pendingBatchConfirmHandler,
);
const confirmBtn = document.getElementById("exportBatchConfirmBtn");
if (pendingBatchConfirmHandler) confirmBtn.removeEventListener("click", pendingBatchConfirmHandler);
pendingBatchConfirmHandler = () => {
pendingBatchConfirmHandler = null;
confirmModal.hide();
startBatchExport(validRows);
};
confirmBtn.addEventListener(
"click",
pendingBatchConfirmHandler,
{ once: true },
);
confirmBtn.addEventListener("click", pendingBatchConfirmHandler, { once: true });
})
.catch((error) => {
.catch(error => {
showValidationSpinner(false);
document.getElementById("exportResponseMessage").textContent =
"Validation error: " + error.message;
document.getElementById(
"exportResponseModalLabel",
).textContent = "Validation Error";
new bootstrap.Modal(
document.getElementById("exportResponseModal"),
{ keyboard: false },
).show();
document.getElementById("exportResponseMessage").textContent = "Validation error: " + error.message;
document.getElementById("exportResponseModalLabel").textContent = "Validation Error";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
});
}
$(document).on("click", ".export-all-lims-btn", function (e) {
$(document).on('click', '.export-all-lims-btn', function (e) {
e.preventDefault();
if (batchRunning) return;
if (hasUnsavedChanges()) {
const unsavedModal = new bootstrap.Modal(
document.getElementById("exportBatchUnsavedModal"),
{ keyboard: false },
);
const unsavedModal = new bootstrap.Modal(document.getElementById("exportBatchUnsavedModal"), { keyboard: false });
unsavedModal.show();
document.getElementById("batchSaveAndExportBtn")?.addEventListener(
"click",
() => {
unsavedModal.hide();
// Trigger save all first — listen for completion
alert("Please Save All first, then Export All.");
},
{ once: true },
);
document.getElementById("batchSaveAndExportBtn")?.addEventListener("click", () => {
unsavedModal.hide();
// Trigger save all first — listen for completion
alert("Please Save All first, then Export All.");
}, { once: true });
return;
}
const eligibleRows = collectEligibleRows();
if (eligibleRows.length === 0) {
document.getElementById("exportResponseMessage").textContent =
"All rows already exported to LIMS.";
document.getElementById("exportResponseModalLabel").textContent =
"Export All";
new bootstrap.Modal(
document.getElementById("exportResponseModal"),
{ keyboard: false },
).show();
document.getElementById("exportResponseMessage").textContent = "All rows already exported to LIMS.";
document.getElementById("exportResponseModalLabel").textContent = "Export All";
new bootstrap.Modal(document.getElementById("exportResponseModal"), { keyboard: false }).show();
return;
}
showBatchConfirm(eligibleRows);
@@ -630,9 +468,7 @@
batchRunning = true;
const batchUuid = crypto.randomUUID();
const total = eligibleRows.length;
let processed = 0,
succeeded = 0,
failed = 0;
let processed = 0, succeeded = 0, failed = 0;
disableAllRowButtons();
@@ -643,25 +479,18 @@
if (cancelBtn) cancelBtn.disabled = false;
if (statusEl) statusEl.textContent = `Exporting 0 / ${total}...`;
cancelBtn?.addEventListener(
"click",
() => {
batchCancelled = true;
if (statusEl)
statusEl.textContent =
"Cancelling... (waiting for current row)";
if (cancelBtn) cancelBtn.disabled = true;
},
{ once: true },
);
cancelBtn?.addEventListener("click", () => {
batchCancelled = true;
if (statusEl) statusEl.textContent = "Cancelling... (waiting for current row)";
if (cancelBtn) cancelBtn.disabled = true;
}, { once: true });
(async () => {
for (let i = 0; i < eligibleRows.length; i++) {
if (batchCancelled) break;
const { iddatadb, row } = eligibleRows[i];
if (statusEl)
statusEl.textContent = `Exporting ${processed + 1} / ${total} (id: ${iddatadb})...`;
if (statusEl) statusEl.textContent = `Exporting ${processed + 1} / ${total} (id: ${iddatadb})...`;
const gridRow = row || getGridRow(iddatadb);
if (gridRow) setRowExporting(gridRow, true);
@@ -674,29 +503,18 @@
} else {
failed++;
const errIdx = getRowIndexByIddatadb(iddatadb);
if (errIdx >= 0)
window.gridData[errIdx]._exportError =
data.message || "Unknown error";
if (gridRow)
showRowError(
gridRow,
iddatadb,
data.message || "Unknown error",
);
if (errIdx >= 0) window.gridData[errIdx]._exportError = data.message || "Unknown error";
if (gridRow) showRowError(gridRow, iddatadb, data.message || "Unknown error");
}
} catch (error) {
processed++;
failed++;
const errIdx = getRowIndexByIddatadb(iddatadb);
if (errIdx >= 0)
window.gridData[errIdx]._exportError = error.message;
if (errIdx >= 0) window.gridData[errIdx]._exportError = error.message;
if (gridRow) showRowError(gridRow, iddatadb, error.message);
}
if (gridRow) {
setRowExporting(gridRow, false);
gridRow.classList.remove("batch-disabled");
}
if (gridRow) { setRowExporting(gridRow, false); gridRow.classList.remove("batch-disabled"); }
}
batchRunning = false;
@@ -711,8 +529,7 @@
(window.gridData || []).forEach((row, idx) => {
if (row._exportError) {
const gridRow = getGridRow(row.iddatadb);
if (gridRow)
showRowError(gridRow, row.iddatadb, row._exportError);
if (gridRow) showRowError(gridRow, row.iddatadb, row._exportError);
}
});
@@ -732,9 +549,7 @@
const modalEl = document.getElementById("exportResponseModal");
new bootstrap.Modal(modalEl, { keyboard: false }).show();
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, {
once: true,
});
modalEl.addEventListener("hidden.bs.modal", cleanupBackdrop, { once: true });
})();
}
})();
+10 -100
View File
@@ -15,40 +15,6 @@ if (!is_dir($logDir)) {
$uploadDir = realpath(__DIR__ . '/../photostrf') . '/';
/**
* Resolve a datadb_photos.file_path to an absolute file path on disk.
*/
function resolvePhotoFullPath(string $filePath, string $uploadDir, string $userareaDir): ?string
{
$filePath = trim($filePath);
if ($filePath === '') {
return null;
}
$base = rtrim($uploadDir, '/');
$candidates = [];
// Userarea-relative form: "../photostrf/annotated/x.png" → resolve from __DIR__.
if (strncmp($filePath, '../', 3) === 0) {
$candidates[] = rtrim($userareaDir, '/') . '/' . $filePath;
}
// Bare filename or "annotated/x.png" subpath - resolve from photostrf/.
$candidates[] = $base . '/' . ltrim($filePath, './');
// Safety net for any odd/legacy value.
$candidates[] = $base . '/' . basename($filePath);
foreach ($candidates as $candidate) {
$real = realpath($candidate);
if ($real && file_exists($real)) {
return $real;
}
}
return null;
}
// 🔹 Base URL API
$apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/';
@@ -150,15 +116,13 @@ try {
// 🔹 STEP 1+2: Fetch Cliente ID from datadb and Schema ID from excel_templates
// Also fetch fixed fields stored in datadb
$stmt = $pdo->prepare("
SELECT d.idclient AS clienteId, et.idschema AS schemaId, et.id AS templateId,
et.template_note,
SELECT d.idclient AS clienteId, et.idschema AS schemaId,
d.cliente_responsabile_id,
d.moltiplicatore_prezzo_id,
d.anagrafica_certest_object_id,
d.anagrafica_certest_service_id,
d.cliente_fornitore_id,
d.clienteAnalisi,
d.ClienteFatturazione,
d.consegna_richiesta
FROM datadb as d
INNER JOIN excel_templates as et ON d.templateid = et.id
@@ -174,7 +138,6 @@ try {
$clienteId = (int) $result['clienteId'];
$schemaId = (int) $result['schemaId'];
$templateId = (int) $result['templateId'];
// Extract fixed fields (nullable INTs)
$clienteResponsabile = !empty($result['cliente_responsabile_id']) ? (int) $result['cliente_responsabile_id'] : null;
@@ -183,12 +146,11 @@ try {
$anagraficaService = !empty($result['anagrafica_certest_service_id']) ? (int) $result['anagrafica_certest_service_id'] : null;
$clienteFornitore = !empty($result['cliente_fornitore_id']) ? (int) $result['cliente_fornitore_id'] : null;
$clienteAnalisi = !empty($result['clienteAnalisi']) ? (int) $result['clienteAnalisi'] : null;
$clienteFatturazione = !empty($result['ClienteFatturazione']) ? (int) $result['ClienteFatturazione'] : null;
$consegnaRichiesta = !empty($result['consegna_richiesta']) ? $result['consegna_richiesta'] : null;
// 🔹 STEP 3: Fetch Parts (including idmatrice and part id for custom fields)
$stmt = $pdo->prepare("
SELECT id AS part_id, part_number, part_description, material, color, mix, idmatrice, dateexpiry, note
SELECT id AS part_id, part_number, part_description, material, color, mix, idmatrice, dateexpiry
FROM identification_parts
WHERE iddatadb = :iddatadb
AND part_description IS NOT NULL
@@ -214,7 +176,6 @@ try {
}
}
// 🔹 STEP 4a: Auto-populate export_date / export_time fields
$stmt = $pdo->prepare("
UPDATE import_data_details idd
@@ -238,9 +199,7 @@ try {
idd.field_value,
m.field_label,
m.schema_id,
m.field_id,
m.field_order,
m.is_addnote
m.field_id
FROM
import_data_details as idd
JOIN template_mapping m ON idd.mapping_id = m.id
@@ -260,35 +219,6 @@ try {
$valueMap[(int) $row['field_id']] = $row['field_value'];
}
// 🔹 Note aggiuntive: custom field flaggati is_addnote e valorizzati.
// Valori a livello di commessa → identici su tutte le parti.
$addnoteChunks = [];
$addnoteRows = array_filter($rows, fn($r) => (int)($r['is_addnote'] ?? 0) === 1);
usort($addnoteRows, function ($a, $b) {
$oa = (int)($a['field_order'] ?? 9999);
$ob = (int)($b['field_order'] ?? 9999);
if ($oa !== $ob) return $oa <=> $ob;
return strcmp((string)($a['field_label'] ?? ''), (string)($b['field_label'] ?? ''));
});
foreach ($addnoteRows as $r) {
$val = trim((string)($r['field_value'] ?? ''));
if ($val !== '') {
$addnoteChunks[] = $val;
}
}
$addnoteText = implode(' - ', $addnoteChunks);
// Nota del template: appesa in fondo alle note in esportazione
$templateNote = trim((string)($result['template_note'] ?? ''));
if ($templateNote !== '') {
$addnoteText = $addnoteText !== ''
? $addnoteText . ' - ' . $templateNote
: $templateNote;
}
// Logga i fieldValues in error_log
$logFieldValues = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT);
error_log($logFieldValues);
@@ -310,7 +240,6 @@ try {
"AnagraficaCertestService" => $anagraficaService,
"ClienteFornitore" => $clienteFornitore, // PLACEHOLDER — to be implemented
"ClienteAnalisi" => $clienteAnalisi, // PLACEHOLDER — to be implemented
// "ClienteFatturazione" => $clienteFatturazione, // ⚠️ TEMP DISABLED - problemi API lato LIMS
// DeliveryRequest goes to Campione, not CommessaWeb
];
@@ -343,33 +272,13 @@ try {
throw new Exception("Invalid or missing idmatrice for part: " . ($part["part_number"] ?? "Unknown"));
}
$partDescription = $part["part_description"] ?? "";
$partNote = trim((string)($part["note"] ?? ""));
// Note aggiuntive dai custom field flaggati is_addnote
$partDescription = $part["part_description"] ?? "";
$partNote = trim((string)($part["note"] ?? ""));
// Componi NoteWeb: descrizione [- note] [- addnote di commessa]
$noteParts = [];
if ($partDescription !== "") {
$noteParts[] = $partDescription;
}
if ($partNote !== "") {
$noteParts[] = $partNote;
}
if ($addnoteText !== "") {
$noteParts[] = $addnoteText;
}
$noteWebValue = implode(" - ", $noteParts);
$campionePayload = [
"Commessa" => $commessaId,
"Matrice" => $matriceId,
"SottoMatrice" => null,
"SchemaCustomField" => $schemaId,
// "Riferimento" => $part["part_description"] ?? "",
"NoteWeb" => $noteWebValue,
"NoteWeb" => $part["part_description"] ?? "",
"ConsegnaRichiesta" => !empty($part["dateexpiry"]) ? $part["dateexpiry"] : $consegnaRichiesta,
];
@@ -511,10 +420,11 @@ try {
$logContentPhotos .= "=== Campione {$campioneId} (main) ===\n";
foreach ($photos as $photo) {
$fullPath = resolvePhotoFullPath($photo['file_path'], $uploadDir, __DIR__);
$photoPath = $uploadDir . '/' . ltrim($photo['file_path'], './');
$fullPath = realpath($photoPath);
if ($fullPath === null) {
$logContentPhotos .= "SKIP (file not found): file_path='{$photo['file_path']}'\n";
if (!$fullPath || !file_exists($fullPath)) {
$logContentPhotos .= "SKIP (file not found): {$photoPath}\n";
continue;
}
@@ -684,10 +594,10 @@ try {
}
}
// 🔹 STEP 8: Update datadb with idcommessaweb, commessaweb, status and export date
// 🔹 STEP 8: Update datadb with idcommessaweb, commessaweb, and status
$stmt = $pdo->prepare("
UPDATE datadb
SET idcommessaweb = :idcommessaweb, commessaweb = :commessaweb, status = 'l', export_date = NOW()
SET idcommessaweb = :idcommessaweb, commessaweb = :commessaweb, status = 'l'
WHERE iddatadb = :iddatadb
");
$stmt->execute([
-394
View File
@@ -1,394 +0,0 @@
<?php
include('include/headscript.php');
require_once __DIR__ . '/include/grid_data_builder.php';
header('Content-Type: application/json');
$pdo = DBHandlerSelect::getInstance()->getConnection();
const FILTER_ALLOWED_PER_PAGE = [20, 40, 60, 100];
const FILTER_ALLIDS_CAP = 5000;
$body = json_decode(file_get_contents('php://input'), true) ?: [];
$templateId = (int)($body['template_id'] ?? 0);
if ($templateId <= 0) {
echo json_encode(['success' => false, 'message' => 'template_id mancante']);
exit;
}
$status = in_array($body['status'] ?? 'i', ['i', 'P', 'l'], true) ? $body['status'] : 'i';
$showAll = !empty($body['all_users']);
$importref = trim((string)($body['importref'] ?? '')); // scope su un singolo import
$filters = is_array($body['filters'] ?? null) ? $body['filters'] : [];
$userId = (int)($iduserlogin ?? 0);
$perPage = in_array((int)($body['per_page'] ?? 20), FILTER_ALLOWED_PER_PAGE, true) ? (int)$body['per_page'] : 20;
$page = max(1, (int)($body['page'] ?? 1));
$wantAllIds = !empty($body['want_all_ids']);
$cacheDir = __DIR__ . '/cache';
$explicitIds = [];
if (isset($body['ids']) && is_array($body['ids'])) {
$explicitIds = array_values(array_unique(array_filter(
array_map('intval', $body['ids']),
fn($v) => $v > 0
)));
}
if (!empty($explicitIds)) {
// Scoping di sicurezza: solo record del template/utente/import corretti.
$conds = ['d.templateid = ?', 'd.status = ?'];
$params = [$templateId, $status];
if (!$showAll) { $conds[] = 'd.user_id = ?'; $params[] = $userId; }
if ($importref !== '') { $conds[] = 'd.importreferencecode = ?'; $params[] = $importref; }
$ph = implode(',', array_fill(0, count($explicitIds), '?'));
$conds[] = "d.iddatadb IN ($ph)";
foreach ($explicitIds as $id) $params[] = $id;
$whereSql = implode(' AND ', $conds);
try {
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM datadb d WHERE {$whereSql}");
$countStmt->execute($params);
$total = (int)$countStmt->fetchColumn();
$totalPages = max(1, (int)ceil($total / $perPage));
if ($page > $totalPages) $page = $totalPages;
$offset = ($page - 1) * $perPage;
$idStmt = $pdo->prepare("
SELECT d.iddatadb FROM datadb d
WHERE {$whereSql}
ORDER BY d.excelrow ASC, d.iddatadb ASC
LIMIT {$perPage} OFFSET {$offset}
");
$idStmt->execute($params);
$pageIds = array_map('intval', $idStmt->fetchAll(PDO::FETCH_COLUMN));
$config = buildGridConfig($pdo, $templateId);
$rows = buildGridRows($pdo, $pageIds, $config);
echo json_encode([
'success' => true,
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'total_pages' => $totalPages,
'rows' => $rows,
'mode' => 'selection',
]);
} catch (Exception $e) {
error_log('filter_records (ids mode) error: ' . $e->getMessage());
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
exit;
}
$fixedAliasMap = [
'ClienteResponsabile' => 'cliente_responsabile_id',
'ClienteFornitore' => 'cliente_fornitore_id',
'ClienteAnalisi' => 'clienteAnalisi',
'ClienteFatturazione' => 'ClienteFatturazione',
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
'ConsegnaRichiesta' => 'consegna_richiesta',
];
$staticCols = ['importreferencecode', 'filename_import', 'importdate', 'commessaweb'];
// ── Risolutori "etichetta → lista di ID" via cache ────────────────────────
function cacheItems(string $file): array
{
if (!is_file($file)) return [];
$data = json_decode(file_get_contents($file), true);
if (!is_array($data)) return [];
return $data['value'] ?? $data ?? [];
}
/** Clienti: label "Nome - X - id" (come search_clienti.php). */
function resolveClientIdsByTerm(string $cacheDir, string $term): array
{
$term = mb_strtolower(trim($term));
if ($term === '') return [];
$ids = [];
foreach (cacheItems($cacheDir . '/clienti.json') as $c) {
$name = trim($c['Nominativo'] ?? '');
$id = trim((string)($c['IdCliente'] ?? ''));
$code = trim((string)($c['CodiceCliente'] ?? ''));
$parts = explode('_', $code);
$suffix = trim($parts[1] ?? '');
if ($suffix === '' && $code !== '') $suffix = substr($code, 0, 1);
if ($suffix === '') $suffix = '--';
$label = mb_strtolower($name . ' - ' . $suffix . ' - ' . $id);
if (mb_strpos($label, $term) !== false) $ids[] = (int)$c['IdCliente'];
}
return array_values(array_unique($ids));
}
/** Dropdown generico: matcha il termine su qualsiasi label field, ritorna gli id. */
function resolveIdsFromCache(string $file, string $idField, array $labelFields, string $term): array
{
$term = mb_strtolower(trim($term));
if ($term === '') return [];
$ids = [];
foreach (cacheItems($file) as $it) {
$hay = '';
foreach ($labelFields as $lf) $hay .= ' ' . ($it[$lf] ?? '');
if (mb_strpos(mb_strtolower($hay), $term) !== false && isset($it[$idField])) {
$ids[] = (int)$it[$idField];
}
}
return array_values(array_unique($ids));
}
/**
* Per un fixed field che memorizza un ID, risolve il termine in lista di id.
* Ritorna: array di id (IN), [] se il termine non matcha nulla, null se la colonna
* non è risolvibile ( il chiamante la salta invece di fare LIKE sbagliato).
*/
function resolveFixedIds(string $cacheDir, string $key, string $term): ?array
{
switch ($key) {
case 'ClienteAnalisi':
case 'ClienteFatturazione':
return resolveClientIdsByTerm($cacheDir, $term);
case 'MoltiplicatorePrezzo':
return resolveIdsFromCache($cacheDir . '/moltiplicatori_prezzo.json', 'IdMoltiplicatorePrezzo', ['Codice', 'Descrizione'], $term);
case 'AnagraficaCertestObject':
return resolveIdsFromCache($cacheDir . '/anagrafica_certest_object.json', 'IdAnagrafica', ['Codice', 'NomeAnagrafica'], $term);
case 'AnagraficaCertestService':
return resolveIdsFromCache($cacheDir . '/anagrafica_certest_service.json', 'IdAnagrafica', ['Codice', 'NomeAnagrafica'], $term);
default:
// ClienteResponsabile (per-cliente) e altri: non risolvibili globalmente
return null;
}
}
/**
* @return array<array{0:int,1:int}> lista di [idclient, idResponsabile]
*/
function resolveResponsabilePairs(string $cacheDir, string $term): array
{
$term = mb_strtolower(trim($term));
if ($term === '') return [];
$pairs = [];
foreach (glob($cacheDir . '/cliente_responsabili_*.json') ?: [] as $file) {
if (!preg_match('/cliente_responsabili_(\d+)\.json$/', $file, $m)) continue;
$cid = (int)$m[1];
$data = json_decode(file_get_contents($file), true);
foreach (($data['Responsabili'] ?? []) as $r) {
$name = mb_strtolower((string)($r['Nominativo'] ?? ''));
if ($name !== '' && mb_strpos($name, $term) !== false && isset($r['IdClienteResponsabile'])) {
$pairs[] = [$cid, (int)$r['IdClienteResponsabile']];
}
}
}
return $pairs;
}
/** Meta di un mapping (data_type, field_id), con cache statica. */
function mappingMeta(PDO $pdo, int $mappingId): array
{
static $cache = [];
if (!array_key_exists($mappingId, $cache)) {
$st = $pdo->prepare("SELECT data_type, field_id FROM template_mapping WHERE id = ?");
$st->execute([$mappingId]);
$cache[$mappingId] = $st->fetch(PDO::FETCH_ASSOC) ?: ['data_type' => 'Testo', 'field_id' => null];
}
return $cache[$mappingId];
}
function resolveCustomFieldValueIds(string $cacheDir, $fieldId, string $term): array
{
$fieldId = (int)$fieldId;
$term = mb_strtolower(trim($term));
if ($fieldId <= 0 || $term === '') return [];
$ids = [];
foreach (cacheItems($cacheDir . "/customfield_{$fieldId}.json") as $it) {
$label = mb_strtolower((string)($it['Valore'] ?? ''));
if (mb_strpos($label, $term) !== false && isset($it['IdCustomFieldsValue'])) {
$ids[] = (int)$it['IdCustomFieldsValue'];
}
}
return array_values(array_unique($ids));
}
// ── Costruzione WHERE ──────────────────────────────────────────────────────
$conds = ['d.templateid = ?', 'd.status = ?'];
$params = [$templateId, $status];
$needUserJoin = false;
if (!$showAll) {
$conds[] = 'd.user_id = ?';
$params[] = $userId;
}
// Scope su un singolo import (come imported.php ?importref=)
if ($importref !== '') {
$conds[] = 'd.importreferencecode = ?';
$params[] = $importref;
}
/** Aggiunge una condizione "colonna id IN (lista risolta)". */
function addIdInCondition(array &$conds, array &$params, string $col, array $ids): void
{
if (empty($ids)) {
$conds[] = '1 = 0'; // termine dato ma nessun match → zero risultati
return;
}
$ph = implode(',', array_fill(0, count($ids), '?'));
$conds[] = "d.`{$col}` IN ($ph)";
foreach ($ids as $id) $params[] = (int)$id;
}
foreach ($filters as $f) {
$term = trim((string)($f['term'] ?? ''));
if ($term === '') continue;
$type = (string)($f['type'] ?? '');
$key = (string)($f['key'] ?? '');
$like = '%' . $term . '%';
switch ($type) {
case 'detail':
case 'main_field':
$mappingId = (int)$key;
if ($mappingId <= 0) break;
$mm = mappingMeta($pdo, $mappingId);
if (($mm['data_type'] ?? '') === 'SceltaMultipla') {
// Dropdown: field_value = ID valore → risolvi label→id, match IN (= non LIKE)
$vids = resolveCustomFieldValueIds($cacheDir, $mm['field_id'], $term);
if (empty($vids)) {
$conds[] = '1 = 0';
break;
}
$inph = implode(',', array_fill(0, count($vids), '?'));
$conds[] = "EXISTS (SELECT 1 FROM import_data_details x
WHERE x.id = d.iddatadb AND x.mapping_id = ? AND x.field_value IN ($inph))";
$params[] = $mappingId;
foreach ($vids as $vid) $params[] = $vid;
} else {
// Testo / Data: match testuale
$conds[] = "EXISTS (SELECT 1 FROM import_data_details x
WHERE x.id = d.iddatadb AND x.mapping_id = ? AND x.field_value LIKE ?)";
$params[] = $mappingId;
$params[] = $like;
}
break;
case 'idclient':
addIdInCondition($conds, $params, 'idclient', resolveClientIdsByTerm($cacheDir, $term));
break;
case 'cliente_fornitore_id':
addIdInCondition($conds, $params, 'cliente_fornitore_id', resolveClientIdsByTerm($cacheDir, $term));
break;
case 'tested_component':
$conds[] = 'd.tested_component LIKE ?';
$params[] = $like;
break;
case 'status':
// Il dropdown invia direttamente il codice ('i'/'P'/'l').
if (in_array($term, ['i', 'P', 'l'], true)) {
$conds[] = 'd.status = ?';
$params[] = $term;
} else {
$conds[] = '1 = 0';
}
break;
case 'fixed':
$col = $fixedAliasMap[$key] ?? null;
if ($col === null) break;
if ($key === 'ConsegnaRichiesta') {
// Data: match testuale sul valore data (es. "2026-03").
$conds[] = "CAST(d.`{$col}` AS CHAR) LIKE ?";
$params[] = $like;
} elseif ($key === 'ClienteResponsabile') {
// Match sulla coppia (idclient, id responsabile).
$pairs = resolveResponsabilePairs($cacheDir, $term);
if (empty($pairs)) {
$conds[] = '1 = 0';
break;
}
$rowvals = implode(',', array_fill(0, count($pairs), '(?,?)'));
$conds[] = "(d.idclient, d.`{$col}`) IN ($rowvals)";
foreach ($pairs as $pr) {
$params[] = $pr[0];
$params[] = $pr[1];
}
} else {
$ids = resolveFixedIds($cacheDir, $key, $term);
if ($ids === null) break; // non risolvibile → non filtrare (niente LIKE su id)
addIdInCondition($conds, $params, $col, $ids);
}
break;
case 'static':
if ($key === 'user_name') {
$needUserJoin = true;
$conds[] = "CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,'')) LIKE ?";
$params[] = $like;
} elseif (in_array($key, $staticCols, true)) {
$conds[] = "d.`{$key}` LIKE ?";
$params[] = $like;
}
break;
default:
break; // tracking/awb ecc. → non filtrabili
}
}
$whereSql = implode(' AND ', $conds);
$joinSql = $needUserJoin ? 'LEFT JOIN auth_users u ON d.user_id = u.id' : '';
try {
// Totale + pagine
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM datadb d {$joinSql} WHERE {$whereSql}");
$countStmt->execute($params);
$total = (int)$countStmt->fetchColumn();
$totalPages = max(1, (int)ceil($total / $perPage));
if ($page > $totalPages) $page = $totalPages;
$offset = ($page - 1) * $perPage;
// Pagina corrente
$pageStmt = $pdo->prepare("
SELECT d.iddatadb
FROM datadb d {$joinSql}
WHERE {$whereSql}
ORDER BY d.excelrow ASC, d.iddatadb ASC
LIMIT {$perPage} OFFSET {$offset}
");
$pageStmt->execute($params);
$pageIds = array_map('intval', $pageStmt->fetchAll(PDO::FETCH_COLUMN));
$config = buildGridConfig($pdo, $templateId);
$rows = buildGridRows($pdo, $pageIds, $config);
$out = [
'success' => true,
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'total_pages' => $totalPages,
'rows' => $rows,
];
// Tutti gli id del match (per "seleziona tutti"), solo su richiesta
if ($wantAllIds) {
$allStmt = $pdo->prepare("
SELECT d.iddatadb FROM datadb d {$joinSql}
WHERE {$whereSql}
ORDER BY d.excelrow ASC, d.iddatadb ASC
LIMIT " . FILTER_ALLIDS_CAP . "
");
$allStmt->execute($params);
$out['all_ids'] = array_map('intval', $allStmt->fetchAll(PDO::FETCH_COLUMN));
$out['all_ids_capped'] = $total > FILTER_ALLIDS_CAP;
}
echo json_encode($out);
} catch (Exception $e) {
error_log('filter_records error: ' . $e->getMessage());
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
+7 -29
View File
@@ -16,11 +16,9 @@ try {
exit;
}
$api = VisualLimsApiClient::getInstance(); // also loads dotenv as a side-effect
$api = VisualLimsApiClient::getInstance();
$webOnly = isset($_GET['web_only']) ? (int)$_GET['web_only'] : 1;
$refresh = isset($_GET['refresh']) ? (int)$_GET['refresh'] : 0;
$simulate = ($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true';
$filterString = "Matrice/IdMatrice eq $idMatrice";
@@ -34,34 +32,19 @@ try {
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$full_url = $base_url . $endpoint;
// Cache file matches the one warm_cache.php writes (web_only=1 is what the analysis modal asks for).
// Caching skipped in simulate mode to avoid polluting real-data cache files.
$cache_file = __DIR__ . '/cache/analisi_matrice_' . $idMatrice . ($webOnly === 1 ? '' : '_all') . '.json';
$data = null;
$fromCache = false;
file_put_contents(__DIR__ . '/last_url_analisi.txt', $full_url . PHP_EOL, FILE_APPEND);
if (!$simulate && !$refresh && file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) { // 1 ora
$data = json_decode(file_get_contents($cache_file), true);
$fromCache = is_array($data);
}
$data = $api->get($endpoint, []);
if (!$fromCache) {
$data = $api->get($endpoint, []);
if (!$simulate) {
file_put_contents($cache_file, json_encode(['value' => $data['value'] ?? []], JSON_UNESCAPED_UNICODE));
}
}
file_put_contents(
__DIR__ . '/analisi_by_matrice_response.json',
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
if ($debug === 1) {
file_put_contents(__DIR__ . '/last_url_analisi.txt', $full_url . PHP_EOL, FILE_APPEND);
file_put_contents(
__DIR__ . '/analisi_by_matrice_response.json',
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
echo json_encode([
'endpoint' => $endpoint,
'full_url' => $full_url,
'from_cache' => $fromCache,
'raw_response' => $data
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
@@ -69,11 +52,6 @@ try {
$analisi = $data['value'] ?? [];
// Escludi le analisi con NominativoCliente compilato
$analisi = array_values(array_filter($analisi, function ($item) {
return empty(trim((string)($item['NominativoCliente'] ?? '')));
}));
echo json_encode([
'value' => $analisi,
'count' => is_array($analisi) ? count($analisi) : 0
+1 -1
View File
@@ -30,7 +30,7 @@ try {
// Parametri OData
$params = [
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
'$select' => 'IdCliente,Nominativo,CodiceCliente',
'$orderby' => 'Nominativo asc'
];
-127
View File
@@ -1,127 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
// Disable PHP error display
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance(); // also loads dotenv
// In simulate mode: return fake clients built from idclient values already in datadb.
// This ensures client dropdowns auto-select the correct row idclient, which in turn
// triggers the ClienteResponsabile select to populate via the mock.
if (($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true') {
require_once __DIR__ . '/class/db-functions.php';
$pdo = DBHandlerSelect::getInstance()->getConnection();
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
$ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$fakeClients = array_map(fn($id) => [
'IdCliente' => (int) $id,
'Nominativo' => "Cliente Simulato {$id}",
'CodiceCliente' => "SIM_{$id}",
], $ids);
echo json_encode(['value' => $fakeClients]);
exit;
}
// ── DEBUG MODE ───────────────────────────────────────────────────────
// Chiamata: get_clienti.php?debug=1
// Bypassa cache e $select per vedere TUTTI i campi grezzi restituiti
// dall'API per l'entità Cliente. Usare solo per ispezione manuale,
// NON lasciare linkato/usato in produzione dal frontend.
if (($_GET['debug'] ?? '') === '1') {
$debugParams = [
'$top' => 5,
'$orderby' => 'Nominativo asc'
// Nessun $select: chiediamo tutti i campi disponibili
];
$debugEndpoint = "Cliente?" . http_build_query($debugParams);
$debugData = $api->get($debugEndpoint);
$debugClients = $debugData['value'] ?? $debugData;
echo json_encode([
'mode' => 'debug - no $select, cache bypassed',
'top_level_keys' => is_array($debugData) ? array_keys($debugData) : null,
'fields_found_in_first_record' => isset($debugClients[0]) ? array_keys($debugClients[0]) : null,
'sample_records' => $debugClients,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
// ── FINE DEBUG MODE ──────────────────────────────────────────────────
// Parametri OData
$params = [
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
'$orderby' => 'Nominativo asc'
];
// Costruisce query string con encoding corretto
$queryString = http_build_query($params);
// Componi endpoint finale
$endpoint = "Cliente?$queryString";
// Funzione per eseguire la chiamata con retry
function makeApiRequest($api, $endpoint, $maxRetries = 3)
{
for ($retry = 0; $retry < $maxRetries; $retry++) {
try {
// Tenta la chiamata API
$data = $api->get($endpoint);
// Salva risposta per debug
file_put_contents(__DIR__ . '/clienti_response.json', json_encode($data, JSON_PRETTY_PRINT));
return $data;
} catch (Exception $e) {
$errorMessage = $e->getMessage();
// Controlla se l'errore è legato all'autenticazione (HTTP 400 con messaggio specifico)
if (strpos($errorMessage, 'HTTP 400') !== false && strpos($errorMessage, 'Cannot persist the object') !== false) {
// Forza il refresh del token
try {
// Assumi che VisualLimsApiClient abbia un metodo per il refresh del token
$api->refreshToken(); // Da implementare in VisualLimsApiClient se non esiste
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
} catch (Exception $refreshEx) {
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
throw new Exception("Impossibile eseguire il refresh del token: " . $refreshEx->getMessage());
}
// Ritarda leggermente prima del retry
usleep(500000); // 500ms
continue;
}
// Altri errori non gestiti dal retry
throw $e;
}
}
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
}
// Cache file (1 hour TTL)
$cacheFile = __DIR__ . '/cache/clienti.json';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
readfile($cacheFile);
exit;
}
// Esegui la chiamata con retry
$data = makeApiRequest($api, $endpoint);
$json = json_encode($data);
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
file_put_contents($cacheFile, $json);
echo $json;
} catch (Exception $e) {
http_response_code(500);
$errorResponse = [
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
echo json_encode($errorResponse);
}
-102
View File
@@ -1,102 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
// Disable PHP error display
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance(); // also loads dotenv
// In simulate mode: return fake clients built from idclient values already in datadb.
// This ensures client dropdowns auto-select the correct row idclient, which in turn
// triggers the ClienteResponsabile select to populate via the mock.
if (($_ENV['SIMULATE_EXPORT_LIMS'] ?? '') === 'true') {
require_once __DIR__ . '/class/db-functions.php';
$pdo = DBHandlerSelect::getInstance()->getConnection();
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
$ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
$fakeClients = array_map(fn($id) => [
'IdCliente' => (int) $id,
'Nominativo' => "Cliente Simulato {$id}",
'CodiceCliente' => "SIM_{$id}",
], $ids);
echo json_encode(['value' => $fakeClients]);
exit;
}
// Parametri OData
$params = [
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
'$orderby' => 'Nominativo asc'
];
// Costruisce query string con encoding corretto
$queryString = http_build_query($params);
// Componi endpoint finale
$endpoint = "Cliente?$queryString";
// Funzione per eseguire la chiamata con retry
function makeApiRequest($api, $endpoint, $maxRetries = 3)
{
for ($retry = 0; $retry < $maxRetries; $retry++) {
try {
// Tenta la chiamata API
$data = $api->get($endpoint);
// Salva risposta per debug
file_put_contents(__DIR__ . '/clienti_response.json', json_encode($data, JSON_PRETTY_PRINT));
return $data;
} catch (Exception $e) {
$errorMessage = $e->getMessage();
// Controlla se l'errore è legato all'autenticazione (HTTP 400 con messaggio specifico)
if (strpos($errorMessage, 'HTTP 400') !== false && strpos($errorMessage, 'Cannot persist the object') !== false) {
// Forza il refresh del token
try {
// Assumi che VisualLimsApiClient abbia un metodo per il refresh del token
$api->refreshToken(); // Da implementare in VisualLimsApiClient se non esiste
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
} catch (Exception $refreshEx) {
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
throw new Exception("Impossibile eseguire il refresh del token: " . $refreshEx->getMessage());
}
// Ritarda leggermente prima del retry
usleep(500000); // 500ms
continue;
}
// Altri errori non gestiti dal retry
throw $e;
}
}
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
}
// Cache file (1 hour TTL)
$cacheFile = __DIR__ . '/cache/clienti.json';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
readfile($cacheFile);
exit;
}
// Esegui la chiamata con retry
$data = makeApiRequest($api, $endpoint);
$json = json_encode($data);
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
file_put_contents($cacheFile, $json);
echo $json;
} catch (Exception $e) {
http_response_code(500);
$errorResponse = [
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
echo json_encode($errorResponse);
}
-844
View File
@@ -1,844 +0,0 @@
/**
* gridFilter.js — Selezione righe + filtri colonna a scomparsa per imported.php
* Dipende da window.gridRenderer (gridRenderer.js). Includere DOPO gridRenderer.js.
*/
(function () {
"use strict";
let filtersActive = false;
let restricted = false;
const selected = new Set(); // iddatadb selezionati
const colFilters = {}; // colKey -> testo filtro
let restrictedIds = null; // Set di iddatadb visibili quando ristretto
function R() {
return window.gridRenderer;
}
function data() {
return R().getData();
}
function meta() {
return R().getMeta();
}
// ── Filtro SERVER-SIDE (paginato, come la lista normale ma filtrata) ──────
// Il filtro gira su TUTTO il template; il server restituisce UNA PAGINA di righe
// filtrate + il totale, e navighiamo le pagine del set filtrato via AJAX.
const TEMPLATE_ID =
new URLSearchParams(location.search).get("id") || (meta().templateId ?? "");
const SHOW_ALL =
new URLSearchParams(location.search).get("all_users") === "1";
const IMPORTREF =
new URLSearchParams(location.search).get("importref") || "";
const FILTER_PER_PAGE =
parseInt(new URLSearchParams(location.search).get("limit"), 10) || 20;
let serverFiltered = false;
let filterPage = 1;
let filterTotalPages = 1;
let filterTotal = 0;
let originalData = null; // snapshot della pagina originale (per ripristino)
let filterReqSeq = 0;
let showingSelection = false; // true quando mostriamo l'UNIONE dei selezionati
let pagerFn = null; // paginazione corrente (applyServerFilter | showSelectedRecords)
function collectActiveFilters() {
const cols = meta().columns || [];
const out = [];
for (const [key, term] of Object.entries(colFilters)) {
if (!term || !String(term).trim()) continue;
const col = cols.find((c) => String(c.key) === String(key));
if (!col) continue;
out.push({ key: String(key), type: col.type, term: String(term).trim() });
}
return out;
}
// Sostituisce il dataset del renderer in-place e ridisegna.
function swapData(rows) {
const arr = data();
arr.length = 0;
(rows || []).forEach((r) => arr.push(r));
R().renderVisibleRows();
}
function hidePagination(hide) {
// Durante il filtro la paginazione server (per pagina, via URL) è fuorviante.
document.querySelectorAll(".pager-bar").forEach((el) => {
el.style.display = hide ? "none" : "";
});
}
// Pager del set filtrato (Prec / pag X di Y / Succ) nella toolbar.
function updateFilterPager() {
const el = document.getElementById("filterPager");
if (!el) return;
if (!serverFiltered) {
el.style.display = "none";
el.innerHTML = "";
return;
}
el.style.display = "inline-flex";
const label = showingSelection ? "selezionati" : "trovati";
el.innerHTML =
`<button type="button" class="btn btn-outline-secondary btn-sm" id="filterPrevBtn" ${filterPage <= 1 ? "disabled" : ""}></button>` +
`<span style="font-size:12px;color:#333;">${filterTotal} ${label} · pag ${filterPage}/${filterTotalPages}</span>` +
`<button type="button" class="btn btn-outline-secondary btn-sm" id="filterNextBtn" ${filterPage >= filterTotalPages ? "disabled" : ""}></button>`;
const go = pagerFn || applyServerFilter;
const prev = document.getElementById("filterPrevBtn");
const next = document.getElementById("filterNextBtn");
if (prev) prev.addEventListener("click", () => go(filterPage - 1));
if (next) next.addEventListener("click", () => go(filterPage + 1));
}
function restoreOriginal() {
serverFiltered = false;
filterPage = 1;
filterTotalPages = 1;
filterTotal = 0;
if (originalData) swapData(originalData);
updateFilterPager();
hidePagination(false);
updateToolbar();
}
async function applyServerFilter(page = 1) {
const active = collectActiveFilters();
if (active.length === 0) {
if (serverFiltered) restoreOriginal();
return;
}
if (!originalData) originalData = [...data()]; // snapshot pagina 1 originale
const seq = ++filterReqSeq;
try {
const resp = await fetch("filter_records.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
template_id: parseInt(TEMPLATE_ID, 10),
all_users: SHOW_ALL,
importref: IMPORTREF,
status: "i",
filters: active,
page: Math.max(1, page),
per_page: FILTER_PER_PAGE,
}),
});
const json = await resp.json();
if (seq !== filterReqSeq) return; // risposta obsoleta: ignora
if (!json.success) {
console.error("[gridFilter] filtro:", json.message);
return;
}
serverFiltered = true;
showingSelection = false;
pagerFn = applyServerFilter;
filterPage = json.page;
filterTotalPages = json.total_pages;
filterTotal = json.total;
swapData(json.rows || []);
updateFilterPager();
hidePagination(true);
updateToolbar();
} catch (e) {
console.error("[gridFilter] fetch filtro fallita", e);
}
}
// ── Mostra i SELEZIONATI (unione tra filtri diversi) ──────────────────
// La selezione si accumula tra filtri; questo carica dal server TUTTI i record
// selezionati (per id) e li mostra, paginati, a prescindere dai filtri correnti.
async function showSelectedRecords(page = 1) {
const ids = [...selected];
if (!ids.length) {
showingSelection = false;
updateToolbar();
return;
}
pagerFn = showSelectedRecords;
try {
const resp = await fetch("filter_records.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
template_id: parseInt(TEMPLATE_ID, 10),
all_users: SHOW_ALL,
importref: IMPORTREF,
status: "i",
ids: ids,
page: Math.max(1, page),
per_page: FILTER_PER_PAGE,
}),
});
const json = await resp.json();
if (!json.success) {
console.error("[gridFilter] mostra selezionati:", json.message);
return;
}
if (!originalData) originalData = [...data()];
showingSelection = true;
serverFiltered = true;
filterPage = json.page;
filterTotalPages = json.total_pages;
filterTotal = json.total;
swapData(json.rows || []);
updateFilterPager();
hidePagination(true);
updateToolbar();
} catch (e) {
console.error("[gridFilter] mostra selezionati fallita", e);
}
}
// Tumbler "Mostra selezionati" / "Torna al filtro".
function toggleShowSelected() {
if (!showingSelection) {
if (!selected.size) return;
showSelectedRecords(1);
} else {
exitSelectionView();
}
}
// Torna dal "mostra selezionati" al filtro corrente (o alla pagina originale).
function exitSelectionView() {
showingSelection = false;
pagerFn = applyServerFilter;
if (collectActiveFilters().length) {
applyServerFilter(1);
} else {
restoreOriginal();
}
updateToolbar();
}
// Deseleziona tutto.
function clearSelection() {
selected.clear();
document
.querySelectorAll("#gridRowContainer .grid-row.row-selected")
.forEach((el) => el.classList.remove("row-selected"));
document
.querySelectorAll(".filter-row-checkbox")
.forEach((cb) => (cb.checked = false));
const sa = document.getElementById("filterSelectAll");
if (sa) sa.checked = false;
if (showingSelection) {
exitSelectionView();
} else {
updateToolbar();
}
}
// "Seleziona tutti i filtrati": chiede al server TUTTI gli id del match e li seleziona.
async function selectAllMatching(on) {
if (!serverFiltered) return;
const active = collectActiveFilters();
try {
const resp = await fetch("filter_records.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
template_id: parseInt(TEMPLATE_ID, 10),
all_users: SHOW_ALL,
importref: IMPORTREF,
status: "i",
filters: active,
page: filterPage,
per_page: FILTER_PER_PAGE,
want_all_ids: true,
}),
});
const json = await resp.json();
if (!json.success) return;
(json.all_ids || []).forEach((id) => (on ? selected.add(id) : selected.delete(id)));
document
.querySelectorAll("#gridRowContainer .grid-row")
.forEach((rowEl) => {
rowEl.classList.toggle("row-selected", on);
const cb = rowEl.querySelector(".filter-row-checkbox");
if (cb) cb.checked = on;
});
updateToolbar();
if (json.all_ids_capped) {
alert("Selezione limitata a " + (json.all_ids || []).length + " record.");
}
} catch (e) {
console.error("[gridFilter] select-all fallita", e);
}
}
function syncFilterRow() {
const fr = document.getElementById("gridFilterRow");
const header = document.getElementById("gridHeaderContainer");
if (!fr || !header) return;
const actHeader = header.querySelector(".button-header");
const actCell = fr.querySelector(".filter-actions-cell");
if (actHeader && actCell) {
const aw = actHeader.offsetWidth;
actCell.style.flex = `0 0 ${aw}px`;
actCell.style.minWidth = `${aw}px`;
}
const headerCells = header.querySelectorAll(
".grid-header:not(.button-header)",
);
const filterCells = fr.querySelectorAll(
".grid-cell:not(.filter-actions-cell)",
);
headerCells.forEach((hc, i) => {
const fc = filterCells[i];
if (fc) fc.style.flex = `0 0 ${hc.offsetWidth}px`;
});
}
function syncStickyLeft() {
const btnHeader = document.querySelector(
"#gridHeaderContainer .button-header",
);
if (!btnHeader) return;
const w1 = btnHeader.offsetWidth; // larghezza reale colonna Actions
const fixRow = (rowEl) => {
if (!rowEl) return;
const cells = rowEl.querySelectorAll(
":scope > .grid-cell, :scope > .grid-header",
);
const c2 = cells[1];
const c3 = cells[2];
if (c2 && getComputedStyle(c2).position === "sticky") {
c2.style.left = `${w1}px`;
}
if (c3 && getComputedStyle(c3).position === "sticky") {
c3.style.left = `${w1 + (c2 ? c2.offsetWidth : 0)}px`;
}
};
fixRow(document.getElementById("gridHeaderContainer"));
document.querySelectorAll(".grid-top").forEach(fixRow);
document
.querySelectorAll("#gridRowContainer .grid-row")
.forEach(fixRow);
fixRow(document.getElementById("gridFilterRow"));
}
let _headerObs = null;
let _rowObs = null;
let _syncRaf = null;
function reconnectObservers() {
const header = document.getElementById("gridHeaderContainer");
const rows = document.getElementById("gridRowContainer");
if (_headerObs && header)
_headerObs.observe(header, {
attributes: true,
attributeFilter: ["style"],
subtree: true,
});
if (_rowObs && rows) _rowObs.observe(rows, { childList: true });
}
function scheduleSync() {
if (_syncRaf) return;
_syncRaf = requestAnimationFrame(() => {
_syncRaf = null;
if (_headerObs) _headerObs.disconnect();
if (_rowObs) _rowObs.disconnect();
syncStickyLeft();
syncFilterRow();
reconnectObservers();
});
}
function hookGridSync() {
_headerObs = new MutationObserver(scheduleSync);
_rowObs = new MutationObserver(scheduleSync);
reconnectObservers();
scheduleSync(); // primo allineamento
}
function rowMatchesFilters(row) {
if (serverFiltered) return true;
for (const [key, term] of Object.entries(colFilters)) {
if (!term) continue;
const t = term.toLowerCase();
const val = getRowColValue(row, key);
if (!String(val).toLowerCase().includes(t)) return false;
}
return true;
}
function getRowColValue(row, key) {
const cols = meta().columns || [];
const col = cols.find((c) => String(c.key) === String(key));
if (!col) return "";
switch (col.type) {
case "detail":
case "main_field":
return row.details?.[String(key)] ?? "";
case "fixed":
return row.fixedFields?.[key] ?? "";
case "idclient":
return row.idclient ?? "";
case "cliente_fornitore_id":
return row.cliente_fornitore_id ?? "";
case "tested_component":
return row.tested_component ?? "";
case "static":
return row[key] ?? "";
case "status":
return row.status ?? "";
default:
return row[key] ?? "";
}
}
// ── Applica visibilità righe (filtri + restrizione) ──
function applyVisibility() {
const rows = document.querySelectorAll("#gridRowContainer .grid-row");
rows.forEach((rowEl) => {
const id = parseInt(rowEl.dataset.id, 10);
const row = data().find((r) => String(r.iddatadb) === String(id));
let show = true;
if (row && !rowMatchesFilters(row)) show = false;
if (restricted && restrictedIds && !restrictedIds.has(id))
show = false;
rowEl.style.display = show ? "" : "none";
});
window.dispatchEvent(new Event("resize")); // ricalcola scrollbar top
}
// ── Lista iddatadb attualmente visibili (per propagazione/export ecc.) ──
window.getVisibleGridIds = function () {
if (serverFiltered) {
return data()
.map((r) => parseInt(r.iddatadb, 10))
.filter(Boolean);
}
const ids = [];
document
.querySelectorAll("#gridRowContainer .grid-row")
.forEach((el) => {
if (el.style.display !== "none")
ids.push(parseInt(el.dataset.id, 10));
});
return ids.filter(Boolean);
};
// ── Righe target per operazioni bulk (propaga/export/clona/save) ──
// Regola: filtri attivi + selezione → solo selezionate;
// filtri attivi senza selezione → solo visibili;
// filtri non attivi → null (= tutte, comportamento originale).
window.getTargetGridIds = function () {
if (!filtersActive) return null; // null = nessun vincolo
if (selected.size > 0) return new Set([...selected]);
return new Set(window.getVisibleGridIds());
};
// Helper: true se l'id è tra i target (o se non c'è vincolo)
window.isTargetGridId = function (id) {
const t = window.getTargetGridIds();
if (t === null) return true;
return t.has(parseInt(id, 10));
};
// ── Toggle selezione riga ──
function toggleRow(id, on) {
id = parseInt(id, 10);
if (on) selected.add(id);
else selected.delete(id);
const rowEl = document.querySelector(
`#gridRowContainer .grid-row[data-id="${id}"]`,
);
if (rowEl) {
rowEl.classList.toggle("row-selected", selected.has(id));
const cb = rowEl.querySelector(".filter-row-checkbox");
if (cb) cb.checked = selected.has(id);
}
updateToolbar();
}
// ── Inietta checkbox nelle righe visibili ──
// ── Inietta checkbox dentro la cella Actions (già sticky) ──
function injectCheckboxes() {
document
.querySelectorAll("#gridRowContainer .grid-row")
.forEach((rowEl) => {
const btnCell = rowEl.querySelector(".button-cell");
if (!btnCell) return;
if (btnCell.querySelector(".filter-row-checkbox")) return;
const id = parseInt(rowEl.dataset.id, 10);
const wrap = document.createElement("label");
wrap.className = "filter-cb-wrap";
wrap.innerHTML = `<input type="checkbox" class="filter-row-checkbox" ${selected.has(id) ? "checked" : ""}>`;
btnCell.insertBefore(wrap, btnCell.firstChild);
if (selected.has(id)) rowEl.classList.add("row-selected");
});
}
function removeCheckboxes() {
document
.querySelectorAll(".filter-cb-wrap")
.forEach((el) => el.remove());
document
.querySelectorAll(".grid-row.row-selected")
.forEach((el) => el.classList.remove("row-selected"));
}
// ── Riga input filtri colonna (allineata alle larghezze reali) ──
// ── Riga input filtri colonna (allineata alle larghezze reali) ──
function injectFilterRow() {
if (document.getElementById("gridFilterRow")) return;
const top = document.getElementById("gridTopContainer");
const header = document.getElementById("gridHeaderContainer");
if (!top || !header) return;
const fr = document.createElement("div");
fr.className = "grid-row grid-filter-row";
fr.id = "gridFilterRow";
// cella allineata alla colonna Actions (sticky, legge larghezza reale)
const actHeader = header.querySelector(".button-header");
const actCell = document.createElement("div");
actCell.className = "grid-cell button-cell filter-actions-cell";
const aw = actHeader ? actHeader.offsetWidth : 220;
actCell.style.flex = `0 0 ${aw}px`;
actCell.style.minWidth = `${aw}px`;
actCell.innerHTML = `<label class="filter-cb-wrap" style="display:inline-flex;" title="Seleziona tutti i filtrati"><input type="checkbox" id="filterSelectAll"></label>`;
fr.appendChild(actCell);
// una cella per colonna, larghezza letta dall'header reale
const headerCells = header.querySelectorAll(
".grid-header:not(.button-header)",
);
(meta().columns || []).forEach((col, i) => {
const hc = headerCells[i];
const w = hc ? hc.offsetWidth : col.width || 150;
const cell = document.createElement("div");
cell.className = "grid-cell";
cell.style.flex = `0 0 ${w}px`;
if (col.type === "tracking" || col.type === "awb") {
// niente filtro
} else if (col.type === "status") {
// Status: dropdown (3 valori) invece del testo libero.
const cur = colFilters[col.key] || "";
const opt = (v, lbl) =>
`<option value="${v}" ${cur === v ? "selected" : ""}>${lbl}</option>`;
cell.innerHTML =
`<select class="filter-col-input" data-col-key="${col.key}" style="width:100%;padding:3px 6px;font-size:12px;border:1px solid #ced4da;border-radius:4px;">` +
opt("", "— tutti —") +
opt("i", "Imported") +
opt("P", "In Progress") +
opt("l", "To LIMS") +
`</select>`;
} else {
cell.innerHTML = `<input type="text" class="filter-col-input" data-col-key="${col.key}" placeholder="🔍 ${col.label || ""}" value="${colFilters[col.key] || ""}" style="width:100%;padding:3px 6px;font-size:12px;border:1px solid #ced4da;border-radius:4px;">`;
}
fr.appendChild(cell);
});
top.parentNode.insertBefore(fr, top);
scheduleSync(); // allinea larghezze filtri + offset sticky
}
function removeFilterRow() {
const fr = document.getElementById("gridFilterRow");
if (fr) fr.remove();
}
// ── Toolbar (bottoni contestuali) ──
function updateToolbar() {
const bar = document.getElementById("filterActionBar");
if (!bar) return;
const count = selected.size;
bar.style.display = filtersActive ? "inline-flex" : "none";
const cntEl = document.getElementById("filterSelCount");
if (cntEl) cntEl.textContent = count;
document.getElementById("filterDeleteBtn").disabled = count === 0;
const showBtn = document.getElementById("filterRestrictBtn");
showBtn.disabled = count === 0 && !showingSelection;
showBtn.innerHTML = showingSelection
? '<i class="fas fa-eye"></i> Torna al filtro'
: '<i class="fas fa-list-check"></i> Mostra selezionati';
const clearSelBtn = document.getElementById("filterClearSelBtn");
if (clearSelBtn) clearSelBtn.disabled = count === 0;
}
// ── Main toggle ──
function toggleFilters() {
filtersActive = !filtersActive;
const grid = document.getElementById("gridContainer");
const btn = document.getElementById("filtersToggleBtn");
if (filtersActive) {
grid.classList.add("filters-on");
btn.classList.add("active");
injectCheckboxes();
injectFilterRow();
} else {
grid.classList.remove("filters-on");
btn.classList.remove("active");
removeCheckboxes();
removeFilterRow();
// reset filtri colonna + ripristina la pagina originale
Object.keys(colFilters).forEach((k) => delete colFilters[k]);
if (serverFiltered) restoreOriginal();
}
updateToolbar();
}
// ── Batch delete ──
async function batchDelete() {
const ids = [...selected];
if (!ids.length) return;
if (
!confirm(
`Eliminare ${ids.length} righe dal database? Operazione irreversibile.`,
)
)
return;
const btn = document.getElementById("filterDeleteBtn");
btn.disabled = true;
const original = btn.innerHTML;
let ok = 0,
fail = 0;
for (const id of ids) {
try {
btn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${ok + fail + 1}/${ids.length}`;
const resp = await fetch("delete_record.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: id }),
});
const json = await resp.json();
if (json.success) {
ok++;
// rimuovi da gridData
const arr = data();
const idx = arr.findIndex(
(r) => String(r.iddatadb) === String(id),
);
if (idx !== -1) arr.splice(idx, 1);
selected.delete(id);
if (restrictedIds) restrictedIds.delete(id);
} else fail++;
} catch (e) {
fail++;
}
}
btn.innerHTML = original;
R().renderVisibleRows();
if (filtersActive) {
injectCheckboxes();
}
applyVisibility();
updateToolbar();
alert(`Eliminate: ${ok}${fail ? " — Falliti: " + fail : ""}`);
}
// ── Restringi / mostra tutti ──
function toggleRestrict() {
if (!restricted) {
if (!selected.size) return;
restrictedIds = new Set([...selected]);
restricted = true;
} else {
restricted = false;
restrictedIds = null;
}
applyVisibility();
updateToolbar();
}
// ── Costruzione UI toolbar (bottoni in cima) ──
function buildUI() {
const addBtn = document.getElementById("addRowBtn");
if (!addBtn) return;
// bottone Filters
const fBtn = document.createElement("button");
fBtn.type = "button";
fBtn.id = "filtersToggleBtn";
fBtn.className = "btn btn-outline-info btn-sm";
fBtn.style.flexShrink = "0";
fBtn.innerHTML = '<i class="fas fa-filter"></i> Filters';
addBtn.parentNode.insertBefore(fBtn, addBtn.nextSibling);
// barra azioni contestuali
const bar = document.createElement("span");
bar.id = "filterActionBar";
bar.style.cssText =
"display:none;align-items:center;gap:8px;flex-shrink:0;";
bar.innerHTML = `
<span id="filterPager" style="display:none;align-items:center;gap:6px;"></span>
<button type="button" id="filterClearBtn" class="btn btn-outline-secondary btn-sm"><i class="fas fa-times"></i> Pulisci filtri</button>
<span style="font-size:12px;color:#555;">Selezionate: <strong id="filterSelCount">0</strong></span>
<button type="button" id="filterRestrictBtn" class="btn btn-outline-primary btn-sm"><i class="fas fa-list-check"></i> Mostra selezionati</button>
<button type="button" id="filterClearSelBtn" class="btn btn-outline-secondary btn-sm"><i class="fas fa-square"></i> Deseleziona</button>
<button type="button" id="filterDeleteBtn" class="btn btn-outline-danger btn-sm"><i class="fas fa-trash"></i> Elimina selezionati</button>
`;
fBtn.parentNode.insertBefore(bar, fBtn.nextSibling);
fBtn.addEventListener("click", toggleFilters);
document
.getElementById("filterClearBtn")
.addEventListener("click", clearFilters);
document
.getElementById("filterDeleteBtn")
.addEventListener("click", batchDelete);
document
.getElementById("filterRestrictBtn")
.addEventListener("click", toggleShowSelected);
document
.getElementById("filterClearSelBtn")
.addEventListener("click", clearSelection);
}
// Svuota tutti i filtri di colonna (input + select) e torna alla pagina originale.
function clearFilters() {
Object.keys(colFilters).forEach((k) => delete colFilters[k]);
document
.querySelectorAll("#gridFilterRow .filter-col-input")
.forEach((el) => {
el.value = "";
});
if (serverFiltered) restoreOriginal();
}
// ── Event delegation ──
function attachEvents() {
// checkbox click
document.addEventListener("change", function (e) {
if (e.target.classList.contains("filter-row-checkbox")) {
const rowEl = e.target.closest(".grid-row");
toggleRow(rowEl.dataset.id, e.target.checked);
}
});
// Seleziona / deseleziona tutti i FILTRATI
document.addEventListener("change", function (e) {
if (e.target.id !== "filterSelectAll") return;
const on = e.target.checked;
// Con filtro server-side: seleziona TUTTI i match (anche altre pagine).
if (serverFiltered) {
selectAllMatching(on);
return;
}
document
.querySelectorAll("#gridRowContainer .grid-row")
.forEach((rowEl) => {
if (rowEl.style.display === "none") return;
const id = parseInt(rowEl.dataset.id, 10);
if (!id) return;
if (on) selected.add(id);
else selected.delete(id);
rowEl.classList.toggle("row-selected", on);
const cb = rowEl.querySelector(".filter-row-checkbox");
if (cb) cb.checked = on;
});
updateToolbar();
});
// click su QUALSIASI punto della riga (tranne campi interattivi) = seleziona
document.addEventListener("click", function (e) {
if (!filtersActive) return;
const rowEl = e.target.closest("#gridRowContainer .grid-row");
if (!rowEl) return;
// ignora click su elementi interattivi
if (
e.target.closest(
"input, select, textarea, button, a, .select2-container, .action-btn, .propagate-btn, .add-part-btn",
)
) {
// ma se ho cliccato proprio la checkbox, lascia fare al change
return;
}
const cb = rowEl.querySelector(".filter-row-checkbox");
if (!cb) return;
cb.checked = !cb.checked;
toggleRow(rowEl.dataset.id, cb.checked);
});
// filtri colonna testo (debounce) → filtro server-side su tutto il template
let t = null;
document.addEventListener("input", function (e) {
if (!e.target.classList.contains("filter-col-input")) return;
if (e.target.tagName === "SELECT") return; // i select li gestisce 'change'
const key = e.target.dataset.colKey;
colFilters[key] = e.target.value;
clearTimeout(t);
t = setTimeout(applyServerFilter, 250);
});
// dropdown (es. Status): applica subito, senza debounce
document.addEventListener("change", function (e) {
if (!e.target.classList.contains("filter-col-input")) return;
if (e.target.tagName !== "SELECT") return;
colFilters[e.target.dataset.colKey] = e.target.value;
applyServerFilter();
});
}
// ── Ripristina stato filtri dopo un re-render (checkbox + restrizione) ──
let _restoring = false;
function restoreFilterState() {
if (!filtersActive || _restoring) return;
_restoring = true; // evita loop con l'observer mentre iniettiamo/nascondiamo
try {
injectCheckboxes();
applyVisibility(); // ri-applica filtri colonna E restringi selezione
} finally {
// rilascia dopo il tick così le nostre mutazioni non ci ri-triggerano subito
setTimeout(() => {
_restoring = false;
}, 0);
}
}
// ── Observer: qualunque cosa ricrei le righe (propagazione, save, ecc.)
// ripristina automaticamente checkbox + restrizione. Robusto perché
// non dipende da chi chiama renderVisibleRows. ──
function hookRerender() {
const rowC = document.getElementById("gridRowContainer");
if (!rowC) return;
const obs = new MutationObserver(() => {
if (!filtersActive || _restoring) return;
// debounce leggero: aspetta che il render finisca
clearTimeout(hookRerender._t);
hookRerender._t = setTimeout(restoreFilterState, 30);
});
obs.observe(rowC, { childList: true });
}
function init() {
if (!window.gridRenderer) {
console.error("[gridFilter] gridRenderer non trovato");
return;
}
buildUI();
attachEvents();
hookRerender();
hookGridSync();
}
// parte dopo che gridRenderer ha finito init
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () =>
setTimeout(init, 300),
);
} else {
setTimeout(init, 300);
}
})();
+39 -345
View File
@@ -66,11 +66,7 @@
// ── Client data (AJAX Select2, no bulk loading) ──────────────────────
function formatClientLabel(client) {
const nome = (
client.Nominativo ||
client.text ||
"Nome non disponibile"
).trim();
const nome = client.Nominativo || client.text || "Nome non disponibile";
const id = client.IdCliente || client.id || "";
const codiceCliente = (
client.CodiceCliente ??
@@ -79,14 +75,11 @@
)
.toString()
.trim();
// Lettera estratta (parte dopo "_", altrimenti prima lettera del codice)
const suffix = (codiceCliente.split("_")[1] || "").trim();
const shortCode =
suffix || (codiceCliente ? codiceCliente.charAt(0) : "--");
// Nome - I - 123
return `${nome} - ${shortCode} - ${id}`;
return `${nome.trim()} - ${shortCode} (ID: ${id})`;
}
// Cache of resolved client names: id → name
@@ -117,9 +110,6 @@
ids.add(String(val));
}
}
if (row.fixedFields && row.fixedFields.ClienteFatturazione) {
ids.add(String(row.fixedFields.ClienteFatturazione));
}
});
// Batch resolve via single request per ID
@@ -143,54 +133,7 @@
}),
);
}
// Ranking inizio/centro/fine per select con dati già in memoria.
// Restituisce { matcher, sorter } da passare a select2().
function rankedLocalSelect2() {
return {
matcher: function (params, data) {
const term = $.trim(params.term || "").toLowerCase();
if (term === "") return data;
if (typeof data.text === "undefined") return null;
// AND multi-termine, posizione indipendente
const text = data.text.toLowerCase();
const terms = term.split(/\s+/).filter(Boolean);
const all = terms.every((t) => text.indexOf(t) !== -1);
return all ? data : null;
},
sorter: function (results) {
const term = $(
".select2-container--open .select2-search__field",
).val();
const search = (term || "").toLowerCase().trim();
if (!search) return results;
const first = search.split(/\s+/)[0];
function score(text) {
const t = (text || "").toLowerCase();
if (t === search) return 0; // match esatto
if (t.indexOf(first) === 0) return 1; // inizia col termine
if (new RegExp("(^|\\s)" + escapeRegExp(first)).test(t))
return 2; // inizio parola
return 3; // in mezzo
}
return results.slice().sort(function (a, b) {
const sa = score(a.text);
const sb = score(b.text);
if (sa !== sb) return sa - sb;
return (a.text || "").localeCompare(b.text || "", "it", {
sensitivity: "base",
});
});
},
};
}
function escapeRegExp(s) {
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function sortSelect2ResultsByStart(data) {
const term = $(".select2-container--open .select2-search__field").val();
@@ -213,49 +156,32 @@
return textA.localeCompare(textB, "it", { sensitivity: "base" });
});
}
// Mappa: quale filtro Inattivo applicare per ogni campo cliente
const clientFieldTipoMap = {
ClienteFornitore: "attivo",
ClienteAnalisi: "inattivo",
ClienteFatturazione: "attivo",
};
// Select2 AJAX config factory for client selects (parametrizzata su "tipo")
function clientSelect2ConfigFor(tipo) {
return {
placeholder: "Search client...",
allowClear: true,
width: "100%",
minimumInputLength: 0,
sorter: sortSelect2ResultsByStart,
dropdownCssClass: "select2-dropdown-smaller",
ajax: {
url: "search_clienti.php",
dataType: "json",
delay: 150,
data: function (params) {
const d = { q: params.term || "", limit: 20 };
if (tipo) d.tipo = tipo;
return d;
},
processResults: function (data) {
const results = (data.results || []).map((item) => ({
...item,
text: formatClientLabel(item),
}));
return { results: results };
},
cache: true,
// Select2 AJAX config for client selects
const clientSelect2Config = {
placeholder: "Search client...",
allowClear: true,
width: "100%",
minimumInputLength: 0,
sorter: sortSelect2ResultsByStart,
dropdownCssClass: "select2-dropdown-smaller",
ajax: {
url: "search_clienti.php",
dataType: "json",
delay: 150,
data: function (params) {
return { q: params.term || "", limit: 20 };
},
};
}
processResults: function (data) {
const results = (data.results || []).map((item) => ({
...item,
text: formatClientLabel(item),
}));
// Helper: inizializza Select2 su un elemento leggendo il tipo da data-client-tipo
function initClientSelect2(el) {
const tipo = el.dataset.clientTipo || "";
$(el).select2(clientSelect2ConfigFor(tipo));
}
return { results: results };
},
cache: true,
},
};
async function loadClientData() {
// No longer loads all clients — AJAX Select2 handles search
@@ -289,7 +215,6 @@
getParams: (cid) => ({ id_cliente: cid }),
},
ClienteFornitore: { source: "clients" },
ClienteFatturazione: { source: "clients" },
ClienteAnalisi: { source: "clients" },
};
@@ -439,6 +364,7 @@
allowClear: true,
width: "100%",
minimumInputLength: 0,
sorter: sortSelect2ResultsByStart,
ajax: {
url: "search_customfield_values.php",
dataType: "json",
@@ -610,7 +536,6 @@
sel.className =
"cell-input dropdown-select client-select searchable-client fornitore-select";
sel.dataset.currentValue = row.cliente_fornitore_id || "";
sel.dataset.clientTipo = "attivo";
sel.innerHTML = buildClientOptionsHTML(
row.cliente_fornitore_id,
);
@@ -706,13 +631,12 @@
if (config && config.source === "clients") {
const reqCls = col.isRequired ? " required-input" : "";
const req = col.isRequired ? " required" : "";
const tipo = clientFieldTipoMap[col.key] || "";
let opts = '<option value="">Select...</option>';
if (value) {
const label = clientNameCache[value] || value;
opts += `<option value="${esc(String(value))}" selected>${esc(String(label))}</option>`;
}
return `<select class="cell-input manual-input fixed-input searchable-client api-fixed-select${reqCls}" data-fixed-key="${escAttr(col.key)}" data-current-value="${escAttr(value)}" data-client-tipo="${escAttr(tipo)}"${req}>${opts}</select>`;
return `<select class="cell-input manual-input fixed-input searchable-client api-fixed-select${reqCls}" data-fixed-key="${escAttr(col.key)}" data-current-value="${escAttr(value)}"${req}>${opts}</select>`;
}
// Select — build from cache
@@ -730,7 +654,7 @@
const reqCls = col.isRequired ? " required-input" : "";
const req = col.isRequired ? " required" : "";
return `<select class="cell-input manual-input fixed-input searchable-fixed ${selectClass}${reqCls}" data-fixed-key="${escAttr(col.key)}" data-current-value="${escAttr(value)}"${req}>${options}</select>`;
return `<select class="cell-input manual-input fixed-input ${selectClass}${reqCls}" data-fixed-key="${escAttr(col.key)}" data-current-value="${escAttr(value)}"${req}>${options}</select>`;
}
function buildDropdownOptionsHTML(fieldId, selectedValue) {
@@ -755,7 +679,9 @@
div.style.position = "relative";
let html = "";
html += `<button type="button" class="export-lims-btn action-btn" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" title="${isExported ? "Already exported" : "Export to LIMS"}" style="background:${isExported ? "#ccc" : "#eb0b0b"}; color:white; border:none; border-radius:5px; cursor:${isExported ? "not-allowed" : "pointer"}; ${isExported ? "opacity:0.5;" : ""}" ${isExported ? "disabled" : ""}><i class="fas fa-upload"></i></button>`;
if (meta.isAdmin) {
html += `<button type="button" class="export-lims-btn action-btn" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" title="${isExported ? "Already exported" : "Export to LIMS"}" style="background:${isExported ? "#ccc" : "#eb0b0b"}; color:white; border:none; border-radius:5px; cursor:${isExported ? "not-allowed" : "pointer"}; ${isExported ? "opacity:0.5;" : ""}" ${isExported ? "disabled" : ""}><i class="fas fa-upload"></i></button>`;
}
html += `<button type="button" class="save-btn action-btn" data-row="${rowIndex}" title="Save" style="background:#28a745; color:white; border:none; border-radius:5px; cursor:pointer;"><i class="fas fa-save"></i></button>`;
html += `<button type="button" class="photos-btn action-btn" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" title="Photos" style="background:#007bff; color:white; border:none; border-radius:5px; cursor:pointer;"><i class="fas fa-camera"></i></button>`;
html += `<button type="button" class="parts-btn action-btn" data-row="${rowIndex}" data-iddatadb="${row.iddatadb}" title="Parts" style="background:#ffc107; color:white; border:none; border-radius:5px; cursor:pointer;"><i class="fas fa-puzzle-piece"></i></button>`;
@@ -1014,11 +940,6 @@
if (!row) return;
// Salta le righe nascoste (filtrate / non nella selezione ristretta):
// non devono essere sincronizzate né marcate dirty.
const rowEl = cell.closest(".grid-row");
if (rowEl && rowEl.style.display === "none") return;
const colType = cell.dataset.colType;
const colKey = cell.dataset.col;
const input = cell.querySelector(".cell-input");
@@ -1151,7 +1072,7 @@
`<button type="button" class="propagate-btn" data-column="idclient" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
} else if (col.type === "cliente_fornitore_id") {
cell.innerHTML =
`<select class="custom-field dropdown-select client-select searchable-client" data-column="cliente_fornitore_id" data-client-tipo="attivo" id="clienteFornitoreSelect"><option value="">Select a supplier...</option></select>` +
`<select class="custom-field dropdown-select client-select searchable-client" data-column="cliente_fornitore_id" id="clienteFornitoreSelect"><option value="">Select a supplier...</option></select>` +
`<button type="button" class="propagate-btn" data-column="cliente_fornitore_id" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
} else if (col.type === "fixed" && col.dataType === "DATE") {
cell.innerHTML =
@@ -1160,14 +1081,8 @@
} else if (col.type === "fixed") {
const isApiField = !!fixedFieldApiConfig[col.key];
if (isApiField) {
const tipo = clientFieldTipoMap[col.key] || "";
const isClientSourced =
fixedFieldApiConfig[col.key]?.source === "clients";
const clientCls = isClientSourced
? " client-select searchable-client"
: "";
cell.innerHTML =
`<select class="custom-field dropdown-select api-fixed-select fixed-top${clientCls}" data-column="fixed_${col.key}" data-fixed-key="${col.key}" data-client-tipo="${tipo}"><option value="">Seleziona...</option></select>` +
`<select class="custom-field dropdown-select api-fixed-select fixed-top" data-column="fixed_${col.key}" data-fixed-key="${col.key}"><option value="">Seleziona...</option></select>` +
`<button type="button" class="propagate-btn" data-column="fixed_${col.key}" data-col-index="${colIdx}"><i class="fas fa-arrow-down"></i></button>`;
} else {
cell.innerHTML =
@@ -1202,12 +1117,12 @@
const clientSel = document.getElementById("clientSelect");
if (clientSel) {
clientSel.innerHTML = buildClientOptionsHTML(meta.defaultIdclient);
initClientSelect2(clientSel);
$(clientSel).select2(clientSelect2Config);
}
const fornitSel = document.getElementById("clienteFornitoreSelect");
if (fornitSel) {
fornitSel.innerHTML = buildClientOptionsHTML("");
initClientSelect2(fornitSel);
$(fornitSel).select2(clientSelect2Config);
}
// Fixed field selects in top row
@@ -1217,7 +1132,7 @@
// Client-sourced → init as AJAX Select2
if (config && config.source === "clients") {
initClientSelect2(sel);
$(sel).select2(clientSelect2Config);
return;
}
@@ -1250,12 +1165,6 @@
items.forEach((item) =>
sel.add(new Option(item.text, item.id)),
);
$(sel).select2({
placeholder: "Seleziona...",
allowClear: true,
width: "100%",
...rankedLocalSelect2(),
});
}
});
@@ -1278,59 +1187,6 @@
function attachEvents() {
if (!rowContainer) return;
// ── Auto-copy: Sample Arrival date/time → Unlock date/time ──────────
// Coppie basate sui fieldId dei campi (source → target)
const AUTO_COPY_FIELD_PAIRS = [
{ from: 259, to: 260 }, // Sample Arrival date → Unlock Date
{ from: 348, to: 349 }, // Sample Arrival time → Unlock time
];
// Risolve fieldId → col.key (mapping id) usando le definizioni colonne
function colKeyByFieldId(fieldId) {
const col = columns.find(
(c) =>
(c.type === "detail" || c.type === "main_field") &&
String(c.fieldId) === String(fieldId),
);
return col ? String(col.key) : null;
}
// Precalcola le coppie come col.key (sourceKey → targetKey)
var AUTO_COPY_KEY_PAIRS = AUTO_COPY_FIELD_PAIRS.map((p) => ({
fromKey: colKeyByFieldId(p.from),
toKey: colKeyByFieldId(p.to),
})).filter((p) => p.fromKey && p.toKey);
function applyAutoCopy(rowIndex, changedColKey, value) {
const pair = AUTO_COPY_KEY_PAIRS.find(
(p) => p.fromKey === String(changedColKey),
);
if (!pair) return;
const row = data[rowIndex];
if (!row) return;
if (!row.details) row.details = {};
row.details[pair.toKey] = value;
row._dirty = true;
// Aggiorna la cella target visibile, se presente
const targetCell = rowContainer.querySelector(
`.grid-cell[data-row="${rowIndex}"][data-col="${pair.toKey}"]`,
);
if (targetCell) {
const input = targetCell.querySelector(".cell-input");
if (input) {
if ($(input).hasClass("select2-hidden-accessible")) {
$(input).val(value).trigger("change.select2");
} else {
input.value = value;
}
}
targetCell.classList.add("cell-changed");
}
}
// Cell value changes → write to gridData
rowContainer.addEventListener("change", function (e) {
const cell = e.target.closest(".grid-cell");
@@ -1345,7 +1201,6 @@
data[rowIndex].mainFieldValue = value;
}
setDetailValue(rowIndex, colKey, value);
applyAutoCopy(rowIndex, colKey, value);
} else if (colType === "fixed") {
setFixedValue(rowIndex, colKey, value);
} else if (colType === "idclient") {
@@ -1499,11 +1354,6 @@
if (column === "idclient") {
data.forEach((row) => {
if (
window.isTargetGridId &&
!window.isTargetGridId(row.iddatadb)
)
return;
const oldClientId = row.idclient || "";
row.idclient = value;
@@ -1537,11 +1387,6 @@
if (column === "cliente_fornitore_id") {
data.forEach((row) => {
if (
window.isTargetGridId &&
!window.isTargetGridId(row.iddatadb)
)
return;
row.cliente_fornitore_id = value;
row._dirty = true;
});
@@ -1555,11 +1400,6 @@
const fixedKey = column.replace("fixed_", "");
data.forEach((row) => {
if (
window.isTargetGridId &&
!window.isTargetGridId(row.iddatadb)
)
return;
if (!row.fixedFields) row.fixedFields = {};
row.fixedFields[fixedKey] = value;
row._dirty = true;
@@ -1571,23 +1411,10 @@
}
if (col && (col.type === "detail" || col.type === "main_field")) {
const autoPair = AUTO_COPY_KEY_PAIRS.find(
(p) => p.fromKey === String(col.key),
);
data.forEach((row) => {
if (
window.isTargetGridId &&
!window.isTargetGridId(row.iddatadb)
)
return;
if (!row.details) row.details = {};
row.details[col.key] = value;
if (autoPair) {
row.details[autoPair.toKey] = value;
}
if (col.type === "main_field") {
row.mainFieldValue = value;
}
@@ -1758,7 +1585,7 @@
$(this)
.find(".searchable-client:not(.select2-hidden-accessible)")
.each(function () {
initClientSelect2(this);
$(this).select2(clientSelect2Config);
});
$(this)
.find(".searchable-dropdown:not(.select2-hidden-accessible)")
@@ -1766,18 +1593,9 @@
const fieldId = this.dataset.fieldId;
if (fieldId) $(this).select2(sceltaSelect2Config(fieldId));
});
$(this)
.find(".searchable-fixed:not(.select2-hidden-accessible)")
.each(function () {
$(this).select2({
placeholder: "Seleziona...",
allowClear: true,
width: "100%",
...rankedLocalSelect2(),
});
});
});
}
// ── Column resize ──────────────────────────────────────────────────────
function initColumnResizers() {
@@ -1908,127 +1726,3 @@
},
};
})();
// ===================
// NAVIGAZIONE GRIGLIA CON FRECCE TASTIERA
// ===================
(function () {
// Naviga solo tra input di testo/numero/time e date-picker.
// I Select2 (client, fornitore, SceltaMultipla) sono esclusi:
// lì le frecce servono al dropdown.
function isNavigable(el) {
if (!el || !el.classList) return false;
if (!el.classList.contains("cell-input")) return false;
// Escludi i select (nativi o Select2)
if (el.tagName === "SELECT") return false;
if (el.classList.contains("select2-hidden-accessible")) return false;
return true;
}
// Tutte le celle navigabili della riga, ordinate per data-index
function navCellsInRow($row) {
return $row.find(".grid-cell[data-index]").filter(function () {
const input = this.querySelector(".cell-input");
return isNavigable(input);
});
}
function focusCellInput($cell) {
if (!$cell || !$cell.length) return;
const input = $cell.get(0).querySelector(".cell-input");
if (!input) return;
input.focus();
if (input.type !== "date" && typeof input.select === "function") {
try {
input.select();
} catch (e) {}
}
}
function atStart(el) {
if (el.type === "date" || el.type === "number" || el.type === "time")
return true;
return el.selectionStart === 0 && el.selectionEnd === 0;
}
function atEnd(el) {
if (el.type === "date" || el.type === "number" || el.type === "time")
return true;
const len = (el.value || "").length;
return el.selectionStart === len && el.selectionEnd === len;
}
$(document).on("keydown", "#gridRowContainer .cell-input", function (e) {
const el = this;
if (!isNavigable(el)) return;
// Ignora se un dropdown Select2 è aperto
if ($(".select2-container--open").length) return;
const key = e.key;
if (
key !== "ArrowUp" &&
key !== "ArrowDown" &&
key !== "ArrowLeft" &&
key !== "ArrowRight"
) {
return;
}
const $cell = $(el).closest(".grid-cell");
const $row = $cell.closest(".grid-row");
if (!$cell.length || !$row.length) return;
const colIndex = $cell.attr("data-index");
// --- SU / GIÙ: stessa colonna (data-index), riga adiacente ---
if (key === "ArrowUp" || key === "ArrowDown") {
const $targetRow =
key === "ArrowUp"
? $row.prev(".grid-row")
: $row.next(".grid-row");
if (!$targetRow.length) return;
e.preventDefault();
// Cella stessa colonna nella riga di destinazione
let $target = $targetRow.find(
`.grid-cell[data-index="${colIndex}"]`,
);
// Se quella colonna non è navigabile (es. è un select),
// cerca la più vicina navigabile nella riga
if (
!$target.length ||
!isNavigable($target.get(0).querySelector(".cell-input"))
) {
const $cells = navCellsInRow($targetRow);
if (!$cells.length) return;
$target = $cells.first();
}
focusCellInput($target);
return;
}
// --- SINISTRA / DESTRA: cella navigabile precedente/successiva nella riga ---
const $cells = navCellsInRow($row);
const curPos = $cells.index($cell);
if (curPos === -1) return;
if (key === "ArrowLeft") {
if (!atStart(el)) return;
if (curPos <= 0) return;
e.preventDefault();
focusCellInput($cells.eq(curPos - 1));
return;
}
if (key === "ArrowRight") {
if (!atEnd(el)) return;
if (curPos >= $cells.length - 1) return;
e.preventDefault();
focusCellInput($cells.eq(curPos + 1));
return;
}
});
})();
+13 -142
View File
@@ -130,60 +130,10 @@
color: #6c757d;
padding: 10px 0;
}
.section-title {
font-size: 13px;
font-weight: 600;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.5px;
width: 100%;
margin-top: 6px;
margin-bottom: 2px;
padding-bottom: 4px;
border-bottom: 1px solid #eee;
}
.section-title:first-child {
margin-top: 0;
}
.template-btn-wrapper {
position: relative;
display: inline-flex;
}
.favorite-star {
position: absolute;
top: -6px;
right: -6px;
width: 22px;
height: 22px;
background: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
font-size: 13px;
color: #ccc;
z-index: 2;
transition: color 0.15s, transform 0.15s;
}
.favorite-star:hover {
transform: scale(1.15);
}
.favorite-star.active {
color: #ffc107;
}
</style>
</head>
<body>
<?php include('include/popup.php'); ?>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
@@ -281,25 +231,13 @@
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function createButtonWrapper(template) {
function createButton(template) {
const sizeClass = getSizeClass(template.button_size);
const sourceType = (template.source_type || '').toUpperCase();
const iconClass = getTemplateIcon(sourceType);
const isFavorite = !!parseInt(template.is_favorite);
const wrapper = document.createElement("div");
wrapper.className = "template-btn-wrapper";
const btn = document.createElement("a");
btn.href = (sourceType === 'PDF') ?
`import_pdf.php?id=${template.id}` :
`import_xls2.php?id=${template.id}`;
btn.href = `import_xls2.php?id=${template.id}`;
btn.className = `btn ${sizeClass}`;
btn.style.backgroundColor = template.button_bg_color || '#0d6efd';
btn.style.color = template.button_text_color || '#ffffff';
@@ -307,62 +245,17 @@
btn.setAttribute("data-source-type", sourceType);
btn.innerHTML = `
<i class="${iconClass} template-icon"></i>
<span>${escapeHtml(template.button_label || 'Unnamed')}</span>
`;
<i class="${iconClass} template-icon"></i>
<span>${escapeHtml(template.button_label || 'Unnamed')}</span>
`;
const star = document.createElement("span");
star.className = "favorite-star" + (isFavorite ? " active" : "");
star.innerHTML = `<i class="bx ${isFavorite ? 'bxs-star' : 'bx-star'}"></i>`;
star.setAttribute("data-template-id", template.id);
star.title = isFavorite ? "Rimuovi dai preferiti" : "Aggiungi ai preferiti";
star.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
toggleFavorite(template.id);
});
wrapper.appendChild(btn);
wrapper.appendChild(star);
return wrapper;
return btn;
}
function toggleFavorite(templateId) {
const template = allTemplates.find(t => String(t.id) === String(templateId));
if (!template) return;
const newState = !parseInt(template.is_favorite);
// Optimistic update
template.is_favorite = newState ? 1 : 0;
renderTemplates(document.getElementById("searchInput").value.toLowerCase().trim());
fetch("toggle_template_favorite.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
template_id: templateId,
is_favorite: newState
})
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("Failed to toggle favorite:", data.message);
// Rollback in caso di errore
template.is_favorite = newState ? 0 : 1;
renderTemplates(document.getElementById("searchInput").value.toLowerCase().trim());
}
})
.catch(error => {
console.error("Fetch error toggling favorite:", error);
template.is_favorite = newState ? 0 : 1;
renderTemplates(document.getElementById("searchInput").value.toLowerCase().trim());
});
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function clearContainers() {
@@ -371,26 +264,6 @@
});
}
function renderSection(container, title, templates) {
if (templates.length === 0) return;
const heading = document.createElement("div");
heading.className = "section-title";
heading.textContent = title;
container.appendChild(heading);
const group = document.createElement("div");
group.className = "template-buttons";
group.style.marginTop = "0";
group.style.paddingTop = "0";
templates.forEach(template => {
group.appendChild(createButtonWrapper(template));
});
container.appendChild(group);
}
function renderTemplates(searchValue = '') {
clearContainers();
@@ -422,11 +295,9 @@
return;
}
const favorites = grouped[type].filter(t => parseInt(t.is_favorite) === 1);
const others = grouped[type].filter(t => parseInt(t.is_favorite) !== 1);
renderSection(container, "★ Preferiti", favorites);
renderSection(container, "Altri", others);
grouped[type].forEach(template => {
container.appendChild(createButton(template));
});
});
}
+2 -82
View File
@@ -40,29 +40,6 @@ error_log("Excelrows: " . json_encode($excelrows));
$user_id = $iduserlogin ?? 1;
/**
* Normalizza un nome colonna per il confronto header Excel <-> mapping.
* Risolve alla radice i mismatch causati da caratteri invisibili:
* - non-breaking space (U+00A0), narrow NBSP (U+202F), figure space (U+2007), ecc. -> spazio normale
* - tab, newline e altri whitespace -> spazio normale
* - spazi multipli consecutivi -> un solo spazio
* - trim ai bordi
* NB: usata SOLO per il matching, non altera i valori importati.
*/
function normalizeColName($s): string
{
$s = (string)$s;
// Converte i principali spazi Unicode "insidiosi" in spazio normale
$s = str_replace(
["\xC2\xA0", "\xE2\x80\xAF", "\xE2\x80\x87", "\xE2\x80\x89", "\xE2\x80\x8A", "\xEF\xBB\xBF"],
' ',
$s
);
// Qualsiasi sequenza di whitespace (spazi, tab, newline) -> singolo spazio
$s = preg_replace('/\s+/u', ' ', $s);
return trim((string)$s);
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
@@ -128,12 +105,7 @@ foreach ($selected_rows as $rowIndex) {
$fieldValue = null;
if (!$mapping['is_manual']) {
$excelColumn = trim($mapping['excel_column']);
// Match robusto: normalizza header e mapping (NBSP, spazi doppi, tab...)
// così un solo/doppio spazio o un non-breaking space non rompono il confronto.
$excelColumnIndex = array_search(
normalizeColName($excelColumn),
array_map('normalizeColName', $columns)
);
$excelColumnIndex = array_search($excelColumn, array_map('trim', $columns));
if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
$fieldValue = $row[$excelColumnIndex];
error_log("Found Excel column '$excelColumn' at index $excelColumnIndex, value: " . var_export($fieldValue, true));
@@ -182,60 +154,8 @@ foreach ($selected_rows as $rowIndex) {
}
}
// ------------------------------------------------------------
// AUTO SET: schema field_id = 244 (Accettatore) con auth_users.lims_user_id
// Spostato dal vecchio flusso (import_edit.php) al flusso nuovo.
// Autocompila il custom field 244 con il lims_user_id dell'utente loggato.
// ------------------------------------------------------------
try {
// 1) Leggi il lims_user_id dell'utente loggato
$stmtUser = $pdo->prepare("SELECT lims_user_id FROM auth_users WHERE id = ? LIMIT 1");
$stmtUser->execute([(int)$user_id]);
$limsUserId = $stmtUser->fetchColumn();
// normalize
$limsUserId = ($limsUserId !== false && $limsUserId !== null && $limsUserId !== '') ? (string)$limsUserId : '';
if ($limsUserId !== '' && !empty($insertedIds)) {
// 2) Trova il mapping_id per field_id = 244 in questo template
$stmtMap = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ? AND field_id = 244 LIMIT 1");
$stmtMap->execute([(int)$template_id]);
$mappingId244 = (int)$stmtMap->fetchColumn();
if ($mappingId244 > 0) {
// 3) Upsert del valore in import_data_details per ogni riga inserita
$pdo->beginTransaction();
$updStmt = $pdo->prepare("UPDATE import_data_details SET field_value = ? WHERE id = ? AND mapping_id = ?");
$insStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
foreach ($insertedIds as $iddatadb) {
$iddatadb = (int)$iddatadb;
if ($iddatadb <= 0) continue;
$updStmt->execute([$limsUserId, $iddatadb, $mappingId244]);
if ($updStmt->rowCount() === 0) {
$insStmt->execute([$iddatadb, $mappingId244, $limsUserId]);
}
}
$pdo->commit();
} else {
error_log("[AUTO FIELD 244] Nessun mapping field_id=244 per template $template_id");
}
} else {
error_log("[AUTO FIELD 244] limsUserId vuoto per user_id=$user_id o nessuna riga inserita");
}
} catch (Exception $e) {
// Non bloccare l'import se questo fallisce; logga soltanto
error_log("[AUTO FIELD 244] " . $e->getMessage());
if ($pdo->inTransaction()) $pdo->rollBack();
}
$_SESSION['inserted_ids'] = $insertedIds;
header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode));
exit;
?>
-173
View File
@@ -1,173 +0,0 @@
<?php
ini_set('display_errors', 0); // in AJAX gli errori devono NON sporcare il JSON
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/import_debug.log');
include('include/headscript.php');
header('Content-Type: application/json');
// Legge il body JSON
$input = json_decode(file_get_contents('php://input'), true);
if (
!$input ||
!isset($input['template_id']) ||
!isset($input['importreferencecode']) ||
!isset($input['filename']) ||
!isset($input['columns']) ||
!isset($input['batch_rows']) ||
!isset($input['batch_excelrows'])
) {
echo json_encode(['ok' => false, 'error' => 'Richiesta non valida']);
exit;
}
$template_id = intval($input['template_id']);
$importReferenceCode = (string)$input['importreferencecode'];
$newFilename = (string)$input['filename'];
$columns = $input['columns']; // array
$batchRows = $input['batch_rows']; // array di righe (ognuna array di celle)
$batchExcelrows = $input['batch_excelrows']; // array di excelrow paralleli a batchRows
$user_id = $iduserlogin ?? 1;
function normalizeColName($s): string
{
$s = (string)$s;
$s = str_replace(
["\xC2\xA0", "\xE2\x80\xAF", "\xE2\x80\x87", "\xE2\x80\x89", "\xE2\x80\x8A", "\xEF\xBB\xBF"],
' ',
$s
);
$s = preg_replace('/\s+/u', ' ', $s);
return trim((string)$s);
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Recupera i mapping
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
$stmt->execute([$template_id]);
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($allMappings)) {
echo json_encode(['ok' => false, 'error' => 'Nessun mapping trovato per il template']);
exit;
}
// idclient di default (una volta sola)
$template_stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
$template_stmt->execute([$template_id]);
$tpl = $template_stmt->fetch(PDO::FETCH_ASSOC);
$default_idclient = $tpl['idclient'] ?? null;
// lims_user_id per il campo 244 (una volta sola)
$stmtUser = $pdo->prepare("SELECT lims_user_id FROM auth_users WHERE id = ? LIMIT 1");
$stmtUser->execute([(int)$user_id]);
$limsUserId = $stmtUser->fetchColumn();
$limsUserId = ($limsUserId !== false && $limsUserId !== null && $limsUserId !== '') ? (string)$limsUserId : '';
$stmtMap = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ? AND field_id = 244 LIMIT 1");
$stmtMap->execute([(int)$template_id]);
$mappingId244 = (int)$stmtMap->fetchColumn();
$insertedIds = [];
try {
$pdo->beginTransaction();
$insDatadb = $pdo->prepare("INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$insDetail = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
foreach ($batchRows as $i => $row) {
$excelrow = $batchExcelrows[$i] ?? null;
if ($row === null || $excelrow === null) {
continue;
}
$insDatadb->execute([
$template_id,
$importReferenceCode,
$newFilename,
'i',
$user_id,
null,
date('Y-m-d'),
$excelrow,
$default_idclient
]);
$iddatadb = $pdo->lastInsertId();
$insertedIds[] = $iddatadb;
foreach ($allMappings as $mapping) {
$fieldValue = null;
if (!$mapping['is_manual']) {
$excelColumn = trim($mapping['excel_column']);
$excelColumnIndex = array_search(
normalizeColName($excelColumn),
array_map('normalizeColName', $columns)
);
if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
$fieldValue = $row[$excelColumnIndex];
} else {
$fieldValue = $mapping['manual_default'] ?? '';
}
switch ($mapping['data_type']) {
case 'INT':
$fieldValue = is_numeric($fieldValue) ? (int)$fieldValue : ($mapping['manual_default'] ?? 0);
break;
case 'DATE':
$fieldValue = !empty($fieldValue) ? date('Y-m-d', strtotime($fieldValue)) : ($mapping['manual_default'] === 'today' ? date('Y-m-d') : ($mapping['manual_default'] ?? ''));
break;
case 'CHAR':
$fieldValue = !empty($fieldValue) ? substr((string)$fieldValue, 0, 1) : ($mapping['manual_default'] ?? '');
break;
case 'Testo':
case 'VARCHAR':
default:
$fieldValue = !empty($fieldValue) ? (string)$fieldValue : ($mapping['manual_default'] ?? '');
break;
}
} else {
$fieldValue = $mapping['manual_default'] ?? '';
if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') {
$fieldValue = date('Y-m-d');
}
}
if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') {
if ($mapping['auto_value'] === 'import_date') {
$fieldValue = date('Y-m-d');
} elseif ($mapping['auto_value'] === 'import_time') {
$fieldValue = date('H:i');
}
}
$insDetail->execute([$iddatadb, $mapping['id'], $fieldValue]);
}
// Campo 244 (Accettatore) con lims_user_id
if ($limsUserId !== '' && $mappingId244 > 0) {
$insDetail->execute([$iddatadb, $mappingId244, $limsUserId]);
}
}
$pdo->commit();
echo json_encode([
'ok' => true,
'inserted' => count($insertedIds)
]);
exit;
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
error_log("[BATCH IMPORT] " . $e->getMessage());
echo json_encode([
'ok' => false,
'error' => $e->getMessage()
]);
exit;
}
-321
View File
@@ -1,321 +0,0 @@
<?php
include('include/headscript.php');
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: template_dashboard.php?status=error&message=" . urlencode("Invalid ID"));
exit;
}
$id = intval($_GET['id']);
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT id, name, source_type, idclient, sample_pdf FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
exit;
}
$sourceType = strtoupper($template['source_type'] ?? '');
if ($sourceType !== 'PDF') {
// Non è un template PDF: rimanda al flusso corretto
header("Location: import_xls2.php?id=" . $id);
exit;
}
// Conta i mapping con riquadri, per avvisare se non è configurato
$stmt = $pdo->prepare("SELECT COUNT(*) FROM template_mapping WHERE template_id = ? AND pdf_regions IS NOT NULL AND pdf_regions <> ''");
$stmt->execute([$id]);
$mappedRegions = (int)$stmt->fetchColumn();
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title><?= htmlspecialchars($template['name']) ?> - PDF Import</title>
<style>
#dropZone {
border: 2px dashed #adb5bd;
border-radius: 10px;
padding: 40px 20px;
text-align: center;
color: #6c757d;
background: #f8f9fa;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
#dropZone.dragover {
background: #e7f1ff;
border-color: #0d6efd;
color: #0d6efd;
}
#dropZone .big {
font-size: 42px;
line-height: 1;
margin-bottom: 10px;
}
.file-row {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border: 1px solid #e5e5e5;
border-radius: 8px;
margin-bottom: 8px;
font-size: 14px;
}
.file-row .status-icon {
font-size: 18px;
width: 24px;
text-align: center;
}
.file-row .fname {
font-weight: 600;
flex: 0 0 260px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-row .fvals {
flex: 1;
color: #555;
font-size: 13px;
}
.file-row.ok {
background: #eaffea;
}
.file-row.err {
background: #ffecec;
}
.file-row.pending {
background: #fff8e1;
}
.val-pair {
display: inline-block;
margin-right: 12px;
}
.val-pair b {
color: #333;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0"><?= htmlspecialchars($template['name']) ?> <span class="badge bg-danger">PDF</span></h6>
<small>Template ID: <?= $id ?> — Ogni PDF diventa una riga importata.</small>
</div>
<div class="card-body">
<?php if ($mappedRegions === 0): ?>
<div class="alert alert-warning">
Questo template non ha ancora riquadri PDF configurati. Configura il mapping prima di importare.
</div>
<?php endif; ?>
<div id="dropZone">
<div class="big">📄⬇</div>
<div><strong>Trascina qui i PDF</strong> oppure clicca per selezionarli</div>
<div style="font-size:13px; margin-top:6px;">Puoi caricarne più di uno insieme</div>
<input type="file" id="pdfInput" accept="application/pdf" multiple style="display:none;">
</div>
<div class="d-flex align-items-center justify-content-between mt-3">
<span class="badge bg-secondary" id="fileCount">0 file</span>
<button id="startImportBtn" class="btn btn-primary" disabled>Importa PDF</button>
</div>
<div id="fileList" class="mt-3"></div>
<div class="mt-4">
<a href="imported.php?id=<?= $id ?>" class="btn btn-warning">Vai agli Imported</a>
<a href="template_dashboard.php" class="btn btn-secondary">Dashboard</a>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script>
(function() {
const TEMPLATE_ID = <?= (int)$id ?>;
const dropZone = document.getElementById('dropZone');
const pdfInput = document.getElementById('pdfInput');
const fileList = document.getElementById('fileList');
const fileCount = document.getElementById('fileCount');
const startBtn = document.getElementById('startImportBtn');
let selectedFiles = [];
function updateCount() {
fileCount.textContent = selectedFiles.length + ' file';
startBtn.disabled = selectedFiles.length === 0;
}
function renderList() {
fileList.innerHTML = '';
selectedFiles.forEach((f, idx) => {
const row = document.createElement('div');
row.className = 'file-row pending';
row.id = 'file-row-' + idx;
row.innerHTML = `
<span class="status-icon"></span>
<span class="fname">${escapeHtml(f.name)}</span>
<span class="fvals">In attesa...</span>
`;
fileList.appendChild(row);
});
}
function escapeHtml(s) {
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function addFiles(files) {
for (const f of files) {
if (f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf')) {
selectedFiles.push(f);
}
}
updateCount();
renderList();
}
// Click per selezionare
dropZone.addEventListener('click', () => pdfInput.click());
pdfInput.addEventListener('change', (e) => addFiles(e.target.files));
// Drag & drop
['dragenter', 'dragover'].forEach(ev => {
dropZone.addEventListener(ev, (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.add('dragover');
});
});
['dragleave', 'drop'].forEach(ev => {
dropZone.addEventListener(ev, (e) => {
e.preventDefault();
e.stopPropagation();
dropZone.classList.remove('dragover');
});
});
dropZone.addEventListener('drop', (e) => {
addFiles(e.dataTransfer.files);
});
// Genera un importreferencecode condiviso da tutto il gruppo
function makeImportRef() {
return new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14) +
'-' + Math.random().toString(36).slice(2, 10);
}
startBtn.addEventListener('click', async () => {
if (selectedFiles.length === 0) return;
startBtn.disabled = true;
startBtn.textContent = 'Importazione...';
const importRef = makeImportRef();
const formData = new FormData();
formData.append('template_id', TEMPLATE_ID);
formData.append('importreferencecode', importRef);
selectedFiles.forEach(f => formData.append('pdf_files[]', f));
// segna tutte le righe come "in corso"
selectedFiles.forEach((f, idx) => {
const row = document.getElementById('file-row-' + idx);
if (row) row.querySelector('.fvals').textContent = 'Elaborazione...';
});
try {
const resp = await fetch('process_import_pdf.php', {
method: 'POST',
body: formData
});
const data = await resp.json();
if (!data.ok) {
alert('Errore: ' + (data.error || 'sconosciuto'));
startBtn.disabled = false;
startBtn.textContent = 'Importa PDF';
return;
}
// Aggiorna ogni riga con l'esito (l'ordine dei results segue quello inviato)
data.results.forEach((res, idx) => {
const row = document.getElementById('file-row-' + idx);
if (!row) return;
const icon = row.querySelector('.status-icon');
const vals = row.querySelector('.fvals');
if (res.ok) {
row.className = 'file-row ok';
icon.textContent = '✅';
const pairs = Object.entries(res.values || {})
.map(([k, v]) => `<span class="val-pair"><b>${escapeHtml(k)}:</b> ${escapeHtml(v || '—')}</span>`)
.join('');
vals.innerHTML = pairs || 'Importato (nessun valore estratto)';
} else {
row.className = 'file-row err';
icon.textContent = '❌';
vals.textContent = res.error || 'Errore sconosciuto';
}
});
startBtn.textContent = 'Importa altri PDF';
startBtn.disabled = false;
// svuota la selezione (i file sono stati processati)
selectedFiles = [];
pdfInput.value = '';
updateCount();
} catch (err) {
console.error(err);
alert('Errore di rete durante l\'importazione.');
startBtn.disabled = false;
startBtn.textContent = 'Importa PDF';
}
});
})();
</script>
</body>
</html>
+20 -172
View File
@@ -50,9 +50,6 @@ error_log("Loaded template: " . print_r($template, true));
border-radius: 0.25rem;
background: #f8f9fa;
display: none;
position: sticky;
top: 0;
z-index: 1020;
}
.top-scrollbar-inner {
@@ -344,9 +341,8 @@ error_log("Loaded template: " . print_r($template, true));
<input type="hidden" name="excelrows" id="selectedExcelRowsData" value="">
<input type="hidden" name="filename" value="${data.filename}">
<!-- TOP BUTTON -->
<div class="d-flex justify-content-end align-items-center mb-3 gap-2">
<span class="badge bg-secondary" id="selectedCountTop">0 selezionati</span>
<!-- TOP BUTTON -->
<div class="d-flex justify-content-end mb-3">
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
</div>
@@ -387,187 +383,46 @@ error_log("Loaded template: " . print_r($template, true));
</table>
</div>
<!-- BOTTOM BUTTON -->
<div class="d-flex align-items-center mt-3 gap-2">
<span class="badge bg-secondary" id="selectedCountBottom">0 selezionati</span>
<button type="submit" class="btn btn-primary" id="proceedButtonBottom" disabled>Prosegui</button>
</div>
<!-- BOTTOM BUTTON -->
<button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</button>
</form>
`;
tableContainer.innerHTML = html;
const selectRowsForm = document.getElementById('selectRowsForm');
// Clic sulla cella (oltre che sulla checkbox) per selezionare/deselezionare la riga
document.querySelectorAll('#importPreviewTable tbody tr').forEach(tr => {
tr.style.cursor = 'pointer';
tr.addEventListener('click', function(e) {
// Se ho cliccato proprio sulla checkbox, lascio fare a lei (evita doppio toggle)
if (e.target.classList.contains('row-checkbox')) return;
const cb = this.querySelector('.row-checkbox');
if (!cb) return;
cb.checked = !cb.checked;
cb.dispatchEvent(new Event('change', {
bubbles: true
}));
});
});
selectRowsForm.addEventListener('submit', async function(e) {
e.preventDefault(); // gestiamo tutto via AJAX a batch
selectRowsForm.addEventListener('submit', function(e) {
const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked'));
if (checkedBoxes.length === 0) {
e.preventDefault();
alert('Seleziona almeno una riga.');
return;
}
// Raccoglie le righe selezionate (riga mappata + excelrow parallelo)
const selectedRows = [];
const selectedExcelRows = [];
checkedBoxes.forEach(cb => {
checkedBoxes.forEach((cb, newIndex) => {
const originalIndex = parseInt(cb.value, 10);
if (data.rows && data.rows[originalIndex]) {
selectedRows.push(data.rows[originalIndex]);
selectedExcelRows.push(
(data.excel_data && data.excel_data[originalIndex]) ?
data.excel_data[originalIndex].excelrow :
null
);
}
if (data.excel_data && data.excel_data[originalIndex]) {
selectedExcelRows.push(data.excel_data[originalIndex].excelrow);
}
// Reindex selected_rows so import_insert.php receives only the reduced rows array
cb.value = newIndex;
});
const total = selectedRows.length;
if (total === 0) {
alert('Nessuna riga valida da importare.');
return;
}
document.getElementById('selectedRowsData').value =
encodeURIComponent(JSON.stringify(selectedRows));
// importreferencecode generato UNA volta, condiviso da tutti i batch
const importReferenceCode =
new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14) +
'-' + Math.random().toString(36).slice(2, 10);
const BATCH_SIZE = 20;
// Disabilita i pulsanti e mostra la barra di progresso
const btnTop = document.getElementById('proceedButtonTop');
const btnBottom = document.getElementById('proceedButtonBottom');
if (btnTop) btnTop.disabled = true;
if (btnBottom) btnBottom.disabled = true;
const progressHtml = `
<div id="importProgressWrap" style="
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
z-index: 20000;
display: flex;
align-items: center;
justify-content: center;">
<div style="
background: #fff;
border-radius: 10px;
padding: 25px 30px;
width: 90%;
max-width: 500px;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);">
<div class="mb-2"><strong>Importazione in corso...</strong>
<span id="importProgressText" class="float-end">0 / ${total}</span>
</div>
<div class="progress" style="height: 26px;">
<div id="importProgressBar" class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%;">0%</div>
</div>
<div id="importProgressError" class="alert alert-danger mt-3 mb-0" style="display:none;"></div>
</div>
</div>`;
document.body.insertAdjacentHTML('beforeend', progressHtml);
const progressBar = document.getElementById('importProgressBar');
const progressText = document.getElementById('importProgressText');
const progressErr = document.getElementById('importProgressError');
let insertedTotal = 0;
// Invia un batch e ritorna la risposta JSON
async function sendBatch(startIndex) {
const batchRows = selectedRows.slice(startIndex, startIndex + BATCH_SIZE);
const batchExcelrows = selectedExcelRows.slice(startIndex, startIndex + BATCH_SIZE);
const resp = await fetch('import_insert_batch.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: data.template_id,
importreferencecode: importReferenceCode,
filename: data.filename,
columns: data.columns,
batch_rows: batchRows,
batch_excelrows: batchExcelrows
})
});
return resp.json();
}
// Cicla i batch in sequenza
for (let start = 0; start < total; start += BATCH_SIZE) {
let result;
try {
result = await sendBatch(start);
} catch (err) {
result = {
ok: false,
error: err.message
};
}
if (!result.ok) {
// Batch fallito: chiediamo all'utente se continuare
progressErr.textContent =
'Errore su un blocco (righe ' + (start + 1) + '-' +
Math.min(start + BATCH_SIZE, total) + '): ' + (result.error || 'errore sconosciuto');
progressErr.style.display = 'block';
const continua = confirm(
'Un blocco di righe non è stato importato.\n\n' +
'Righe inserite finora: ' + insertedTotal + ' di ' + total + '.\n\n' +
'Vuoi continuare con i blocchi successivi? (Annulla = interrompi)'
);
if (!continua) {
progressBar.classList.remove('progress-bar-animated');
progressBar.classList.add('bg-warning');
return;
}
// se continua, prosegue col prossimo blocco
continue;
}
insertedTotal += (result.inserted || 0);
const done = Math.min(start + BATCH_SIZE, total);
const pct = Math.round((done / total) * 100);
progressBar.style.width = pct + '%';
progressBar.textContent = pct + '%';
progressText.textContent = insertedTotal + ' / ' + total;
}
// Fine: barra verde e redirect a imported.php
progressBar.classList.remove('progress-bar-animated', 'progress-bar-striped');
progressBar.classList.add('bg-success');
progressText.textContent = insertedTotal + ' / ' + total + ' completate';
// PAUSA temporanea di 2 secondi per vedere la barra completa (da rimuovere dopo il test)
await new Promise(r => setTimeout(r, 2000));
window.location.href = 'imported.php?id=' +
encodeURIComponent(data.template_id) +
'&importref=' + encodeURIComponent(importReferenceCode);
document.getElementById('selectedExcelRowsData').value =
encodeURIComponent(JSON.stringify(selectedExcelRows));
});
const topTableScrollbar = document.getElementById('topTableScrollbar');
@@ -617,17 +472,10 @@ error_log("Loaded template: " . print_r($template, true));
const checkboxes = document.querySelectorAll('.row-checkbox');
function updateProceedButton() {
const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
const enabled = checkedCount > 0;
const enabled = Array.from(checkboxes).some(cb => cb.checked);
if (proceedButtonTop) proceedButtonTop.disabled = !enabled;
if (proceedButtonBottom) proceedButtonBottom.disabled = !enabled;
const label = checkedCount + ' selezionati';
const countTop = document.getElementById('selectedCountTop');
const countBottom = document.getElementById('selectedCountBottom');
if (countTop) countTop.textContent = label;
if (countBottom) countBottom.textContent = label;
}
selectAllCheckbox.addEventListener('change', function() {
+13 -154
View File
@@ -99,7 +99,7 @@ $stmt = $pdo->prepare("
FROM datadb d
LEFT JOIN auth_users u ON d.user_id = u.id
{$baseWhere}
ORDER BY CAST(d.excelrow AS UNSIGNED) ASC, d.excelrow ASC, d.iddatadb ASC
ORDER BY d.excelrow ASC, d.iddatadb ASC
{$limitClause}
");
$stmt->execute($baseParams);
@@ -147,7 +147,6 @@ $desiredOrder = [
'ClienteResponsabile',
'ClienteFornitore',
'ClienteAnalisi',
'ClienteFatturazione',
'AnagraficaCertestObject',
'AnagraficaCertestService',
'MoltiplicatorePrezzo',
@@ -182,7 +181,6 @@ $fixedAliasMap = [
'ClienteResponsabile' => 'cliente_responsabile_id',
'ClienteFornitore' => 'cliente_fornitore_id',
'ClienteAnalisi' => 'clienteAnalisi',
'ClienteFatturazione' => 'ClienteFatturazione',
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
@@ -287,21 +285,6 @@ $gridColumns[] = ['type' => 'idclient', 'key' => 'idclient', 'label' => 'Client'
// 4. Cliente Fornitore
$gridColumns[] = ['type' => 'cliente_fornitore_id', 'key' => 'cliente_fornitore_id', 'label' => $slugMapping['ClienteFornitore'] ?? 'ClienteFornitore', 'width' => 300];
// 4b. Fatturazione Avanzata — subito dopo Cliente Fornitore
foreach ($fixedFields as $f) {
if ($f['fixed_field_key'] === 'ClienteFatturazione') {
$gridColumns[] = [
'type' => 'fixed',
'key' => 'ClienteFatturazione',
'label' => 'Fatturazione avanzata',
'dataType' => $f['data_type'],
'isRequired' => (bool)$f['is_required'],
'width' => 180,
];
break;
}
}
// 5. Other custom fields in schema order
foreach ($allMappings as $mapping) {
if (
@@ -338,18 +321,7 @@ $gridColumns[] = ['type' => 'tracking', 'key' => 'tracking', 'label' => 'Trackin
// 10. Fixed fields (with importreferencecode/filename/importdate after ConsegnaRichiesta)
foreach ($fixedFields as $f) {
$key = $f['fixed_field_key'];
if ($key === 'ClienteFatturazione') continue; // già renderizzata al punto 4b
// Cambia solo il titolo della colonna ClienteAnalisi
if ($key === 'ClienteAnalisi') {
$label = 'Buyer';
} elseif ($key === 'ClienteFatturazione') {
$label = 'Fatturazione avanzata';
} else {
$label = $slugMapping[$key] ?? $key;
}
$label = $slugMapping[$key] ?? $key;
$gridColumns[] = [
'type' => 'fixed',
'key' => $key,
@@ -358,7 +330,6 @@ foreach ($fixedFields as $f) {
'isRequired' => (bool)$f['is_required'],
'width' => 180,
];
if ($key === 'ConsegnaRichiesta') {
$gridColumns[] = ['type' => 'static', 'key' => 'importreferencecode', 'label' => $slugMapping['importreferencecode'] ?? 'Import Reference Code', 'width' => 150, 'editable' => false];
$gridColumns[] = ['type' => 'static', 'key' => 'filename_import', 'label' => $slugMapping['filename_import'] ?? 'File', 'width' => 150, 'editable' => false];
@@ -497,16 +468,8 @@ $gridMeta = [
border: 1px solid #dee2e6;
border-radius: 0.25rem;
background: #f8f9fa;
position: sticky;
top: 70px;
/* regola sull'altezza reale della tua topbar fissa */
position: sticky;
top: 70px;
z-index: 30;
}
.top-scrollbar-inner {
height: 1px;
}
@@ -787,7 +750,6 @@ $gridMeta = [
padding: 10px 0;
min-height: 0;
flex-wrap: nowrap;
min-width: fit-content;
}
.grid-top .grid-cell {
@@ -1329,117 +1291,6 @@ $gridMeta = [
outline: 2px solid #dc3545 !important;
outline-offset: -2px;
}
/* ── Filtri / selezione righe ── */
#filtersToggleBtn.active {
background-color: #0dcaf0 !important;
color: #fff !important;
border-color: #0dcaf0 !important;
}
/* checkbox dentro la cella Actions */
.filter-cb-wrap {
display: none;
align-items: center;
justify-content: center;
margin-right: 6px;
flex-shrink: 0;
}
.grid-container.filters-on .filter-cb-wrap {
display: inline-flex;
}
.filter-cb-wrap input {
width: 18px !important;
height: 18px !important;
cursor: pointer;
}
.grid-container.filters-on .button-cell {
display: flex;
align-items: center;
}
.grid-row.row-selected {
background-color: #cfe9ff !important;
}
.grid-row.row-selected .button-cell,
.grid-row.row-selected .grid-cell:nth-child(2),
.grid-row.row-selected .grid-cell:nth-child(3) {
background-color: #cfe9ff !important;
}
.grid-filter-row {
background: #eef6f9 !important;
border-bottom: 2px solid #0dcaf0;
}
.grid-filter-row .grid-cell {
padding: 4px 8px;
}
.grid-filter-row .filter-actions-cell {
background: #eef6f9 !important;
}
.grid-container.filters-on .grid-row {
cursor: pointer;
}
.grid-container.filters-on .grid-row input,
.grid-container.filters-on .grid-row select,
.grid-container.filters-on .grid-row textarea,
.grid-container.filters-on .grid-row button,
.grid-container.filters-on .grid-row a {
cursor: auto;
}
/* Sticky su riga propagazione e filtri (fix scroll orizzontale) */
.grid-top .grid-cell.save-all-cell {
position: sticky !important;
left: 0;
z-index: 9;
background: #fff;
}
.grid-top .grid-cell:nth-child(2) {
position: sticky !important;
left: 210px;
z-index: 8;
background: #fff;
}
<?php if (isset($mainFieldMappings) && count($mainFieldMappings) >= 2): ?>.grid-top .grid-cell:nth-child(3) {
position: sticky !important;
left: 360px;
z-index: 7;
background: #fff;
}
<?php endif; ?>.grid-filter-row .filter-actions-cell {
position: sticky !important;
left: 0;
z-index: 9;
background: #eef6f9 !important;
}
.grid-filter-row .grid-cell:nth-child(2) {
position: sticky !important;
left: 210px;
z-index: 8;
background: #eef6f9 !important;
}
<?php if (isset($mainFieldMappings) && count($mainFieldMappings) >= 2): ?>.grid-filter-row .grid-cell:nth-child(3) {
position: sticky !important;
left: 360px;
z-index: 7;
background: #eef6f9 !important;
}
<?php endif; ?>
</style>
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
@@ -1516,8 +1367,17 @@ $gridMeta = [
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center" style="min-height: 42px; gap: 12px;">
<a href="#" class="btn btn-outline-danger btn-sm export-all-lims-btn" style="flex-shrink:0;"><i class="fas fa-upload"></i> Export All</a>
<a href="#" class="btn btn-outline-success btn-sm save-all-btn" style="flex-shrink:0;"><i class="fas fa-save"></i> Save All</a>
<div class="dropdown actions-dropdown" style="flex-shrink: 0;">
<button class="dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-cogs"></i> Actions
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li>
<li><a class="dropdown-item save-all-btn" href="#"><i class="fas fa-save" style="color: #28a745;"></i>Save All</a></li>
</ul>
</div>
<button type="button" class="btn btn-outline-success btn-sm" id="addRowBtn" style="flex-shrink:0;"><i class="fas fa-plus"></i> Add Row</button>
<div id="unsavedChanges" style="display:none; color: red; font-weight: bold; font-size: 13px;">
⚠️ Unsaved changes detected!
@@ -1568,7 +1428,6 @@ $gridMeta = [
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script src="tracking.js"></script>
<script src="gridRenderer.js"></script>
<script src="gridFilter.js"></script>
<script src="saveAll.js"></script>
<script src="exportLims_gridData.js"></script>
<script src="modals_gridData.js"></script>
@@ -1,213 +0,0 @@
<?php
if (!function_exists('gdbFixedDefaultValue')) {
/** Default di un fixed field (DATE 'today' → data odierna). */
function gdbFixedDefaultValue(array $f): string
{
$v = $f['default_value'] ?? '';
if (($f['data_type'] ?? '') === 'DATE' && $v === 'today') {
return date('Y-m-d');
}
return (string)$v;
}
}
if (!function_exists('buildGridConfig')) {
/**
* Config del template necessaria per costruire le righe: fixed fields (ordinati
* come in imported.php), main field mappings, alias map, default idclient.
*
* @return array{fixedFields:array, fixedAliasMap:array, mainFieldMappings:array, default_idclient:mixed}
*/
function buildGridConfig(PDO $pdo, int $templateId): array
{
// Mappa logica fixed_field_key → colonna reale su datadb (come imported.php)
$fixedAliasMap = [
'ClienteResponsabile' => 'cliente_responsabile_id',
'ClienteFornitore' => 'cliente_fornitore_id',
'ClienteAnalisi' => 'clienteAnalisi',
'ClienteFatturazione' => 'ClienteFatturazione',
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
'ConsegnaRichiesta' => 'consegna_richiesta',
];
// Fixed fields visibili, ordinati come imported.php
$fixedStmt = $pdo->prepare("
SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
FROM template_fixed_mapping
WHERE template_id = ? AND is_visible_import = 1
ORDER BY id
");
$fixedStmt->execute([$templateId]);
$fixedFieldsRaw = $fixedStmt->fetchAll(PDO::FETCH_ASSOC);
$desiredOrder = [
'ClienteResponsabile', 'ClienteFornitore', 'ClienteAnalisi', 'ClienteFatturazione',
'AnagraficaCertestObject', 'AnagraficaCertestService', 'MoltiplicatorePrezzo', 'ConsegnaRichiesta',
];
$excludeFromFixed = ['ClienteFornitore']; // reso come colonna a sé
$fixedFields = [];
$tempMap = [];
foreach ($fixedFieldsRaw as $f) {
if (in_array($f['fixed_field_key'], $excludeFromFixed, true)) continue;
$tempMap[$f['fixed_field_key']] = $f;
}
foreach ($desiredOrder as $key) {
if (isset($tempMap[$key])) {
$fixedFields[] = $tempMap[$key];
unset($tempMap[$key]);
}
}
foreach ($tempMap as $f) {
$fixedFields[] = $f;
}
// Main field mappings (max 2), come imported.php
$mapStmt = $pdo->prepare("
SELECT id, field_label, data_type, is_required, field_id, field_order,
main_field, is_visible_import, manual_default
FROM template_mapping
WHERE template_id = ?
ORDER BY field_order ASC, id ASC
");
$mapStmt->execute([$templateId]);
$allMappings = $mapStmt->fetchAll(PDO::FETCH_ASSOC);
$mainFieldMappings = [];
foreach ($allMappings as $mapping) {
if ((string)$mapping['main_field'] === '1' && (int)$mapping['is_visible_import'] === 1) {
$mainFieldMappings[] = $mapping;
}
if (count($mainFieldMappings) >= 2) break;
}
// Default idclient dal template
$tplStmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
$tplStmt->execute([$templateId]);
$default_idclient = $tplStmt->fetchColumn();
$default_idclient = $default_idclient !== false ? $default_idclient : null;
return [
'fixedFields' => $fixedFields,
'fixedAliasMap' => $fixedAliasMap,
'mainFieldMappings' => $mainFieldMappings,
'default_idclient' => $default_idclient,
];
}
}
if (!function_exists('buildGridRows')) {
/**
* Costruisce le righe gridData per la lista di iddatadb data, nell'ordine passato.
*
* @param int[] $iddatadbList
* @param array $config output di buildGridConfig()
* @return array righe nella stessa forma di imported.php $gridDataArray
*/
function buildGridRows(PDO $pdo, array $iddatadbList, array $config): array
{
$iddatadbList = array_values(array_filter(array_map('intval', $iddatadbList), fn($v) => $v > 0));
if (empty($iddatadbList)) {
return [];
}
$fixedFields = $config['fixedFields'];
$fixedAliasMap = $config['fixedAliasMap'];
$mainFieldMappings = $config['mainFieldMappings'];
$default_idclient = $config['default_idclient'];
$ph = implode(',', array_fill(0, count($iddatadbList), '?'));
// Righe datadb + user_name
$stmt = $pdo->prepare("
SELECT d.*, CONCAT(u.first_name, ' ', u.last_name) AS user_name
FROM datadb d
LEFT JOIN auth_users u ON d.user_id = u.id
WHERE d.iddatadb IN ($ph)
");
$stmt->execute($iddatadbList);
$byId = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
$byId[(int)$r['iddatadb']] = $r;
}
// Dettagli (import_data_details)
$detStmt = $pdo->prepare("
SELECT d.id AS datadb_id, d.mapping_id, d.field_value,
m.field_id, m.field_label, m.data_type, m.is_required, m.manual_default
FROM import_data_details d
JOIN template_mapping m ON d.mapping_id = m.id
WHERE d.id IN ($ph)
");
$detStmt->execute($iddatadbList);
$detailsByRow = [];
foreach ($detStmt->fetchAll(PDO::FETCH_ASSOC) as $d) {
$detailsByRow[(int)$d['datadb_id']][] = $d;
}
// Costruzione righe nell'ordine della lista passata
$rows = [];
foreach ($iddatadbList as $id) {
$row = $byId[$id] ?? null;
if ($row === null) continue;
$rowObj = [
'iddatadb' => (int)$row['iddatadb'],
'status' => $row['status'] ?? 'i',
'idclient' => $row['idclient'] ?? $default_idclient,
'cliente_fornitore_id' => $row['cliente_fornitore_id'] ?? null,
'tested_component' => $row['tested_component'] ?? '',
'commessaweb' => $row['commessaweb'] ?? null,
'user_name' => $row['user_name'] ?? '',
'importreferencecode' => $row['importreferencecode'] ?? '',
'filename_import' => $row['filename_import'] ?? '',
'importdate' => $row['importdate'] ?? '',
];
// Fixed fields
$rowObj['fixedFields'] = [];
foreach ($fixedFields as $f) {
$key = $f['fixed_field_key'];
$dbCol = $fixedAliasMap[$key] ?? $key;
$val = $row[$dbCol] ?? '';
if ($val === '' || $val === null) {
$val = gdbFixedDefaultValue($f);
}
$rowObj['fixedFields'][$key] = (string)$val;
}
// Details
$rowObj['details'] = [];
$rowDetails = $detailsByRow[$id] ?? [];
foreach ($rowDetails as $d) {
$rowObj['details'][(string)$d['mapping_id']] = $d['field_value'] ?? '';
}
// Main field values
foreach ($mainFieldMappings as $mainMapping) {
$found = null;
foreach ($rowDetails as $d) {
if ($d['mapping_id'] == $mainMapping['id']) {
$found = $d;
break;
}
}
$rowObj['details'][(string)$mainMapping['id']] =
($found['field_value'] ?? null) ?? ($mainMapping['manual_default'] ?? '');
}
if (!empty($mainFieldMappings)) {
$firstMain = $mainFieldMappings[0];
$rowObj['mainFieldValue'] = $rowObj['details'][(string)$firstMain['id']] ?? '';
}
$rowObj['_dirty'] = false;
$rows[] = $rowObj;
}
return $rows;
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ $db = DBHandlerSelect::getInstance()->getConnection();
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
error_reporting(E_ALL | E_STRICT);
// This should be equal to: PATH_TO_VANGUARD_FOLDER/extra/auth.php
include('../../extra/auth.php');
//require_once __DIR__ . '/extra/auth.php';
+1 -17
View File
@@ -39,7 +39,7 @@
<ul>
<li>
<a href="import_dashboard.php">
<i class='bx bx-radio-circle'></i>Import Dashboard
<i class='bx bx-radio-circle'></i>XLS Import
</a>
</li>
</ul>
@@ -95,22 +95,6 @@
<div class="menu-title">User Admin</div>
</a>
</li>
<li>
<a href="stats_export_lims.php">
<div class="parent-icon">
<i class="bx bx-bar-chart-alt-2"></i>
</div>
<div class="menu-title">Stats</div>
</a>
</li>
<li>
<a href="warm_cache_run.php">
<div class="parent-icon">
<i class="bx bx-bar-chart-alt-2"></i>
</div>
<div class="menu-title">Warm Cache</div>
</a>
</li>
<?php endif; ?>
<li class="menu-label">Others</li>
-55
View File
@@ -1,55 +0,0 @@
<?php
$popupEnabled = (($_ENV['POPUP_ENABLED'] ?? getenv('POPUP_ENABLED')) === 'true');
if ($popupEnabled):
?>
<div id="app-popup-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,.65);display:flex;align-items:center;justify-content:center;z-index:99999;padding:20px;">
<div style="background:#fff;max-width:640px;width:100%;max-height:90vh;overflow-y:auto;padding:40px;border-radius:14px;position:relative;box-shadow:0 20px 60px rgba(0,0,0,.4);text-align:center;">
<div style="font-size:46px;line-height:1;margin-bottom:10px;">🚀</div>
<h2 style="margin:0 0 8px;font-size:26px;color:#111;">Nuovo sito in produzione</h2>
<p style="font-size:16px;color:#444;margin:0 0 20px;">Il portale si è spostato su un nuovo indirizzo.</p>
<div style="background:#f1f5ff;border:1px solid #c9d8ff;border-radius:10px;padding:16px;margin-bottom:24px;">
<p style="margin:0 0 4px;font-size:13px;color:#667;text-transform:uppercase;letter-spacing:.5px;">Nuovo indirizzo</p>
<p style="margin:0;font-size:19px;font-weight:700;color:#1d4ed8;word-break:break-all;">https://trfsmart-bv.cesoft.io/</p>
</div>
<a href="https://trfsmart-bv.cesoft.io/"
style="display:inline-block;padding:16px 36px;background:#2563eb;color:#fff;text-decoration:none;border-radius:8px;font-size:18px;font-weight:600;box-shadow:0 4px 14px rgba(37,99,235,.35);">
Clicca qui per accedere al nuovo sito
</a>
<p style="font-size:13px;color:#888;margin:16px 0 0;">Ti consigliamo di salvare il nuovo indirizzo nei preferiti.</p>
<hr style="border:none;border-top:1px solid #eee;margin:28px 0 20px;">
<div style="background:#fff8e6;border:1px solid #ffe08a;border-radius:10px;padding:16px 18px;text-align:left;margin-bottom:20px;">
<p style="margin:0;font-size:14px;color:#8a6d00;">
<strong>⚠️ Attenzione:</strong> chiudi questo messaggio <u>solo</u> se riscontri problemi
con il nuovo sito. Continuando su questo portale userai la versione precedente.
</p>
</div>
<button onclick="chiudiPopupMigrazione()"
style="padding:11px 22px;background:#f0f0f0;color:#555;border:1px solid #ddd;border-radius:8px;cursor:pointer;font-size:14px;">
Ho problemi col nuovo sito, resto su questo portale
</button>
</div>
</div>
<script>
function chiudiPopupMigrazione() {
var conferma = confirm(
"Sei sicuro di voler restare sul portale precedente?\n\n" +
"Il nuovo sito è quello ufficiale in produzione. " +
"Chiudi questo avviso solo se hai riscontrato un problema reale sul nuovo sito."
);
if (conferma) {
document.getElementById('app-popup-overlay').remove();
}
}
</script>
<?php endif; ?>
+2 -16
View File
@@ -1,22 +1,8 @@
<header>
<div class="topbar d-flex align-items-center">
<?php
// Controllo ambiente TEST
$testEnv = strtolower($_ENV['TEST_ENVIRONMENT'] ?? getenv('TEST_ENVIRONMENT') ?? 'true');
if ($testEnv === 'true'):
?>
<div class="alert alert-danger border-0 rounded-0 mb-0 py-2 text-center fw-bold fs-5 w-100 shadow-sm"
style="margin: 0 -15px 0 -15px;">
<i class='bx bx-test-tube me-2'></i>
AMBIENTE DI TEST NON È PRODUZIONE
<nav class="navbar navbar-expand gap-3">
<div class="mobile-toggle-menu"><i class='bx bx-menu'></i>
</div>
<?php endif; ?>
<nav class="navbar navbar-expand gap-3 w-100">
<!-- Tutto il resto del tuo codice della topbar rimane identico -->
<div class="mobile-toggle-menu"><i class='bx bx-menu'></i></div>
<div class="search-bar d-lg-block d-none" data-bs-toggle="modal" data-bs-target="#SearchModal">
<!-- <a href="avascript:;" class="btn d-flex align-items-center"><i class='bx bx-search'></i>Search</a> -->
+28 -6
View File
@@ -95,6 +95,21 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
<input type="text" name="start_column" id="startColumn" class="form-control" value="A" required>
</div>
<div class="mb-3" id="xlsEndColumnWrapper">
<label class="form-label">Last Column included</label>
<input
type="text"
name="xls_end_column"
id="xlsEndColumn"
class="form-control"
value=""
placeholder="Example: AN">
<small class="text-danger fw-semibold">
Attention: if left empty, the system will read the entire XLS/XLSX sheet.
Dirty Excel files may cause memory errors or timeout.
</small>
</div>
<div class="mb-3" id="xlsSheetNumberWrapper">
<label class="form-label">XLS Sheet Number</label>
<input
@@ -253,11 +268,13 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
const headerRowWrapper = document.getElementById("headerRowWrapper");
const startColumnWrapper = document.getElementById("startColumnWrapper");
const xlsEndColumnWrapper = document.getElementById("xlsEndColumnWrapper");
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
const headerRow = document.getElementById("headerRow");
const startColumn = document.getElementById("startColumn");
const xlsEndColumn = document.getElementById("xlsEndColumn");
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
const apiConfigSelect = document.getElementById("apiConfigSelect");
@@ -291,19 +308,24 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
const isXls = selectedSource === 'XLS';
const isApiJson = selectedSource === 'API';
const isPdf = selectedSource === 'PDF';
if (isXls) {
headerRowWrapper.style.display = 'block';
startColumnWrapper.style.display = 'block';
xlsEndColumnWrapper.style.display = 'block';
xlsSheetNumberWrapper.style.display = 'block';
headerRow.required = true;
startColumn.required = true;
xlsSheetIndex.required = true;
// Last Column is optional.
// If empty, the import will read the entire sheet.
xlsEndColumn.required = false;
headerRow.disabled = false;
startColumn.disabled = false;
xlsEndColumn.disabled = false;
xlsSheetIndex.disabled = false;
apiConfigWrapper.style.display = 'none';
@@ -313,23 +335,26 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
headerRowWrapper.style.display = 'none';
startColumnWrapper.style.display = 'none';
xlsEndColumnWrapper.style.display = 'none';
xlsSheetNumberWrapper.style.display = 'none';
headerRow.required = false;
startColumn.required = false;
xlsEndColumn.required = false;
xlsSheetIndex.required = false;
headerRow.disabled = true;
startColumn.disabled = true;
xlsEndColumn.disabled = true;
xlsSheetIndex.disabled = true;
xlsEndColumn.value = '';
if (isApiJson) {
apiConfigWrapper.style.display = 'block';
apiConfigSelect.required = true;
apiConfigSelect.disabled = false;
} else {
// PDF (o qualsiasi altro): niente config API, niente campi XLS.
// I riquadri PDF si tracciano nella pagina di configurazione.
apiConfigWrapper.style.display = 'none';
apiConfigSelect.required = false;
apiConfigSelect.disabled = true;
@@ -519,9 +544,6 @@ $apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
return;
}
// PDF: nessuna validazione extra qui. Il PDF di esempio e i
// riquadri (etichetta + dato) si configurano nella pagina successiva.
const clientSelect = document.getElementById("clientSelect");
const clientId = clientSelect.value;
const selectedClientOption = clientSelect.options[clientSelect.selectedIndex];
+5 -20
View File
@@ -1,6 +1,5 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
require_once 'class/db-functions.php';
$response = ["success" => false, "data" => [], "message" => ""];
@@ -13,25 +12,11 @@ try {
throw new Exception('Database connection failed.');
}
$userId = $iduserlogin ?? 0;
// Recupera i template attivi, con flag is_favorite per l'utente corrente
$stmt = $pdo->prepare("
SELECT
et.id,
et.button_label,
et.button_size,
et.button_bg_color,
et.button_text_color,
et.source_type,
CASE WHEN uf.id IS NOT NULL THEN 1 ELSE 0 END AS is_favorite
FROM excel_templates et
LEFT JOIN user_template_favorites uf
ON uf.template_id = et.id AND uf.user_id = :user_id
WHERE et.status = 'active'
ORDER BY et.button_label ASC
");
$stmt->execute(['user_id' => $userId]);
// Recupera solo i template attivi
$stmt = $pdo->query("SELECT id, button_label, button_size, button_bg_color, button_text_color, source_type
FROM excel_templates
WHERE status = 'active'
ORDER BY button_label ASC");
$templates = $stmt->fetchAll(PDO::FETCH_ASSOC);
$response["success"] = true;
+23 -206
View File
@@ -24,9 +24,7 @@ $stmt = $pdo->prepare("
schemajson,
xls_headers,
api_sample_json,
json_nodes,
sample_pdf,
pdf_page_meta
json_nodes
FROM excel_templates
WHERE id = ?
");
@@ -63,7 +61,6 @@ $stmt = $pdo->prepare("
field_order,
excel_column,
json_node,
pdf_regions,
is_manual,
manual_default,
auto_value,
@@ -80,8 +77,7 @@ $stmt = $pdo->prepare("
field_label,
main_field,
is_visible_import,
is_visible_parts,
is_addnote
is_visible_parts
FROM template_mapping
WHERE template_id = ?
ORDER BY field_order ASC, id ASC
@@ -120,14 +116,6 @@ if (!is_array($jsonNodes)) {
}
$apiSampleJson = $template['api_sample_json'] ?? '';
// Dati PDF: nome file di esempio e metadati pagine
$samplePdf = $template['sample_pdf'] ?? '';
$pdfPageMeta = $template['pdf_page_meta'] ? json_decode($template['pdf_page_meta'], true) : [];
if (!is_array($pdfPageMeta)) {
$pdfPageMeta = [];
}
?>
<!doctype html>
@@ -140,7 +128,6 @@ if (!is_array($pdfPageMeta)) {
<?php include('cssinclude.php'); ?>
<title>Configure Template <?= htmlspecialchars($template['name'], ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<style>
@@ -196,9 +183,7 @@ if (!is_array($pdfPageMeta)) {
#schemaFieldsTable th:nth-child(2),
#schemaFieldsTable td:nth-child(2),
#schemaFieldsTable th:nth-child(3),
#schemaFieldsTable td:nth-child(3),
#schemaFieldsTable th:nth-child(4),
#schemaFieldsTable td:nth-child(4) {
#schemaFieldsTable td:nth-child(3) {
width: 48px;
/* 🔥 più stretto */
text-align: center;
@@ -210,8 +195,7 @@ if (!is_array($pdfPageMeta)) {
/* Make checkbox itself not oversized */
#schemaFieldsTable td:nth-child(1) input[type="checkbox"],
#schemaFieldsTable td:nth-child(2) input[type="checkbox"],
#schemaFieldsTable td:nth-child(3) input[type="checkbox"],
#schemaFieldsTable td:nth-child(4) input[type="checkbox"] {
#schemaFieldsTable td:nth-child(3) input[type="checkbox"] {
transform: scale(0.95);
margin: 0;
}
@@ -395,20 +379,8 @@ if (!is_array($pdfPageMeta)) {
</div>
</div>
<?php elseif ($sourceType === 'PDF'): ?>
<div class="mb-4">
<label class="form-label">Upload PDF Example:</label>
<input type="file" id="pdfUpload" class="form-control" accept="application/pdf">
<small id="pdfStatus" class="text-muted d-block mt-1">
<?php if (!empty($samplePdf)): ?>
Current file: <a href="pdftemplates/<?php echo htmlspecialchars($samplePdf); ?>" target="_blank"><?php echo htmlspecialchars($samplePdf); ?></a>
<?php else: ?>
No PDF uploaded yet.
<?php endif; ?>
</small>
<div class="alert alert-info mt-2 mb-2" style="font-size:13px;">
Per ogni campo, clicca <strong>Traccia PDF</strong> nella tabella qui sotto: prima disegna il riquadro dell'<strong>etichetta</strong> (blu), poi quello del <strong>dato</strong> (verde).
</div>
<div id="pdfPreviewContainer" style="max-height:70vh; overflow:auto; background:#525659; padding:20px 0; border-radius:6px;"></div>
<div class="alert alert-warning">
PDF source type is not implemented yet.
</div>
<?php endif; ?>
@@ -426,7 +398,6 @@ if (!is_array($pdfPageMeta)) {
<th style="width:45px; text-align:center;">Main</th>
<th style="width:45px; text-align:center;">Import</th>
<th style="width:45px; text-align:center;">Parts</th>
<th style="width:45px; text-align:center;">Note</th>
<th style="width:320px;">Title</th>
<th style="width:70px; text-align:center;">Order</th>
<th style="width:120px;">Type</th>
@@ -457,12 +428,7 @@ if (!is_array($pdfPageMeta)) {
data-mapping-id="<?php echo (int)$mapping['id']; ?>"
<?php echo ((int)($mapping['is_visible_parts'] ?? 0) === 1) ? 'checked' : ''; ?>>
</td>
<td class="text-center">
<input type="checkbox"
class="addnote-checkbox"
data-mapping-id="<?php echo (int)$mapping['id']; ?>"
<?php echo ((int)($mapping['is_addnote'] ?? 0) === 1) ? 'checked' : ''; ?>>
</td>
<td class="title-col">
<?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?>
<?php if ((int)$mapping['is_required'] === 1): ?>
@@ -476,6 +442,8 @@ if (!is_array($pdfPageMeta)) {
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
<td>
<?php
$isSceltaMultipla = ($mapping['data_type'] === 'SceltaMultipla');
@@ -491,8 +459,6 @@ if (!is_array($pdfPageMeta)) {
$mappingValue = 'xls';
} elseif ($sourceType === 'API' && !empty($mapping['json_node'])) {
$mappingValue = 'json';
} elseif ($sourceType === 'PDF' && !empty($mapping['pdf_regions'])) {
$mappingValue = 'pdf';
} elseif ((int)$mapping['is_manual'] === 1) {
$mappingValue = 'manual';
} else {
@@ -512,9 +478,8 @@ if (!is_array($pdfPageMeta)) {
<option value="xls" <?php echo ($mappingValue === 'xls') ? 'selected' : ''; ?>>Map to XLS Column</option>
<?php elseif ($sourceType === 'API'): ?>
<option value="json" <?php echo ($mappingValue === 'json') ? 'selected' : ''; ?>>Map to JSON Node</option>
<?php elseif ($sourceType === 'PDF'): ?>
<option value="pdf" <?php echo ($mappingValue === 'pdf') ? 'selected' : ''; ?>>Map to PDF Region</option>
<?php endif; ?>
<option value="auto" <?php echo ($mappingValue === 'auto') ? 'selected' : ''; ?>>Auto value</option>
<?php endif; ?>
@@ -547,33 +512,6 @@ if (!is_array($pdfPageMeta)) {
<option value="export_time" <?php echo ($autoValue === 'export_time') ? 'selected' : ''; ?>>Current time (export)</option>
</select>
<?php if ($sourceType === 'PDF'): ?>
<div class="pdf-track-wrap"
style="display:<?php echo ($mappingValue === 'pdf') ? 'block' : 'none'; ?>; margin-top:6px;">
<button type="button"
class="btn btn-outline-danger btn-sm pdf-track-btn"
data-id="<?php echo (int)$mapping['id']; ?>">
Traccia PDF
</button>
<span class="pdf-mapped-info"
data-id="<?php echo (int)$mapping['id']; ?>"
style="margin-left:6px; font-weight:600; color:#198754; display:<?php echo !empty($mapping['pdf_regions']) ? 'inline' : 'none'; ?>;">
<?php
if (!empty($mapping['pdf_regions'])) {
$reg = json_decode($mapping['pdf_regions'], true);
echo '✓ pag.' . (isset($reg['page']) ? (int)$reg['page'] : '?');
}
?>
</span>
<button type="button"
class="btn btn-danger btn-sm pdf-remove-btn"
data-id="<?php echo (int)$mapping['id']; ?>"
style="margin-left:5px; display:<?php echo !empty($mapping['pdf_regions']) ? 'inline-block' : 'none'; ?>;">
X
</button>
</div>
<?php endif; ?>
<?php if ($sourceType === 'XLS' && !empty($mapping['excel_column'])): ?>
<span class="mapped-column" style="margin-left:5px;">
(<?php echo htmlspecialchars($mapping['excel_column']); ?>)
@@ -671,11 +609,7 @@ if (!is_array($pdfPageMeta)) {
</td>
<td>
<strong><?php
$fixedFieldLabels = ['ClienteAnalisi' => 'Buyer'];
$fixedKey = $fm['fixed_field_key'];
echo htmlspecialchars($fixedFieldLabels[$fixedKey] ?? $fixedKey);
?></strong>
<strong><?php echo htmlspecialchars($fm['fixed_field_key']); ?></strong>
</td>
<td><?php echo htmlspecialchars($fm['data_type']); ?></td>
@@ -693,7 +627,6 @@ if (!is_array($pdfPageMeta)) {
'ClienteResponsabile',
'ClienteFornitore',
'ClienteAnalisi',
'ClienteFatturazione',
'MoltiplicatorePrezzo',
'AnagraficaCertestObject',
'AnagraficaCertestService'
@@ -1742,12 +1675,6 @@ if (!is_array($pdfPageMeta)) {
const removeBtn = tr.querySelector('.remove-xls');
const removeJsonBtn = tr.querySelector('.remove-json');
const pdfTrackWrap = tr.querySelector('.pdf-track-wrap');
// Mostra il blocco "Traccia PDF" solo quando il tipo è pdf
if (pdfTrackWrap) {
pdfTrackWrap.style.display = (mappingSelect.value === 'pdf') ? 'block' : 'none';
}
function destroyJsonSelect2() {
if (jsonSelect && window.jQuery && $(jsonSelect).hasClass('select2-hidden-accessible')) {
$(jsonSelect).select2('destroy');
@@ -1870,18 +1797,14 @@ if (!is_array($pdfPageMeta)) {
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
}
// Per il PDF il salvataggio avviene tramite save_pdf_regions.php
// quando l'utente traccia i riquadri, non qui.
if (mappingSelect.value !== 'pdf') {
saveMapping(
mappingId,
mappingSelect.value,
manualInput ? manualInput.value : '',
xlsSelect ? xlsSelect.value : null,
autoSelect ? autoSelect.value : null,
jsonSelect ? jsonSelect.value : null
);
}
saveMapping(
mappingId,
mappingSelect.value,
manualInput ? manualInput.value : '',
xlsSelect ? xlsSelect.value : null,
autoSelect ? autoSelect.value : null,
jsonSelect ? jsonSelect.value : null
);
if (sourceType === 'XLS') updateXlsDropdowns();
});
@@ -1951,74 +1874,7 @@ if (!is_array($pdfPageMeta)) {
document.querySelectorAll('#schemaFieldsBody .main-field-checkbox').forEach(cb => {
cb.dataset.originalChecked = cb.checked ? 'true' : 'false';
});
// =======================
// SAVE: Main / Import / Parts checkboxes
// =======================
function saveMappingFlag(checkboxEl, field) {
const mappingId = checkboxEl.getAttribute('data-mapping-id');
const checked = checkboxEl.checked;
fetch('update_mapping_flag.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: mappingId,
field: field,
value: checked ? 1 : 0
})
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error saving flag:", field, data.message);
if (data.limit_reached) {
alert(data.message);
}
// Revert: il server ha rifiutato, riporto la checkbox allo stato precedente
checkboxEl.checked = !checked;
return;
}
console.log("✅ Flag saved:", field, "=", checked ? 1 : 0, "for id", mappingId);
// Se il server ha deselezionato un'altra riga (caso "parts"), rifletto anche in UI
if (data.unchecked_others && field === 'is_visible_parts') {
document.querySelectorAll('#schemaFieldsBody .visible-parts-checkbox').forEach(cb => {
if (cb !== checkboxEl) {
cb.checked = false;
}
});
}
})
.catch(error => {
console.error("❌ Fetch error saving flag:", field, error);
checkboxEl.checked = !checked;
});
}
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
const el = event.target;
if (el.classList.contains('main-field-checkbox')) {
saveMappingFlag(el, 'main_field');
}
if (el.classList.contains('visible-import-checkbox')) {
saveMappingFlag(el, 'is_visible_import');
}
if (el.classList.contains('visible-parts-checkbox')) {
saveMappingFlag(el, 'is_visible_parts');
}
if (el.classList.contains('addnote-checkbox')) {
saveMappingFlag(el, 'is_addnote');
}
});
// AUTO VALUE select change -> save auto_value
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (!event.target.classList.contains('auto-value-select')) return;
@@ -2260,19 +2116,15 @@ if (!is_array($pdfPageMeta)) {
const tbody = document.querySelector('#fixedFieldsTable tbody');
if (!tbody) return;
const fixedFieldLabels = {
'ClienteAnalisi': 'Buyer'
};
const keysWithDropdown = [
'ClienteResponsabile',
'ClienteFornitore',
'ClienteAnalisi',
'ClienteFatturazione', // ← AGGIUNGI QUESTA RIGA
'MoltiplicatorePrezzo',
'AnagraficaCertestObject',
'AnagraficaCertestService'
];
tbody.innerHTML = rows.map(r => {
const type = String(r.data_type || '').toUpperCase();
const dv = r.default_value ?? '';
@@ -2295,7 +2147,7 @@ if (!is_array($pdfPageMeta)) {
${parseInt(r.is_required) === 1 ? 'checked' : ''}>
</td>
<td><strong>${escapeHtml(fixedFieldLabels[r.fixed_field_key] || r.fixed_field_key)}</strong></td>
<td><strong>${escapeHtml(r.fixed_field_key)}</strong></td>
<td>${escapeHtml(r.data_type || '')}</td>
@@ -2576,22 +2428,11 @@ if (!is_array($pdfPageMeta)) {
})).filter(x => x.id != null && x.label);
}
if (key === 'ClienteFornitore' || key === 'ClienteAnalisi' || key === 'ClienteFatturazione') {
if (key === 'ClienteFornitore' || key === 'ClienteAnalisi') {
const j = await fetchJson('get_clienti.php');
const list = normalizeList(j, key);
const isInattivo = (x) => {
const v = x.Inattivo;
return v === true || v === 1 || v === '1' || v === 'true';
};
// Fornitore e Fatturazione: solo clienti ATTIVI (Inattivo = false)
// Analisi: solo clienti INATTIVI (Inattivo = true)
const filtered = (key === 'ClienteAnalisi') ?
list.filter(isInattivo) :
list.filter(x => !isInattivo(x));
return filtered.map(x => ({
return list.map(x => ({
id: x.IdCliente ?? x.Id ?? x.id,
label: formatClientLabel(x)
})).filter(x => x.id != null && x.label);
@@ -2635,30 +2476,6 @@ if (!is_array($pdfPageMeta)) {
}
</script>
<?php if ($sourceType === 'PDF'): ?>
<script>
// Variabili globali usate da pdf_mapping.js
window.PDF_TEMPLATE_ID = <?php echo (int)$id; ?>;
window.PDF_SAMPLE_FILE = <?php echo json_encode($samplePdf); ?>;
window.PDF_PAGE_META = <?php echo json_encode($pdfPageMeta); ?>;
window.PDF_TARGET_TABLE = <?php echo json_encode($template['target_table']); ?>;
// Riquadri già salvati nel DB: mapping_id -> regions object
window.PDF_SAVED_REGIONS = {
<?php foreach ($mappings as $m): ?>
<?php if (!empty($m['pdf_regions'])): ?>
<?php
$decodedRegions = json_decode($m['pdf_regions'], true);
if (is_array($decodedRegions)):
?> "<?php echo (int)$m['id']; ?>": <?php echo json_encode($decodedRegions); ?>,
<?php endif; ?>
<?php endif; ?>
<?php endforeach; ?>
};
</script>
<script src="pdf_mapping.js"></script>
<?php endif; ?>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -1,106 +0,0 @@
<?php
/**
* ONE-OFF MIGRATION
* Aggiunge la riga ClienteFatturazione a tutti i template che hanno
* già dei fixed fields (quindi hanno già usato "Add Fixed Fields")
* ma non hanno ancora ClienteFatturazione.
*
* Idempotente: si può rilanciare più volte senza creare duplicati.
*
* Uso:
* - Dry run (default): migrate_add_fatturazione.php
* - Esecuzione reale: migrate_add_fatturazione.php?confirm=1
*/
header('Content-Type: application/json');
require_once(__DIR__ . '/include/headscript.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$confirm = isset($_GET['confirm']) && $_GET['confirm'] == '1';
// Stessi default usati da create_fixed_fields.php per ClienteFatturazione
$FIELD_KEY = 'ClienteFatturazione';
$DATA_TYPE = 'INT';
$IS_MANUAL = 1;
$IS_REQUIRED = 1;
$DEFAULT_VAL = null;
$IS_VISIBLE = 1;
try {
// 1. Tutti i template_id distinti che hanno già fixed fields
$stmt = $pdo->query("SELECT DISTINCT template_id FROM template_fixed_mapping ORDER BY template_id ASC");
$allTemplateIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
// 2. Tra questi, quelli che NON hanno già ClienteFatturazione
$placeholders = implode(',', array_fill(0, count($allTemplateIds), '?'));
$missingTemplateIds = $allTemplateIds;
if (!empty($allTemplateIds)) {
$stmt = $pdo->prepare("
SELECT DISTINCT template_id
FROM template_fixed_mapping
WHERE fixed_field_key = ?
AND template_id IN ({$placeholders})
");
$stmt->execute(array_merge([$FIELD_KEY], $allTemplateIds));
$alreadyHave = $stmt->fetchAll(PDO::FETCH_COLUMN);
$missingTemplateIds = array_values(array_diff($allTemplateIds, $alreadyHave));
}
$report = [
'total_templates_with_fixed_fields' => count($allTemplateIds),
'templates_missing_fatturazione' => count($missingTemplateIds),
'template_ids_missing' => $missingTemplateIds,
'dry_run' => !$confirm,
'inserted' => 0,
];
if (empty($missingTemplateIds)) {
echo json_encode(['success' => true] + $report, JSON_PRETTY_PRINT);
exit;
}
if (!$confirm) {
// Dry run: mostra solo cosa farebbe, non inserisce nulla
echo json_encode(['success' => true] + $report, JSON_PRETTY_PRINT);
exit;
}
// 3. Esecuzione reale
$pdo->beginTransaction();
$ins = $pdo->prepare("
INSERT INTO template_fixed_mapping
(template_id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import)
VALUES
(:template_id, :fixed_field_key, :is_manual, :data_type, :is_required, :default_value, :is_visible_import)
");
$insertedCount = 0;
foreach ($missingTemplateIds as $tid) {
$ins->execute([
':template_id' => $tid,
':fixed_field_key' => $FIELD_KEY,
':is_manual' => $IS_MANUAL,
':data_type' => $DATA_TYPE,
':is_required' => $IS_REQUIRED,
':default_value' => $DEFAULT_VAL,
':is_visible_import' => $IS_VISIBLE,
]);
$insertedCount++;
}
$pdo->commit();
$report['inserted'] = $insertedCount;
echo json_encode(['success' => true] + $report, JSON_PRETTY_PRINT);
} catch (Exception $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
+16 -6
View File
@@ -136,9 +136,14 @@ $matrixGroups = array_values($matrixGroups);
<button type="button"
class="list-group-item list-group-item-action analysis-matrix-item <?= $index === 0 ? 'active' : '' ?>"
data-matrix-id="<?= htmlspecialchars((string)($group['idmatrice'] ?? 'NO_MATRIX'), ENT_QUOTES, 'UTF-8') ?>">
<div class="fw-semibold analysis-matrix-name"
data-matrix-id="<?= htmlspecialchars((string)($group['idmatrice'] ?? 'NO_MATRIX'), ENT_QUOTES, 'UTF-8') ?>">
<?= !empty($group['idmatrice']) ? ('#' . (int)$group['idmatrice']) : 'No Matrix' ?>
<div class="fw-semibold">
<?= htmlspecialchars(
!empty($group['NomeMatriceTraduzione'])
? $group['NomeMatriceTraduzione']
: (!empty($group['idmatrice']) ? ('Matrix without translation') : 'No Matrix'),
ENT_QUOTES,
'UTF-8'
) ?>
</div>
<div class="small text-muted">
ID: <?= !empty($group['idmatrice']) ? (int)$group['idmatrice'] : '-' ?>
@@ -188,9 +193,14 @@ $matrixGroups = array_values($matrixGroups);
<div class="small text-muted mt-1">
Matrix:
<strong class="analysis-part-matrix-name"
data-matrix-id="<?= htmlspecialchars($matrixId, ENT_QUOTES, 'UTF-8') ?>">
<?= !empty($part['idmatrice']) ? ('#' . (int)$part['idmatrice']) : 'No Matrix' ?>
<strong>
<?= htmlspecialchars(
!empty($part['NomeMatriceTraduzione'])
? $part['NomeMatriceTraduzione']
: (!empty($part['idmatrice']) ? 'Matrix without translation' : 'No Matrix'),
ENT_QUOTES,
'UTF-8'
) ?>
</strong>
<?php if (!empty($part['idmatrice'])): ?>
<span class="small text-muted">(ID: <?= (int)$part['idmatrice'] ?>)</span>
-7
View File
@@ -57,7 +57,6 @@
style="width: 34px; height: 28px; padding: 1px; border: 1px solid #ccc; border-radius: 4px; cursor: pointer;">
</div>
<button type="button" class="btn btn-primary btn-sm" id="addDescriptionsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Aggiungi Lista Descrizioni</button>
<button type="button" class="btn btn-outline-dark btn-sm" id="addFreeTextBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-font"></i> Testo Libero</button>
<button type="button" class="btn btn-danger btn-sm" id="removeAnnotationsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rimuovi Descrizioni</button>
<button type="button" class="btn btn-warning btn-sm" id="undoMarkerBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Undo Marker</button>
<button type="button" class="btn btn-success btn-sm" id="savePhotoBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Salva Foto con Nome</button>
@@ -281,11 +280,5 @@
margin: 2px 0;
border-radius: 4px;
}
#addFreeTextBtnAnnotations.active {
background-color: #343a40 !important;
border-color: #343a40 !important;
color: #fff !important;
}
</style>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
-68
View File
@@ -29,9 +29,6 @@
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; min-width: 0;">
<h6 style="margin: 0; white-space: nowrap;">Elenco Parti</h6>
<div style="display: flex; align-items: center; min-width: 0; gap: 8px;">
<button type="button" id="savePartsBtn" class="btn btn-success btn-sm">
<i class="fas fa-save"></i> Salva
</button>
<button type="button"
class="btn btn-dark btn-sm open-analysis-modal-btn"
id="openAnalysisModalBtn"
@@ -48,9 +45,6 @@
<button type="button" class="btn btn-primary btn-sm" id="clonePartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
<i class="fas fa-clone"></i> Clona Parti
</button>
<button type="button" class="btn btn-warning btn-sm d-none" id="quotationeBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
<i class="fas fa-file-invoice-dollar"></i> Collega Quotazione
</button>
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
<i class="fas fa-microphone"></i> Voce
</button>
@@ -194,43 +188,6 @@
</div>
</div>
</div>
<!-- Modale di conferma per la clonazione delle parti -->
<div class="modal fade" id="cloneConfirmModal" tabindex="-1" aria-labelledby="cloneConfirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="cloneConfirmModalLabel">Conferma Clonazione</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="clone-confirm-message mb-3"></p>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="cloneNotesCheckbox" checked>
<label class="form-check-label" for="cloneNotesCheckbox">
Duplica note
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="cloneAnalysesCheckbox">
<label class="form-check-label" for="cloneAnalysesCheckbox">
Duplica anche le analisi collegate
</label>
</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="cloneOverwriteCheckbox" checked>
<label class="form-check-label" for="cloneOverwriteCheckbox">
<strong>Sovrascrivi parti esistenti</strong> (elimina le parti già presenti nei record di destinazione prima di inserire)
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary btn-sm" id="cloneConfirmBtn">Conferma</button>
</div>
</div>
</div>
</div>
<!-- Finestra modale "Aggiungi preventivo" -->
<div class="modal fade" id="addQuotationModal" tabindex="-1" aria-labelledby="addQuotationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
@@ -251,23 +208,6 @@
</div>
<style>
#partsModalLabel {
font-size: 1rem;
font-weight: 600;
color: #666;
}
#trfHeader {
font-size: 1rem;
font-weight: 500;
color: #666;
}
/* --- Base --- */
#partsModal {
z-index: 1060 !important
}
/* --- Base --- */
#partsModal {
z-index: 1060 !important
@@ -798,13 +738,5 @@
padding: 0.1rem 0.25rem !important;
}
#cloneConfirmModal {
z-index: 1070 !important
}
#cloneConfirmModal .modal-backdrop {
z-index: 1065 !important
}
/* rosso */
</style>
-10
View File
@@ -171,11 +171,6 @@
);
if (idx >= 0) window.gridData.splice(idx, 1);
// Aggiorna la lista dei record visibili (navigazione parti + clone)
window.visibleIddatadbList = window.gridData
.map((r) => parseInt(r.iddatadb, 10))
.filter(Boolean);
// Re-render
const gr = window.gridRenderer;
if (gr) gr.renderVisibleRows();
@@ -238,11 +233,6 @@
// Add to beginning of gridData
window.gridData.unshift(newRow);
// Aggiorna la lista dei record visibili (navigazione parti + clone)
window.visibleIddatadbList = window.gridData
.map((r) => parseInt(r.iddatadb, 10))
.filter(Boolean);
// Re-render
const gr = window.gridRenderer;
if (gr) gr.renderVisibleRows();
File diff suppressed because it is too large Load Diff
-241
View File
@@ -1,241 +0,0 @@
<?php
/*
* pdf_extract_lib.php
* ------------------------------------------------------------------
* Cuore dell'estrazione PDF. Dato un file PDF e i riquadri (pdf_regions)
* salvati per ogni campo, usa `pdftotext -bbox -enc UTF-8` per ottenere
* le parole con le loro coordinate, e per ogni riquadro "value" raccoglie
* le parole il cui centro cade dentro il rettangolo.
*
* Le coordinate di pdftotext -bbox sono in punti PDF, origine in alto a
* sinistra: lo stesso sistema in cui PDF.js ha salvato i riquadri a scala
* 1.0. Quindi il match è diretto, senza conversioni.
*
* Funzioni pubbliche:
* - pdftotext_binary(): trova il percorso del binario su Win/Linux
* - pdf_extract_words($pdfPath): array di pagine con parole+coordinate
* - pdf_extract_values($pdfPath, $mappings): mapping_id => testo estratto
* ------------------------------------------------------------------
*/
/**
* Trova il binario pdftotext in base al sistema operativo.
* Su Windows cerca prima il percorso noto, poi il PATH.
* Su Linux usa /usr/bin/pdftotext, poi il PATH.
*/
function pdftotext_binary(): string
{
$isWindows = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
if ($isWindows) {
// Percorso noto sull'ambiente locale
$known = 'C:\\poppler\\poppler-26.02.0\\Library\\bin\\pdftotext.exe';
if (is_file($known)) {
return $known;
}
// Fallback: confida nel PATH
return 'pdftotext.exe';
}
// Linux / VPS
if (is_file('/usr/bin/pdftotext')) {
return '/usr/bin/pdftotext';
}
return 'pdftotext';
}
/**
* Lancia pdftotext -bbox e restituisce l'XHTML grezzo.
* @throws RuntimeException se il comando fallisce.
*/
function pdf_run_bbox(string $pdfPath): string
{
if (!is_file($pdfPath)) {
throw new RuntimeException("PDF non trovato: {$pdfPath}");
}
$bin = pdftotext_binary();
// -bbox: coordinate parole; -enc UTF-8: fix simboli (€, accenti); - : stdout
$cmd = escapeshellarg($bin)
. ' -bbox -enc UTF-8 '
. escapeshellarg($pdfPath)
. ' -';
$descriptors = [
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$proc = proc_open($cmd, $descriptors, $pipes);
if (!is_resource($proc)) {
throw new RuntimeException("Impossibile avviare pdftotext.");
}
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exit = proc_close($proc);
if ($exit !== 0) {
throw new RuntimeException("pdftotext ha restituito codice {$exit}: {$stderr}");
}
return $stdout;
}
/**
* Parsa l'XHTML di pdftotext -bbox in una struttura:
* [
* 1 => [ 'width'=>612, 'height'=>792, 'words'=>[ ['t'=>'Invoice','xMin'=>..,'yMin'=>..,'xMax'=>..,'yMax'=>..], ... ] ],
* 2 => [ ... ],
* ]
* Chiave = numero pagina (1-based).
*/
function pdf_parse_bbox(string $xhtml): array
{
$pages = [];
// Isola ogni blocco <page ...> ... </page>
if (!preg_match_all('#<page\b[^>]*>(.*?)</page>#si', $xhtml, $pageMatches, PREG_OFFSET_CAPTURE)) {
return $pages;
}
// Recupera anche gli attributi width/height di ogni page
preg_match_all('#<page\s+width="([0-9.]+)"\s+height="([0-9.]+)"#i', $xhtml, $dimMatches, PREG_SET_ORDER);
$pageNum = 0;
foreach ($pageMatches[1] as $idx => $inner) {
$pageNum++;
$body = $inner[0];
$width = isset($dimMatches[$idx][1]) ? (float)$dimMatches[$idx][1] : 0.0;
$height = isset($dimMatches[$idx][2]) ? (float)$dimMatches[$idx][2] : 0.0;
$words = [];
if (preg_match_all(
'#<word\s+xMin="([0-9.\-]+)"\s+yMin="([0-9.\-]+)"\s+xMax="([0-9.\-]+)"\s+yMax="([0-9.\-]+)"\s*>(.*?)</word>#si',
$body,
$wm,
PREG_SET_ORDER
)) {
foreach ($wm as $w) {
$text = html_entity_decode($w[5], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$words[] = [
't' => $text,
'xMin' => (float)$w[1],
'yMin' => (float)$w[2],
'xMax' => (float)$w[3],
'yMax' => (float)$w[4],
];
}
}
$pages[$pageNum] = [
'width' => $width,
'height' => $height,
'words' => $words,
];
}
return $pages;
}
/**
* Estrae tutte le parole con coordinate da un PDF.
* Ritorna la struttura di pdf_parse_bbox().
*/
function pdf_extract_words(string $pdfPath): array
{
$xhtml = pdf_run_bbox($pdfPath);
return pdf_parse_bbox($xhtml);
}
/**
* Dato un rettangolo {x,y,w,h} e la lista parole di una pagina, restituisce
* il testo delle parole il cui CENTRO cade dentro il rettangolo, ordinate
* per riga (yMin) e poi per colonna (xMin), unite da spazio.
*
* $tolerance espande il rettangolo di N punti su ogni lato (default 2),
* per non perdere parole tracciate al pelo del bordo.
*/
function pdf_text_in_rect(array $rect, array $words, float $tolerance = 2.0): string
{
$x1 = $rect['x'] - $tolerance;
$y1 = $rect['y'] - $tolerance;
$x2 = $rect['x'] + $rect['w'] + $tolerance;
$y2 = $rect['y'] + $rect['h'] + $tolerance;
$hits = [];
foreach ($words as $w) {
$cx = ($w['xMin'] + $w['xMax']) / 2.0;
$cy = ($w['yMin'] + $w['yMax']) / 2.0;
if ($cx >= $x1 && $cx <= $x2 && $cy >= $y1 && $cy <= $y2) {
$hits[] = $w;
}
}
if (empty($hits)) {
return '';
}
// Ordina per riga (yMin arrotondato a gruppi di ~3pt) poi per xMin
usort($hits, function ($a, $b) {
$ra = round($a['yMin'] / 3.0);
$rb = round($b['yMin'] / 3.0);
if ($ra !== $rb) {
return $ra <=> $rb;
}
return $a['xMin'] <=> $b['xMin'];
});
$parts = array_map(fn($w) => $w['t'], $hits);
$text = implode(' ', $parts);
// Normalizza spazi multipli
$text = preg_replace('/\s+/u', ' ', $text);
return trim($text);
}
/**
* Estrae i valori per ogni mapping che ha pdf_regions.
*
* @param string $pdfPath percorso del PDF da leggere
* @param array $mappings righe di template_mapping (devono contenere: id, pdf_regions)
* @return array [ mapping_id => testo_estratto ] (solo per i mapping con regione value)
*
* Nota: usa la regione "value" per estrarre il dato. La regione "label" al
* momento non è usata per l'estrazione (serve per la strategia futura di
* ricerca per etichetta, es. PDF con layout variabile).
*/
function pdf_extract_values(string $pdfPath, array $mappings): array
{
$pages = pdf_extract_words($pdfPath);
$result = [];
foreach ($mappings as $m) {
if (empty($m['pdf_regions'])) {
continue;
}
$regions = json_decode($m['pdf_regions'], true);
if (!is_array($regions) || empty($regions['value']) || empty($regions['page'])) {
continue;
}
$page = (int)$regions['page'];
if (!isset($pages[$page])) {
// pagina inesistente in questo PDF: valore vuoto
$result[(int)$m['id']] = '';
continue;
}
$value = $pdf_value_rect = $regions['value'];
$text = pdf_text_in_rect($value, $pages[$page]['words']);
$result[(int)$m['id']] = $text;
}
return $result;
}
-631
View File
@@ -1,631 +0,0 @@
/*
* pdf_mapping.js
* ------------------------------------------------------------------
* Logica di anteprima PDF e tracciamento riquadri (etichetta + dato)
* per la pagina mapping_template_xls_scheme2.php quando source = PDF.
*
* Dipendenze (caricate dalla pagina PHP):
* - PDF.js (pdfjsLib) via CDN
* - Le variabili globali definite inline nella pagina:
* window.PDF_TEMPLATE_ID -> id del template (int)
* window.PDF_SAMPLE_FILE -> nome file PDF di esempio (string, può essere '')
* window.PDF_PAGE_META -> metadati pagine salvati (object/array)
*
* Convenzioni:
* - Le coordinate dei riquadri sono salvate in "unità PDF" (punti PDF,
* scala 1.0), NON in pixel schermo. Così restano valide a qualsiasi zoom.
* - Ogni campo mappato ha un bottone "Traccia PDF" che attiva la modalità
* disegno per quel campo: prima l'etichetta, poi il dato.
*
* NB: questo file NON tocca la logica XLS/API della pagina. Si attiva solo
* se sourceType === 'PDF'.
* ------------------------------------------------------------------
*/
(function () {
"use strict";
// Esci subito se non siamo su un template PDF
if (typeof sourceType === "undefined" || sourceType !== "PDF") {
return;
}
// ----- Stato modulo -----
const state = {
pdfDoc: null, // documento PDF.js caricato
pageCanvases: {}, // page number -> { canvas, viewport, scale }
renderScale: 1.5, // scala di rendering a schermo
drawing: null, // { mappingId, phase:'label'|'value', startX, startY, rectEl, page }
pendingLabel: null, // riquadro etichetta in attesa (unità PDF) durante il disegno a due fasi
activeMappingId: null, // campo attualmente in tracciamento
};
// ----- Utility DOM -----
function el(tag, attrs = {}, children = []) {
const node = document.createElement(tag);
Object.entries(attrs).forEach(([k, v]) => {
if (k === "style" && typeof v === "object") {
Object.assign(node.style, v);
} else if (k === "class") {
node.className = v;
} else if (k === "text") {
node.textContent = v;
} else {
node.setAttribute(k, v);
}
});
(Array.isArray(children) ? children : [children]).forEach((c) => {
if (c)
node.appendChild(
typeof c === "string" ? document.createTextNode(c) : c,
);
});
return node;
}
function getPdfContainer() {
return document.getElementById("pdfPreviewContainer");
}
// ----- Caricamento e rendering PDF -----
async function loadPdf(url) {
const status = document.getElementById("pdfStatus");
try {
if (status) status.textContent = "Loading PDF...";
const loadingTask = pdfjsLib.getDocument(url);
state.pdfDoc = await loadingTask.promise;
await renderAllPages();
if (status)
status.textContent = `✅ PDF loaded: ${state.pdfDoc.numPages} page(s)`;
// Salva i metadati pagine (dimensioni a scala 1.0)
savePageMeta();
// Ridisegna i riquadri già salvati nel DB
redrawSavedRegions();
} catch (err) {
console.error("PDF load error:", err);
if (status)
status.textContent = "❌ Error loading PDF: " + err.message;
}
}
async function renderAllPages() {
const container = getPdfContainer();
if (!container) return;
container.innerHTML = "";
state.pageCanvases = {};
for (let pageNum = 1; pageNum <= state.pdfDoc.numPages; pageNum++) {
const page = await state.pdfDoc.getPage(pageNum);
const viewport = page.getViewport({ scale: state.renderScale });
// Wrapper posizionato per contenere canvas + overlay riquadri
const pageWrap = el("div", {
class: "pdf-page-wrap",
"data-page": pageNum,
style: {
position: "relative",
margin: "0 auto 20px auto",
width: viewport.width + "px",
height: viewport.height + "px",
boxShadow: "0 0 6px rgba(0,0,0,0.3)",
},
});
const canvas = el("canvas");
canvas.width = viewport.width;
canvas.height = viewport.height;
canvas.style.display = "block";
const overlay = el("div", {
class: "pdf-region-overlay",
"data-page": pageNum,
style: {
position: "absolute",
top: "0",
left: "0",
width: viewport.width + "px",
height: viewport.height + "px",
cursor: "crosshair",
},
});
pageWrap.appendChild(canvas);
pageWrap.appendChild(overlay);
container.appendChild(pageWrap);
const ctx = canvas.getContext("2d");
await page.render({ canvasContext: ctx, viewport }).promise;
state.pageCanvases[pageNum] = {
canvas,
overlay,
viewport,
scale: state.renderScale,
pdfWidth: viewport.width / state.renderScale, // larghezza a scala 1.0
pdfHeight: viewport.height / state.renderScale, // altezza a scala 1.0
};
attachOverlayEvents(overlay, pageNum);
}
}
// ----- Conversione pixel schermo <-> unità PDF -----
function screenToPdf(pageNum, xPx, yPx) {
const p = state.pageCanvases[pageNum];
return { x: xPx / p.scale, y: yPx / p.scale };
}
function pdfToScreen(pageNum, xPdf, yPdf) {
const p = state.pageCanvases[pageNum];
return { x: xPdf * p.scale, y: yPdf * p.scale };
}
// ----- Disegno riquadri sull'overlay -----
function attachOverlayEvents(overlay, pageNum) {
let startX = 0,
startY = 0;
let rectEl = null;
overlay.addEventListener("mousedown", (e) => {
if (!state.activeMappingId) return; // nessun campo in tracciamento
const rect = overlay.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
rectEl = el("div", {
class: "pdf-draw-rect",
style: {
position: "absolute",
left: startX + "px",
top: startY + "px",
width: "0px",
height: "0px",
border: "2px solid #dc3545",
background: "rgba(220,53,69,0.15)",
pointerEvents: "none",
},
});
overlay.appendChild(rectEl);
});
overlay.addEventListener("mousemove", (e) => {
if (!rectEl) return;
const rect = overlay.getBoundingClientRect();
const curX = e.clientX - rect.left;
const curY = e.clientY - rect.top;
const x = Math.min(startX, curX);
const y = Math.min(startY, curY);
const w = Math.abs(curX - startX);
const h = Math.abs(curY - startY);
Object.assign(rectEl.style, {
left: x + "px",
top: y + "px",
width: w + "px",
height: h + "px",
});
});
overlay.addEventListener("mouseup", (e) => {
if (!rectEl) return;
const rect = overlay.getBoundingClientRect();
const endX = e.clientX - rect.left;
const endY = e.clientY - rect.top;
const x = Math.min(startX, endX);
const y = Math.min(startY, endY);
const w = Math.abs(endX - startX);
const h = Math.abs(endY - startY);
overlay.removeChild(rectEl);
rectEl = null;
if (w < 4 || h < 4) return; // click accidentale, ignora
// Converti in unità PDF
const topLeft = screenToPdf(pageNum, x, y);
const pdfRect = {
x: topLeft.x,
y: topLeft.y,
w: w / state.pageCanvases[pageNum].scale,
h: h / state.pageCanvases[pageNum].scale,
};
handleRectDrawn(pageNum, pdfRect);
});
}
// ----- Logica a due fasi: prima etichetta, poi dato -----
function startTracking(mappingId) {
state.activeMappingId = mappingId;
state.pendingLabel = null;
setBanner(
`Traccia il riquadro dell'ETICHETTA per il campo #${mappingId} (poi il dato). ESC per annullare.`,
);
}
function handleRectDrawn(pageNum, pdfRect) {
if (!state.activeMappingId) return;
if (state.pendingLabel === null) {
// Fase 1: etichetta
state.pendingLabel = { page: pageNum, rect: pdfRect };
drawPersistentRect(
pageNum,
pdfRect,
"label",
state.activeMappingId,
);
setBanner(
`Ora traccia il riquadro del DATO per il campo #${state.activeMappingId}. ESC per annullare.`,
);
} else {
// Fase 2: dato — deve stare sulla stessa pagina dell'etichetta
if (pageNum !== state.pendingLabel.page) {
alert("Etichetta e dato devono stare sulla stessa pagina.");
return;
}
const regions = {
page: pageNum,
label: state.pendingLabel.rect,
value: pdfRect,
};
drawPersistentRect(
pageNum,
pdfRect,
"value",
state.activeMappingId,
);
saveRegions(state.activeMappingId, regions);
// Reset stato tracciamento
const doneId = state.activeMappingId;
state.activeMappingId = null;
state.pendingLabel = null;
clearBanner();
updateRowBadge(doneId, regions);
}
}
// Disegna un riquadro persistente (già confermato) sull'overlay
function drawPersistentRect(pageNum, pdfRect, kind, mappingId) {
const p = state.pageCanvases[pageNum];
if (!p) return;
const tl = pdfToScreen(pageNum, pdfRect.x, pdfRect.y);
const w = pdfRect.w * p.scale;
const h = pdfRect.h * p.scale;
const color = kind === "label" ? "#0d6efd" : "#198754";
const box = el(
"div",
{
class: "pdf-saved-rect",
"data-mapping-id": mappingId,
"data-kind": kind,
style: {
position: "absolute",
left: tl.x + "px",
top: tl.y + "px",
width: w + "px",
height: h + "px",
border: "2px solid " + color,
background:
kind === "label"
? "rgba(13,110,253,0.12)"
: "rgba(25,135,84,0.12)",
pointerEvents: "none",
},
},
[
el("span", {
style: {
position: "absolute",
top: "-16px",
left: "0",
fontSize: "10px",
fontWeight: "600",
color: "#fff",
background: color,
padding: "0 4px",
borderRadius: "3px",
whiteSpace: "nowrap",
},
text:
(kind === "label" ? "Etichetta" : "Dato") +
" #" +
mappingId,
}),
],
);
p.overlay.appendChild(box);
}
// Rimuove i riquadri disegnati per un dato campo (prima di ridisegnarli)
function clearRectsForMapping(mappingId) {
document
.querySelectorAll(`.pdf-saved-rect[data-mapping-id="${mappingId}"]`)
.forEach((node) => node.remove());
}
// ----- Banner istruzioni -----
function setBanner(msg) {
let banner = document.getElementById("pdfTrackingBanner");
if (!banner) {
banner = el("div", {
id: "pdfTrackingBanner",
class: "alert alert-info",
style: {
position: "sticky",
top: "0",
zIndex: "1000",
marginBottom: "10px",
},
});
const container = getPdfContainer();
container.parentNode.insertBefore(banner, container);
}
banner.textContent = msg;
banner.style.display = "block";
}
function clearBanner() {
const banner = document.getElementById("pdfTrackingBanner");
if (banner) banner.style.display = "none";
}
// ESC annulla il tracciamento in corso
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && state.activeMappingId) {
clearRectsForMapping(state.activeMappingId);
// ridisegna eventuali riquadri salvati per quel campo
const saved = getSavedRegionsForMapping(state.activeMappingId);
if (saved) {
if (saved.label)
drawPersistentRect(
saved.page,
saved.label,
"label",
state.activeMappingId,
);
if (saved.value)
drawPersistentRect(
saved.page,
saved.value,
"value",
state.activeMappingId,
);
}
state.activeMappingId = null;
state.pendingLabel = null;
clearBanner();
}
});
// ----- Persistenza -----
function saveRegions(mappingId, regions) {
fetch("save_pdf_regions.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id: mappingId,
mapping_type: "pdf",
pdf_regions: regions,
manual_default: null,
auto_value: "none",
tablename:
typeof PDF_TARGET_TABLE !== "undefined"
? PDF_TARGET_TABLE
: "",
}),
})
.then((r) => r.json())
.then((d) => {
if (!d.success) {
console.error("❌ Error saving PDF regions:", d.message);
alert("Errore nel salvataggio dei riquadri: " + d.message);
} else {
console.log("✅ PDF regions saved for mapping", mappingId);
// aggiorna la cache locale
savedRegionsCache[mappingId] = regions;
}
})
.catch((err) =>
console.error("❌ Fetch error saving PDF regions:", err),
);
}
function savePageMeta() {
const dims = [];
Object.entries(state.pageCanvases).forEach(([pageNum, p]) => {
dims.push({
page: parseInt(pageNum, 10),
width: Math.round(p.pdfWidth),
height: Math.round(p.pdfHeight),
});
});
const meta = { pages: state.pdfDoc.numPages, dims };
fetch("update_pdf_page_meta.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
template_id: PDF_TEMPLATE_ID,
pdf_page_meta: JSON.stringify(meta),
}),
})
.then((r) => r.json())
.then((d) => {
if (!d.success)
console.error("❌ Error saving page meta:", d.message);
})
.catch((err) =>
console.error("❌ Fetch error saving page meta:", err),
);
}
// ----- Riquadri già salvati (letti dal DOM al load) -----
// Popolata dalla pagina PHP: mapping_id -> regions object
const savedRegionsCache =
typeof window.PDF_SAVED_REGIONS === "object" && window.PDF_SAVED_REGIONS
? window.PDF_SAVED_REGIONS
: {};
function getSavedRegionsForMapping(mappingId) {
return savedRegionsCache[mappingId] || null;
}
function redrawSavedRegions() {
Object.entries(savedRegionsCache).forEach(([mappingId, regions]) => {
if (!regions) return;
const page = regions.page;
if (!state.pageCanvases[page]) return;
if (regions.label)
drawPersistentRect(page, regions.label, "label", mappingId);
if (regions.value)
drawPersistentRect(page, regions.value, "value", mappingId);
updateRowBadge(mappingId, regions);
});
}
// ----- Aggiorna il badge/etichetta nella riga della tabella -----
function updateRowBadge(mappingId, regions) {
const badge = document.querySelector(
`.pdf-mapped-info[data-id="${mappingId}"]`,
);
if (badge) {
if (regions && regions.value) {
badge.textContent = `✓ pag.${regions.page}`;
badge.style.display = "inline";
} else {
badge.textContent = "";
badge.style.display = "none";
}
}
}
// ----- Aggancio dei bottoni "Traccia PDF" nelle righe -----
function wireTrackButtons() {
document.querySelectorAll(".pdf-track-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const mappingId = btn.getAttribute("data-id");
if (!state.pdfDoc) {
alert("Carica prima un PDF di esempio.");
return;
}
// se il campo aveva già riquadri, li ripuliamo per ridisegnarli
clearRectsForMapping(mappingId);
startTracking(mappingId);
// scrolla alla preview
getPdfContainer().scrollIntoView({
behavior: "smooth",
block: "start",
});
});
});
}
// ----- Bottone rimuovi riquadri per campo -----
function wireRemoveButtons() {
document.querySelectorAll(".pdf-remove-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const mappingId = btn.getAttribute("data-id");
clearRectsForMapping(mappingId);
delete savedRegionsCache[mappingId];
updateRowBadge(mappingId, null);
// salva reset (mapping vuoto)
fetch("save_pdf_regions.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id: mappingId,
mapping_type: "",
pdf_regions: null,
manual_default: null,
auto_value: "none",
tablename:
typeof PDF_TARGET_TABLE !== "undefined"
? PDF_TARGET_TABLE
: "",
}),
})
.then((r) => r.json())
.then((d) => {
if (!d.success)
console.error(
"❌ Error removing PDF regions:",
d.message,
);
})
.catch((err) =>
console.error(
"❌ Fetch error removing PDF regions:",
err,
),
);
});
});
}
// ----- Upload del PDF di esempio -----
function wireUpload() {
const input = document.getElementById("pdfUpload");
if (!input) return;
input.addEventListener("change", (event) => {
const file = event.target.files[0];
if (!file) return;
const status = document.getElementById("pdfStatus");
if (status) status.textContent = "Uploading...";
const formData = new FormData();
formData.append("pdf_file", file);
formData.append("template_id", PDF_TEMPLATE_ID);
fetch("upload_pdf_example.php", { method: "POST", body: formData })
.then((r) => r.json())
.then((data) => {
if (!data.success) {
if (status)
status.textContent =
"❌ Upload failed: " + data.message;
return;
}
if (status) {
status.innerHTML = `✅ File uploaded: <a href="pdftemplates/${data.filename}" target="_blank">${data.filename}</a>`;
}
// carica subito per l'anteprima usando il file locale scelto
const localUrl = URL.createObjectURL(file);
loadPdf(localUrl);
})
.catch((err) => {
if (status)
status.textContent = "❌ Upload failed. Check console.";
console.error(err);
});
});
}
// ----- Init -----
document.addEventListener("DOMContentLoaded", function () {
// Configura il worker di PDF.js
if (typeof pdfjsLib !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
}
wireUpload();
wireTrackButtons();
wireRemoveButtons();
// Se c'è già un PDF di esempio salvato, caricalo dal server
if (typeof PDF_SAMPLE_FILE !== "undefined" && PDF_SAMPLE_FILE) {
loadPdf("pdftemplates/" + PDF_SAMPLE_FILE);
}
});
})();
+53 -3
View File
@@ -4,6 +4,35 @@ require_once 'class/db-functions.php';
$response = ["success" => false, "message" => ""];
function excelColumnToIndex($column)
{
$column = strtoupper(trim((string)$column));
if ($column === '') {
return null;
}
// Numeric column index, example: 40
if (ctype_digit($column)) {
$index = (int)$column;
return $index > 0 ? $index : null;
}
// Excel column letters, example: A, AN, XFC
if (!preg_match('/^[A-Z]+$/', $column)) {
return null;
}
$index = 0;
$length = strlen($column);
for ($i = 0; $i < $length; $i++) {
$index = ($index * 26) + (ord($column[$i]) - ord('A') + 1);
}
return $index;
}
try {
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
throw new Exception("Invalid request method.");
@@ -19,6 +48,8 @@ try {
: null;
$start_column = trim($_POST['start_column'] ?? '');
$xls_end_column = strtoupper(trim($_POST['xls_end_column'] ?? ''));
$xls_end_column = $xls_end_column !== '' ? $xls_end_column : null;
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
? intval($_POST['xls_sheet_index'])
@@ -39,7 +70,6 @@ try {
$button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff');
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
$button_label = trim($_POST['button_label'] ?? 'Click Me');
$template_note = trim($_POST['template_note'] ?? '');
// Allowed source types
if (!in_array($source_type, ['XLS', 'API', 'JSON', 'PDF'], true)) {
@@ -61,6 +91,24 @@ try {
throw new Exception("XLS Sheet Number cannot be negative.");
}
$startColumnIndex = excelColumnToIndex($start_column);
if ($startColumnIndex === null) {
throw new Exception("Start Column is not valid. Use Excel column letters like A, AN or a positive number.");
}
if ($xls_end_column !== null) {
$endColumnIndex = excelColumnToIndex($xls_end_column);
if ($endColumnIndex === null) {
throw new Exception("Last Column is not valid. Use Excel column letters like AN or a positive number.");
}
if ($endColumnIndex < $startColumnIndex) {
throw new Exception("Last Column cannot be before Start Column.");
}
}
$api_config_id = null;
}
@@ -72,6 +120,7 @@ try {
$header_row = null;
$start_column = null;
$xls_end_column = null;
$xls_sheet_index = null;
}
@@ -79,6 +128,7 @@ try {
if ($source_type === 'PDF') {
$header_row = null;
$start_column = null;
$xls_end_column = null;
$xls_sheet_index = null;
$api_config_id = null;
}
@@ -110,6 +160,7 @@ try {
source_type = ?,
header_row = ?,
start_column = ?,
xls_end_column = ?,
xls_sheet_index = ?,
api_config_id = ?,
description = ?,
@@ -123,7 +174,6 @@ try {
button_bg_color = ?,
button_text_color = ?,
button_label = ?,
template_note = ?,
updated_at = NOW()
WHERE id = ?
");
@@ -133,6 +183,7 @@ try {
$source_type,
$header_row,
$start_column,
$xls_end_column,
$xls_sheet_index,
$api_config_id,
$description,
@@ -146,7 +197,6 @@ try {
$button_bg_color,
$button_text_color,
$button_label,
$template_note,
$id
]);
-256
View File
@@ -1,256 +0,0 @@
<?php
/*
* process_import_pdf.php
* ------------------------------------------------------------------
* Riceve N PDF (multipart, campo pdf_files[]) + template_id + importreferencecode.
* Per ogni PDF:
* 1. salva il file in public/userarea/imported_pdf/
* 2. estrae i valori dai riquadri (pdf_extract_lib.php)
* 3. inserisce UNA riga in datadb + i dettagli in import_data_details
* applicando la STESSA logica di import_insert_batch.php
* (tipi di dato, auto_value, campo 244 con lims_user_id)
*
* Tutti i PDF del gruppo condividono lo stesso importreferencecode.
*
* Ritorna JSON: { ok, results: [ {filename, ok, iddatadb, values{}, error} ] }
* ------------------------------------------------------------------
*/
ini_set('display_errors', 0);
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/pdf_import_debug.log');
include('include/headscript.php');
require_once __DIR__ . '/pdf_extract_lib.php';
header('Content-Type: application/json; charset=utf-8');
function respond(array $payload): void
{
echo json_encode($payload);
exit;
}
// ----- Validazione input -----
$template_id = isset($_POST['template_id']) ? (int)$_POST['template_id'] : 0;
$importReferenceCode = isset($_POST['importreferencecode']) ? (string)$_POST['importreferencecode'] : '';
if ($template_id <= 0) {
respond(['ok' => false, 'error' => 'template_id non valido']);
}
if ($importReferenceCode === '') {
respond(['ok' => false, 'error' => 'importreferencecode mancante']);
}
if (empty($_FILES['pdf_files']) || !is_array($_FILES['pdf_files']['name'])) {
respond(['ok' => false, 'error' => 'Nessun PDF ricevuto']);
}
$user_id = $iduserlogin ?? 1;
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// ----- Mapping del template -----
$stmt = $pdo->prepare("
SELECT id, pdf_regions, data_type, is_required, manual_default, is_manual,
field_label, field_id, main_field, auto_value
FROM template_mapping
WHERE template_id = ?
");
$stmt->execute([$template_id]);
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($allMappings)) {
respond(['ok' => false, 'error' => 'Nessun mapping trovato per il template']);
}
// ----- idclient di default -----
$tplStmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
$tplStmt->execute([$template_id]);
$tpl = $tplStmt->fetch(PDO::FETCH_ASSOC);
$default_idclient = $tpl['idclient'] ?? null;
// ----- lims_user_id per il campo 244 (come nel batch XLS) -----
$stmtUser = $pdo->prepare("SELECT lims_user_id FROM auth_users WHERE id = ? LIMIT 1");
$stmtUser->execute([(int)$user_id]);
$limsUserId = $stmtUser->fetchColumn();
$limsUserId = ($limsUserId !== false && $limsUserId !== null && $limsUserId !== '') ? (string)$limsUserId : '';
$stmtMap = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ? AND field_id = 244 LIMIT 1");
$stmtMap->execute([(int)$template_id]);
$mappingId244 = (int)$stmtMap->fetchColumn();
// ----- Cartella di destinazione dei PDF importati -----
$importFolder = __DIR__ . DIRECTORY_SEPARATOR . 'pdftemplates' . DIRECTORY_SEPARATOR . 'imported';
if (!is_dir($importFolder)) {
@mkdir($importFolder, 0775, true);
}
/**
* Applica la conversione del valore in base al data_type,
* REPLICA della logica di import_insert_batch.php.
*/
function convertFieldValue(array $mapping, ?string $rawValue): string
{
$fieldValue = $rawValue;
if (!$mapping['is_manual']) {
// Valore estratto dal PDF; se vuoto, fallback al manual_default
if ($fieldValue === null || $fieldValue === '') {
$fieldValue = $mapping['manual_default'] ?? '';
}
switch ($mapping['data_type']) {
case 'INT':
$fieldValue = is_numeric($fieldValue) ? (int)$fieldValue : ($mapping['manual_default'] ?? 0);
break;
case 'DATE':
$fieldValue = !empty($fieldValue)
? date('Y-m-d', strtotime($fieldValue))
: ($mapping['manual_default'] === 'today' ? date('Y-m-d') : ($mapping['manual_default'] ?? ''));
break;
case 'CHAR':
$fieldValue = !empty($fieldValue) ? substr((string)$fieldValue, 0, 1) : ($mapping['manual_default'] ?? '');
break;
case 'Testo':
case 'VARCHAR':
default:
$fieldValue = !empty($fieldValue) ? (string)$fieldValue : ($mapping['manual_default'] ?? '');
break;
}
} else {
// Campo manuale: usa manual_default
$fieldValue = $mapping['manual_default'] ?? '';
if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') {
$fieldValue = date('Y-m-d');
}
}
// auto_value se ancora vuoto (come nel batch XLS)
if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') {
if ($mapping['auto_value'] === 'import_date') {
$fieldValue = date('Y-m-d');
} elseif ($mapping['auto_value'] === 'import_time') {
$fieldValue = date('H:i');
}
}
return (string)($fieldValue ?? '');
}
$results = [];
$nFiles = count($_FILES['pdf_files']['name']);
for ($i = 0; $i < $nFiles; $i++) {
$origName = $_FILES['pdf_files']['name'][$i];
$tmpName = $_FILES['pdf_files']['tmp_name'][$i];
$err = $_FILES['pdf_files']['error'][$i];
$fileResult = [
'filename' => $origName,
'ok' => false,
'iddatadb' => null,
'values' => [],
'error' => '',
];
// Errore di upload
if ($err !== UPLOAD_ERR_OK) {
$fileResult['error'] = 'Errore upload (codice ' . $err . ')';
$results[] = $fileResult;
continue;
}
// Estensione PDF
if (strtolower(pathinfo($origName, PATHINFO_EXTENSION)) !== 'pdf') {
$fileResult['error'] = 'Non è un PDF';
$results[] = $fileResult;
continue;
}
// Nome sicuro e univoco
$safeBase = preg_replace('/[^A-Za-z0-9_\-]/', '_', pathinfo($origName, PATHINFO_FILENAME));
$safeBase = trim($safeBase, '_') ?: 'pdf';
$storedName = 'imp' . $template_id . '_' . $safeBase . '_' . date('YmdHis') . '_' . $i . '.pdf';
$storedPath = $importFolder . DIRECTORY_SEPARATOR . $storedName;
if (!move_uploaded_file($tmpName, $storedPath)) {
$fileResult['error'] = 'Impossibile salvare il file';
$results[] = $fileResult;
continue;
}
// ----- Estrazione dai riquadri -----
try {
$extracted = pdf_extract_values($storedPath, $allMappings); // [mapping_id => testo]
} catch (Throwable $e) {
$fileResult['error'] = 'Estrazione fallita: ' . $e->getMessage();
$results[] = $fileResult;
continue;
}
// ----- Inserimento in datadb + dettagli (una riga per PDF) -----
try {
$pdo->beginTransaction();
$insDatadb = $pdo->prepare("INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$insDetail = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
// excelrow non ha senso per il PDF: usiamo 0 (o l'indice del file)
$insDatadb->execute([
$template_id,
$importReferenceCode,
$storedName,
'i',
$user_id,
null,
date('Y-m-d'),
0,
$default_idclient
]);
$iddatadb = $pdo->lastInsertId();
$fileResult['iddatadb'] = $iddatadb;
$shownValues = [];
foreach ($allMappings as $mapping) {
$rawValue = $extracted[(int)$mapping['id']] ?? null;
$fieldValue = convertFieldValue($mapping, $rawValue);
$insDetail->execute([$iddatadb, $mapping['id'], $fieldValue]);
// per il feedback UI mostra solo i campi con riquadro
if (!empty($mapping['pdf_regions'])) {
$shownValues[$mapping['field_label'] ?? ('#' . $mapping['id'])] = $fieldValue;
}
}
// Campo 244 (Accettatore) con lims_user_id
if ($limsUserId !== '' && $mappingId244 > 0) {
$insDetail->execute([$iddatadb, $mappingId244, $limsUserId]);
}
$pdo->commit();
$fileResult['ok'] = true;
$fileResult['values'] = $shownValues;
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
error_log('[PDF IMPORT] ' . $e->getMessage());
$fileResult['error'] = 'Inserimento DB fallito: ' . $e->getMessage();
}
$results[] = $fileResult;
}
respond([
'ok' => true,
'importreferencecode' => $importReferenceCode,
'template_id' => $template_id,
'results' => $results,
]);
+68 -191
View File
@@ -53,143 +53,7 @@ function normalizeColumnIndex($value): int
return 1;
}
/**
* Trim a cell value treating invisible/Unicode spaces as empty.
* PHP's native trim() strips only ASCII whitespace (" \t\n\r\0\x0B"), so a cell
* that contains only a non-breaking space (U+00A0), zero-width space (U+200B),
* BOM (U+FEFF) or another Unicode space looks blank to a human but would still
* count as "filled" pulling a ghost column into the import or retaining a
* visually-empty row. Normalize those to a real space before trimming.
*/
function cleanCellText($value): string
{
$raw = (string)$value;
$cleaned = preg_replace(
'/[\x{00A0}\x{200B}\x{FEFF}\x{2000}-\x{200A}\x{202F}\x{205F}\x{3000}]+/u',
' ',
$raw
);
// preg_replace returns null on malformed UTF-8; fall back to the raw value.
return trim($cleaned ?? $raw);
}
/**
* Pre-clean an .xlsx by streaming out "ghost" cells: empty, self-closing
* <c .../> (and <c ...></c>) elements that carry only leftover styling.
*/
function slimXlsxGhostCells(string $path): ?string
{
if (!class_exists('ZipArchive')) {
return null;
}
$slim = $path . '.slim.xlsx';
try {
if (!copy($path, $slim)) {
return null;
}
// Phase 1: stream-strip each worksheet to a temp file (low memory).
$zip = new ZipArchive();
if ($zip->open($slim) !== true) {
@unlink($slim);
return null;
}
$temps = [];
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if (!preg_match('#^xl/worksheets/sheet\d+\.xml$#', $name)) {
continue;
}
$in = $zip->getStream($name);
if (!$in) {
continue;
}
$tmp = tempnam(sys_get_temp_dir(), 'slim');
$out = fopen($tmp, 'w');
$carry = '';
while (!feof($in)) {
$chunk = fread($in, 4194304);
if ($chunk === '' || $chunk === false) {
break;
}
// Only process up to the last complete '>' so a cell tag is
// never split across a chunk boundary; carry the remainder.
$buf = $carry . $chunk;
$lastGt = strrpos($buf, '>');
if ($lastGt === false) {
$carry = $buf;
continue;
}
$proc = substr($buf, 0, $lastGt + 1);
$carry = substr($buf, $lastGt + 1);
$proc = preg_replace(['#<c [^>]*/>#', '#<c [^>]*></c>#'], '', $proc);
fwrite($out, $proc);
}
if ($carry !== '') {
fwrite($out, $carry);
}
fclose($in);
fclose($out);
$temps[$name] = $tmp;
}
$zip->close();
if (!$temps) {
@unlink($slim);
return null;
}
// Phase 2: swap the stripped worksheets back into the archive.
$zip = new ZipArchive();
if ($zip->open($slim) !== true) {
foreach ($temps as $t) {
@unlink($t);
}
@unlink($slim);
return null;
}
foreach ($temps as $name => $tmp) {
$zip->deleteName($name);
$zip->addFile($tmp, $name);
}
$zip->close(); // addFile streams from disk here, so unlink only after.
foreach ($temps as $t) {
@unlink($t);
}
return $slim;
} catch (\Throwable $e) {
error_log('slimXlsxGhostCells failed: ' . $e->getMessage());
@unlink($slim);
return null;
}
}
try {
// Quando il body POST supera post_max_size, PHP scarta $_POST e $_FILES
// (warning "Content-Length exceeds the limit ... in Unknown on line 0") e lo
// script riceve una richiesta vuota. Lo intercettiamo per dare un messaggio
// chiaro invece di "Richiesta non valida".
if (
$_SERVER['REQUEST_METHOD'] === 'POST'
&& empty($_POST) && empty($_FILES)
&& (int)($_SERVER['CONTENT_LENGTH'] ?? 0) > 0
) {
$postMax = ini_get('post_max_size');
throw new Exception(
"Il file caricato supera il limite di upload del server (post_max_size = {$postMax}). " .
"Chiedi all'amministratore di aumentare post_max_size e upload_max_filesize."
);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['excel_file'])) {
$template_id = isset($_POST['template_id']) ? intval($_POST['template_id']) : 0;
@@ -206,10 +70,11 @@ try {
* Così non dipendiamo solo dal form e siamo sicuri di usare i dati salvati.
*/
$stmt = $pdo->prepare("
SELECT
SELECT
id,
header_row,
start_column,
xls_end_column,
xls_sheet_index,
idroutine,
idclient
@@ -229,6 +94,12 @@ try {
$start_column_raw = $template['start_column'] ?? 'A';
$start_column = normalizeColumnIndex($start_column_raw);
$xls_end_column_raw = trim((string)($template['xls_end_column'] ?? ''));
$xls_end_column = $xls_end_column_raw !== '' ? normalizeColumnIndex($xls_end_column_raw) : 0;
if ($xls_end_column > 0 && $xls_end_column < $start_column) {
throw new Exception("Last Column cannot be before Start Column.");
}
$xlsSheetIndex = isset($template['xls_sheet_index']) && $template['xls_sheet_index'] !== null
? (int)$template['xls_sheet_index']
@@ -245,7 +116,15 @@ try {
// Debug del template_id ricevuto
error_log("Received template_id from POST: " . print_r($_POST['template_id'], true));
error_log("Converted template_id: $template_id");
error_log("Template XLS settings - header_row: $header_row, start_column_raw: $start_column_raw, start_column_index: $start_column, xls_sheet_index: $xlsSheetIndex");
error_log(
"Template XLS settings - " .
"header_row: $header_row, " .
"start_column_raw: $start_column_raw, " .
"start_column_index: $start_column, " .
"xls_end_column_raw: " . ($xls_end_column_raw !== '' ? $xls_end_column_raw : '[empty = read all]') . ", " .
"xls_end_column_index: " . ($xls_end_column > 0 ? $xls_end_column : '[no limit]') . ", " .
"xls_sheet_index: $xlsSheetIndex"
);
$file = $_FILES['excel_file'];
$fileError = $file['error'];
@@ -278,14 +157,14 @@ try {
// Recupera il mapping da template_mapping
$stmt = $pdo->prepare("
SELECT
field_id AS excel_column,
field_id AS mysql_column,
data_type,
is_required,
default_value,
is_manual
FROM template_mapping
SELECT
field_id AS excel_column,
field_id AS mysql_column,
data_type,
is_required,
default_value,
is_manual
FROM template_mapping
WHERE template_id = ?
");
$stmt->execute([$template_id]);
@@ -297,28 +176,33 @@ try {
if (empty($mappings)) {
$response['error'] = "Nessun mapping trovato per il template con ID $template_id";
} else {
// Pre-clean ghost cells for .xlsx so a bloated worksheet (millions
// of empty styled cells) doesn't make the load time out. Falls back
// to the original file if slimming fails for any reason.
$loadPath = $destination;
$slimPath = null;
if (preg_match('/\.xlsx$/i', $destination)) {
$slimPath = slimXlsxGhostCells($destination);
if ($slimPath !== null) {
$loadPath = $slimPath;
error_log("Ghost-cell pre-clean applied, loading slimmed copy: $slimPath");
}
// Load the XLS/XLSX file.
// If Last Column is configured in the template, load only the configured column range.
// If Last Column is empty, keep the original behavior and read the entire sheet.
$reader = IOFactory::createReaderForFile($destination);
$reader->setReadDataOnly(true);
if ($xls_end_column > 0) {
$reader->setReadFilter(new class($start_column, $xls_end_column) implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter {
private int $startColumn;
private int $endColumn;
public function __construct(int $startColumn, int $endColumn)
{
$this->startColumn = $startColumn;
$this->endColumn = $endColumn;
}
public function readCell($columnAddress, $row, $worksheetName = ''): bool
{
$columnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($columnAddress);
return $columnIndex >= $this->startColumn && $columnIndex <= $this->endColumn;
}
});
}
// Carica il file con PHPSpreadsheet.
$reader = IOFactory::createReaderForFile($loadPath);
$reader->setReadEmptyCells(false);
$spreadsheet = $reader->load($loadPath);
// The slimmed copy is only needed for parsing; drop it now.
if ($slimPath !== null) {
@unlink($slimPath);
}
$spreadsheet = $reader->load($destination);
$sheetCount = $spreadsheet->getSheetCount();
$sheetNames = $spreadsheet->getSheetNames();
@@ -345,17 +229,23 @@ try {
error_log("Selected XLS sheet - index: {$xlsSheetIndex}, name: {$selectedSheetName}");
$highestRow = $worksheet->getHighestDataRow();
$highestColumn = $worksheet->getHighestDataColumn();
$highestRow = $worksheet->getHighestRow();
$highestColumn = $worksheet->getHighestColumn();
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
// Force the effective highest column to Last Column, if configured.
if ($xls_end_column > 0) {
$highestColumnIndex = $xls_end_column;
$highestColumn = Coordinate::stringFromColumnIndex($highestColumnIndex);
}
$startRow = max(1, $header_row);
$startColumn = max(1, $start_column);
// Advance startColumn to first non-empty cell in header row, matching JS behavior
for ($sc = $startColumn; $sc <= $highestColumnIndex; $sc++) {
$cl = Coordinate::stringFromColumnIndex($sc);
$cv = cleanCellText($worksheet->getCell($cl . $header_row)->getCalculatedValue() ?? '');
$cv = trim((string)($worksheet->getCell($cl . $header_row)->getCalculatedValue() ?? ''));
if ($cv !== '') {
$startColumn = $sc;
@@ -363,19 +253,6 @@ try {
}
}
$lastHeaderCol = $startColumn;
for ($hc = $startColumn; $hc <= $highestColumnIndex; $hc++) {
$hl = Coordinate::stringFromColumnIndex($hc);
$hv = cleanCellText($worksheet->getCell($hl . $header_row)->getCalculatedValue() ?? '');
if ($hv !== '') {
$lastHeaderCol = $hc;
}
}
$highestColumnIndex = $lastHeaderCol;
$highestColumn = Coordinate::stringFromColumnIndex($highestColumnIndex);
// Debug dei parametri
error_log(
"Processing - template_id: $template_id, " .
@@ -438,7 +315,7 @@ try {
$columnLetter = Coordinate::stringFromColumnIndex($physCol);
$cell = $worksheet->getCell($columnLetter . $header_row);
$cellValue = cleanCellText($cell ? $cell->getCalculatedValue() : '');
$cellValue = trim((string)($cell ? $cell->getCalculatedValue() : ''));
$cellValue = preg_replace('/[\r\n\t]+/', ' ', $cellValue);
// Empty headers get __empty_N__ to match mapping page
@@ -475,7 +352,7 @@ try {
$filledCount = 0;
foreach ($headerFilledIndices as $idx) {
if (isset($rowData[$idx]) && cleanCellText($rowData[$idx]) !== '') {
if (isset($rowData[$idx]) && trim((string)$rowData[$idx]) !== '') {
$filledCount++;
}
}
@@ -491,13 +368,13 @@ try {
// Recupera routine dal template
if ($template && !empty($template['idroutine'])) {
$stmtRoutine = $pdo->prepare("
SELECT
idroutine,
name,
filename,
headerrow,
instruction
FROM routine
SELECT
idroutine,
name,
filename,
headerrow,
instruction
FROM routine
WHERE idroutine = ?
");
$stmtRoutine->execute([$template['idroutine']]);
@@ -4,6 +4,35 @@ require_once 'class/db-functions.php';
$response = ["success" => false, "message" => ""];
function excelColumnToIndex($column)
{
$column = strtoupper(trim((string)$column));
if ($column === '') {
return null;
}
// Numeric column index, example: 40
if (ctype_digit($column)) {
$index = (int)$column;
return $index > 0 ? $index : null;
}
// Excel column letters, example: A, AN, XFC
if (!preg_match('/^[A-Z]+$/', $column)) {
return null;
}
$index = 0;
$length = strlen($column);
for ($i = 0; $i < $length; $i++) {
$index = ($index * 26) + (ord($column[$i]) - ord('A') + 1);
}
return $index;
}
try {
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
throw new Exception("Invalid request method.");
@@ -18,6 +47,8 @@ try {
: null;
$start_column = trim($_POST['start_column'] ?? '');
$xls_end_column = strtoupper(trim($_POST['xls_end_column'] ?? ''));
$xls_end_column = $xls_end_column !== '' ? $xls_end_column : null;
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
? intval($_POST['xls_sheet_index'])
@@ -63,6 +94,24 @@ try {
throw new Exception("XLS Sheet Number cannot be negative.");
}
$startColumnIndex = excelColumnToIndex($start_column);
if ($startColumnIndex === null) {
throw new Exception("Start Column is not valid. Use Excel column letters like A, AN or a positive number.");
}
if ($xls_end_column !== null) {
$endColumnIndex = excelColumnToIndex($xls_end_column);
if ($endColumnIndex === null) {
throw new Exception("Last Column is not valid. Use Excel column letters like AN or a positive number.");
}
if ($endColumnIndex < $startColumnIndex) {
throw new Exception("Last Column cannot be before Start Column.");
}
}
$api_config_id = null;
}
@@ -75,6 +124,7 @@ try {
$header_row = null;
$start_column = null;
$xls_sheet_index = null;
$xls_end_column = null;
}
// PDF currently does not require XLS coordinates or API configuration
@@ -82,6 +132,7 @@ try {
$header_row = null;
$start_column = null;
$xls_sheet_index = null;
$xls_end_column = null;
$api_config_id = null;
}
@@ -112,6 +163,7 @@ try {
source_type,
header_row,
start_column,
xls_end_column,
xls_sheet_index,
api_config_id,
description,
@@ -130,7 +182,7 @@ try {
)
VALUES
(
?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?,?,
?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?,
NOW(), NOW()
@@ -142,6 +194,7 @@ try {
$source_type,
$header_row,
$start_column,
$xls_end_column,
$xls_sheet_index,
$api_config_id,
$description,
-90
View File
@@ -1,90 +0,0 @@
<?php
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/routine_debug.log');
function applyRoutine(&$excelData, $routineData)
{
try {
// Initial log
error_log("Inizio esecuzione routine Le Silla: " . date('Y-m-d H:i:s'));
error_log("Dati routine: " . print_r($routineData, true));
error_log("Dati excel_data: " . print_r($excelData, true));
// Check if excelData is empty
if (empty($excelData)) {
throw new Exception("excelData è vuoto o non valido.");
}
// Extract routine settings with default values
$articolo_header = trim($routineData['articolo'] ?? 'ARTICOLO');
$descrizione_header = trim($routineData['descrizione'] ?? 'DESCRIZIONE');
$headers = $routineData['xls_headers'] ?? [];
if (empty($headers)) {
throw new Exception("Nessun header trovato per la routine Le Silla.");
}
// If headers arrive as JSON string, decode them
if (!is_array($headers) && is_string($headers)) {
$decoded_headers = json_decode($headers, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded_headers)) {
$headers = $decoded_headers;
}
}
if (!is_array($headers) || empty($headers)) {
throw new Exception("Gli header della routine non sono validi.");
}
error_log("Header ricevuti: " . print_r($headers, true));
// Normalize headers
$normalized_headers = array_map(function ($header) {
return trim((string)$header);
}, $headers);
error_log("Header normalizzati: " . print_r($normalized_headers, true));
error_log("Header values - ARTICOLO: '$articolo_header', DESCRIZIONE: '$descrizione_header'");
// Find column indexes
$articolo_index = array_search($articolo_header, $normalized_headers, true);
$descrizione_index = array_search($descrizione_header, $normalized_headers, true);
if ($articolo_index === false) {
throw new Exception("Colonna ARTICOLO non trovata.");
}
if ($descrizione_index === false) {
throw new Exception("Colonna DESCRIZIONE non trovata.");
}
error_log("Indici colonne - ARTICOLO: $articolo_index, DESCRIZIONE: $descrizione_index");
/**
* Merge ARTICOLO + DESCRIZIONE into ARTICOLO separated by " - "
*/
foreach ($excelData as $index => $row) {
if (!isset($row['data']) || !is_array($row['data'])) {
error_log("Riga non valida, manca 'data' all'indice $index");
continue;
}
$value_articolo = trim((string)($row['data'][$articolo_index] ?? ''));
$value_descrizione = trim((string)($row['data'][$descrizione_index] ?? ''));
$parts = [];
if ($value_articolo !== '') {
$parts[] = $value_articolo;
}
if ($value_descrizione !== '') {
$parts[] = $value_descrizione;
}
$excelData[$index]['data'][$articolo_index] = implode(' - ', $parts);
}
error_log("Routine Le Silla completata con successo - Merge applicato su " . count($excelData) . " righe");
} catch (Exception $e) {
error_log("Eccezione nella routine Le Silla: " . $e->getMessage());
throw $e;
}
}
+41 -77
View File
@@ -1,8 +1,8 @@
/**
* saveAll.js — Save All functionality using gridData
*/
(function () {
"use strict";
(function() {
'use strict';
let saveAllRunning = false;
@@ -11,7 +11,7 @@
}
// ── Save single row ──────────────────────────────────────────────────
$(document).on("click", ".save-btn", async function () {
$(document).on('click', '.save-btn', async function() {
const btn = this;
const rowIndex = parseInt(btn.dataset.row);
const row = window.gridData?.[rowIndex];
@@ -23,44 +23,29 @@
try {
const formData = window.buildSavePayload(rowIndex);
const resp = await fetch("save_edited_row.php", {
method: "POST",
body: formData,
});
const resp = await fetch('save_edited_row.php', { method: 'POST', body: formData });
const result = await resp.json();
if (result.success) {
row._dirty = false;
if (window.gridRenderer?.clearDirty)
window.gridRenderer.clearDirty(rowIndex);
if (window.gridRenderer?.clearDirty) window.gridRenderer.clearDirty(rowIndex);
// Flash success on row without re-rendering (preserves Select2 state)
const gridRow = document.querySelector(
`.grid-row[data-id="${row.iddatadb}"]`,
);
const gridRow = document.querySelector(`.grid-row[data-id="${row.iddatadb}"]`);
if (gridRow) {
gridRow.classList.remove("row-dirty");
gridRow.querySelectorAll(".grid-cell").forEach((cell) => {
cell.classList.remove("cell-changed");
cell.classList.add("flash-success");
gridRow.classList.remove('row-dirty');
gridRow.querySelectorAll('.grid-cell').forEach(cell => {
cell.classList.remove('cell-changed');
cell.classList.add('flash-success');
});
setTimeout(
() =>
gridRow
.querySelectorAll(".flash-success")
.forEach((c) =>
c.classList.remove("flash-success"),
),
500,
);
setTimeout(() => gridRow.querySelectorAll('.flash-success').forEach(c => c.classList.remove('flash-success')), 500);
}
const toastEl = document.getElementById("saveSuccessToast");
if (toastEl)
bootstrap.Toast.getOrCreateInstance(toastEl).show();
const toastEl = document.getElementById('saveSuccessToast');
if (toastEl) bootstrap.Toast.getOrCreateInstance(toastEl).show();
} else {
alert("Errore: " + result.message);
alert('Errore: ' + result.message);
}
} catch (e) {
alert("Errore: " + e.message);
alert('Errore: ' + e.message);
} finally {
btn.innerHTML = origHtml;
btn.disabled = false;
@@ -68,68 +53,49 @@
});
// ── Save All ─────────────────────────────────────────────────────────
$(document).on("click", ".save-all-btn", function (e) {
$(document).on('click', '.save-all-btn', function(e) {
e.preventDefault();
if (isBusy()) return;
const modalEl = document.getElementById("saveAllConfirmModal");
const modalEl = document.getElementById('saveAllConfirmModal');
if (!modalEl) return;
new bootstrap.Modal(modalEl, { keyboard: false }).show();
});
$(document).on("click", "#saveAllConfirmBtn", async function () {
const confirmModal = bootstrap.Modal.getInstance(
document.getElementById("saveAllConfirmModal"),
);
$(document).on('click', '#saveAllConfirmBtn', async function() {
const confirmModal = bootstrap.Modal.getInstance(document.getElementById('saveAllConfirmModal'));
if (confirmModal) confirmModal.hide();
saveAllRunning = true;
const bar = document.getElementById("batchExportBar");
const statusEl = document.getElementById("batchExportStatus");
const cancelBtn = document.getElementById("exportBatchCancelBtn");
if (bar) bar.style.display = "";
if (cancelBtn) cancelBtn.style.display = "none";
if (statusEl) statusEl.textContent = "Saving...";
const bar = document.getElementById('batchExportBar');
const statusEl = document.getElementById('batchExportStatus');
const cancelBtn = document.getElementById('exportBatchCancelBtn');
if (bar) bar.style.display = '';
if (cancelBtn) cancelBtn.style.display = 'none';
if (statusEl) statusEl.textContent = 'Saving...';
const data = window.gridData || [];
const target =
typeof window.getTargetGridIds === "function"
? window.getTargetGridIds()
: null;
const dirtyRows = data
.map((r, i) => (r._dirty ? i : -1))
.filter((i) => i >= 0)
.filter(
(i) => !target || target.has(parseInt(data[i].iddatadb, 10)),
);
const dirtyRows = data.map((r, i) => r._dirty ? i : -1).filter(i => i >= 0);
if (dirtyRows.length === 0) {
saveAllRunning = false;
if (bar) bar.style.display = "none";
const msgEl = document.getElementById("saveAllResultMessage");
if (msgEl) msgEl.textContent = "No changes to save.";
new bootstrap.Modal(
document.getElementById("saveAllResultModal"),
).show();
if (bar) bar.style.display = 'none';
const msgEl = document.getElementById('saveAllResultMessage');
if (msgEl) msgEl.textContent = 'No changes to save.';
new bootstrap.Modal(document.getElementById('saveAllResultModal')).show();
return;
}
let success = 0,
fail = 0;
let success = 0, fail = 0;
for (const idx of dirtyRows) {
if (statusEl)
statusEl.textContent = `Saving ${success + fail + 1} / ${dirtyRows.length}...`;
if (statusEl) statusEl.textContent = `Saving ${success + fail + 1} / ${dirtyRows.length}...`;
try {
const formData = window.buildSavePayload(idx);
const resp = await fetch("save_edited_row.php", {
method: "POST",
body: formData,
});
const resp = await fetch('save_edited_row.php', { method: 'POST', body: formData });
const result = await resp.json();
if (result.success) {
data[idx]._dirty = false;
if (window.gridRenderer?.clearDirty)
window.gridRenderer.clearDirty(idx);
if (window.gridRenderer?.clearDirty) window.gridRenderer.clearDirty(idx);
success++;
} else {
fail++;
@@ -140,24 +106,22 @@
}
saveAllRunning = false;
if (bar) bar.style.display = "none";
if (bar) bar.style.display = 'none';
const gr = window.gridRenderer;
if (gr) gr.renderVisibleRows();
const msg = `Saved: ${success}` + (fail > 0 ? `, Errors: ${fail}` : "");
const msgEl = document.getElementById("saveAllResultMessage");
const msg = `Saved: ${success}` + (fail > 0 ? `, Errors: ${fail}` : '');
const msgEl = document.getElementById('saveAllResultMessage');
if (msgEl) msgEl.textContent = msg;
new bootstrap.Modal(
document.getElementById("saveAllResultModal"),
).show();
new bootstrap.Modal(document.getElementById('saveAllResultModal')).show();
});
// ── beforeunload ─────────────────────────────────────────────────────
window.addEventListener("beforeunload", function (e) {
if (window.gridData && window.gridData.some((r) => r._dirty)) {
window.addEventListener('beforeunload', function(e) {
if (window.gridData && window.gridData.some(r => r._dirty)) {
e.preventDefault();
e.returnValue = "";
e.returnValue = '';
}
});
+10 -2
View File
@@ -54,6 +54,15 @@ try {
:is_web_selectable,
:is_accredited
)
ON DUPLICATE KEY UPDATE
analysis_name = VALUES(analysis_name),
analysis_method = VALUES(analysis_method),
analysis_level = VALUES(analysis_level),
is_web_selectable = VALUES(is_web_selectable),
is_accredited = VALUES(is_accredited),
iddatadb = VALUES(iddatadb),
idmatrice = VALUES(idmatrice),
updated_at = CURRENT_TIMESTAMP
");
$stmt->execute([
@@ -70,8 +79,7 @@ try {
echo json_encode([
'success' => true,
'message' => 'Association saved',
'id' => (int)$pdo->lastInsertId()
'message' => 'Association saved'
]);
} catch (Throwable $e) {
http_response_code(500);
+22 -50
View File
@@ -4,7 +4,6 @@ include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$data = json_decode(file_get_contents('php://input'), true);
@@ -16,73 +15,46 @@ if (!$idquotations || empty($parts)) {
exit;
}
try {
$pdo->beginTransaction();
$results = [];
foreach ($parts as $part) {
$partId = $part['id'] ?? null;
$partNumber = $part['part_number'] ?? null;
$partDescription = $part['part_description'] ?? '';
$mix = $part['mix'] ?? 'N';
$idmatrice = $part['idmatrice'] ?? null;
$note = $part['note'] ?? null;
$dateexpiry = $part['dateexpiry'] ?? null;
$part = $parts[0];
$partId = $part['id'] ?? null;
$partNumber = $part['part_number'] ?? null;
$partDescription = $part['part_description'] ?? '';
$mix = $part['mix'] ?? 'N';
if ($partDescription) {
try {
if ($partId) {
// UPDATE se la parte esiste
// UPDATE se esiste già la parte
$stmt = $pdo->prepare("UPDATE identification_parts
SET part_number = :part_number,
part_description = :part_description,
mix = :mix,
idmatrice = :idmatrice,
note = :note,
dateexpiry = :dateexpiry,
updated_at = NOW()
WHERE id = :id AND idquotations = :idquotations");
WHERE id = :id");
$stmt->execute([
':id' => $partId,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix,
':idmatrice' => $idmatrice,
':note' => $note,
':dateexpiry' => $dateexpiry,
':idquotations' => $idquotations,
':mix' => $mix
]);
$results[] = [
'part_id' => $partId,
'part_number' => $partNumber,
'message' => 'Parte aggiornata con successo'
];
} else if ($partDescription || $note || $dateexpiry) {
// INSERT per nuova parte (solo se ha contenuto)
echo json_encode(['success' => true, 'part_id' => $partId, 'part_number' => $partNumber, 'message' => 'Parte aggiornata con successo']);
} else {
// INSERT se è nuova
$stmt = $pdo->prepare("INSERT INTO identification_parts
(idquotations, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
VALUES (:idquotations, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())");
(idquotations, part_number, part_description, mix, created_at, updated_at)
VALUES (:idquotations, :part_number, :part_description, :mix, NOW(), NOW())");
$stmt->execute([
':idquotations' => $idquotations,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix,
':idmatrice' => $idmatrice,
':note' => $note,
':dateexpiry' => $dateexpiry,
':mix' => $mix
]);
$newId = (int)$pdo->lastInsertId();
$results[] = [
'part_id' => $newId,
'part_number' => $partNumber,
'message' => 'Parte salvata con successo'
];
$newId = $pdo->lastInsertId();
echo json_encode(['success' => true, 'part_id' => $newId, 'part_number' => $partNumber, 'message' => 'Parte salvata con successo']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
}
$pdo->commit();
echo json_encode(['success' => true, 'results' => $results]);
} catch (PDOException $e) {
$pdo->rollBack();
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
} else {
echo json_encode(['success' => false, 'message' => 'Descrizione mancante']);
}
-143
View File
@@ -1,143 +0,0 @@
<?php
// save_pdf_regions.php
// Salva i riquadri (etichetta + dato) di un singolo campo mappato
// nella colonna pdf_regions della tabella template_mapping.
//
// Riceve JSON:
// {
// id: <mapping id>,
// mapping_type: 'pdf' | 'manual' | 'auto' | '',
// pdf_regions: { page, label:{x,y,w,h}, value:{x,y,w,h} } | null,
// manual_default: <string|null>,
// auto_value: <string>,
// tablename: <string>
// }
//
// Coerente con save_mapping_json.php: quando mapping_type = 'pdf'
// azzera excel_column/json_node e scrive pdf_regions; per gli altri
// tipi azzera pdf_regions.
header('Content-Type: application/json');
include('include/headscript.php');
function respond(bool $success, string $message = ''): void
{
echo json_encode(['success' => $success, 'message' => $message]);
exit;
}
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
if (!is_array($data)) {
respond(false, 'Invalid JSON payload.');
}
$mappingId = isset($data['id']) ? (int)$data['id'] : 0;
$mappingType = isset($data['mapping_type']) ? (string)$data['mapping_type'] : '';
$manualDef = $data['manual_default'] ?? null;
$autoValue = $data['auto_value'] ?? 'none';
$regions = $data['pdf_regions'] ?? null;
if ($mappingId <= 0) {
respond(false, 'Missing or invalid mapping id.');
}
// --- Validazione e normalizzazione dei riquadri ---
// Accettiamo solo se mapping_type = 'pdf'. Negli altri casi pdf_regions = NULL.
$pdfRegionsJson = null;
if ($mappingType === 'pdf') {
if (!is_array($regions)) {
respond(false, 'pdf_regions must be an object when mapping_type is pdf.');
}
// Valida un singolo rettangolo {x,y,w,h} con numeri >= 0
$validateRect = function ($rect): ?array {
if (!is_array($rect)) {
return null;
}
$out = [];
foreach (['x', 'y', 'w', 'h'] as $k) {
if (!isset($rect[$k]) || !is_numeric($rect[$k])) {
return null;
}
$out[$k] = (float)$rect[$k];
if ($out[$k] < 0) {
$out[$k] = 0;
}
}
return $out;
};
$page = isset($regions['page']) ? (int)$regions['page'] : 0;
if ($page < 1) {
respond(false, 'pdf_regions.page must be >= 1.');
}
// value è obbligatorio, label è opzionale (modalità "solo dato")
$valueRect = $validateRect($regions['value'] ?? null);
if ($valueRect === null) {
respond(false, 'pdf_regions.value rectangle is invalid.');
}
$labelRect = null;
if (isset($regions['label']) && $regions['label'] !== null) {
$labelRect = $validateRect($regions['label']);
if ($labelRect === null) {
respond(false, 'pdf_regions.label rectangle is invalid.');
}
}
$clean = [
'page' => $page,
'label' => $labelRect,
'value' => $valueRect,
];
$pdfRegionsJson = json_encode($clean, JSON_UNESCAPED_UNICODE);
}
// --- Normalizza manual/auto in base al tipo ---
$manualToSave = ($mappingType === 'manual') ? $manualDef : null;
$autoToSave = ($mappingType === 'auto') ? ($autoValue ?: 'none') : 'none';
// is_manual coerente con il tipo
$isManual = ($mappingType === 'manual') ? 1 : 0;
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Quando è PDF: azzero excel_column e json_node, scrivo pdf_regions.
// Quando NON è PDF: azzero pdf_regions.
$sql = "
UPDATE template_mapping
SET
pdf_regions = :pdf_regions,
excel_column = CASE WHEN :is_pdf = 1 THEN NULL ELSE excel_column END,
json_node = CASE WHEN :is_pdf2 = 1 THEN NULL ELSE json_node END,
is_manual = :is_manual,
manual_default = :manual_default,
auto_value = :auto_value
WHERE id = :id
";
$isPdf = ($mappingType === 'pdf') ? 1 : 0;
$stmt = $pdo->prepare($sql);
$stmt->execute([
':pdf_regions' => $pdfRegionsJson,
':is_pdf' => $isPdf,
':is_pdf2' => $isPdf,
':is_manual' => $isManual,
':manual_default' => $manualToSave,
':auto_value' => $autoToSave,
':id' => $mappingId,
]);
} catch (Throwable $e) {
respond(false, 'Database update failed: ' . $e->getMessage());
}
respond(true, 'PDF regions saved.');
+1 -19
View File
@@ -815,7 +815,7 @@
"IdSchemaCustomFields": 191,
"ConteggioClienti": 0,
"Nome": "KIABI",
"Descrizione": "Schema per il cliente Kiabi\r\n\r\n"
"Descrizione": "Schema per il cliente Kiabi\r\n"
},
{
"IdSchemaCustomFields": 192,
@@ -900,24 +900,6 @@
"ConteggioClienti": 0,
"Nome": "Chanel Flammability",
"Descrizione": "Schema per Chanel Flammability\r\n"
},
{
"IdSchemaCustomFields": 206,
"ConteggioClienti": 0,
"Nome": "COLDWATER CREEK",
"Descrizione": "\r\n"
},
{
"IdSchemaCustomFields": 208,
"ConteggioClienti": 0,
"Nome": "ETON",
"Descrizione": "Schema da usare per ETON\r\n"
},
{
"IdSchemaCustomFields": 209,
"ConteggioClienti": 0,
"Nome": "LBS Flammability",
"Descrizione": "Schema per tutti i campioni di LBS con Flammability\r\n"
}
]
}
+6 -24
View File
@@ -19,14 +19,6 @@ $q = mb_strtolower(trim($_GET['q'] ?? ''));
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
$tipo = $_GET['tipo'] ?? ''; // 'attivo' | 'inattivo' | '' (nessun filtro)
function isInattivo(array $c): bool
{
$v = $c['Inattivo'] ?? false;
return $v === true || $v === 1 || $v === '1' || $v === 'true';
}
function formatClientLabel(array $client): string
{
$name = trim($client['Nominativo'] ?? '');
@@ -44,7 +36,7 @@ function formatClientLabel(array $client): string
$suffix = '--';
}
return $name . ' - ' . $suffix . ' - ' . $id;
return $name . ' - ' . $suffix . ' (ID: ' . $id . ')';
}
try {
@@ -57,7 +49,7 @@ try {
$api = VisualLimsApiClient::getInstance();
$params = [
'$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo',
'$select' => 'IdCliente,Nominativo,CodiceCliente',
'$orderby' => 'Nominativo asc'
];
@@ -72,14 +64,6 @@ try {
$clients = $data['value'] ?? [];
// Filtro tipo, applicato SOLO alla ricerca, non al lookup per id
$searchClients = $clients;
if ($tipo === 'attivo') {
$searchClients = array_values(array_filter($clients, fn($c) => !isInattivo($c)));
} elseif ($tipo === 'inattivo') {
$searchClients = array_values(array_filter($clients, fn($c) => isInattivo($c)));
}
// If requesting by specific ID, used for loading selected value
if ($id !== null) {
foreach ($clients as $c) {
@@ -104,20 +88,18 @@ try {
// Search by query
$results = [];
foreach ($searchClients as $c) {
foreach ($clients as $c) {
$name = trim($c['Nominativo'] ?? '');
$code = trim($c['CodiceCliente'] ?? '');
// Cerca solo su ciò che viene visualizzato: Nome - Lettera - ID
$label = formatClientLabel($c);
if (
$q === '' ||
mb_strpos(mb_strtolower($label), $q) !== false
mb_strpos(mb_strtolower($name), $q) !== false ||
mb_strpos(mb_strtolower($code), $q) !== false
) {
$results[] = [
'id' => $c['IdCliente'],
'text' => $label,
'text' => formatClientLabel($c),
'IdCliente' => $c['IdCliente'],
'Nominativo' => $name,
'CodiceCliente' => $code
+7 -63
View File
@@ -51,74 +51,18 @@ try {
exit;
}
// Search — multi-term AND, position-independent, ranked
$terms = array_values(array_filter(
preg_split('/\s+/u', $q, -1, PREG_SPLIT_NO_EMPTY)
));
// Nessun termine digitato: alfabetico, taglio al limite
if (empty($terms)) {
$all = [];
foreach ($values as $v) {
$all[] = ['id' => $v['IdCustomFieldsValue'], 'text' => $v['Valore'] ?? ''];
}
usort($all, fn($a, $b) => strcasecmp($a['text'], $b['text']));
if ($limit > 0) $all = array_slice($all, 0, $limit);
echo json_encode(['results' => $all]);
exit;
}
$firstTerm = $terms[0];
$scored = [];
// Search by query
$results = [];
foreach ($values as $v) {
$text = $v['Valore'] ?? '';
$textLower = mb_strtolower($text);
// AND: tutti i termini devono essere presenti, in qualsiasi posizione
$matchAll = true;
foreach ($terms as $t) {
if (mb_strpos($textLower, $t) === false) {
$matchAll = false;
break;
}
if ($q === '' || mb_strpos(mb_strtolower($text), $q) !== false) {
$results[] = ['id' => $v['IdCustomFieldsValue'], 'text' => $text];
if ($limit > 0 && count($results) >= $limit) break;
}
if (!$matchAll) continue;
// Punteggio di pertinenza (più basso = più rilevante)
if ($textLower === $q) {
$score = 0; // match esatto
} elseif (mb_strpos($textLower, $firstTerm) === 0) {
$score = 1; // inizia col primo termine
} elseif (preg_match('/(^|\s)' . preg_quote($firstTerm, '/') . '/u', $textLower)) {
$score = 2; // primo termine a inizio di una parola
} else {
$score = 3; // presente ma non a inizio parola
}
$scored[] = [
'id' => $v['IdCustomFieldsValue'],
'text' => $text,
'_s' => $score,
'_n' => $textLower
];
}
// Ordina per punteggio, poi alfabetico
usort($scored, function ($a, $b) {
if ($a['_s'] !== $b['_s']) return $a['_s'] - $b['_s'];
return strcmp($a['_n'], $b['_n']);
});
// Taglia DOPO l'ordinamento e rimuovi i campi interni
if ($limit > 0) {
$scored = array_slice($scored, 0, $limit);
}
$results = array_map(
fn($r) => ['id' => $r['id'], 'text' => $r['text']],
$scored
);
// Sort alphabetically
usort($results, fn($a, $b) => strcasecmp($a['text'], $b['text']));
echo json_encode(['results' => $results]);
} catch (Exception $e) {
+6 -65
View File
@@ -2,11 +2,7 @@
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/db-functions.php';
include dirname(__DIR__) . '/../extra/auth.php';
if (!Auth::check()) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
header('Content-Type: application/json');
ini_set('display_errors', '0');
@@ -62,71 +58,16 @@ if (isset($_GET['macro_list'])) {
echo json_encode(['success' => true, 'value' => $macros]);
exit;
}
// Return full matrix list (same shape as get_matrici_db.php)
if (isset($_GET['full_list'])) {
echo json_encode(['success' => true, 'value' => $matrici]);
exit;
}
// Search (with optional MacroMatrice filter) - multi-term AND, position-independent, ranked
$terms = array_values(array_filter(
preg_split('/\s+/u', $q, -1, PREG_SPLIT_NO_EMPTY)
));
// Nessun termine digitato: comportamento invariato (alfabetico, taglio al limite)
if (empty($terms)) {
$results = [];
foreach ($matrici as $m) {
if ($macro !== '' && ($m['MacroMatrice'] ?? '') !== $macro) continue;
$results[] = ['id' => $m['IdMatrice'], 'text' => $m['NomeMatrice'] ?? ''];
if (count($results) >= $limit) break;
}
echo json_encode(['results' => $results]);
exit;
}
$firstTerm = $terms[0];
$scored = [];
// Search (with optional MacroMatrice filter)
$results = [];
foreach ($matrici as $m) {
$nome = $m['NomeMatrice'] ?? '';
if ($macro !== '' && ($m['MacroMatrice'] ?? '') !== $macro) continue;
$nomeLower = mb_strtolower($nome);
// AND: tutti i termini devono essere presenti (in qualsiasi posizione)
$matchAll = true;
foreach ($terms as $t) {
if (mb_strpos($nomeLower, $t) === false) {
$matchAll = false;
break;
}
if ($q === '' || mb_strpos(mb_strtolower($nome), $q) !== false) {
$results[] = ['id' => $m['IdMatrice'], 'text' => $nome];
if (count($results) >= $limit) break;
}
if (!$matchAll) continue;
// Punteggio di pertinenza (più basso = più rilevante, per ordinamento crescente)
if ($nomeLower === $q) {
$score = 0; // match esatto
} elseif (mb_strpos($nomeLower, $firstTerm) === 0) {
$score = 1; // il nome inizia col primo termine
} elseif (preg_match('/(^|\s)' . preg_quote($firstTerm, '/') . '/u', $nomeLower)) {
$score = 2; // primo termine a inizio di una parola
} else {
$score = 3; // termine presente ma non a inizio parola
}
$scored[] = ['id' => $m['IdMatrice'], 'text' => $nome, '_s' => $score, '_n' => $nomeLower];
}
// Ordina per punteggio, poi alfabetico (stabile e prevedibile)
usort($scored, function ($a, $b) {
if ($a['_s'] !== $b['_s']) return $a['_s'] - $b['_s'];
return strcmp($a['_n'], $b['_n']);
});
// Taglia al limite DOPO l'ordinamento e rimuovi i campi interni
$results = array_map(
fn($r) => ['id' => $r['id'], 'text' => $r['text']],
array_slice($scored, 0, $limit)
);
echo json_encode(['results' => $results]);
-546
View File
@@ -1,546 +0,0 @@
<?php include('include/headscript.php'); ?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>TRF-Project - Statistiche Export LIMS</title>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<style>
.stat-card .card-body {
padding: 1rem 1.25rem;
}
.stat-value {
font-size: 1.75rem;
font-weight: 700;
line-height: 1.1;
}
.stat-label {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: .03em;
color: #6c757d;
}
.chart-wrap {
position: relative;
height: 420px;
}
#tblClienti,
#tblUtenti {
font-size: 13px;
}
#tblClienti th,
#tblClienti td,
#tblUtenti th,
#tblUtenti td {
vertical-align: middle;
}
.filter-bar {
gap: .75rem;
}
.stat-card-extra {
background: linear-gradient(135deg, #021bff 0%, #010983 100%);
border: none;
}
.stat-card-extra .stat-label {
color: #d7d2f5;
}
.stat-card-extra .stat-value {
color: #ffffff !important;
}
.stat-card-extra small.text-muted {
color: #c9c2ec !important;
}
.kpi-row {
margin-bottom: 1rem;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<!-- ===== FILTRO GIORNATA ===== -->
<div class="card radius-10">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap filter-bar">
<h6 class="mb-0 me-3"><i class="bx bx-bar-chart-alt-2"></i> Statistiche Export LIMS</h6>
<div class="d-flex align-items-center">
<label for="filterDate" class="me-2 mb-0 fw-semibold">Giornata:</label>
<input type="date" id="filterDate" class="form-control form-control-sm" style="width:auto;">
</div>
<div class="btn-group btn-group-sm ms-2" role="group">
<button class="btn btn-outline-secondary" id="btnToday">Oggi</button>
<button class="btn btn-outline-secondary" id="btnYesterday">Ieri</button>
</div>
<a href="stats_export_lims_monthly.php" class="btn btn-outline-primary btn-sm ms-auto">
<i class="bx bx-calendar-alt"></i> Vista mensile
</a>
<button class="btn btn-primary btn-sm ms-2" id="btnRefresh">
<i class="bx bx-refresh"></i> Aggiorna
</button>
</div>
</div>
</div>
<!-- ===== KPI ===== -->
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-5 g-3 kpi-row">
<div class="col">
<div class="card radius-10 stat-card stat-card-extra h-100">
<div class="card-body">
<div class="stat-label">Campioni mese <span id="kpiMonthLabel"></span></div>
<div class="stat-value" id="kpiPartsMonth">0</div>
<small class="text-muted">Commesse: <span id="kpiExportMonth">0</span> · dal 16/07/2026</small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card stat-card-extra h-100">
<div class="card-body">
<div class="stat-label">Campioni anno <span id="kpiYearLabel"></span></div>
<div class="stat-value" id="kpiPartsYear">0</div>
<small class="text-muted">Commesse: <span id="kpiExportYear">0</span> · dal 16/07/2026</small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Campioni esportati (giorno)</div>
<div class="stat-value text-primary" id="kpiParts">0</div>
<small class="text-muted">Commesse: <span id="kpiExport">0</span></small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Campioni inseriti LIMS</div>
<div class="stat-value text-info" id="kpiLimsSamples">0</div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">% Esportati / inseriti</div>
<div class="stat-value text-success" id="kpiPerc"></div>
</div>
</div>
</div>
</div>
<!-- ===== DATO GIORNALIERO CAMPIONI LIMS ===== -->
<div class="card radius-10">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap" style="gap:.75rem;">
<span class="fw-semibold">Campioni totali inseriti nel LIMS per questa giornata:</span>
<input type="number" min="0" id="limsSamplesInput" class="form-control form-control-sm" style="width:140px;" placeholder="0">
<button class="btn btn-success btn-sm" id="btnSaveLimsSamples">
<i class="bx bx-save"></i> Salva dato
</button>
<small class="text-muted">Un valore al giorno. Usato per calcolare la % di export.</small>
</div>
</div>
</div>
<!-- ===== GRAFICO TORTA ESPORTATI vs TOTALE LIMS ===== -->
<div class="row" id="pieRow" style="display:none;">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Esportati vs totale inserito nel LIMS</h6>
</div>
<div class="card-body">
<div class="chart-wrap" style="height:340px;"><canvas id="chartPie"></canvas></div>
</div>
</div>
</div>
</div>
<!-- ===== GRAFICI ===== -->
<div class="row">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Export per cliente (idclient) <small class="text-muted"> top 15</small></h6>
</div>
<div class="card-body">
<div class="chart-wrap"><canvas id="chartClienti"></canvas></div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Export per utente (user_id) <small class="text-muted"> top 15</small></h6>
</div>
<div class="card-body">
<div class="chart-wrap"><canvas id="chartUtenti"></canvas></div>
</div>
</div>
</div>
</div>
<!-- ===== LISTE ===== -->
<div class="row">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Dettaglio per cliente</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tblClienti" class="table table-striped table-bordered table-sm w-100">
<thead>
<tr>
<th>ID Cliente</th>
<th>Cliente</th>
<th>Campioni</th>
<th>Commesse</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Dettaglio per utente</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tblUtenti" class="table table-striped table-bordered table-sm w-100">
<thead>
<tr>
<th>ID Utente</th>
<th>Utente</th>
<th>Campioni</th>
<th>Commesse</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
let chartClienti = null;
let chartUtenti = null;
let chartPie = null;
let dtClienti = null;
let dtUtenti = null;
function todayStr() {
const d = new Date();
const off = d.getTimezoneOffset();
return new Date(d.getTime() - off * 60000).toISOString().slice(0, 10);
}
function initTables() {
dtClienti = $('#tblClienti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'idclient'
},
{
data: 'clientname',
defaultContent: '—'
},
{
data: 'parts',
className: 'text-end'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questa giornata'
}
});
dtUtenti = $('#tblUtenti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'user_id'
},
{
data: 'username',
defaultContent: '—'
},
{
data: 'parts',
className: 'text-end'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questa giornata'
}
});
}
function renderBarChart(existing, canvasId, labels, values, label) {
if (existing) existing.destroy();
const ctx = document.getElementById(canvasId).getContext('2d');
return new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: label,
data: values,
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
// Torta: esportati vs non esportati (totale LIMS - esportati)
function renderPie(exported, limsTotal) {
const pieRow = document.getElementById('pieRow');
// Mostra solo se il totale è inserito e valido
if (!limsTotal || limsTotal <= 0) {
pieRow.style.display = 'none';
if (chartPie) {
chartPie.destroy();
chartPie = null;
}
return;
}
pieRow.style.display = '';
const notExported = Math.max(0, limsTotal - exported);
if (chartPie) chartPie.destroy();
const ctx = document.getElementById('chartPie').getContext('2d');
chartPie = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Esportati', 'Totali LIMS'],
datasets: [{
data: [exported, notExported],
backgroundColor: ['#198754', '#dee2e6'],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
},
tooltip: {
callbacks: {
label: function(c) {
const perc = limsTotal > 0 ? ((c.parsed / limsTotal) * 100).toFixed(1) : 0;
return c.label + ': ' + c.parsed + ' (' + perc + '%)';
}
}
}
}
}
});
}
// Prende i primi N record (già ordinati desc dal backend) per il grafico
const CHART_TOP_N = 15;
function topN(arr) {
return arr.slice(0, CHART_TOP_N);
}
function loadStats() {
const date = $('#filterDate').val() || todayStr();
$.getJSON('stats_export_lims_data.php', {
date: date
}, function(res) {
if (!res || !res.success) {
Swal.fire('Errore', (res && res.message) || 'Errore nel caricamento', 'error');
return;
}
// KPI — dato principale = parti/campioni, sotto le commesse
$('#kpiParts').text(res.totals.parts ?? 0);
$('#kpiExport').text(res.totals.export ?? 0);
$('#kpiPartsMonth').text(res.totalsMonth ? (res.totalsMonth.parts ?? 0) : 0);
$('#kpiExportMonth').text(res.totalsMonth ? (res.totalsMonth.export ?? 0) : 0);
$('#kpiPartsYear').text(res.totalsYear ? (res.totalsYear.parts ?? 0) : 0);
$('#kpiExportYear').text(res.totalsYear ? (res.totalsYear.export ?? 0) : 0);
$('#kpiLimsSamples').text(res.limsSamples ?? 0);
$('#limsSamplesInput').val(res.limsSamples ?? '');
// Etichette mese/anno dei box totali
if (res.month) {
const MESI_IT = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'
];
const mParts = res.month.split('-');
const mIdx = parseInt(mParts[1], 10) - 1;
if (mIdx >= 0 && mIdx < 12) {
$('#kpiMonthLabel').text('(' + MESI_IT[mIdx] + ' ' + mParts[0] + ')');
}
}
if (res.year) {
$('#kpiYearLabel').text('(' + res.year + ')');
}
const limsTot = parseInt(res.limsSamples ?? 0, 10);
const partsTot = parseInt(res.totals.parts ?? 0, 10);
if (limsTot > 0) {
const perc = (partsTot / limsTot) * 100;
$('#kpiPerc').text(perc.toFixed(1) + '%');
} else {
$('#kpiPerc').text('—');
}
// Torta parti esportate vs totale inserito nel LIMS
renderPie(partsTot, limsTot);
// Grafici — valori = campioni/parti
const clientTop = topN(res.byClient);
const userTop = topN(res.byUser);
chartClienti = renderBarChart(chartClienti, 'chartClienti',
clientTop.map(r => r.clientname ? (r.clientname + ' (' + r.idclient + ')') : ('ID ' + r.idclient)),
clientTop.map(r => r.parts), 'Campioni');
chartUtenti = renderBarChart(chartUtenti, 'chartUtenti',
userTop.map(r => r.username ? (r.username + ' (' + r.user_id + ')') : ('ID ' + r.user_id)),
userTop.map(r => r.parts), 'Campioni');
// Tabelle
dtClienti.clear().rows.add(res.byClient).draw();
dtUtenti.clear().rows.add(res.byUser).draw();
}).fail(function(xhr) {
Swal.fire('Errore', 'Richiesta fallita: ' + xhr.status, 'error');
});
}
$(document).ready(function() {
$('#filterDate').val(todayStr());
initTables();
loadStats();
$('#btnRefresh').on('click', loadStats);
$('#filterDate').on('change', loadStats);
$('#btnToday').on('click', function() {
$('#filterDate').val(todayStr());
loadStats();
});
$('#btnYesterday').on('click', function() {
const d = new Date();
d.setDate(d.getDate() - 1);
const off = d.getTimezoneOffset();
$('#filterDate').val(new Date(d.getTime() - off * 60000).toISOString().slice(0, 10));
loadStats();
});
$('#btnSaveLimsSamples').on('click', function() {
const date = $('#filterDate').val() || todayStr();
const total = parseInt($('#limsSamplesInput').val(), 10);
if (isNaN(total) || total < 0) {
Swal.fire('Attenzione', 'Inserisci un numero valido', 'warning');
return;
}
$.ajax({
url: 'stats_save_lims_samples.php',
type: 'POST',
dataType: 'json',
data: {
date: date,
total_samples: total
},
success: function(res) {
if (res && res.success) {
Swal.fire({
title: 'Salvato',
icon: 'success',
timer: 1200,
showConfirmButton: false
});
loadStats();
} else {
Swal.fire('Errore', (res && res.message) || 'Salvataggio fallito', 'error');
}
},
error: function(xhr) {
Swal.fire('Errore', 'Richiesta fallita: ' + xhr.status, 'error');
}
});
});
});
</script>
</body>
</html>
-156
View File
@@ -1,156 +0,0 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
try {
// Data richiesta (default oggi). Formato atteso: Y-m-d
$date = $_GET['date'] ?? date('Y-m-d');
$d = DateTime::createFromFormat('Y-m-d', $date);
if (!$d || $d->format('Y-m-d') !== $date) {
throw new Exception('Data non valida');
}
// --- Export per cliente (status = 'l', giornata su export_date) ---
// tot = numero commesse, parts = numero parti/campioni
$stmtC = $pdo->prepare("
SELECT d.idclient AS idclient,
COUNT(DISTINCT d.iddatadb) AS tot,
COUNT(ip.id) AS parts
FROM datadb d
LEFT JOIN identification_parts ip ON ip.iddatadb = d.iddatadb
WHERE d.status = 'l'
AND DATE(d.export_date) = :d
GROUP BY d.idclient
ORDER BY parts DESC
");
$stmtC->execute(['d' => $date]);
$byClient = $stmtC->fetchAll(PDO::FETCH_ASSOC);
// --- Risolvi Nominativo cliente dalla cache locale (search_clienti.php) ---
$clientNames = [];
$cacheFile = __DIR__ . '/cache/clienti.json';
if (is_file($cacheFile)) {
$cache = json_decode(file_get_contents($cacheFile), true);
foreach (($cache['value'] ?? []) as $c) {
$clientNames[(int)($c['IdCliente'] ?? 0)] = trim($c['Nominativo'] ?? '');
}
}
// --- Export per utente (status = 'l', giornata su export_date) ---
// tot = numero commesse, parts = numero parti/campioni
$stmtU = $pdo->prepare("
SELECT d.user_id AS user_id,
TRIM(CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,''))) AS username,
COUNT(DISTINCT d.iddatadb) AS tot,
COUNT(ip.id) AS parts
FROM datadb d
LEFT JOIN auth_users u ON u.id = d.user_id
LEFT JOIN identification_parts ip ON ip.iddatadb = d.iddatadb
WHERE d.status = 'l'
AND DATE(d.export_date) = :d
GROUP BY d.user_id, username
ORDER BY parts DESC
");
$stmtU->execute(['d' => $date]);
$byUser = $stmtU->fetchAll(PDO::FETCH_ASSOC);
// Normalizza tipi (COUNT torna stringa in PDO)
foreach ($byClient as &$r) {
$r['idclient'] = $r['idclient'] !== null ? (int)$r['idclient'] : null;
$r['tot'] = (int)$r['tot'];
$r['parts'] = (int)$r['parts'];
$nm = $clientNames[$r['idclient']] ?? '';
$r['clientname'] = $nm !== '' ? $nm : null;
}
unset($r);
foreach ($byUser as &$r) {
$r['user_id'] = $r['user_id'] !== null ? (int)$r['user_id'] : null;
$r['tot'] = (int)$r['tot'];
$r['parts'] = (int)$r['parts'];
if (isset($r['username'])) $r['username'] = $r['username'] !== null && $r['username'] !== '' ? $r['username'] : null;
}
unset($r);
// --- Totale export della giornata ---
$totExport = array_sum(array_column($byClient, 'tot'));
// --- Totale PARTI/CAMPIONI della giornata (righe identification_parts legate
// ai datadb esportati status='l' in questa giornata) ---
$stmtP = $pdo->prepare("
SELECT COUNT(*)
FROM identification_parts ip
INNER JOIN datadb d ON d.iddatadb = ip.iddatadb
WHERE d.status = 'l'
AND DATE(d.export_date) = :d
");
$stmtP->execute(['d' => $date]);
$totParts = (int)$stmtP->fetchColumn();
// --- Totale COMMESSE del MESE (status = 'l') ---
$stmtM = $pdo->prepare("
SELECT COUNT(*)
FROM datadb d
WHERE d.status = 'l'
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
");
$stmtM->execute(['m' => $d->format('Y-m')]);
$totExportMonth = (int)$stmtM->fetchColumn();
// --- Totale PARTI del MESE (status = 'l') ---
$stmtMP = $pdo->prepare("
SELECT COUNT(*)
FROM identification_parts ip
INNER JOIN datadb d ON d.iddatadb = ip.iddatadb
WHERE d.status = 'l'
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
");
$stmtMP->execute(['m' => $d->format('Y-m')]);
$totPartsMonth = (int)$stmtMP->fetchColumn();
// --- Totale COMMESSE dell'ANNO (status = 'l') ---
$stmtY = $pdo->prepare("
SELECT COUNT(*)
FROM datadb d
WHERE d.status = 'l'
AND YEAR(d.export_date) = :y
");
$stmtY->execute(['y' => $d->format('Y')]);
$totExportYear = (int)$stmtY->fetchColumn();
// --- Totale PARTI dell'ANNO (status = 'l') ---
$stmtYP = $pdo->prepare("
SELECT COUNT(*)
FROM identification_parts ip
INNER JOIN datadb d ON d.iddatadb = ip.iddatadb
WHERE d.status = 'l'
AND YEAR(d.export_date) = :y
");
$stmtYP->execute(['y' => $d->format('Y')]);
$totPartsYear = (int)$stmtYP->fetchColumn();
// --- Campioni totali inseriti nel LIMS per la giornata (tabella lims_daily_samples) ---
$stmtL = $pdo->prepare("SELECT total_samples FROM lims_daily_samples WHERE sample_date = :d LIMIT 1");
$stmtL->execute(['d' => $date]);
$limsSamples = $stmtL->fetchColumn();
$limsSamples = ($limsSamples !== false) ? (int)$limsSamples : null;
echo json_encode([
'success' => true,
'date' => $date,
'month' => $d->format('Y-m'),
'year' => (int)$d->format('Y'),
'totals' => ['export' => (int)$totExport, 'parts' => $totParts],
'totalsMonth' => ['export' => $totExportMonth, 'parts' => $totPartsMonth],
'totalsYear' => ['export' => $totExportYear, 'parts' => $totPartsYear],
'byClient' => $byClient,
'byUser' => $byUser,
'limsSamples' => $limsSamples,
]);
} catch (Exception $e) {
error_log('Stats export LIMS error: ' . $e->getMessage());
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
@@ -1,440 +0,0 @@
<?php include('include/headscript.php'); ?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>TRF-Project - Statistiche Export LIMS Mensili</title>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<style>
.stat-card .card-body {
padding: 1rem 1.25rem;
}
.stat-value {
font-size: 1.75rem;
font-weight: 700;
line-height: 1.1;
}
.stat-label {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: .03em;
color: #6c757d;
}
.chart-wrap {
position: relative;
height: 420px;
}
#tblClienti,
#tblUtenti {
font-size: 13px;
}
#tblClienti th,
#tblClienti td,
#tblUtenti th,
#tblUtenti td {
vertical-align: middle;
}
.filter-bar {
gap: .75rem;
}
.month-label {
font-size: 1.05rem;
font-weight: 600;
}
.stat-card-extra {
background: linear-gradient(135deg, #021bff 0%, #010983 100%);
border: none;
}
.stat-card-extra .stat-label {
color: #c9cfff;
}
.stat-card-extra .stat-value {
color: #ffffff !important;
}
.stat-card-extra small.text-muted {
color: #b3bbff !important;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<!-- ===== FILTRO MESE ===== -->
<div class="card radius-10">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap filter-bar">
<h6 class="mb-0 me-3"><i class="bx bx-bar-chart-alt-2"></i> Statistiche Export LIMS Mensili</h6>
<div class="d-flex align-items-center">
<label for="filterMonth" class="me-2 mb-0 fw-semibold">Mese:</label>
<input type="month" id="filterMonth" class="form-control form-control-sm" style="width:auto;">
</div>
<div class="btn-group btn-group-sm ms-2" role="group">
<button class="btn btn-outline-secondary" id="btnCurrentMonth">Mese corrente</button>
<button class="btn btn-outline-secondary" id="btnPrevMonth">Mese precedente</button>
</div>
<span class="month-label ms-2 text-primary" id="monthLabel"></span>
<a href="stats_export_lims.php" class="btn btn-outline-primary btn-sm ms-auto">
<i class="bx bx-calendar"></i> Vista giornaliera
</a>
<button class="btn btn-primary btn-sm ms-2" id="btnRefresh">
<i class="bx bx-refresh"></i> Aggiorna
</button>
</div>
</div>
</div>
<!-- ===== KPI (una sola riga) ===== -->
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-5 g-3">
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Campioni mese</div>
<div class="stat-value text-primary" id="kpiParts">0</div>
<small class="text-muted">Commesse: <span id="kpiExport">0</span></small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card stat-card-extra h-100">
<div class="card-body">
<div class="stat-label">Campioni anno <span id="yearLabel"></span></div>
<div class="stat-value" id="kpiPartsYear">0</div>
<small class="text-muted">Commesse: <span id="kpiExportYear">0</span> · dal 16/07/2026</small>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Campioni inseriti LIMS (mese)</div>
<div class="stat-value text-info" id="kpiLimsSamples">0</div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">% Esportati / inseriti</div>
<div class="stat-value text-success" id="kpiPerc"></div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 stat-card h-100">
<div class="card-body">
<div class="stat-label">Clienti distinti (mese)</div>
<div class="stat-value text-warning" id="kpiClienti">0</div>
</div>
</div>
</div>
</div>
<!-- ===== GRAFICI ===== -->
<div class="row mt-1">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Campioni per cliente (idclient) <small class="text-muted"> top 15</small></h6>
</div>
<div class="card-body">
<div class="chart-wrap"><canvas id="chartClienti"></canvas></div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Campioni per utente (user_id) <small class="text-muted"> top 15</small></h6>
</div>
<div class="card-body">
<div class="chart-wrap"><canvas id="chartUtenti"></canvas></div>
</div>
</div>
</div>
</div>
<!-- ===== LISTE ===== -->
<div class="row">
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Dettaglio per cliente</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tblClienti" class="table table-striped table-bordered table-sm w-100">
<thead>
<tr>
<th>ID Cliente</th>
<th>Cliente</th>
<th>Campioni</th>
<th>Commesse</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Dettaglio per utente</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tblUtenti" class="table table-striped table-bordered table-sm w-100">
<thead>
<tr>
<th>ID Utente</th>
<th>Utente</th>
<th>Campioni</th>
<th>Commesse</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
let chartClienti = null;
let chartUtenti = null;
let dtClienti = null;
let dtUtenti = null;
const MESI_IT = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'
];
function currentMonthStr() {
const d = new Date();
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
return `${y}-${m}`;
}
function monthLabelFromStr(monthStr) {
const parts = (monthStr || '').split('-');
if (parts.length !== 2) return '—';
const y = parseInt(parts[0], 10);
const m = parseInt(parts[1], 10);
if (isNaN(y) || isNaN(m) || m < 1 || m > 12) return '—';
return `${MESI_IT[m - 1]} ${y}`;
}
function initTables() {
dtClienti = $('#tblClienti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'idclient'
},
{
data: 'clientname',
defaultContent: '—'
},
{
data: 'parts',
className: 'text-end'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questo mese'
}
});
dtUtenti = $('#tblUtenti').DataTable({
paging: false,
searching: false,
info: false,
order: [
[2, 'desc']
],
columns: [{
data: 'user_id'
},
{
data: 'username',
defaultContent: '—'
},
{
data: 'parts',
className: 'text-end'
},
{
data: 'tot',
className: 'text-end'
}
],
language: {
emptyTable: 'Nessun export in questo mese'
}
});
}
function renderBarChart(existing, canvasId, labels, values, label) {
if (existing) existing.destroy();
const ctx = document.getElementById(canvasId).getContext('2d');
return new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: label,
data: values,
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
}
const CHART_TOP_N = 15;
function topN(arr) {
return arr.slice(0, CHART_TOP_N);
}
function loadStats() {
const month = $('#filterMonth').val() || currentMonthStr();
$('#monthLabel').text(monthLabelFromStr(month));
$.getJSON('stats_export_lims_monthly_data.php', {
month: month
}, function(res) {
if (!res || !res.success) {
Swal.fire('Errore', (res && res.message) || 'Errore nel caricamento', 'error');
return;
}
// KPI mese — dato principale = campioni/parti, sotto le commesse
$('#kpiParts').text(res.totals.parts ?? 0);
$('#kpiExport').text(res.totals.export ?? 0);
$('#kpiClienti').text(res.byClient.length);
$('#kpiLimsSamples').text(res.limsSamples ?? 0);
const limsTot = parseInt(res.limsSamples ?? 0, 10);
const partsTot = parseInt(res.totals.parts ?? 0, 10);
if (limsTot > 0) {
const perc = (partsTot / limsTot) * 100;
$('#kpiPerc').text(perc.toFixed(1) + '%');
} else {
$('#kpiPerc').text('—');
}
// Box annuale — campioni + commesse
$('#kpiPartsYear').text(res.totalsYear ? (res.totalsYear.parts ?? 0) : 0);
$('#kpiExportYear').text(res.totalsYear ? (res.totalsYear.export ?? 0) : 0);
$('#yearLabel').text(res.year);
// Grafici — valori = campioni/parti
const clientTop = topN(res.byClient);
const userTop = topN(res.byUser);
chartClienti = renderBarChart(chartClienti, 'chartClienti',
clientTop.map(r => r.clientname ? (r.clientname + ' (' + r.idclient + ')') : ('ID ' + r.idclient)),
clientTop.map(r => r.parts), 'Campioni');
chartUtenti = renderBarChart(chartUtenti, 'chartUtenti',
userTop.map(r => r.username ? (r.username + ' (' + r.user_id + ')') : ('ID ' + r.user_id)),
userTop.map(r => r.parts), 'Campioni');
// Tabelle
dtClienti.clear().rows.add(res.byClient).draw();
dtUtenti.clear().rows.add(res.byUser).draw();
}).fail(function(xhr) {
Swal.fire('Errore', 'Richiesta fallita: ' + xhr.status, 'error');
});
}
$(document).ready(function() {
$('#filterMonth').val(currentMonthStr());
initTables();
loadStats();
$('#btnRefresh').on('click', loadStats);
$('#filterMonth').on('change', loadStats);
$('#btnCurrentMonth').on('click', function() {
$('#filterMonth').val(currentMonthStr());
loadStats();
});
$('#btnPrevMonth').on('click', function() {
const cur = $('#filterMonth').val() || currentMonthStr();
const parts = cur.split('-');
let y = parseInt(parts[0], 10);
let m = parseInt(parts[1], 10);
m -= 1;
if (m < 1) {
m = 12;
y -= 1;
}
$('#filterMonth').val(`${y}-${String(m).padStart(2, '0')}`);
loadStats();
});
});
</script>
</body>
</html>
@@ -1,137 +0,0 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
try {
// Mese richiesto (default mese corrente). Formato atteso: Y-m
$month = $_GET['month'] ?? date('Y-m');
$dm = DateTime::createFromFormat('Y-m', $month);
if (!$dm || $dm->format('Y-m') !== $month) {
throw new Exception('Mese non valido');
}
$year = $dm->format('Y');
// --- Export per cliente (status = 'l', filtro mese) ---
// tot = numero commesse, parts = numero parti/campioni
$stmtC = $pdo->prepare("
SELECT d.idclient AS idclient,
COUNT(DISTINCT d.iddatadb) AS tot,
COUNT(ip.id) AS parts
FROM datadb d
LEFT JOIN identification_parts ip ON ip.iddatadb = d.iddatadb
WHERE d.status = 'l'
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
GROUP BY d.idclient
ORDER BY parts DESC
");
$stmtC->execute(['m' => $month]);
$byClient = $stmtC->fetchAll(PDO::FETCH_ASSOC);
// --- Risolvi Nominativo cliente dalla cache locale ---
$clientNames = [];
$cacheFile = __DIR__ . '/cache/clienti.json';
if (is_file($cacheFile)) {
$cache = json_decode(file_get_contents($cacheFile), true);
foreach (($cache['value'] ?? []) as $c) {
$clientNames[(int)($c['IdCliente'] ?? 0)] = trim($c['Nominativo'] ?? '');
}
}
// --- Export per utente (status = 'l', filtro mese) ---
// tot = numero commesse, parts = numero parti/campioni
$stmtU = $pdo->prepare("
SELECT d.user_id AS user_id,
TRIM(CONCAT(COALESCE(u.first_name,''),' ',COALESCE(u.last_name,''))) AS username,
COUNT(DISTINCT d.iddatadb) AS tot,
COUNT(ip.id) AS parts
FROM datadb d
LEFT JOIN auth_users u ON u.id = d.user_id
LEFT JOIN identification_parts ip ON ip.iddatadb = d.iddatadb
WHERE d.status = 'l'
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
GROUP BY d.user_id, username
ORDER BY parts DESC
");
$stmtU->execute(['m' => $month]);
$byUser = $stmtU->fetchAll(PDO::FETCH_ASSOC);
// Normalizza tipi
foreach ($byClient as &$r) {
$r['idclient'] = $r['idclient'] !== null ? (int)$r['idclient'] : null;
$r['tot'] = (int)$r['tot'];
$r['parts'] = (int)$r['parts'];
$nm = $clientNames[$r['idclient']] ?? '';
$r['clientname'] = $nm !== '' ? $nm : null;
}
unset($r);
foreach ($byUser as &$r) {
$r['user_id'] = $r['user_id'] !== null ? (int)$r['user_id'] : null;
$r['tot'] = (int)$r['tot'];
$r['parts'] = (int)$r['parts'];
if (isset($r['username'])) $r['username'] = $r['username'] !== null && $r['username'] !== '' ? $r['username'] : null;
}
unset($r);
// --- Totale COMMESSE del mese ---
$totExport = array_sum(array_column($byClient, 'tot'));
// --- Totale PARTI/CAMPIONI del mese (status = 'l') ---
$stmtP = $pdo->prepare("
SELECT COUNT(*)
FROM identification_parts ip
INNER JOIN datadb d ON d.iddatadb = ip.iddatadb
WHERE d.status = 'l'
AND DATE_FORMAT(d.export_date, '%Y-%m') = :m
");
$stmtP->execute(['m' => $month]);
$totParts = (int)$stmtP->fetchColumn();
// --- Totale COMMESSE dell'ANNO del mese selezionato ---
$stmtY = $pdo->prepare("
SELECT COUNT(*)
FROM datadb d
WHERE d.status = 'l'
AND YEAR(d.export_date) = :y
");
$stmtY->execute(['y' => $year]);
$totExportYear = (int)$stmtY->fetchColumn();
// --- Totale PARTI dell'ANNO del mese selezionato ---
$stmtYP = $pdo->prepare("
SELECT COUNT(*)
FROM identification_parts ip
INNER JOIN datadb d ON d.iddatadb = ip.iddatadb
WHERE d.status = 'l'
AND YEAR(d.export_date) = :y
");
$stmtYP->execute(['y' => $year]);
$totPartsYear = (int)$stmtYP->fetchColumn();
// --- Campioni LIMS: somma dei total_samples di tutti i giorni del mese ---
$stmtL = $pdo->prepare("
SELECT COALESCE(SUM(total_samples), 0)
FROM lims_daily_samples
WHERE DATE_FORMAT(sample_date, '%Y-%m') = :m
");
$stmtL->execute(['m' => $month]);
$limsSamples = (int)$stmtL->fetchColumn();
echo json_encode([
'success' => true,
'month' => $month,
'year' => (int)$year,
'totals' => ['export' => (int)$totExport, 'parts' => $totParts],
'totalsYear' => ['export' => $totExportYear, 'parts' => $totPartsYear],
'byClient' => $byClient,
'byUser' => $byUser,
'limsSamples' => $limsSamples,
]);
} catch (Exception $e) {
error_log('Stats export LIMS monthly error: ' . $e->getMessage());
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
@@ -1,36 +0,0 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json');
// Usa la connessione in scrittura se disponibile nel tuo progetto;
// qui riuso il pattern del resto dell'app.
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
try {
$date = $_POST['date'] ?? null;
$total = $_POST['total_samples'] ?? null;
$d = $date ? DateTime::createFromFormat('Y-m-d', $date) : null;
if (!$d || $d->format('Y-m-d') !== $date) {
throw new Exception('Data non valida');
}
if (!is_numeric($total) || (int)$total < 0) {
throw new Exception('Valore campioni non valido');
}
$total = (int)$total;
// Upsert: un solo record per giornata (unique su sample_date)
$stmt = $pdo->prepare("
INSERT INTO lims_daily_samples (sample_date, total_samples)
VALUES (:d, :t)
ON DUPLICATE KEY UPDATE total_samples = VALUES(total_samples)
");
$stmt->execute(['d' => $date, 't' => $total]);
echo json_encode(['success' => true, 'date' => $date, 'total_samples' => $total]);
} catch (Exception $e) {
error_log('Save LIMS samples error: ' . $e->getMessage());
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
}
-8
View File
@@ -78,11 +78,6 @@
color: #198754;
}
.badge-source-pdf {
background-color: #ffeaea;
color: #dc3545;
}
#xlsTemplatesTable {
font-size: 13px;
}
@@ -250,9 +245,6 @@
if (sourceType === 'API') {
return '<span class="badge-source badge-source-api">API</span>';
}
if (sourceType === 'PDF') {
return '<span class="badge-source badge-source-pdf">PDF</span>';
}
return '<span class="badge-source badge-source-xls">XLS</span>';
}
-112
View File
@@ -1,112 +0,0 @@
<?php
/*
* test_pdf_extract.php
* ------------------------------------------------------------------
* Pagina di test ISOLATA per verificare l'estrazione PDF.
* Da aprire nel browser passando ?id=<template_id> di un template PDF
* che ha già i riquadri tracciati e un sample_pdf caricato.
*
* http://localhost/trf_certest/test_pdf_extract.php?id=67
*
* Mostra, per ogni campo mappato con pdf_regions, il valore estratto dal
* PDF di esempio. Serve a confermare che i riquadri prendano i dati giusti
* PRIMA di collegare l'inserimento in datadb.
*
* DA CANCELLARE dopo il test.
* ------------------------------------------------------------------
*/
include('include/headscript.php');
require_once __DIR__ . '/pdf_extract_lib.php';
header('Content-Type: text/html; charset=utf-8');
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
die('Passa ?id=<template_id> di un template PDF.');
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Template
$stmt = $pdo->prepare("SELECT id, name, source_type, sample_pdf FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
die('Template non trovato.');
}
if (strtoupper($template['source_type']) !== 'PDF') {
die('Questo template non è di tipo PDF (source_type = ' . htmlspecialchars($template['source_type']) . ').');
}
if (empty($template['sample_pdf'])) {
die('Nessun PDF di esempio caricato per questo template.');
}
// Percorso del PDF di esempio (adatta se la cartella è diversa)
$pdfPath = __DIR__ . '/pdftemplates/' . $template['sample_pdf'];
// Mapping con eventuali riquadri
$stmt = $pdo->prepare("
SELECT id, field_label, data_type, pdf_regions
FROM template_mapping
WHERE template_id = ?
ORDER BY field_order ASC, id ASC
");
$stmt->execute([$id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "<h2>Test estrazione PDF — Template: " . htmlspecialchars($template['name']) . "</h2>";
echo "<p><strong>PDF:</strong> " . htmlspecialchars($template['sample_pdf']) . "<br>";
echo "<strong>Percorso:</strong> " . htmlspecialchars($pdfPath) . "</p>";
if (!is_file($pdfPath)) {
die('<p style="color:red">❌ File PDF non trovato al percorso indicato. Correggi $pdfPath nel test.</p>');
}
echo "<p><strong>Binario pdftotext:</strong> " . htmlspecialchars(pdftotext_binary()) . "</p>";
try {
$values = pdf_extract_values($pdfPath, $mappings);
} catch (Throwable $e) {
die('<p style="color:red">❌ Errore estrazione: ' . htmlspecialchars($e->getMessage()) . '</p>');
}
echo '<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;font-family:sans-serif;font-size:14px;">';
echo '<tr style="background:#f0f0f0;"><th>ID</th><th>Campo</th><th>Tipo</th><th>Pagina</th><th>Riquadro value</th><th>VALORE ESTRATTO</th></tr>';
foreach ($mappings as $m) {
$hasRegions = !empty($m['pdf_regions']);
$regions = $hasRegions ? json_decode($m['pdf_regions'], true) : null;
$page = $regions['page'] ?? '';
$valueRect = isset($regions['value'])
? sprintf(
'x=%.0f y=%.0f w=%.0f h=%.0f',
$regions['value']['x'],
$regions['value']['y'],
$regions['value']['w'],
$regions['value']['h']
)
: '—';
$extracted = $values[(int)$m['id']] ?? '';
$rowBg = $hasRegions ? ($extracted !== '' ? '#eaffea' : '#fff3cd') : '#ffffff';
echo '<tr style="background:' . $rowBg . ';">';
echo '<td>' . (int)$m['id'] . '</td>';
echo '<td>' . htmlspecialchars($m['field_label'] ?? '') . '</td>';
echo '<td>' . htmlspecialchars($m['data_type'] ?? '') . '</td>';
echo '<td style="text-align:center;">' . htmlspecialchars((string)$page) . '</td>';
echo '<td style="font-family:monospace;font-size:12px;">' . htmlspecialchars($valueRect) . '</td>';
echo '<td><strong>' . htmlspecialchars($extracted) . '</strong></td>';
echo '</tr>';
}
echo '</table>';
echo '<p style="color:#666;margin-top:15px;">Verde = valore estratto. Giallo = riquadro presente ma nessun testo trovato. Bianco = campo senza riquadro PDF.</p>';
echo '<p style="color:#a00;"><strong>Ricorda di cancellare questo file dopo il test.</strong></p>';
-21
View File
@@ -1,21 +0,0 @@
<?php
echo "<pre>";
echo "== SO ==\n";
echo PHP_OS . "\n\n";
echo "== disable_functions ==\n";
echo ini_get('disable_functions') ?: "(nessuna)";
echo "\n\n";
echo "== test exec con percorso completo ==\n";
if (function_exists('exec')) {
$cmd = '"C:\\poppler\\poppler-26.02.0\\Library\\bin\\pdftotext.exe" -v 2>&1';
exec($cmd, $out, $ret);
echo "return code: $ret\n";
echo implode("\n", $out);
} else {
echo "exec() NON disponibile";
}
echo "\n</pre>";
@@ -1,53 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
require_once 'class/db-functions.php';
$response = ["success" => false, "message" => ""];
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
if (!$pdo) {
throw new Exception('Database connection failed.');
}
$userId = $iduserlogin ?? 0;
if (!$userId) {
throw new Exception('User not authenticated.');
}
$data = json_decode(file_get_contents('php://input'), true);
$templateId = isset($data['template_id']) ? (int)$data['template_id'] : 0;
$isFavorite = !empty($data['is_favorite']);
if ($templateId <= 0) {
throw new Exception('Invalid template_id.');
}
if ($isFavorite) {
// Aggiungi ai preferiti (ignora se già presente)
$stmt = $pdo->prepare("
INSERT INTO user_template_favorites (user_id, template_id)
VALUES (:user_id, :template_id)
ON DUPLICATE KEY UPDATE id = id
");
$stmt->execute(['user_id' => $userId, 'template_id' => $templateId]);
} else {
// Rimuovi dai preferiti
$stmt = $pdo->prepare("
DELETE FROM user_template_favorites
WHERE user_id = :user_id AND template_id = :template_id
");
$stmt->execute(['user_id' => $userId, 'template_id' => $templateId]);
}
$response["success"] = true;
} catch (PDOException $e) {
$response["message"] = "Database error: " . $e->getMessage();
} catch (Exception $e) {
$response["message"] = "Error: " . $e->getMessage();
}
echo json_encode($response);
-108
View File
@@ -1,108 +0,0 @@
<?php
header('Content-Type: application/json');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$data = json_decode(file_get_contents("php://input"), true);
if (!$data || !isset($data['id'], $data['field'])) {
echo json_encode(["success" => false, "message" => "Invalid or missing parameters"]);
exit;
}
$mappingId = (int)$data['id'];
$field = $data['field'];
$value = isset($data['value']) && $data['value'] ? 1 : 0;
// Whitelist rigorosa dei campi aggiornabili: evita SQL injection sul nome colonna
$allowedFields = ['main_field', 'is_visible_import', 'is_visible_parts', 'is_addnote'];
if (!in_array($field, $allowedFields, true)) {
echo json_encode(["success" => false, "message" => "Invalid field"]);
exit;
}
try {
// Recupero il template_id della riga per poter applicare i vincoli a livello di template
$stmt = $pdo->prepare("SELECT template_id FROM template_mapping WHERE id = ?");
$stmt->execute([$mappingId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
echo json_encode(["success" => false, "message" => "Mapping not found"]);
exit;
}
$templateId = (int)$row['template_id'];
// Vincolo: max 2 righe con main_field = 1 per template
if ($field === 'main_field' && $value === 1) {
$stmt = $pdo->prepare("
SELECT COUNT(*)
FROM template_mapping
WHERE template_id = ? AND main_field = '1' AND id != ?
");
$stmt->execute([$templateId, $mappingId]);
$count = (int)$stmt->fetchColumn();
if ($count >= 2) {
echo json_encode([
"success" => false,
"limit_reached" => true,
"message" => "Massimo 2 campi Main consentiti per questo template"
]);
exit;
}
}
$pdo->beginTransaction();
$uncheckedOthers = false;
// Vincolo: solo 1 riga con is_visible_parts = 1 per template (comportamento radio)
if ($field === 'is_visible_parts' && $value === 1) {
$stmt = $pdo->prepare("
UPDATE template_mapping
SET is_visible_parts = 0
WHERE template_id = ? AND id != ? AND is_visible_parts = 1
");
$stmt->execute([$templateId, $mappingId]);
$uncheckedOthers = $stmt->rowCount() > 0;
}
$bindValue = ($field === 'main_field') ? (string)$value : $value;
$stmt = $pdo->prepare("UPDATE template_mapping SET {$field} = ? WHERE id = ?");
$result = $stmt->execute([$bindValue, $mappingId]);
if (!$result) {
$pdo->rollBack();
echo json_encode(["success" => false, "message" => "Database update failed"]);
exit;
}
$pdo->commit();
echo json_encode([
"success" => true,
"message" => "Flag updated successfully",
"unchecked_others" => $uncheckedOthers,
"saved" => [
"id" => $mappingId,
"field" => $field,
"value" => $value
]
]);
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
echo json_encode(["success" => false, "message" => "Error: " . $e->getMessage()]);
}
exit;
-57
View File
@@ -1,57 +0,0 @@
<?php
// update_pdf_page_meta.php
// Salva i metadati delle pagine del PDF (numero pagine e dimensioni renderizzate)
// nella colonna pdf_page_meta del template.
//
// Riceve JSON: { template_id, pdf_page_meta }
// dove pdf_page_meta è una stringa JSON tipo:
// {"pages":3,"dims":[{"page":1,"width":1000,"height":1414}, ...]}
header('Content-Type: application/json');
include('include/headscript.php');
function respond(bool $success, string $message = ''): void
{
echo json_encode(['success' => $success, 'message' => $message]);
exit;
}
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
if (!is_array($data)) {
respond(false, 'Invalid JSON payload.');
}
$templateId = isset($data['template_id']) ? (int)$data['template_id'] : 0;
$pageMeta = $data['pdf_page_meta'] ?? null;
if ($templateId <= 0) {
respond(false, 'Missing or invalid template_id.');
}
// pdf_page_meta deve essere una stringa JSON valida
if (!is_string($pageMeta)) {
respond(false, 'pdf_page_meta must be a JSON string.');
}
$decoded = json_decode($pageMeta, true);
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
respond(false, 'pdf_page_meta is not valid JSON: ' . json_last_error_msg());
}
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("UPDATE excel_templates SET pdf_page_meta = :meta WHERE id = :id");
$stmt->execute([
':meta' => $pageMeta,
':id' => $templateId,
]);
} catch (Throwable $e) {
respond(false, 'Database update failed: ' . $e->getMessage());
}
respond(true, 'Page metadata saved.');
+17 -18
View File
@@ -135,26 +135,25 @@ try {
// Update existing field AND clear user-entered value if type changed
$updateChangedTypeStmt = $pdo->prepare("
UPDATE template_mapping
SET
schema_id = :schema_id,
field_order = :field_order,
data_type = :data_type,
is_required = :is_required,
default_value = :default_value,
has_list = :has_list,
length = :length,
decimals = :decimals,
min_value = :min_value,
max_value = :max_value,
default_curr_date = :default_curr_date,
tablename = :tablename,
field_label = :field_label,
UPDATE template_mapping
SET
schema_id = :schema_id,
data_type = :data_type,
is_required = :is_required,
default_value = :default_value,
has_list = :has_list,
length = :length,
decimals = :decimals,
min_value = :min_value,
max_value = :max_value,
default_curr_date = :default_curr_date,
tablename = :tablename,
field_label = :field_label,
manual_default = NULL,
auto_value = 'none'
manual_default = NULL,
auto_value = 'none'
WHERE id = :id
WHERE id = :id
");
$currentFieldIds = [];
-95
View File
@@ -1,95 +0,0 @@
<?php
// upload_pdf_example.php
// Riceve un PDF di esempio via POST (multipart), lo salva in pdftemplates/
// e aggiorna la colonna sample_pdf del template.
//
// Rispecchia la logica di upload_xls_example.php ma per i PDF.
header('Content-Type: application/json');
include('include/headscript.php');
// --- Helper risposta JSON ---
function respond(bool $success, string $message = '', array $extra = []): void
{
echo json_encode(array_merge([
'success' => $success,
'message' => $message,
], $extra));
exit;
}
// --- Validazione input ---
$templateId = isset($_POST['template_id']) ? (int)$_POST['template_id'] : 0;
if ($templateId <= 0) {
respond(false, 'Missing or invalid template_id.');
}
if (!isset($_FILES['pdf_file']) || $_FILES['pdf_file']['error'] !== UPLOAD_ERR_OK) {
respond(false, 'No file uploaded or upload error.');
}
$file = $_FILES['pdf_file'];
// --- Controllo estensione e MIME ---
$originalName = $file['name'];
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if ($ext !== 'pdf') {
respond(false, 'Only PDF files are allowed.');
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if ($mime !== 'application/pdf') {
respond(false, 'The uploaded file is not a valid PDF (mime: ' . htmlspecialchars($mime) . ').');
}
// --- Cartella di destinazione ---
$targetDir = __DIR__ . '/pdftemplates';
if (!is_dir($targetDir)) {
if (!mkdir($targetDir, 0775, true) && !is_dir($targetDir)) {
respond(false, 'Cannot create destination folder pdftemplates/.');
}
}
if (!is_writable($targetDir)) {
respond(false, 'Destination folder pdftemplates/ is not writable.');
}
// --- Nome file sicuro e univoco ---
$safeBase = preg_replace('/[^A-Za-z0-9_\-]/', '_', pathinfo($originalName, PATHINFO_FILENAME));
$safeBase = trim($safeBase, '_');
if ($safeBase === '') {
$safeBase = 'sample';
}
$filename = 'tpl' . $templateId . '_' . $safeBase . '_' . date('YmdHis') . '.pdf';
$targetPath = $targetDir . '/' . $filename;
// --- Sposta il file ---
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
respond(false, 'Failed to move uploaded file.');
}
// --- Aggiorna il database ---
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("UPDATE excel_templates SET sample_pdf = :sample_pdf WHERE id = :id");
$stmt->execute([
':sample_pdf' => $filename,
':id' => $templateId,
]);
} catch (Throwable $e) {
// Se il DB fallisce, rimuovo il file per non lasciare orfani
@unlink($targetPath);
respond(false, 'Database update failed: ' . $e->getMessage());
}
respond(true, 'File uploaded successfully.', ['filename' => $filename]);
+4 -14
View File
@@ -129,7 +129,6 @@ $fixedAliasMap = [
'ClienteResponsabile' => 'cliente_responsabile_id',
'ClienteFornitore' => 'cliente_fornitore_id',
'ClienteAnalisi' => 'clienteAnalisi',
'ClienteFatturazione' => 'ClienteFatturazione',
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
@@ -138,11 +137,9 @@ $fixedAliasMap = [
// Fixed keys NOT enforced by the generic mandatory check above:
// - ConsegnaRichiesta: handled by its dedicated validator (also checks the date)
// - ClienteFornitore: nullable placeholder, sent as null on export and
// accepted by LIMS.
// - ClienteFatturazione: TEMP disabled - problemi API lato LIMS, non viene più
// inviato in save_export_lims.php, quindi non deve bloccare la validazione.
$skipRequiredFixed = ['ConsegnaRichiesta', 'ClienteFornitore', 'ClienteFatturazione'];
// - ClienteFornitore / ClienteAnalisi: nullable placeholders, sent as null on
// export and accepted by LIMS.
$skipRequiredFixed = ['ConsegnaRichiesta', 'ClienteFornitore', 'ClienteAnalisi'];
// ── Main ────────────────────────────────────────────────────────────────────
@@ -165,7 +162,7 @@ try {
SELECT iddatadb, templateid, consegna_richiesta,
cliente_responsabile_id, moltiplicatore_prezzo_id,
anagrafica_certest_object_id, anagrafica_certest_service_id,
cliente_fornitore_id, clienteAnalisi, ClienteFatturazione
cliente_fornitore_id, clienteAnalisi
FROM datadb
WHERE iddatadb IN ($placeholders)
");
@@ -180,8 +177,6 @@ try {
SELECT iddatadb, part_number, part_description, idmatrice
FROM identification_parts
WHERE iddatadb IN ($placeholders)
AND part_description IS NOT NULL
AND TRIM(part_description) <> ''
");
$stmt->execute($iddatadbList);
$partsInfo = [];
@@ -217,10 +212,6 @@ try {
}
// Required custom fields that are visible in the import grid (excluding filed_id = 189 Tested component)
// Required custom fields that are visible in the import grid (excluding filed_id = 189 Tested component)
// Also exclude field_id 258 (Login Date) and 350 (Login Time): auto-populated at
// export time (save_export_lims.php STEP 4a), so they must not block validation
// even when empty at this stage.
$stmt = $pdo->prepare("
SELECT id AS mapping_id, template_id, field_label
FROM template_mapping
@@ -228,7 +219,6 @@ try {
AND is_required = 1
AND is_visible_import = 1
AND id <> 189
AND field_id NOT IN (258, 350)
");
$stmt->execute($templateIds);
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
+10 -76
View File
@@ -6,11 +6,6 @@
$startTime = microtime(true);
$isCli = (php_sapi_name() === 'cli');
$isStream = (!$isCli && isset($_GET['stream']));
// Analisi incluse? Default SÌ (il cron non passa il parametro → fa tutto).
// Solo una richiesta HTTP con analisi=0 le salta.
$includeAnalisi = !(!$isCli && isset($_GET['analisi']) && $_GET['analisi'] === '0');
if (!$isCli) {
// When called via HTTP, require auth
@@ -20,17 +15,7 @@ if (!$isCli) {
echo json_encode(['error' => 'Unauthorized']);
exit;
}
if ($isStream) {
// Streaming mode: send progress line-by-line (Server-Sent Events)
set_time_limit(0);
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // disable nginx buffering
while (ob_get_level() > 0) ob_end_flush();
} else {
header('Content-Type: application/json');
}
header('Content-Type: application/json');
}
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
@@ -38,38 +23,14 @@ require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
require_once __DIR__ . '/class/db-functions.php';
$cacheDir = __DIR__ . '/cache';
if (!is_dir($cacheDir)) mkdir($cacheDir, 0755, true);
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
$log = [];
// Prevent two runs at the same time (e.g. cron + manual click)
$lockHandle = fopen($cacheDir . '/.warm.lock', 'w');
if ($lockHandle === false || !flock($lockHandle, LOCK_EX | LOCK_NB)) {
$busyMsg = 'Un aggiornamento è già in corso in questo momento. Riprova tra poco.';
if ($isCli) {
echo $busyMsg . PHP_EOL;
} elseif ($isStream) {
header('Content-Type: text/event-stream');
echo "event: busy\ndata: " . $busyMsg . "\n\n";
@flush();
} else {
echo json_encode(['success' => false, 'busy' => true, 'message' => $busyMsg]);
}
exit;
}
function warmLog(string $msg, bool $isCli)
{
global $log, $isStream;
function warmLog(string $msg, bool $isCli) {
global $log;
$line = date('H:i:s') . " $msg";
$log[] = $line;
if ($isCli) {
echo $line . PHP_EOL;
} elseif (!empty($isStream)) {
echo "data: " . str_replace("\n", ' ', $line) . "\n\n";
@ob_flush();
@flush();
}
if ($isCli) echo $line . PHP_EOL;
}
try {
@@ -78,7 +39,7 @@ try {
// 1. Clients
warmLog('[clients] Fetching...', $isCli);
$params = ['$select' => 'IdCliente,Nominativo,CodiceCliente,Inattivo', '$orderby' => 'Nominativo asc'];
$params = ['$select' => 'IdCliente,Nominativo,CodiceCliente', '$orderby' => 'Nominativo asc'];
$clientData = $api->get("Cliente?" . http_build_query($params));
file_put_contents($cacheDir . '/clienti.json', json_encode($clientData));
$clientCount = count($clientData['value'] ?? []);
@@ -151,40 +112,13 @@ try {
file_put_contents($cacheDir . '/matrici.json', json_encode(['success' => true, 'value' => $matrici]));
warmLog("[matrici] Cached " . count($matrici) . " items", $isCli);
// 6. Analisi — only for matrici actually used by parts (warming all ~2500 matrici
// would mean one API call each; the used set is a few dozen).
if (!$includeAnalisi) {
warmLog('[analisi] Saltate su richiesta.', $isCli);
} else {
$stmt = $pdo->query("SELECT DISTINCT idmatrice FROM identification_parts WHERE idmatrice IS NOT NULL AND idmatrice > 0 ORDER BY idmatrice ASC");
$matriceIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
warmLog("[analisi] Fetching for " . count($matriceIds) . " matrici in use...", $isCli);
foreach ($matriceIds as $mid) {
$mid = (int)$mid;
// Same query as get_analisi_matrice_filter.php with web_only=1 (what the analysis modal requests)
$filter = rawurlencode("Matrice/IdMatrice eq $mid and SelezionabileSuWeb eq true");
try {
$data = $api->get("Analisi?\$filter={$filter}");
$values = $data['value'] ?? [];
file_put_contents($cacheDir . '/analisi_matrice_' . $mid . '.json', json_encode(['value' => $values]));
warmLog("[analisi] Matrice $mid: " . count($values) . " analisi", $isCli);
} catch (Exception $e) {
warmLog("[analisi] Matrice $mid: ERROR " . $e->getMessage(), $isCli);
}
}
} // fine if includeAnalisi
$elapsed = round(microtime(true) - $startTime, 1);
warmLog("Aggiornamento completato in {$elapsed}s", $isCli);
warmLog("Done in {$elapsed}s", $isCli);
} catch (Exception $e) {
$elapsed = round(microtime(true) - $startTime, 1);
warmLog("ERRORE: " . $e->getMessage(), $isCli);
warmLog("FATAL: " . $e->getMessage(), $isCli);
}
if ($isStream) {
echo "event: done\ndata: " . json_encode(['elapsed' => $elapsed ?? 0]) . "\n\n";
@flush();
} elseif (!$isCli) {
if (!$isCli) {
echo json_encode(['success' => true, 'log' => $log]);
}
-519
View File
@@ -1,519 +0,0 @@
<?php
include('include/headscript.php');
/*
* NOTE PERMESSI:
* Il login è già gestito da headscript.php (Auth::check + redirect).
* Se vuoi limitare questa pagina a determinati ruoli, aggiungi qui il tuo
* controllo, esattamente come nelle altre pagine. Esempio:
*
* if (!$user->hasRole('Admin') && !$user->hasRole('SuperUser')) {
* header('Location: import_dashboard.php');
* exit;
* }
*/
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Aggiornamento dati - <?= htmlspecialchars($titlewebsite ?? 'SmartTRF', ENT_QUOTES, 'UTF-8'); ?></title>
<style>
.warm-hero {
display: flex;
align-items: center;
gap: 18px;
}
.warm-hero .warm-icon {
width: 64px;
height: 64px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
color: #fff;
background: linear-gradient(135deg, #0f172a, #1d4ed8);
flex-shrink: 0;
}
.warm-progress-wrap {
display: none;
}
.warm-progress-wrap.is-visible {
display: block;
}
.progress.warm-progress {
height: 22px;
border-radius: 12px;
background: #eef2f7;
}
.progress.warm-progress .progress-bar {
font-size: 12px;
font-weight: 600;
}
.warm-steps {
list-style: none;
padding: 0;
margin: 18px 0 0 0;
}
.warm-steps li {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 10px;
margin-bottom: 6px;
background: #f8fafc;
border: 1px solid #eef2f7;
transition: background .2s, border-color .2s;
}
.warm-steps li .step-icon {
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
background: #e5e7eb;
color: #94a3b8;
}
.warm-steps li.is-active {
background: #eff6ff;
border-color: #bfdbfe;
}
.warm-steps li.is-active .step-icon {
background: #2563eb;
color: #fff;
}
.warm-steps li.is-done {
background: #f0fdf4;
border-color: #bbf7d0;
}
.warm-steps li.is-done .step-icon {
background: #16a34a;
color: #fff;
}
.warm-steps li .step-label {
font-weight: 600;
color: #1f2937;
}
.warm-steps li .step-detail {
font-size: 12px;
color: #64748b;
margin-left: auto;
text-align: right;
}
.warm-log {
display: none;
margin-top: 18px;
}
.warm-log.is-visible {
display: block;
}
.warm-log pre {
max-height: 320px;
overflow: auto;
background: #0f172a;
color: #cbd5e1;
padding: 16px;
border-radius: 12px;
font-size: 12px;
line-height: 1.6;
margin: 0;
}
.warm-result {
display: none;
margin-top: 18px;
}
.warm-result.is-visible {
display: block;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card radius-10">
<div class="card-body p-4">
<div class="warm-hero">
<div class="warm-icon"><i class="bx bx-refresh"></i></div>
<div>
<h5 class="mb-1">Aggiornamento dati dal gestionale</h5>
<p class="mb-0 text-muted">
Ricarica clienti, matrici, analisi e le altre liste dal software di laboratorio.
Da usare quando sono state fatte modifiche sul gestionale e vuoi vederle subito
senza aspettare l'aggiornamento automatico.
</p>
</div>
</div>
<hr class="my-4">
<div class="form-check form-switch mb-3" id="analisiSwitchWrap">
<input class="form-check-input" type="checkbox" role="switch" id="includeAnalisi" checked>
<label class="form-check-label" for="includeAnalisi">
Aggiorna anche le <strong>analisi</strong>
<span class="text-muted small">(più lento &mdash; togli la spunta se hai modificato solo clienti o altre liste)</span>
</label>
</div>
<button type="button" class="btn btn-primary btn-lg px-4" id="btnStart">
<i class="bx bx-play-circle me-1"></i> Avvia aggiornamento
</button>
<div class="small text-muted mt-2" id="idleHint">
L'operazione può richiedere da qualche secondo a un paio di minuti.
</div>
<!-- Progress -->
<div class="warm-progress-wrap mt-4" id="progressWrap">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="fw-semibold" id="progressTitle">Aggiornamento in corso…</span>
<span class="text-muted small" id="progressElapsed">0s</span>
</div>
<div class="progress warm-progress">
<div class="progress-bar progress-bar-striped progress-bar-animated bg-primary"
id="progressBar" role="progressbar" style="width: 0%;">0%</div>
</div>
<ul class="warm-steps" id="stepsList">
<!-- steps injected by JS -->
</ul>
<div class="mt-3">
<a href="javascript:;" class="small text-decoration-none" id="toggleLog">
<i class="bx bx-code-alt"></i> Mostra dettaglio tecnico
</a>
</div>
<div class="warm-log" id="logBox">
<pre id="logPre"></pre>
</div>
</div>
<!-- Result -->
<div class="warm-result" id="resultBox">
<div class="alert d-flex align-items-center" id="resultAlert" role="alert">
<i class="bx me-2 fs-4" id="resultIcon"></i>
<div id="resultText"></div>
</div>
<button type="button" class="btn btn-outline-primary" id="btnAgain">
<i class="bx bx-refresh me-1"></i> Esegui di nuovo
</button>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
/*
* Se sposti warm_cache.php in un'altra cartella, cambia SOLO questa riga.
*/
const WARM_ENDPOINT = 'warm_cache.php';
const btnStart = document.getElementById('btnStart');
const btnAgain = document.getElementById('btnAgain');
const idleHint = document.getElementById('idleHint');
const includeAnalisi = document.getElementById('includeAnalisi');
const progressWrap = document.getElementById('progressWrap');
const progressBar = document.getElementById('progressBar');
const progressTitle = document.getElementById('progressTitle');
const progressElapsed = document.getElementById('progressElapsed');
const stepsList = document.getElementById('stepsList');
const toggleLog = document.getElementById('toggleLog');
const logBox = document.getElementById('logBox');
const logPre = document.getElementById('logPre');
const resultBox = document.getElementById('resultBox');
const resultAlert = document.getElementById('resultAlert');
const resultIcon = document.getElementById('resultIcon');
const resultText = document.getElementById('resultText');
/*
* I "passi" mostrati al cliente in modo leggibile.
* key = frammento che compare nel log tecnico (dentro le parentesi quadre)
* label = nome umano
* Il progresso avanza man mano che questi tag compaiono nel log.
*/
const STEPS = [{
key: 'clients',
label: 'Clienti'
},
{
key: 'MoltiplicatorePrezzo',
label: 'Listini prezzi'
},
{
key: 'AnagraficaCertest',
label: 'Anagrafiche Certest'
},
{
key: 'ClienteResponsabile',
label: 'Responsabili cliente'
},
{
key: 'CustomField',
label: 'Campi personalizzati'
},
{
key: 'matrici',
label: 'Matrici'
},
{
key: 'analisi',
label: 'Analisi'
}
];
let stepEls = {};
let currentStepIndex = -1;
let timerId = null;
let startedAt = 0;
let activeSteps = []; // i passi effettivamente in gioco per questa esecuzione
function buildSteps() {
stepsList.innerHTML = '';
stepEls = {};
// Se le analisi sono disattivate, escludo quel passo dalla lista.
const withAnalisi = includeAnalisi.checked;
activeSteps = STEPS.filter(function(s) {
return withAnalisi || s.key !== 'analisi';
});
activeSteps.forEach(function(step, i) {
const li = document.createElement('li');
li.dataset.index = i;
li.innerHTML =
'<span class="step-icon"><i class="bx bx-time"></i></span>' +
'<span class="step-label">' + step.label + '</span>' +
'<span class="step-detail"></span>';
stepsList.appendChild(li);
stepEls[i] = li;
});
}
function markStep(index, state, detail) {
const li = stepEls[index];
if (!li) return;
li.classList.remove('is-active', 'is-done');
const icon = li.querySelector('.step-icon i');
if (state === 'active') {
li.classList.add('is-active');
icon.className = 'bx bx-loader-alt bx-spin';
} else if (state === 'done') {
li.classList.add('is-done');
icon.className = 'bx bx-check';
}
if (detail !== undefined) {
li.querySelector('.step-detail').textContent = detail;
}
}
function advanceTo(index, detail) {
if (index <= currentStepIndex) {
// stesso passo, aggiorna solo il dettaglio (es. conteggio)
if (detail !== undefined) markStep(index, 'active', detail);
return;
}
// chiudi i passi precedenti
for (let i = 0; i <= currentStepIndex; i++) markStep(i, 'done');
currentStepIndex = index;
markStep(index, 'active', detail);
const pct = Math.round(((index) / activeSteps.length) * 100);
setProgress(Math.max(pct, 3));
}
function setProgress(pct) {
pct = Math.max(0, Math.min(100, pct));
progressBar.style.width = pct + '%';
progressBar.textContent = pct + '%';
}
function matchStep(line) {
// cerca [clients], [matrici], [analisi] ecc. nella riga di log
for (let i = 0; i < activeSteps.length; i++) {
if (line.indexOf('[' + activeSteps[i].key) !== -1 ||
line.indexOf(activeSteps[i].key + ']') !== -1) {
return i;
}
}
return -1;
}
function extractDetail(line) {
// prova a estrarre un conteggio tipo "Cached 128 clients" o "34 analisi"
const m = line.match(/(\d+)\s+(clients|items|values|responsabili|analisi)/i);
if (m) return m[1] + ' elementi';
return undefined;
}
function startTimer() {
startedAt = Date.now();
timerId = setInterval(function() {
const s = Math.round((Date.now() - startedAt) / 1000);
progressElapsed.textContent = s + 's';
}, 1000);
}
function stopTimer() {
if (timerId) clearInterval(timerId);
timerId = null;
}
function resetUI() {
buildSteps();
currentStepIndex = -1;
setProgress(0);
logPre.textContent = '';
progressTitle.textContent = 'Aggiornamento in corso…';
progressElapsed.textContent = '0s';
resultBox.classList.remove('is-visible');
progressWrap.classList.add('is-visible');
}
function showResult(type, message) {
stopTimer();
progressBar.classList.remove('progress-bar-animated', 'progress-bar-striped');
resultAlert.className = 'alert d-flex align-items-center';
if (type === 'success') {
resultAlert.classList.add('alert-success');
resultIcon.className = 'bx bx-check-circle me-2 fs-4';
progressBar.classList.add('bg-success');
} else if (type === 'warning') {
resultAlert.classList.add('alert-warning');
resultIcon.className = 'bx bx-error me-2 fs-4';
progressBar.classList.add('bg-warning');
} else {
resultAlert.classList.add('alert-danger');
resultIcon.className = 'bx bx-x-circle me-2 fs-4';
progressBar.classList.add('bg-danger');
}
resultText.textContent = message;
resultBox.classList.add('is-visible');
btnStart.disabled = false;
includeAnalisi.disabled = false;
}
function run() {
btnStart.disabled = true;
includeAnalisi.disabled = true;
idleHint.style.display = 'none';
resetUI();
startTimer();
const analisiParam = includeAnalisi.checked ? '1' : '0';
const es = new EventSource(WARM_ENDPOINT + '?stream=1&analisi=' + analisiParam);
es.onmessage = function(e) {
const line = e.data;
logPre.textContent += line + '\n';
logPre.scrollTop = logPre.scrollHeight;
const idx = matchStep(line);
if (idx !== -1) {
advanceTo(idx, extractDetail(line));
}
};
es.addEventListener('busy', function(e) {
es.close();
showResult('warning', e.data ||
'Un aggiornamento è già in corso. Attendi qualche istante e riprova.');
});
es.addEventListener('done', function(e) {
es.close();
// completa tutti i passi
for (let i = 0; i < activeSteps.length; i++) markStep(i, 'done');
setProgress(100);
let elapsed = '';
try {
elapsed = JSON.parse(e.data).elapsed;
} catch (err) {}
// se nel log è comparsa la parola ERRORE, segnala come warning
if (logPre.textContent.indexOf('ERRORE') !== -1) {
showResult('warning',
'Aggiornamento terminato, ma alcuni dati potrebbero non essere stati caricati. ' +
'Controlla il dettaglio tecnico.');
} else {
progressTitle.textContent = 'Completato';
showResult('success',
'Dati aggiornati correttamente' +
(elapsed ? ' in ' + elapsed + ' secondi.' : '.') +
' Le nuove informazioni sono ora disponibili.');
}
});
es.onerror = function() {
// EventSource tenta il reconnect da solo: lo blocchiamo.
es.close();
stopTimer();
// Se avevamo già finito, il done è già scattato: non sovrascrivere.
if (!resultBox.classList.contains('is-visible')) {
showResult('error',
'Connessione interrotta durante l\'aggiornamento. ' +
'Verifica la rete e riprova.');
}
};
}
btnStart.addEventListener('click', run);
btnAgain.addEventListener('click', run);
toggleLog.addEventListener('click', function() {
logBox.classList.toggle('is-visible');
toggleLog.innerHTML = logBox.classList.contains('is-visible') ?
'<i class="bx bx-hide"></i> Nascondi dettaglio tecnico' :
'<i class="bx bx-code-alt"></i> Mostra dettaglio tecnico';
});
});
</script>
</body>
</html>
+4 -1
View File
@@ -14,7 +14,10 @@ if (PHP_VERSION_ID < 50600) {
echo $err;
}
}
throw new RuntimeException($err);
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
+4 -41
View File
@@ -26,23 +26,12 @@ use Composer\Semver\VersionParser;
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
@@ -320,24 +309,6 @@ class InstalledVersions
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
@@ -351,27 +322,19 @@ class InstalledVersions
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
@@ -387,7 +350,7 @@ class InstalledVersions
}
}
if (self::$installed !== array() && !$copiedLocalDir) {
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
-407
View File
@@ -184,196 +184,6 @@ return array(
'Brick\\Math\\Internal\\Calculator\\GmpCalculator' => $vendorDir . '/brick/math/src/Internal/Calculator/GmpCalculator.php',
'Brick\\Math\\Internal\\Calculator\\NativeCalculator' => $vendorDir . '/brick/math/src/Internal/Calculator/NativeCalculator.php',
'Brick\\Math\\RoundingMode' => $vendorDir . '/brick/math/src/RoundingMode.php',
'Cake\\Chronos\\Chronos' => $vendorDir . '/cakephp/chronos/src/Chronos.php',
'Cake\\Chronos\\ChronosDate' => $vendorDir . '/cakephp/chronos/src/ChronosDate.php',
'Cake\\Chronos\\ChronosDatePeriod' => $vendorDir . '/cakephp/chronos/src/ChronosDatePeriod.php',
'Cake\\Chronos\\ChronosInterval' => $vendorDir . '/cakephp/chronos/src/ChronosInterval.php',
'Cake\\Chronos\\ChronosPeriod' => $vendorDir . '/cakephp/chronos/src/ChronosPeriod.php',
'Cake\\Chronos\\ChronosTime' => $vendorDir . '/cakephp/chronos/src/ChronosTime.php',
'Cake\\Chronos\\ClockFactory' => $vendorDir . '/cakephp/chronos/src/ClockFactory.php',
'Cake\\Chronos\\DifferenceFormatter' => $vendorDir . '/cakephp/chronos/src/DifferenceFormatter.php',
'Cake\\Chronos\\DifferenceFormatterInterface' => $vendorDir . '/cakephp/chronos/src/DifferenceFormatterInterface.php',
'Cake\\Chronos\\FormattingTrait' => $vendorDir . '/cakephp/chronos/src/FormattingTrait.php',
'Cake\\Chronos\\Translator' => $vendorDir . '/cakephp/chronos/src/Translator.php',
'Cake\\Core\\App' => $vendorDir . '/cakephp/core/App.php',
'Cake\\Core\\Attribute\\Configure' => $vendorDir . '/cakephp/core/Attribute/Configure.php',
'Cake\\Core\\BasePlugin' => $vendorDir . '/cakephp/core/BasePlugin.php',
'Cake\\Core\\Configure' => $vendorDir . '/cakephp/core/Configure.php',
'Cake\\Core\\Configure\\ConfigEngineInterface' => $vendorDir . '/cakephp/core/Configure/ConfigEngineInterface.php',
'Cake\\Core\\Configure\\Engine\\IniConfig' => $vendorDir . '/cakephp/core/Configure/Engine/IniConfig.php',
'Cake\\Core\\Configure\\Engine\\JsonConfig' => $vendorDir . '/cakephp/core/Configure/Engine/JsonConfig.php',
'Cake\\Core\\Configure\\Engine\\PhpConfig' => $vendorDir . '/cakephp/core/Configure/Engine/PhpConfig.php',
'Cake\\Core\\Configure\\FileConfigTrait' => $vendorDir . '/cakephp/core/Configure/FileConfigTrait.php',
'Cake\\Core\\ConsoleApplicationInterface' => $vendorDir . '/cakephp/core/ConsoleApplicationInterface.php',
'Cake\\Core\\Container' => $vendorDir . '/cakephp/core/Container.php',
'Cake\\Core\\ContainerApplicationInterface' => $vendorDir . '/cakephp/core/ContainerApplicationInterface.php',
'Cake\\Core\\ContainerInterface' => $vendorDir . '/cakephp/core/ContainerInterface.php',
'Cake\\Core\\ConventionsTrait' => $vendorDir . '/cakephp/core/ConventionsTrait.php',
'Cake\\Core\\EventAwareApplicationInterface' => $vendorDir . '/cakephp/core/EventAwareApplicationInterface.php',
'Cake\\Core\\Exception\\CakeException' => $vendorDir . '/cakephp/core/Exception/CakeException.php',
'Cake\\Core\\Exception\\HttpErrorCodeInterface' => $vendorDir . '/cakephp/core/Exception/HttpErrorCodeInterface.php',
'Cake\\Core\\Exception\\MissingPluginException' => $vendorDir . '/cakephp/core/Exception/MissingPluginException.php',
'Cake\\Core\\HttpApplicationInterface' => $vendorDir . '/cakephp/core/HttpApplicationInterface.php',
'Cake\\Core\\InstanceConfigTrait' => $vendorDir . '/cakephp/core/InstanceConfigTrait.php',
'Cake\\Core\\ObjectRegistry' => $vendorDir . '/cakephp/core/ObjectRegistry.php',
'Cake\\Core\\Plugin' => $vendorDir . '/cakephp/core/Plugin.php',
'Cake\\Core\\PluginApplicationInterface' => $vendorDir . '/cakephp/core/PluginApplicationInterface.php',
'Cake\\Core\\PluginCollection' => $vendorDir . '/cakephp/core/PluginCollection.php',
'Cake\\Core\\PluginConfig' => $vendorDir . '/cakephp/core/PluginConfig.php',
'Cake\\Core\\PluginInterface' => $vendorDir . '/cakephp/core/PluginInterface.php',
'Cake\\Core\\Retry\\CommandRetry' => $vendorDir . '/cakephp/core/Retry/CommandRetry.php',
'Cake\\Core\\Retry\\RetryStrategyInterface' => $vendorDir . '/cakephp/core/Retry/RetryStrategyInterface.php',
'Cake\\Core\\ServiceConfig' => $vendorDir . '/cakephp/core/ServiceConfig.php',
'Cake\\Core\\ServiceProvider' => $vendorDir . '/cakephp/core/ServiceProvider.php',
'Cake\\Core\\StaticConfigTrait' => $vendorDir . '/cakephp/core/StaticConfigTrait.php',
'Cake\\Core\\TestSuite\\ContainerStubTrait' => $vendorDir . '/cakephp/core/TestSuite/ContainerStubTrait.php',
'Cake\\Database\\Connection' => $vendorDir . '/cakephp/database/Connection.php',
'Cake\\Database\\ConstraintsInterface' => $vendorDir . '/cakephp/database/ConstraintsInterface.php',
'Cake\\Database\\Driver' => $vendorDir . '/cakephp/database/Driver.php',
'Cake\\Database\\DriverFeatureEnum' => $vendorDir . '/cakephp/database/DriverFeatureEnum.php',
'Cake\\Database\\Driver\\Mysql' => $vendorDir . '/cakephp/database/Driver/Mysql.php',
'Cake\\Database\\Driver\\Postgres' => $vendorDir . '/cakephp/database/Driver/Postgres.php',
'Cake\\Database\\Driver\\Sqlite' => $vendorDir . '/cakephp/database/Driver/Sqlite.php',
'Cake\\Database\\Driver\\Sqlserver' => $vendorDir . '/cakephp/database/Driver/Sqlserver.php',
'Cake\\Database\\Driver\\TupleComparisonTranslatorTrait' => $vendorDir . '/cakephp/database/Driver/TupleComparisonTranslatorTrait.php',
'Cake\\Database\\Exception\\DatabaseException' => $vendorDir . '/cakephp/database/Exception/DatabaseException.php',
'Cake\\Database\\Exception\\MissingConnectionException' => $vendorDir . '/cakephp/database/Exception/MissingConnectionException.php',
'Cake\\Database\\Exception\\MissingDriverException' => $vendorDir . '/cakephp/database/Exception/MissingDriverException.php',
'Cake\\Database\\Exception\\MissingExtensionException' => $vendorDir . '/cakephp/database/Exception/MissingExtensionException.php',
'Cake\\Database\\Exception\\NestedTransactionRollbackException' => $vendorDir . '/cakephp/database/Exception/NestedTransactionRollbackException.php',
'Cake\\Database\\Exception\\QueryException' => $vendorDir . '/cakephp/database/Exception/QueryException.php',
'Cake\\Database\\ExpressionInterface' => $vendorDir . '/cakephp/database/ExpressionInterface.php',
'Cake\\Database\\Expression\\AggregateExpression' => $vendorDir . '/cakephp/database/Expression/AggregateExpression.php',
'Cake\\Database\\Expression\\BetweenExpression' => $vendorDir . '/cakephp/database/Expression/BetweenExpression.php',
'Cake\\Database\\Expression\\CaseExpressionTrait' => $vendorDir . '/cakephp/database/Expression/CaseExpressionTrait.php',
'Cake\\Database\\Expression\\CaseStatementExpression' => $vendorDir . '/cakephp/database/Expression/CaseStatementExpression.php',
'Cake\\Database\\Expression\\CommonTableExpression' => $vendorDir . '/cakephp/database/Expression/CommonTableExpression.php',
'Cake\\Database\\Expression\\ComparisonExpression' => $vendorDir . '/cakephp/database/Expression/ComparisonExpression.php',
'Cake\\Database\\Expression\\FieldInterface' => $vendorDir . '/cakephp/database/Expression/FieldInterface.php',
'Cake\\Database\\Expression\\FieldTrait' => $vendorDir . '/cakephp/database/Expression/FieldTrait.php',
'Cake\\Database\\Expression\\FunctionExpression' => $vendorDir . '/cakephp/database/Expression/FunctionExpression.php',
'Cake\\Database\\Expression\\IdentifierExpression' => $vendorDir . '/cakephp/database/Expression/IdentifierExpression.php',
'Cake\\Database\\Expression\\OrderByExpression' => $vendorDir . '/cakephp/database/Expression/OrderByExpression.php',
'Cake\\Database\\Expression\\OrderClauseExpression' => $vendorDir . '/cakephp/database/Expression/OrderClauseExpression.php',
'Cake\\Database\\Expression\\QueryExpression' => $vendorDir . '/cakephp/database/Expression/QueryExpression.php',
'Cake\\Database\\Expression\\StringExpression' => $vendorDir . '/cakephp/database/Expression/StringExpression.php',
'Cake\\Database\\Expression\\TupleComparison' => $vendorDir . '/cakephp/database/Expression/TupleComparison.php',
'Cake\\Database\\Expression\\UnaryExpression' => $vendorDir . '/cakephp/database/Expression/UnaryExpression.php',
'Cake\\Database\\Expression\\ValuesExpression' => $vendorDir . '/cakephp/database/Expression/ValuesExpression.php',
'Cake\\Database\\Expression\\WhenThenExpression' => $vendorDir . '/cakephp/database/Expression/WhenThenExpression.php',
'Cake\\Database\\Expression\\WindowExpression' => $vendorDir . '/cakephp/database/Expression/WindowExpression.php',
'Cake\\Database\\Expression\\WindowInterface' => $vendorDir . '/cakephp/database/Expression/WindowInterface.php',
'Cake\\Database\\FieldTypeConverter' => $vendorDir . '/cakephp/database/FieldTypeConverter.php',
'Cake\\Database\\FunctionsBuilder' => $vendorDir . '/cakephp/database/FunctionsBuilder.php',
'Cake\\Database\\IdentifierQuoter' => $vendorDir . '/cakephp/database/IdentifierQuoter.php',
'Cake\\Database\\Log\\LoggedQuery' => $vendorDir . '/cakephp/database/Log/LoggedQuery.php',
'Cake\\Database\\Log\\QueryLogger' => $vendorDir . '/cakephp/database/Log/QueryLogger.php',
'Cake\\Database\\PostgresCompiler' => $vendorDir . '/cakephp/database/PostgresCompiler.php',
'Cake\\Database\\Query' => $vendorDir . '/cakephp/database/Query.php',
'Cake\\Database\\QueryCompiler' => $vendorDir . '/cakephp/database/QueryCompiler.php',
'Cake\\Database\\Query\\DeleteQuery' => $vendorDir . '/cakephp/database/Query/DeleteQuery.php',
'Cake\\Database\\Query\\InsertQuery' => $vendorDir . '/cakephp/database/Query/InsertQuery.php',
'Cake\\Database\\Query\\QueryFactory' => $vendorDir . '/cakephp/database/Query/QueryFactory.php',
'Cake\\Database\\Query\\SelectQuery' => $vendorDir . '/cakephp/database/Query/SelectQuery.php',
'Cake\\Database\\Query\\UpdateQuery' => $vendorDir . '/cakephp/database/Query/UpdateQuery.php',
'Cake\\Database\\Retry\\ErrorCodeWaitStrategy' => $vendorDir . '/cakephp/database/Retry/ErrorCodeWaitStrategy.php',
'Cake\\Database\\Retry\\ReconnectStrategy' => $vendorDir . '/cakephp/database/Retry/ReconnectStrategy.php',
'Cake\\Database\\SchemaCache' => $vendorDir . '/cakephp/database/SchemaCache.php',
'Cake\\Database\\Schema\\CachedCollection' => $vendorDir . '/cakephp/database/Schema/CachedCollection.php',
'Cake\\Database\\Schema\\CheckConstraint' => $vendorDir . '/cakephp/database/Schema/CheckConstraint.php',
'Cake\\Database\\Schema\\Collection' => $vendorDir . '/cakephp/database/Schema/Collection.php',
'Cake\\Database\\Schema\\CollectionInterface' => $vendorDir . '/cakephp/database/Schema/CollectionInterface.php',
'Cake\\Database\\Schema\\Column' => $vendorDir . '/cakephp/database/Schema/Column.php',
'Cake\\Database\\Schema\\Constraint' => $vendorDir . '/cakephp/database/Schema/Constraint.php',
'Cake\\Database\\Schema\\ForeignKey' => $vendorDir . '/cakephp/database/Schema/ForeignKey.php',
'Cake\\Database\\Schema\\Index' => $vendorDir . '/cakephp/database/Schema/Index.php',
'Cake\\Database\\Schema\\MysqlSchemaDialect' => $vendorDir . '/cakephp/database/Schema/MysqlSchemaDialect.php',
'Cake\\Database\\Schema\\PostgresSchemaDialect' => $vendorDir . '/cakephp/database/Schema/PostgresSchemaDialect.php',
'Cake\\Database\\Schema\\SchemaDialect' => $vendorDir . '/cakephp/database/Schema/SchemaDialect.php',
'Cake\\Database\\Schema\\SqlGeneratorInterface' => $vendorDir . '/cakephp/database/Schema/SqlGeneratorInterface.php',
'Cake\\Database\\Schema\\SqliteSchemaDialect' => $vendorDir . '/cakephp/database/Schema/SqliteSchemaDialect.php',
'Cake\\Database\\Schema\\SqlserverSchemaDialect' => $vendorDir . '/cakephp/database/Schema/SqlserverSchemaDialect.php',
'Cake\\Database\\Schema\\TableSchema' => $vendorDir . '/cakephp/database/Schema/TableSchema.php',
'Cake\\Database\\Schema\\TableSchemaInterface' => $vendorDir . '/cakephp/database/Schema/TableSchemaInterface.php',
'Cake\\Database\\Schema\\UniqueKey' => $vendorDir . '/cakephp/database/Schema/UniqueKey.php',
'Cake\\Database\\SqlserverCompiler' => $vendorDir . '/cakephp/database/SqlserverCompiler.php',
'Cake\\Database\\StatementInterface' => $vendorDir . '/cakephp/database/StatementInterface.php',
'Cake\\Database\\Statement\\SqliteStatement' => $vendorDir . '/cakephp/database/Statement/SqliteStatement.php',
'Cake\\Database\\Statement\\SqlserverStatement' => $vendorDir . '/cakephp/database/Statement/SqlserverStatement.php',
'Cake\\Database\\Statement\\Statement' => $vendorDir . '/cakephp/database/Statement/Statement.php',
'Cake\\Database\\TypeFactory' => $vendorDir . '/cakephp/database/TypeFactory.php',
'Cake\\Database\\TypeInterface' => $vendorDir . '/cakephp/database/TypeInterface.php',
'Cake\\Database\\TypeMap' => $vendorDir . '/cakephp/database/TypeMap.php',
'Cake\\Database\\TypeMapTrait' => $vendorDir . '/cakephp/database/TypeMapTrait.php',
'Cake\\Database\\Type\\BaseType' => $vendorDir . '/cakephp/database/Type/BaseType.php',
'Cake\\Database\\Type\\BatchCastingInterface' => $vendorDir . '/cakephp/database/Type/BatchCastingInterface.php',
'Cake\\Database\\Type\\BinaryType' => $vendorDir . '/cakephp/database/Type/BinaryType.php',
'Cake\\Database\\Type\\BinaryUuidType' => $vendorDir . '/cakephp/database/Type/BinaryUuidType.php',
'Cake\\Database\\Type\\BoolType' => $vendorDir . '/cakephp/database/Type/BoolType.php',
'Cake\\Database\\Type\\ColumnSchemaAwareInterface' => $vendorDir . '/cakephp/database/Type/ColumnSchemaAwareInterface.php',
'Cake\\Database\\Type\\DateTimeFractionalType' => $vendorDir . '/cakephp/database/Type/DateTimeFractionalType.php',
'Cake\\Database\\Type\\DateTimeTimezoneType' => $vendorDir . '/cakephp/database/Type/DateTimeTimezoneType.php',
'Cake\\Database\\Type\\DateTimeType' => $vendorDir . '/cakephp/database/Type/DateTimeType.php',
'Cake\\Database\\Type\\DateType' => $vendorDir . '/cakephp/database/Type/DateType.php',
'Cake\\Database\\Type\\DecimalType' => $vendorDir . '/cakephp/database/Type/DecimalType.php',
'Cake\\Database\\Type\\EnumLabelInterface' => $vendorDir . '/cakephp/database/Type/EnumLabelInterface.php',
'Cake\\Database\\Type\\EnumType' => $vendorDir . '/cakephp/database/Type/EnumType.php',
'Cake\\Database\\Type\\ExpressionTypeCasterTrait' => $vendorDir . '/cakephp/database/Type/ExpressionTypeCasterTrait.php',
'Cake\\Database\\Type\\ExpressionTypeInterface' => $vendorDir . '/cakephp/database/Type/ExpressionTypeInterface.php',
'Cake\\Database\\Type\\FloatType' => $vendorDir . '/cakephp/database/Type/FloatType.php',
'Cake\\Database\\Type\\IntegerType' => $vendorDir . '/cakephp/database/Type/IntegerType.php',
'Cake\\Database\\Type\\JsonType' => $vendorDir . '/cakephp/database/Type/JsonType.php',
'Cake\\Database\\Type\\OptionalConvertInterface' => $vendorDir . '/cakephp/database/Type/OptionalConvertInterface.php',
'Cake\\Database\\Type\\StringType' => $vendorDir . '/cakephp/database/Type/StringType.php',
'Cake\\Database\\Type\\TimeType' => $vendorDir . '/cakephp/database/Type/TimeType.php',
'Cake\\Database\\Type\\UuidType' => $vendorDir . '/cakephp/database/Type/UuidType.php',
'Cake\\Database\\TypedResultInterface' => $vendorDir . '/cakephp/database/TypedResultInterface.php',
'Cake\\Database\\TypedResultTrait' => $vendorDir . '/cakephp/database/TypedResultTrait.php',
'Cake\\Database\\ValueBinder' => $vendorDir . '/cakephp/database/ValueBinder.php',
'Cake\\Datasource\\ConnectionInterface' => $vendorDir . '/cakephp/datasource/ConnectionInterface.php',
'Cake\\Datasource\\ConnectionManager' => $vendorDir . '/cakephp/datasource/ConnectionManager.php',
'Cake\\Datasource\\ConnectionRegistry' => $vendorDir . '/cakephp/datasource/ConnectionRegistry.php',
'Cake\\Datasource\\EntityInterface' => $vendorDir . '/cakephp/datasource/EntityInterface.php',
'Cake\\Datasource\\EntityTrait' => $vendorDir . '/cakephp/datasource/EntityTrait.php',
'Cake\\Datasource\\Exception\\InvalidPrimaryKeyException' => $vendorDir . '/cakephp/datasource/Exception/InvalidPrimaryKeyException.php',
'Cake\\Datasource\\Exception\\MissingDatasourceConfigException' => $vendorDir . '/cakephp/datasource/Exception/MissingDatasourceConfigException.php',
'Cake\\Datasource\\Exception\\MissingDatasourceException' => $vendorDir . '/cakephp/datasource/Exception/MissingDatasourceException.php',
'Cake\\Datasource\\Exception\\MissingModelException' => $vendorDir . '/cakephp/datasource/Exception/MissingModelException.php',
'Cake\\Datasource\\Exception\\MissingPropertyException' => $vendorDir . '/cakephp/datasource/Exception/MissingPropertyException.php',
'Cake\\Datasource\\Exception\\RecordNotFoundException' => $vendorDir . '/cakephp/datasource/Exception/RecordNotFoundException.php',
'Cake\\Datasource\\FactoryLocator' => $vendorDir . '/cakephp/datasource/FactoryLocator.php',
'Cake\\Datasource\\FixtureInterface' => $vendorDir . '/cakephp/datasource/FixtureInterface.php',
'Cake\\Datasource\\InvalidPropertyInterface' => $vendorDir . '/cakephp/datasource/InvalidPropertyInterface.php',
'Cake\\Datasource\\Locator\\AbstractLocator' => $vendorDir . '/cakephp/datasource/Locator/AbstractLocator.php',
'Cake\\Datasource\\Locator\\LocatorInterface' => $vendorDir . '/cakephp/datasource/Locator/LocatorInterface.php',
'Cake\\Datasource\\ModelAwareTrait' => $vendorDir . '/cakephp/datasource/ModelAwareTrait.php',
'Cake\\Datasource\\Paging\\Exception\\PageOutOfBoundsException' => $vendorDir . '/cakephp/datasource/Paging/Exception/PageOutOfBoundsException.php',
'Cake\\Datasource\\Paging\\NumericPaginator' => $vendorDir . '/cakephp/datasource/Paging/NumericPaginator.php',
'Cake\\Datasource\\Paging\\PaginatedInterface' => $vendorDir . '/cakephp/datasource/Paging/PaginatedInterface.php',
'Cake\\Datasource\\Paging\\PaginatedResultSet' => $vendorDir . '/cakephp/datasource/Paging/PaginatedResultSet.php',
'Cake\\Datasource\\Paging\\PaginatorInterface' => $vendorDir . '/cakephp/datasource/Paging/PaginatorInterface.php',
'Cake\\Datasource\\Paging\\SimplePaginator' => $vendorDir . '/cakephp/datasource/Paging/SimplePaginator.php',
'Cake\\Datasource\\Paging\\SortField' => $vendorDir . '/cakephp/datasource/Paging/SortField.php',
'Cake\\Datasource\\Paging\\SortableFieldsBuilder' => $vendorDir . '/cakephp/datasource/Paging/SortableFieldsBuilder.php',
'Cake\\Datasource\\QueryCacher' => $vendorDir . '/cakephp/datasource/QueryCacher.php',
'Cake\\Datasource\\QueryInterface' => $vendorDir . '/cakephp/datasource/QueryInterface.php',
'Cake\\Datasource\\RepositoryInterface' => $vendorDir . '/cakephp/datasource/RepositoryInterface.php',
'Cake\\Datasource\\ResultSetDecorator' => $vendorDir . '/cakephp/datasource/ResultSetDecorator.php',
'Cake\\Datasource\\ResultSetInterface' => $vendorDir . '/cakephp/datasource/ResultSetInterface.php',
'Cake\\Datasource\\RuleInvoker' => $vendorDir . '/cakephp/datasource/RuleInvoker.php',
'Cake\\Datasource\\RulesAwareTrait' => $vendorDir . '/cakephp/datasource/RulesAwareTrait.php',
'Cake\\Datasource\\RulesChecker' => $vendorDir . '/cakephp/datasource/RulesChecker.php',
'Cake\\Datasource\\SchemaInterface' => $vendorDir . '/cakephp/datasource/SchemaInterface.php',
'Cake\\Utility\\CookieCryptTrait' => $vendorDir . '/cakephp/utility/CookieCryptTrait.php',
'Cake\\Utility\\Crypto\\OpenSsl' => $vendorDir . '/cakephp/utility/Crypto/OpenSsl.php',
'Cake\\Utility\\Exception\\XmlException' => $vendorDir . '/cakephp/utility/Exception/XmlException.php',
'Cake\\Utility\\Filesystem' => $vendorDir . '/cakephp/utility/Filesystem.php',
'Cake\\Utility\\Hash' => $vendorDir . '/cakephp/utility/Hash.php',
'Cake\\Utility\\Inflector' => $vendorDir . '/cakephp/utility/Inflector.php',
'Cake\\Utility\\MergeVariablesTrait' => $vendorDir . '/cakephp/utility/MergeVariablesTrait.php',
'Cake\\Utility\\Security' => $vendorDir . '/cakephp/utility/Security.php',
'Cake\\Utility\\Text' => $vendorDir . '/cakephp/utility/Text.php',
'Cake\\Utility\\Xml' => $vendorDir . '/cakephp/utility/Xml.php',
'Carbon\\AbstractTranslator' => $vendorDir . '/nesbot/carbon/src/Carbon/AbstractTranslator.php',
'Carbon\\Callback' => $vendorDir . '/nesbot/carbon/src/Carbon/Callback.php',
'Carbon\\Carbon' => $vendorDir . '/nesbot/carbon/src/Carbon/Carbon.php',
@@ -3603,55 +3413,6 @@ return array(
'League\\Config\\MutableConfigurationInterface' => $vendorDir . '/league/config/src/MutableConfigurationInterface.php',
'League\\Config\\ReadOnlyConfiguration' => $vendorDir . '/league/config/src/ReadOnlyConfiguration.php',
'League\\Config\\SchemaBuilderInterface' => $vendorDir . '/league/config/src/SchemaBuilderInterface.php',
'League\\Container\\Argument\\ArgumentInterface' => $vendorDir . '/league/container/src/Argument/ArgumentInterface.php',
'League\\Container\\Argument\\ArgumentReflectorInterface' => $vendorDir . '/league/container/src/Argument/ArgumentReflectorInterface.php',
'League\\Container\\Argument\\ArgumentReflectorTrait' => $vendorDir . '/league/container/src/Argument/ArgumentReflectorTrait.php',
'League\\Container\\Argument\\ArgumentResolverInterface' => $vendorDir . '/league/container/src/Argument/ArgumentResolverInterface.php',
'League\\Container\\Argument\\ArgumentResolverTrait' => $vendorDir . '/league/container/src/Argument/ArgumentResolverTrait.php',
'League\\Container\\Argument\\DefaultValueArgument' => $vendorDir . '/league/container/src/Argument/DefaultValueArgument.php',
'League\\Container\\Argument\\DefaultValueInterface' => $vendorDir . '/league/container/src/Argument/DefaultValueInterface.php',
'League\\Container\\Argument\\LiteralArgument' => $vendorDir . '/league/container/src/Argument/LiteralArgument.php',
'League\\Container\\Argument\\LiteralArgumentInterface' => $vendorDir . '/league/container/src/Argument/LiteralArgumentInterface.php',
'League\\Container\\Argument\\Literal\\ArrayArgument' => $vendorDir . '/league/container/src/Argument/Literal/ArrayArgument.php',
'League\\Container\\Argument\\Literal\\BooleanArgument' => $vendorDir . '/league/container/src/Argument/Literal/BooleanArgument.php',
'League\\Container\\Argument\\Literal\\CallableArgument' => $vendorDir . '/league/container/src/Argument/Literal/CallableArgument.php',
'League\\Container\\Argument\\Literal\\FloatArgument' => $vendorDir . '/league/container/src/Argument/Literal/FloatArgument.php',
'League\\Container\\Argument\\Literal\\IntegerArgument' => $vendorDir . '/league/container/src/Argument/Literal/IntegerArgument.php',
'League\\Container\\Argument\\Literal\\ObjectArgument' => $vendorDir . '/league/container/src/Argument/Literal/ObjectArgument.php',
'League\\Container\\Argument\\Literal\\StringArgument' => $vendorDir . '/league/container/src/Argument/Literal/StringArgument.php',
'League\\Container\\Argument\\ResolvableArgument' => $vendorDir . '/league/container/src/Argument/ResolvableArgument.php',
'League\\Container\\Argument\\ResolvableArgumentInterface' => $vendorDir . '/league/container/src/Argument/ResolvableArgumentInterface.php',
'League\\Container\\Attribute\\AttributeInterface' => $vendorDir . '/league/container/src/Attribute/AttributeInterface.php',
'League\\Container\\Attribute\\Inject' => $vendorDir . '/league/container/src/Attribute/Inject.php',
'League\\Container\\Attribute\\Resolve' => $vendorDir . '/league/container/src/Attribute/Resolve.php',
'League\\Container\\Container' => $vendorDir . '/league/container/src/Container.php',
'League\\Container\\ContainerAwareInterface' => $vendorDir . '/league/container/src/ContainerAwareInterface.php',
'League\\Container\\ContainerAwareTrait' => $vendorDir . '/league/container/src/ContainerAwareTrait.php',
'League\\Container\\DefinitionContainerInterface' => $vendorDir . '/league/container/src/DefinitionContainerInterface.php',
'League\\Container\\Definition\\Definition' => $vendorDir . '/league/container/src/Definition/Definition.php',
'League\\Container\\Definition\\DefinitionAggregate' => $vendorDir . '/league/container/src/Definition/DefinitionAggregate.php',
'League\\Container\\Definition\\DefinitionAggregateInterface' => $vendorDir . '/league/container/src/Definition/DefinitionAggregateInterface.php',
'League\\Container\\Definition\\DefinitionInterface' => $vendorDir . '/league/container/src/Definition/DefinitionInterface.php',
'League\\Container\\Event\\BeforeResolveEvent' => $vendorDir . '/league/container/src/Event/BeforeResolveEvent.php',
'League\\Container\\Event\\ContainerEvent' => $vendorDir . '/league/container/src/Event/ContainerEvent.php',
'League\\Container\\Event\\DefinitionResolvedEvent' => $vendorDir . '/league/container/src/Event/DefinitionResolvedEvent.php',
'League\\Container\\Event\\EventAwareTrait' => $vendorDir . '/league/container/src/Event/EventAwareTrait.php',
'League\\Container\\Event\\EventDispatcher' => $vendorDir . '/league/container/src/Event/EventDispatcher.php',
'League\\Container\\Event\\EventFilter' => $vendorDir . '/league/container/src/Event/EventFilter.php',
'League\\Container\\Event\\OnDefineEvent' => $vendorDir . '/league/container/src/Event/OnDefineEvent.php',
'League\\Container\\Event\\ServiceResolvedEvent' => $vendorDir . '/league/container/src/Event/ServiceResolvedEvent.php',
'League\\Container\\Exception\\ContainerException' => $vendorDir . '/league/container/src/Exception/ContainerException.php',
'League\\Container\\Exception\\NotFoundException' => $vendorDir . '/league/container/src/Exception/NotFoundException.php',
'League\\Container\\Inflector\\Inflector' => $vendorDir . '/league/container/src/Inflector/Inflector.php',
'League\\Container\\Inflector\\InflectorAggregate' => $vendorDir . '/league/container/src/Inflector/InflectorAggregate.php',
'League\\Container\\Inflector\\InflectorAggregateInterface' => $vendorDir . '/league/container/src/Inflector/InflectorAggregateInterface.php',
'League\\Container\\Inflector\\InflectorInterface' => $vendorDir . '/league/container/src/Inflector/InflectorInterface.php',
'League\\Container\\ReflectionContainer' => $vendorDir . '/league/container/src/ReflectionContainer.php',
'League\\Container\\ServiceProvider\\AbstractServiceProvider' => $vendorDir . '/league/container/src/ServiceProvider/AbstractServiceProvider.php',
'League\\Container\\ServiceProvider\\BootableServiceProviderInterface' => $vendorDir . '/league/container/src/ServiceProvider/BootableServiceProviderInterface.php',
'League\\Container\\ServiceProvider\\ServiceProviderAggregate' => $vendorDir . '/league/container/src/ServiceProvider/ServiceProviderAggregate.php',
'League\\Container\\ServiceProvider\\ServiceProviderAggregateInterface' => $vendorDir . '/league/container/src/ServiceProvider/ServiceProviderAggregateInterface.php',
'League\\Container\\ServiceProvider\\ServiceProviderInterface' => $vendorDir . '/league/container/src/ServiceProvider/ServiceProviderInterface.php',
'League\\Flysystem\\CalculateChecksumFromStream' => $vendorDir . '/league/flysystem/src/CalculateChecksumFromStream.php',
'League\\Flysystem\\ChecksumAlgoIsNotSupported' => $vendorDir . '/league/flysystem/src/ChecksumAlgoIsNotSupported.php',
'League\\Flysystem\\ChecksumProvider' => $vendorDir . '/league/flysystem/src/ChecksumProvider.php',
@@ -5160,76 +4921,6 @@ return array(
'PharIo\\Version\\VersionConstraintParser' => $vendorDir . '/phar-io/version/src/VersionConstraintParser.php',
'PharIo\\Version\\VersionConstraintValue' => $vendorDir . '/phar-io/version/src/VersionConstraintValue.php',
'PharIo\\Version\\VersionNumber' => $vendorDir . '/phar-io/version/src/VersionNumber.php',
'Phinx\\Config\\Config' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/Config.php',
'Phinx\\Config\\ConfigInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php',
'Phinx\\Config\\FeatureFlags' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/FeatureFlags.php',
'Phinx\\Config\\NamespaceAwareInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php',
'Phinx\\Config\\NamespaceAwareTrait' => $vendorDir . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php',
'Phinx\\Console\\Command\\AbstractCommand' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php',
'Phinx\\Console\\Command\\Breakpoint' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php',
'Phinx\\Console\\Command\\Create' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Create.php',
'Phinx\\Console\\Command\\Init' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Init.php',
'Phinx\\Console\\Command\\ListAliases' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/ListAliases.php',
'Phinx\\Console\\Command\\Migrate' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php',
'Phinx\\Console\\Command\\Rollback' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php',
'Phinx\\Console\\Command\\SeedCreate' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php',
'Phinx\\Console\\Command\\SeedRun' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php',
'Phinx\\Console\\Command\\Status' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Status.php',
'Phinx\\Console\\Command\\Test' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/Command/Test.php',
'Phinx\\Console\\PhinxApplication' => $vendorDir . '/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php',
'Phinx\\Db\\Action\\Action' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/Action.php',
'Phinx\\Db\\Action\\AddColumn' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/AddColumn.php',
'Phinx\\Db\\Action\\AddForeignKey' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/AddForeignKey.php',
'Phinx\\Db\\Action\\AddIndex' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/AddIndex.php',
'Phinx\\Db\\Action\\ChangeColumn' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/ChangeColumn.php',
'Phinx\\Db\\Action\\ChangeComment' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/ChangeComment.php',
'Phinx\\Db\\Action\\ChangePrimaryKey' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/ChangePrimaryKey.php',
'Phinx\\Db\\Action\\CreateTable' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/CreateTable.php',
'Phinx\\Db\\Action\\DropForeignKey' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/DropForeignKey.php',
'Phinx\\Db\\Action\\DropIndex' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/DropIndex.php',
'Phinx\\Db\\Action\\DropTable' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/DropTable.php',
'Phinx\\Db\\Action\\RemoveColumn' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/RemoveColumn.php',
'Phinx\\Db\\Action\\RenameColumn' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/RenameColumn.php',
'Phinx\\Db\\Action\\RenameTable' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Action/RenameTable.php',
'Phinx\\Db\\Adapter\\AbstractAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AbstractAdapter.php',
'Phinx\\Db\\Adapter\\AdapterFactory' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php',
'Phinx\\Db\\Adapter\\AdapterInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php',
'Phinx\\Db\\Adapter\\AdapterWrapper' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php',
'Phinx\\Db\\Adapter\\DirectActionInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/DirectActionInterface.php',
'Phinx\\Db\\Adapter\\MysqlAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php',
'Phinx\\Db\\Adapter\\PdoAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php',
'Phinx\\Db\\Adapter\\PostgresAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php',
'Phinx\\Db\\Adapter\\ProxyAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php',
'Phinx\\Db\\Adapter\\SQLiteAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php',
'Phinx\\Db\\Adapter\\SqlServerAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php',
'Phinx\\Db\\Adapter\\TablePrefixAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php',
'Phinx\\Db\\Adapter\\TimedOutputAdapter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php',
'Phinx\\Db\\Adapter\\UnsupportedColumnTypeException' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/UnsupportedColumnTypeException.php',
'Phinx\\Db\\Adapter\\WrapperInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php',
'Phinx\\Db\\Plan\\AlterTable' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Plan/AlterTable.php',
'Phinx\\Db\\Plan\\Intent' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Plan/Intent.php',
'Phinx\\Db\\Plan\\NewTable' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Plan/NewTable.php',
'Phinx\\Db\\Plan\\Plan' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Plan/Plan.php',
'Phinx\\Db\\Plan\\Solver\\ActionSplitter' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Plan/Solver/ActionSplitter.php',
'Phinx\\Db\\Table' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table.php',
'Phinx\\Db\\Table\\Column' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/Column.php',
'Phinx\\Db\\Table\\ForeignKey' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php',
'Phinx\\Db\\Table\\Index' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/Index.php',
'Phinx\\Db\\Table\\Table' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Table/Table.php',
'Phinx\\Db\\Util\\AlterInstructions' => $vendorDir . '/robmorgan/phinx/src/Phinx/Db/Util/AlterInstructions.php',
'Phinx\\Migration\\AbstractMigration' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php',
'Phinx\\Migration\\AbstractTemplateCreation' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php',
'Phinx\\Migration\\CreationInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php',
'Phinx\\Migration\\IrreversibleMigrationException' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php',
'Phinx\\Migration\\Manager' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/Manager.php',
'Phinx\\Migration\\Manager\\Environment' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php',
'Phinx\\Migration\\MigrationInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Migration/MigrationInterface.php',
'Phinx\\Seed\\AbstractSeed' => $vendorDir . '/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php',
'Phinx\\Seed\\SeedInterface' => $vendorDir . '/robmorgan/phinx/src/Phinx/Seed/SeedInterface.php',
'Phinx\\Util\\Expression' => $vendorDir . '/robmorgan/phinx/src/Phinx/Util/Expression.php',
'Phinx\\Util\\Literal' => $vendorDir . '/robmorgan/phinx/src/Phinx/Util/Literal.php',
'Phinx\\Util\\Util' => $vendorDir . '/robmorgan/phinx/src/Phinx/Util/Util.php',
'Phinx\\Wrapper\\TextWrapper' => $vendorDir . '/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php',
'PhpOffice\\PhpSpreadsheet\\Calculation\\ArrayEnabled' => $vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php',
'PhpOffice\\PhpSpreadsheet\\Calculation\\BinaryComparison' => $vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php',
'PhpOffice\\PhpSpreadsheet\\Calculation\\Calculation' => $vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php',
@@ -6970,96 +6661,6 @@ return array(
'Symfony\\Component\\Clock\\MonotonicClock' => $vendorDir . '/symfony/clock/MonotonicClock.php',
'Symfony\\Component\\Clock\\NativeClock' => $vendorDir . '/symfony/clock/NativeClock.php',
'Symfony\\Component\\Clock\\Test\\ClockSensitiveTrait' => $vendorDir . '/symfony/clock/Test/ClockSensitiveTrait.php',
'Symfony\\Component\\Config\\Builder\\ClassBuilder' => $vendorDir . '/symfony/config/Builder/ClassBuilder.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGenerator.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderInterface' => $vendorDir . '/symfony/config/Builder/ConfigBuilderInterface.php',
'Symfony\\Component\\Config\\Builder\\Method' => $vendorDir . '/symfony/config/Builder/Method.php',
'Symfony\\Component\\Config\\Builder\\Property' => $vendorDir . '/symfony/config/Builder/Property.php',
'Symfony\\Component\\Config\\ConfigCache' => $vendorDir . '/symfony/config/ConfigCache.php',
'Symfony\\Component\\Config\\ConfigCacheFactory' => $vendorDir . '/symfony/config/ConfigCacheFactory.php',
'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => $vendorDir . '/symfony/config/ConfigCacheFactoryInterface.php',
'Symfony\\Component\\Config\\ConfigCacheInterface' => $vendorDir . '/symfony/config/ConfigCacheInterface.php',
'Symfony\\Component\\Config\\Definition\\ArrayNode' => $vendorDir . '/symfony/config/Definition/ArrayNode.php',
'Symfony\\Component\\Config\\Definition\\ArrayShapeGenerator' => $vendorDir . '/symfony/config/Definition/ArrayShapeGenerator.php',
'Symfony\\Component\\Config\\Definition\\BaseNode' => $vendorDir . '/symfony/config/Definition/BaseNode.php',
'Symfony\\Component\\Config\\Definition\\BooleanNode' => $vendorDir . '/symfony/config/Definition/BooleanNode.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => $vendorDir . '/symfony/config/Definition/Builder/BuilderAwareInterface.php',
'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/EnumNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ExprBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/FloatNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/MergeBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NodeBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => $vendorDir . '/symfony/config/Definition/Builder/NodeParentInterface.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NormalizationBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NumericNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => $vendorDir . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\StringNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/StringNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/TreeBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ValidationBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/VariableNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\ConfigurableInterface' => $vendorDir . '/symfony/config/Definition/ConfigurableInterface.php',
'Symfony\\Component\\Config\\Definition\\Configuration' => $vendorDir . '/symfony/config/Definition/Configuration.php',
'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => $vendorDir . '/symfony/config/Definition/ConfigurationInterface.php',
'Symfony\\Component\\Config\\Definition\\Configurator\\DefinitionConfigurator' => $vendorDir . '/symfony/config/Definition/Configurator/DefinitionConfigurator.php',
'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php',
'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php',
'Symfony\\Component\\Config\\Definition\\EnumNode' => $vendorDir . '/symfony/config/Definition/EnumNode.php',
'Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => $vendorDir . '/symfony/config/Definition/Exception/DuplicateKeyException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\Exception' => $vendorDir . '/symfony/config/Definition/Exception/Exception.php',
'Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => $vendorDir . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidConfigurationException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidDefinitionException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidTypeException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => $vendorDir . '/symfony/config/Definition/Exception/UnsetKeyException.php',
'Symfony\\Component\\Config\\Definition\\FloatNode' => $vendorDir . '/symfony/config/Definition/FloatNode.php',
'Symfony\\Component\\Config\\Definition\\IntegerNode' => $vendorDir . '/symfony/config/Definition/IntegerNode.php',
'Symfony\\Component\\Config\\Definition\\Loader\\DefinitionFileLoader' => $vendorDir . '/symfony/config/Definition/Loader/DefinitionFileLoader.php',
'Symfony\\Component\\Config\\Definition\\NodeInterface' => $vendorDir . '/symfony/config/Definition/NodeInterface.php',
'Symfony\\Component\\Config\\Definition\\NumericNode' => $vendorDir . '/symfony/config/Definition/NumericNode.php',
'Symfony\\Component\\Config\\Definition\\Processor' => $vendorDir . '/symfony/config/Definition/Processor.php',
'Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => $vendorDir . '/symfony/config/Definition/PrototypeNodeInterface.php',
'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => $vendorDir . '/symfony/config/Definition/PrototypedArrayNode.php',
'Symfony\\Component\\Config\\Definition\\ScalarNode' => $vendorDir . '/symfony/config/Definition/ScalarNode.php',
'Symfony\\Component\\Config\\Definition\\StringNode' => $vendorDir . '/symfony/config/Definition/StringNode.php',
'Symfony\\Component\\Config\\Definition\\VariableNode' => $vendorDir . '/symfony/config/Definition/VariableNode.php',
'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => $vendorDir . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php',
'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => $vendorDir . '/symfony/config/Exception/FileLocatorFileNotFoundException.php',
'Symfony\\Component\\Config\\Exception\\LoaderLoadException' => $vendorDir . '/symfony/config/Exception/LoaderLoadException.php',
'Symfony\\Component\\Config\\Exception\\LogicException' => $vendorDir . '/symfony/config/Exception/LogicException.php',
'Symfony\\Component\\Config\\FileLocator' => $vendorDir . '/symfony/config/FileLocator.php',
'Symfony\\Component\\Config\\FileLocatorInterface' => $vendorDir . '/symfony/config/FileLocatorInterface.php',
'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => $vendorDir . '/symfony/config/Loader/DelegatingLoader.php',
'Symfony\\Component\\Config\\Loader\\DirectoryAwareLoaderInterface' => $vendorDir . '/symfony/config/Loader/DirectoryAwareLoaderInterface.php',
'Symfony\\Component\\Config\\Loader\\FileLoader' => $vendorDir . '/symfony/config/Loader/FileLoader.php',
'Symfony\\Component\\Config\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/config/Loader/GlobFileLoader.php',
'Symfony\\Component\\Config\\Loader\\Loader' => $vendorDir . '/symfony/config/Loader/Loader.php',
'Symfony\\Component\\Config\\Loader\\LoaderInterface' => $vendorDir . '/symfony/config/Loader/LoaderInterface.php',
'Symfony\\Component\\Config\\Loader\\LoaderResolver' => $vendorDir . '/symfony/config/Loader/LoaderResolver.php',
'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => $vendorDir . '/symfony/config/Loader/LoaderResolverInterface.php',
'Symfony\\Component\\Config\\Loader\\ParamConfigurator' => $vendorDir . '/symfony/config/Loader/ParamConfigurator.php',
'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => $vendorDir . '/symfony/config/ResourceCheckerConfigCache.php',
'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => $vendorDir . '/symfony/config/ResourceCheckerConfigCacheFactory.php',
'Symfony\\Component\\Config\\ResourceCheckerInterface' => $vendorDir . '/symfony/config/ResourceCheckerInterface.php',
'Symfony\\Component\\Config\\Resource\\ClassExistenceResource' => $vendorDir . '/symfony/config/Resource/ClassExistenceResource.php',
'Symfony\\Component\\Config\\Resource\\ComposerResource' => $vendorDir . '/symfony/config/Resource/ComposerResource.php',
'Symfony\\Component\\Config\\Resource\\DirectoryResource' => $vendorDir . '/symfony/config/Resource/DirectoryResource.php',
'Symfony\\Component\\Config\\Resource\\FileExistenceResource' => $vendorDir . '/symfony/config/Resource/FileExistenceResource.php',
'Symfony\\Component\\Config\\Resource\\FileResource' => $vendorDir . '/symfony/config/Resource/FileResource.php',
'Symfony\\Component\\Config\\Resource\\GlobResource' => $vendorDir . '/symfony/config/Resource/GlobResource.php',
'Symfony\\Component\\Config\\Resource\\ReflectionClassResource' => $vendorDir . '/symfony/config/Resource/ReflectionClassResource.php',
'Symfony\\Component\\Config\\Resource\\ResourceInterface' => $vendorDir . '/symfony/config/Resource/ResourceInterface.php',
'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceChecker.php',
'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceInterface.php',
'Symfony\\Component\\Config\\Resource\\SkippingResourceChecker' => $vendorDir . '/symfony/config/Resource/SkippingResourceChecker.php',
'Symfony\\Component\\Config\\Util\\Exception\\InvalidXmlException' => $vendorDir . '/symfony/config/Util/Exception/InvalidXmlException.php',
'Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => $vendorDir . '/symfony/config/Util/Exception/XmlParsingException.php',
'Symfony\\Component\\Config\\Util\\XmlUtils' => $vendorDir . '/symfony/config/Util/XmlUtils.php',
'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php',
'Symfony\\Component\\Console\\Attribute\\Argument' => $vendorDir . '/symfony/console/Attribute/Argument.php',
'Symfony\\Component\\Console\\Attribute\\AsCommand' => $vendorDir . '/symfony/console/Attribute/AsCommand.php',
@@ -7268,14 +6869,6 @@ return array(
'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/EventSubscriberInterface.php',
'Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/GenericEvent.php',
'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ImmutableEventDispatcher.php',
'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php',
'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php',
'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php',
'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php',
'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php',
'Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => $vendorDir . '/symfony/filesystem/Exception/RuntimeException.php',
'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php',
'Symfony\\Component\\Filesystem\\Path' => $vendorDir . '/symfony/filesystem/Path.php',
'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php',
'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php',
'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php',
+1 -3
View File
@@ -11,15 +11,14 @@ return array(
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'72142d7b40a3a0b14e91825290b5ad82' => $vendorDir . '/cakephp/core/functions.php',
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
'2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php',
'09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php',
@@ -34,7 +33,6 @@ return array(
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'3bd81c9b8fcc150b69d8b63b4d2ccf23' => $vendorDir . '/spatie/flare-client-php/src/helpers.php',
'948ad5488880985ff1c06721a4e447fe' => $vendorDir . '/cakephp/utility/bootstrap.php',
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
'801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php',
'b84a5ba6d9a8ee8cf648b71c42c799b7' => $vendorDir . '/akaunting/laravel-setting/src/helpers.php',
+3 -12
View File
@@ -8,7 +8,7 @@ $baseDir = dirname($vendorDir);
return array(
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
'phpseclib3\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-common/src'),
'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/type-resolver/src'),
'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
@@ -47,18 +47,16 @@ return array(
'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'),
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Symfony\\Component\\ErrorHandler\\' => array($vendorDir . '/symfony/error-handler'),
'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'),
'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'),
'Spatie\\QueryBuilder\\Database\\Factories\\' => array($vendorDir . '/spatie/laravel-query-builder/database/factories'),
'Spatie\\QueryBuilder\\' => array($vendorDir . '/spatie/laravel-query-builder/src'),
'Spatie\\LaravelPackageTools\\' => array($vendorDir . '/spatie/laravel-package-tools/src'),
'Spatie\\LaravelIgnition\\' => array($vendorDir . '/spatie/laravel-ignition/src', $vendorDir . '/spatie/error-solutions/legacy/laravel-ignition'),
'Spatie\\Ignition\\' => array($vendorDir . '/spatie/ignition/src', $vendorDir . '/spatie/error-solutions/legacy/ignition'),
'Spatie\\LaravelIgnition\\' => array($vendorDir . '/spatie/error-solutions/legacy/laravel-ignition', $vendorDir . '/spatie/laravel-ignition/src'),
'Spatie\\Ignition\\' => array($vendorDir . '/spatie/error-solutions/legacy/ignition', $vendorDir . '/spatie/ignition/src'),
'Spatie\\FlareClient\\' => array($vendorDir . '/spatie/flare-client-php/src'),
'Spatie\\ErrorSolutions\\' => array($vendorDir . '/spatie/error-solutions/src'),
'Spatie\\Backtrace\\' => array($vendorDir . '/spatie/backtrace/src'),
@@ -79,7 +77,6 @@ return array(
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'),
'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'),
'Phinx\\' => array($vendorDir . '/robmorgan/phinx/src/Phinx'),
'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
'ParaTest\\' => array($vendorDir . '/brianium/paratest/src'),
'PHPStan\\PhpDocParser\\' => array($vendorDir . '/phpstan/phpdoc-parser/src'),
@@ -92,7 +89,6 @@ return array(
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\Local\\' => array($vendorDir . '/league/flysystem-local'),
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
'League\\Container\\' => array($vendorDir . '/league/container/src'),
'League\\Config\\' => array($vendorDir . '/league/config/src'),
'League\\CommonMark\\' => array($vendorDir . '/league/commonmark/src'),
'Laravel\\Ui\\' => array($vendorDir . '/laravel/ui/src'),
@@ -138,11 +134,6 @@ return array(
'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'),
'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'),
'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'),
'Cake\\Utility\\' => array($vendorDir . '/cakephp/utility'),
'Cake\\Datasource\\' => array($vendorDir . '/cakephp/datasource'),
'Cake\\Database\\' => array($vendorDir . '/cakephp/database'),
'Cake\\Core\\' => array($vendorDir . '/cakephp/core'),
'Cake\\Chronos\\' => array($vendorDir . '/cakephp/chronos/src'),
'Brick\\Math\\' => array($vendorDir . '/brick/math/src'),
'Barryvdh\\LaravelIdeHelper\\' => array($vendorDir . '/barryvdh/laravel-ide-helper/src'),
'Barryvdh\\Debugbar\\' => array($vendorDir . '/barryvdh/laravel-debugbar/src'),
+7 -461
View File
@@ -12,15 +12,14 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'72142d7b40a3a0b14e91825290b5ad82' => __DIR__ . '/..' . '/cakephp/core/functions.php',
'35a6ad97d21e794e7e22a17d806652e4' => __DIR__ . '/..' . '/nunomaduro/termwind/src/Functions.php',
'2203a247e6fda86070a5e4e07aed533a' => __DIR__ . '/..' . '/symfony/clock/Resources/now.php',
'09f6b20656683369174dd6fa83b7e5fb' => __DIR__ . '/..' . '/symfony/polyfill-uuid/bootstrap.php',
@@ -35,7 +34,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'58571171fd5812e6e447dce228f52f4d' => __DIR__ . '/..' . '/laravel/framework/src/Illuminate/Support/helpers.php',
'6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'3bd81c9b8fcc150b69d8b63b4d2ccf23' => __DIR__ . '/..' . '/spatie/flare-client-php/src/helpers.php',
'948ad5488880985ff1c06721a4e447fe' => __DIR__ . '/..' . '/cakephp/utility/bootstrap.php',
'ec07570ca5a812141189b1fa81503674' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
'801c31d8ed748cfa537fa45402288c95' => __DIR__ . '/..' . '/psy/psysh/src/functions.php',
'b84a5ba6d9a8ee8cf648b71c42c799b7' => __DIR__ . '/..' . '/akaunting/laravel-setting/src/helpers.php',
@@ -109,12 +107,10 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'Symfony\\Component\\HttpKernel\\' => 29,
'Symfony\\Component\\HttpFoundation\\' => 33,
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\Filesystem\\' => 29,
'Symfony\\Component\\EventDispatcher\\' => 34,
'Symfony\\Component\\ErrorHandler\\' => 31,
'Symfony\\Component\\CssSelector\\' => 30,
'Symfony\\Component\\Console\\' => 26,
'Symfony\\Component\\Config\\' => 25,
'Symfony\\Component\\Clock\\' => 24,
'Spatie\\QueryBuilder\\Database\\Factories\\' => 39,
'Spatie\\QueryBuilder\\' => 20,
@@ -147,7 +143,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'PhpParser\\' => 10,
'PhpOption\\' => 10,
'PhpOffice\\PhpSpreadsheet\\' => 25,
'Phinx\\' => 6,
'ParagonIE\\ConstantTime\\' => 23,
'ParaTest\\' => 9,
'PHPStan\\PhpDocParser\\' => 21,
@@ -169,7 +164,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'League\\MimeTypeDetection\\' => 25,
'League\\Flysystem\\Local\\' => 23,
'League\\Flysystem\\' => 17,
'League\\Container\\' => 17,
'League\\Config\\' => 14,
'League\\CommonMark\\' => 18,
'Laravel\\Ui\\' => 11,
@@ -236,11 +230,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'Complex\\' => 8,
'Carbon\\Doctrine\\' => 16,
'Carbon\\' => 7,
'Cake\\Utility\\' => 13,
'Cake\\Datasource\\' => 16,
'Cake\\Database\\' => 14,
'Cake\\Core\\' => 10,
'Cake\\Chronos\\' => 13,
),
'B' =>
array (
@@ -268,8 +257,8 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
),
'phpDocumentor\\Reflection\\' =>
array (
0 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
1 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
),
'ZipStream\\' =>
array (
@@ -423,10 +412,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
array (
0 => __DIR__ . '/..' . '/symfony/finder',
),
'Symfony\\Component\\Filesystem\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/filesystem',
),
'Symfony\\Component\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
@@ -443,10 +428,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'Symfony\\Component\\Config\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/config',
),
'Symfony\\Component\\Clock\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/clock',
@@ -465,13 +446,13 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
),
'Spatie\\LaravelIgnition\\' =>
array (
0 => __DIR__ . '/..' . '/spatie/laravel-ignition/src',
1 => __DIR__ . '/..' . '/spatie/error-solutions/legacy/laravel-ignition',
0 => __DIR__ . '/..' . '/spatie/error-solutions/legacy/laravel-ignition',
1 => __DIR__ . '/..' . '/spatie/laravel-ignition/src',
),
'Spatie\\Ignition\\' =>
array (
0 => __DIR__ . '/..' . '/spatie/ignition/src',
1 => __DIR__ . '/..' . '/spatie/error-solutions/legacy/ignition',
0 => __DIR__ . '/..' . '/spatie/error-solutions/legacy/ignition',
1 => __DIR__ . '/..' . '/spatie/ignition/src',
),
'Spatie\\FlareClient\\' =>
array (
@@ -554,10 +535,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
array (
0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet',
),
'Phinx\\' =>
array (
0 => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx',
),
'ParagonIE\\ConstantTime\\' =>
array (
0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src',
@@ -606,10 +583,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
array (
0 => __DIR__ . '/..' . '/league/flysystem/src',
),
'League\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/league/container/src',
),
'League\\Config\\' =>
array (
0 => __DIR__ . '/..' . '/league/config/src',
@@ -794,26 +767,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
array (
0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon',
),
'Cake\\Utility\\' =>
array (
0 => __DIR__ . '/..' . '/cakephp/utility',
),
'Cake\\Datasource\\' =>
array (
0 => __DIR__ . '/..' . '/cakephp/datasource',
),
'Cake\\Database\\' =>
array (
0 => __DIR__ . '/..' . '/cakephp/database',
),
'Cake\\Core\\' =>
array (
0 => __DIR__ . '/..' . '/cakephp/core',
),
'Cake\\Chronos\\' =>
array (
0 => __DIR__ . '/..' . '/cakephp/chronos/src',
),
'Brick\\Math\\' =>
array (
0 => __DIR__ . '/..' . '/brick/math/src',
@@ -1047,196 +1000,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'Brick\\Math\\Internal\\Calculator\\GmpCalculator' => __DIR__ . '/..' . '/brick/math/src/Internal/Calculator/GmpCalculator.php',
'Brick\\Math\\Internal\\Calculator\\NativeCalculator' => __DIR__ . '/..' . '/brick/math/src/Internal/Calculator/NativeCalculator.php',
'Brick\\Math\\RoundingMode' => __DIR__ . '/..' . '/brick/math/src/RoundingMode.php',
'Cake\\Chronos\\Chronos' => __DIR__ . '/..' . '/cakephp/chronos/src/Chronos.php',
'Cake\\Chronos\\ChronosDate' => __DIR__ . '/..' . '/cakephp/chronos/src/ChronosDate.php',
'Cake\\Chronos\\ChronosDatePeriod' => __DIR__ . '/..' . '/cakephp/chronos/src/ChronosDatePeriod.php',
'Cake\\Chronos\\ChronosInterval' => __DIR__ . '/..' . '/cakephp/chronos/src/ChronosInterval.php',
'Cake\\Chronos\\ChronosPeriod' => __DIR__ . '/..' . '/cakephp/chronos/src/ChronosPeriod.php',
'Cake\\Chronos\\ChronosTime' => __DIR__ . '/..' . '/cakephp/chronos/src/ChronosTime.php',
'Cake\\Chronos\\ClockFactory' => __DIR__ . '/..' . '/cakephp/chronos/src/ClockFactory.php',
'Cake\\Chronos\\DifferenceFormatter' => __DIR__ . '/..' . '/cakephp/chronos/src/DifferenceFormatter.php',
'Cake\\Chronos\\DifferenceFormatterInterface' => __DIR__ . '/..' . '/cakephp/chronos/src/DifferenceFormatterInterface.php',
'Cake\\Chronos\\FormattingTrait' => __DIR__ . '/..' . '/cakephp/chronos/src/FormattingTrait.php',
'Cake\\Chronos\\Translator' => __DIR__ . '/..' . '/cakephp/chronos/src/Translator.php',
'Cake\\Core\\App' => __DIR__ . '/..' . '/cakephp/core/App.php',
'Cake\\Core\\Attribute\\Configure' => __DIR__ . '/..' . '/cakephp/core/Attribute/Configure.php',
'Cake\\Core\\BasePlugin' => __DIR__ . '/..' . '/cakephp/core/BasePlugin.php',
'Cake\\Core\\Configure' => __DIR__ . '/..' . '/cakephp/core/Configure.php',
'Cake\\Core\\Configure\\ConfigEngineInterface' => __DIR__ . '/..' . '/cakephp/core/Configure/ConfigEngineInterface.php',
'Cake\\Core\\Configure\\Engine\\IniConfig' => __DIR__ . '/..' . '/cakephp/core/Configure/Engine/IniConfig.php',
'Cake\\Core\\Configure\\Engine\\JsonConfig' => __DIR__ . '/..' . '/cakephp/core/Configure/Engine/JsonConfig.php',
'Cake\\Core\\Configure\\Engine\\PhpConfig' => __DIR__ . '/..' . '/cakephp/core/Configure/Engine/PhpConfig.php',
'Cake\\Core\\Configure\\FileConfigTrait' => __DIR__ . '/..' . '/cakephp/core/Configure/FileConfigTrait.php',
'Cake\\Core\\ConsoleApplicationInterface' => __DIR__ . '/..' . '/cakephp/core/ConsoleApplicationInterface.php',
'Cake\\Core\\Container' => __DIR__ . '/..' . '/cakephp/core/Container.php',
'Cake\\Core\\ContainerApplicationInterface' => __DIR__ . '/..' . '/cakephp/core/ContainerApplicationInterface.php',
'Cake\\Core\\ContainerInterface' => __DIR__ . '/..' . '/cakephp/core/ContainerInterface.php',
'Cake\\Core\\ConventionsTrait' => __DIR__ . '/..' . '/cakephp/core/ConventionsTrait.php',
'Cake\\Core\\EventAwareApplicationInterface' => __DIR__ . '/..' . '/cakephp/core/EventAwareApplicationInterface.php',
'Cake\\Core\\Exception\\CakeException' => __DIR__ . '/..' . '/cakephp/core/Exception/CakeException.php',
'Cake\\Core\\Exception\\HttpErrorCodeInterface' => __DIR__ . '/..' . '/cakephp/core/Exception/HttpErrorCodeInterface.php',
'Cake\\Core\\Exception\\MissingPluginException' => __DIR__ . '/..' . '/cakephp/core/Exception/MissingPluginException.php',
'Cake\\Core\\HttpApplicationInterface' => __DIR__ . '/..' . '/cakephp/core/HttpApplicationInterface.php',
'Cake\\Core\\InstanceConfigTrait' => __DIR__ . '/..' . '/cakephp/core/InstanceConfigTrait.php',
'Cake\\Core\\ObjectRegistry' => __DIR__ . '/..' . '/cakephp/core/ObjectRegistry.php',
'Cake\\Core\\Plugin' => __DIR__ . '/..' . '/cakephp/core/Plugin.php',
'Cake\\Core\\PluginApplicationInterface' => __DIR__ . '/..' . '/cakephp/core/PluginApplicationInterface.php',
'Cake\\Core\\PluginCollection' => __DIR__ . '/..' . '/cakephp/core/PluginCollection.php',
'Cake\\Core\\PluginConfig' => __DIR__ . '/..' . '/cakephp/core/PluginConfig.php',
'Cake\\Core\\PluginInterface' => __DIR__ . '/..' . '/cakephp/core/PluginInterface.php',
'Cake\\Core\\Retry\\CommandRetry' => __DIR__ . '/..' . '/cakephp/core/Retry/CommandRetry.php',
'Cake\\Core\\Retry\\RetryStrategyInterface' => __DIR__ . '/..' . '/cakephp/core/Retry/RetryStrategyInterface.php',
'Cake\\Core\\ServiceConfig' => __DIR__ . '/..' . '/cakephp/core/ServiceConfig.php',
'Cake\\Core\\ServiceProvider' => __DIR__ . '/..' . '/cakephp/core/ServiceProvider.php',
'Cake\\Core\\StaticConfigTrait' => __DIR__ . '/..' . '/cakephp/core/StaticConfigTrait.php',
'Cake\\Core\\TestSuite\\ContainerStubTrait' => __DIR__ . '/..' . '/cakephp/core/TestSuite/ContainerStubTrait.php',
'Cake\\Database\\Connection' => __DIR__ . '/..' . '/cakephp/database/Connection.php',
'Cake\\Database\\ConstraintsInterface' => __DIR__ . '/..' . '/cakephp/database/ConstraintsInterface.php',
'Cake\\Database\\Driver' => __DIR__ . '/..' . '/cakephp/database/Driver.php',
'Cake\\Database\\DriverFeatureEnum' => __DIR__ . '/..' . '/cakephp/database/DriverFeatureEnum.php',
'Cake\\Database\\Driver\\Mysql' => __DIR__ . '/..' . '/cakephp/database/Driver/Mysql.php',
'Cake\\Database\\Driver\\Postgres' => __DIR__ . '/..' . '/cakephp/database/Driver/Postgres.php',
'Cake\\Database\\Driver\\Sqlite' => __DIR__ . '/..' . '/cakephp/database/Driver/Sqlite.php',
'Cake\\Database\\Driver\\Sqlserver' => __DIR__ . '/..' . '/cakephp/database/Driver/Sqlserver.php',
'Cake\\Database\\Driver\\TupleComparisonTranslatorTrait' => __DIR__ . '/..' . '/cakephp/database/Driver/TupleComparisonTranslatorTrait.php',
'Cake\\Database\\Exception\\DatabaseException' => __DIR__ . '/..' . '/cakephp/database/Exception/DatabaseException.php',
'Cake\\Database\\Exception\\MissingConnectionException' => __DIR__ . '/..' . '/cakephp/database/Exception/MissingConnectionException.php',
'Cake\\Database\\Exception\\MissingDriverException' => __DIR__ . '/..' . '/cakephp/database/Exception/MissingDriverException.php',
'Cake\\Database\\Exception\\MissingExtensionException' => __DIR__ . '/..' . '/cakephp/database/Exception/MissingExtensionException.php',
'Cake\\Database\\Exception\\NestedTransactionRollbackException' => __DIR__ . '/..' . '/cakephp/database/Exception/NestedTransactionRollbackException.php',
'Cake\\Database\\Exception\\QueryException' => __DIR__ . '/..' . '/cakephp/database/Exception/QueryException.php',
'Cake\\Database\\ExpressionInterface' => __DIR__ . '/..' . '/cakephp/database/ExpressionInterface.php',
'Cake\\Database\\Expression\\AggregateExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/AggregateExpression.php',
'Cake\\Database\\Expression\\BetweenExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/BetweenExpression.php',
'Cake\\Database\\Expression\\CaseExpressionTrait' => __DIR__ . '/..' . '/cakephp/database/Expression/CaseExpressionTrait.php',
'Cake\\Database\\Expression\\CaseStatementExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/CaseStatementExpression.php',
'Cake\\Database\\Expression\\CommonTableExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/CommonTableExpression.php',
'Cake\\Database\\Expression\\ComparisonExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/ComparisonExpression.php',
'Cake\\Database\\Expression\\FieldInterface' => __DIR__ . '/..' . '/cakephp/database/Expression/FieldInterface.php',
'Cake\\Database\\Expression\\FieldTrait' => __DIR__ . '/..' . '/cakephp/database/Expression/FieldTrait.php',
'Cake\\Database\\Expression\\FunctionExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/FunctionExpression.php',
'Cake\\Database\\Expression\\IdentifierExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/IdentifierExpression.php',
'Cake\\Database\\Expression\\OrderByExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/OrderByExpression.php',
'Cake\\Database\\Expression\\OrderClauseExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/OrderClauseExpression.php',
'Cake\\Database\\Expression\\QueryExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/QueryExpression.php',
'Cake\\Database\\Expression\\StringExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/StringExpression.php',
'Cake\\Database\\Expression\\TupleComparison' => __DIR__ . '/..' . '/cakephp/database/Expression/TupleComparison.php',
'Cake\\Database\\Expression\\UnaryExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/UnaryExpression.php',
'Cake\\Database\\Expression\\ValuesExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/ValuesExpression.php',
'Cake\\Database\\Expression\\WhenThenExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/WhenThenExpression.php',
'Cake\\Database\\Expression\\WindowExpression' => __DIR__ . '/..' . '/cakephp/database/Expression/WindowExpression.php',
'Cake\\Database\\Expression\\WindowInterface' => __DIR__ . '/..' . '/cakephp/database/Expression/WindowInterface.php',
'Cake\\Database\\FieldTypeConverter' => __DIR__ . '/..' . '/cakephp/database/FieldTypeConverter.php',
'Cake\\Database\\FunctionsBuilder' => __DIR__ . '/..' . '/cakephp/database/FunctionsBuilder.php',
'Cake\\Database\\IdentifierQuoter' => __DIR__ . '/..' . '/cakephp/database/IdentifierQuoter.php',
'Cake\\Database\\Log\\LoggedQuery' => __DIR__ . '/..' . '/cakephp/database/Log/LoggedQuery.php',
'Cake\\Database\\Log\\QueryLogger' => __DIR__ . '/..' . '/cakephp/database/Log/QueryLogger.php',
'Cake\\Database\\PostgresCompiler' => __DIR__ . '/..' . '/cakephp/database/PostgresCompiler.php',
'Cake\\Database\\Query' => __DIR__ . '/..' . '/cakephp/database/Query.php',
'Cake\\Database\\QueryCompiler' => __DIR__ . '/..' . '/cakephp/database/QueryCompiler.php',
'Cake\\Database\\Query\\DeleteQuery' => __DIR__ . '/..' . '/cakephp/database/Query/DeleteQuery.php',
'Cake\\Database\\Query\\InsertQuery' => __DIR__ . '/..' . '/cakephp/database/Query/InsertQuery.php',
'Cake\\Database\\Query\\QueryFactory' => __DIR__ . '/..' . '/cakephp/database/Query/QueryFactory.php',
'Cake\\Database\\Query\\SelectQuery' => __DIR__ . '/..' . '/cakephp/database/Query/SelectQuery.php',
'Cake\\Database\\Query\\UpdateQuery' => __DIR__ . '/..' . '/cakephp/database/Query/UpdateQuery.php',
'Cake\\Database\\Retry\\ErrorCodeWaitStrategy' => __DIR__ . '/..' . '/cakephp/database/Retry/ErrorCodeWaitStrategy.php',
'Cake\\Database\\Retry\\ReconnectStrategy' => __DIR__ . '/..' . '/cakephp/database/Retry/ReconnectStrategy.php',
'Cake\\Database\\SchemaCache' => __DIR__ . '/..' . '/cakephp/database/SchemaCache.php',
'Cake\\Database\\Schema\\CachedCollection' => __DIR__ . '/..' . '/cakephp/database/Schema/CachedCollection.php',
'Cake\\Database\\Schema\\CheckConstraint' => __DIR__ . '/..' . '/cakephp/database/Schema/CheckConstraint.php',
'Cake\\Database\\Schema\\Collection' => __DIR__ . '/..' . '/cakephp/database/Schema/Collection.php',
'Cake\\Database\\Schema\\CollectionInterface' => __DIR__ . '/..' . '/cakephp/database/Schema/CollectionInterface.php',
'Cake\\Database\\Schema\\Column' => __DIR__ . '/..' . '/cakephp/database/Schema/Column.php',
'Cake\\Database\\Schema\\Constraint' => __DIR__ . '/..' . '/cakephp/database/Schema/Constraint.php',
'Cake\\Database\\Schema\\ForeignKey' => __DIR__ . '/..' . '/cakephp/database/Schema/ForeignKey.php',
'Cake\\Database\\Schema\\Index' => __DIR__ . '/..' . '/cakephp/database/Schema/Index.php',
'Cake\\Database\\Schema\\MysqlSchemaDialect' => __DIR__ . '/..' . '/cakephp/database/Schema/MysqlSchemaDialect.php',
'Cake\\Database\\Schema\\PostgresSchemaDialect' => __DIR__ . '/..' . '/cakephp/database/Schema/PostgresSchemaDialect.php',
'Cake\\Database\\Schema\\SchemaDialect' => __DIR__ . '/..' . '/cakephp/database/Schema/SchemaDialect.php',
'Cake\\Database\\Schema\\SqlGeneratorInterface' => __DIR__ . '/..' . '/cakephp/database/Schema/SqlGeneratorInterface.php',
'Cake\\Database\\Schema\\SqliteSchemaDialect' => __DIR__ . '/..' . '/cakephp/database/Schema/SqliteSchemaDialect.php',
'Cake\\Database\\Schema\\SqlserverSchemaDialect' => __DIR__ . '/..' . '/cakephp/database/Schema/SqlserverSchemaDialect.php',
'Cake\\Database\\Schema\\TableSchema' => __DIR__ . '/..' . '/cakephp/database/Schema/TableSchema.php',
'Cake\\Database\\Schema\\TableSchemaInterface' => __DIR__ . '/..' . '/cakephp/database/Schema/TableSchemaInterface.php',
'Cake\\Database\\Schema\\UniqueKey' => __DIR__ . '/..' . '/cakephp/database/Schema/UniqueKey.php',
'Cake\\Database\\SqlserverCompiler' => __DIR__ . '/..' . '/cakephp/database/SqlserverCompiler.php',
'Cake\\Database\\StatementInterface' => __DIR__ . '/..' . '/cakephp/database/StatementInterface.php',
'Cake\\Database\\Statement\\SqliteStatement' => __DIR__ . '/..' . '/cakephp/database/Statement/SqliteStatement.php',
'Cake\\Database\\Statement\\SqlserverStatement' => __DIR__ . '/..' . '/cakephp/database/Statement/SqlserverStatement.php',
'Cake\\Database\\Statement\\Statement' => __DIR__ . '/..' . '/cakephp/database/Statement/Statement.php',
'Cake\\Database\\TypeFactory' => __DIR__ . '/..' . '/cakephp/database/TypeFactory.php',
'Cake\\Database\\TypeInterface' => __DIR__ . '/..' . '/cakephp/database/TypeInterface.php',
'Cake\\Database\\TypeMap' => __DIR__ . '/..' . '/cakephp/database/TypeMap.php',
'Cake\\Database\\TypeMapTrait' => __DIR__ . '/..' . '/cakephp/database/TypeMapTrait.php',
'Cake\\Database\\Type\\BaseType' => __DIR__ . '/..' . '/cakephp/database/Type/BaseType.php',
'Cake\\Database\\Type\\BatchCastingInterface' => __DIR__ . '/..' . '/cakephp/database/Type/BatchCastingInterface.php',
'Cake\\Database\\Type\\BinaryType' => __DIR__ . '/..' . '/cakephp/database/Type/BinaryType.php',
'Cake\\Database\\Type\\BinaryUuidType' => __DIR__ . '/..' . '/cakephp/database/Type/BinaryUuidType.php',
'Cake\\Database\\Type\\BoolType' => __DIR__ . '/..' . '/cakephp/database/Type/BoolType.php',
'Cake\\Database\\Type\\ColumnSchemaAwareInterface' => __DIR__ . '/..' . '/cakephp/database/Type/ColumnSchemaAwareInterface.php',
'Cake\\Database\\Type\\DateTimeFractionalType' => __DIR__ . '/..' . '/cakephp/database/Type/DateTimeFractionalType.php',
'Cake\\Database\\Type\\DateTimeTimezoneType' => __DIR__ . '/..' . '/cakephp/database/Type/DateTimeTimezoneType.php',
'Cake\\Database\\Type\\DateTimeType' => __DIR__ . '/..' . '/cakephp/database/Type/DateTimeType.php',
'Cake\\Database\\Type\\DateType' => __DIR__ . '/..' . '/cakephp/database/Type/DateType.php',
'Cake\\Database\\Type\\DecimalType' => __DIR__ . '/..' . '/cakephp/database/Type/DecimalType.php',
'Cake\\Database\\Type\\EnumLabelInterface' => __DIR__ . '/..' . '/cakephp/database/Type/EnumLabelInterface.php',
'Cake\\Database\\Type\\EnumType' => __DIR__ . '/..' . '/cakephp/database/Type/EnumType.php',
'Cake\\Database\\Type\\ExpressionTypeCasterTrait' => __DIR__ . '/..' . '/cakephp/database/Type/ExpressionTypeCasterTrait.php',
'Cake\\Database\\Type\\ExpressionTypeInterface' => __DIR__ . '/..' . '/cakephp/database/Type/ExpressionTypeInterface.php',
'Cake\\Database\\Type\\FloatType' => __DIR__ . '/..' . '/cakephp/database/Type/FloatType.php',
'Cake\\Database\\Type\\IntegerType' => __DIR__ . '/..' . '/cakephp/database/Type/IntegerType.php',
'Cake\\Database\\Type\\JsonType' => __DIR__ . '/..' . '/cakephp/database/Type/JsonType.php',
'Cake\\Database\\Type\\OptionalConvertInterface' => __DIR__ . '/..' . '/cakephp/database/Type/OptionalConvertInterface.php',
'Cake\\Database\\Type\\StringType' => __DIR__ . '/..' . '/cakephp/database/Type/StringType.php',
'Cake\\Database\\Type\\TimeType' => __DIR__ . '/..' . '/cakephp/database/Type/TimeType.php',
'Cake\\Database\\Type\\UuidType' => __DIR__ . '/..' . '/cakephp/database/Type/UuidType.php',
'Cake\\Database\\TypedResultInterface' => __DIR__ . '/..' . '/cakephp/database/TypedResultInterface.php',
'Cake\\Database\\TypedResultTrait' => __DIR__ . '/..' . '/cakephp/database/TypedResultTrait.php',
'Cake\\Database\\ValueBinder' => __DIR__ . '/..' . '/cakephp/database/ValueBinder.php',
'Cake\\Datasource\\ConnectionInterface' => __DIR__ . '/..' . '/cakephp/datasource/ConnectionInterface.php',
'Cake\\Datasource\\ConnectionManager' => __DIR__ . '/..' . '/cakephp/datasource/ConnectionManager.php',
'Cake\\Datasource\\ConnectionRegistry' => __DIR__ . '/..' . '/cakephp/datasource/ConnectionRegistry.php',
'Cake\\Datasource\\EntityInterface' => __DIR__ . '/..' . '/cakephp/datasource/EntityInterface.php',
'Cake\\Datasource\\EntityTrait' => __DIR__ . '/..' . '/cakephp/datasource/EntityTrait.php',
'Cake\\Datasource\\Exception\\InvalidPrimaryKeyException' => __DIR__ . '/..' . '/cakephp/datasource/Exception/InvalidPrimaryKeyException.php',
'Cake\\Datasource\\Exception\\MissingDatasourceConfigException' => __DIR__ . '/..' . '/cakephp/datasource/Exception/MissingDatasourceConfigException.php',
'Cake\\Datasource\\Exception\\MissingDatasourceException' => __DIR__ . '/..' . '/cakephp/datasource/Exception/MissingDatasourceException.php',
'Cake\\Datasource\\Exception\\MissingModelException' => __DIR__ . '/..' . '/cakephp/datasource/Exception/MissingModelException.php',
'Cake\\Datasource\\Exception\\MissingPropertyException' => __DIR__ . '/..' . '/cakephp/datasource/Exception/MissingPropertyException.php',
'Cake\\Datasource\\Exception\\RecordNotFoundException' => __DIR__ . '/..' . '/cakephp/datasource/Exception/RecordNotFoundException.php',
'Cake\\Datasource\\FactoryLocator' => __DIR__ . '/..' . '/cakephp/datasource/FactoryLocator.php',
'Cake\\Datasource\\FixtureInterface' => __DIR__ . '/..' . '/cakephp/datasource/FixtureInterface.php',
'Cake\\Datasource\\InvalidPropertyInterface' => __DIR__ . '/..' . '/cakephp/datasource/InvalidPropertyInterface.php',
'Cake\\Datasource\\Locator\\AbstractLocator' => __DIR__ . '/..' . '/cakephp/datasource/Locator/AbstractLocator.php',
'Cake\\Datasource\\Locator\\LocatorInterface' => __DIR__ . '/..' . '/cakephp/datasource/Locator/LocatorInterface.php',
'Cake\\Datasource\\ModelAwareTrait' => __DIR__ . '/..' . '/cakephp/datasource/ModelAwareTrait.php',
'Cake\\Datasource\\Paging\\Exception\\PageOutOfBoundsException' => __DIR__ . '/..' . '/cakephp/datasource/Paging/Exception/PageOutOfBoundsException.php',
'Cake\\Datasource\\Paging\\NumericPaginator' => __DIR__ . '/..' . '/cakephp/datasource/Paging/NumericPaginator.php',
'Cake\\Datasource\\Paging\\PaginatedInterface' => __DIR__ . '/..' . '/cakephp/datasource/Paging/PaginatedInterface.php',
'Cake\\Datasource\\Paging\\PaginatedResultSet' => __DIR__ . '/..' . '/cakephp/datasource/Paging/PaginatedResultSet.php',
'Cake\\Datasource\\Paging\\PaginatorInterface' => __DIR__ . '/..' . '/cakephp/datasource/Paging/PaginatorInterface.php',
'Cake\\Datasource\\Paging\\SimplePaginator' => __DIR__ . '/..' . '/cakephp/datasource/Paging/SimplePaginator.php',
'Cake\\Datasource\\Paging\\SortField' => __DIR__ . '/..' . '/cakephp/datasource/Paging/SortField.php',
'Cake\\Datasource\\Paging\\SortableFieldsBuilder' => __DIR__ . '/..' . '/cakephp/datasource/Paging/SortableFieldsBuilder.php',
'Cake\\Datasource\\QueryCacher' => __DIR__ . '/..' . '/cakephp/datasource/QueryCacher.php',
'Cake\\Datasource\\QueryInterface' => __DIR__ . '/..' . '/cakephp/datasource/QueryInterface.php',
'Cake\\Datasource\\RepositoryInterface' => __DIR__ . '/..' . '/cakephp/datasource/RepositoryInterface.php',
'Cake\\Datasource\\ResultSetDecorator' => __DIR__ . '/..' . '/cakephp/datasource/ResultSetDecorator.php',
'Cake\\Datasource\\ResultSetInterface' => __DIR__ . '/..' . '/cakephp/datasource/ResultSetInterface.php',
'Cake\\Datasource\\RuleInvoker' => __DIR__ . '/..' . '/cakephp/datasource/RuleInvoker.php',
'Cake\\Datasource\\RulesAwareTrait' => __DIR__ . '/..' . '/cakephp/datasource/RulesAwareTrait.php',
'Cake\\Datasource\\RulesChecker' => __DIR__ . '/..' . '/cakephp/datasource/RulesChecker.php',
'Cake\\Datasource\\SchemaInterface' => __DIR__ . '/..' . '/cakephp/datasource/SchemaInterface.php',
'Cake\\Utility\\CookieCryptTrait' => __DIR__ . '/..' . '/cakephp/utility/CookieCryptTrait.php',
'Cake\\Utility\\Crypto\\OpenSsl' => __DIR__ . '/..' . '/cakephp/utility/Crypto/OpenSsl.php',
'Cake\\Utility\\Exception\\XmlException' => __DIR__ . '/..' . '/cakephp/utility/Exception/XmlException.php',
'Cake\\Utility\\Filesystem' => __DIR__ . '/..' . '/cakephp/utility/Filesystem.php',
'Cake\\Utility\\Hash' => __DIR__ . '/..' . '/cakephp/utility/Hash.php',
'Cake\\Utility\\Inflector' => __DIR__ . '/..' . '/cakephp/utility/Inflector.php',
'Cake\\Utility\\MergeVariablesTrait' => __DIR__ . '/..' . '/cakephp/utility/MergeVariablesTrait.php',
'Cake\\Utility\\Security' => __DIR__ . '/..' . '/cakephp/utility/Security.php',
'Cake\\Utility\\Text' => __DIR__ . '/..' . '/cakephp/utility/Text.php',
'Cake\\Utility\\Xml' => __DIR__ . '/..' . '/cakephp/utility/Xml.php',
'Carbon\\AbstractTranslator' => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon/AbstractTranslator.php',
'Carbon\\Callback' => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon/Callback.php',
'Carbon\\Carbon' => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon/Carbon.php',
@@ -4466,55 +4229,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'League\\Config\\MutableConfigurationInterface' => __DIR__ . '/..' . '/league/config/src/MutableConfigurationInterface.php',
'League\\Config\\ReadOnlyConfiguration' => __DIR__ . '/..' . '/league/config/src/ReadOnlyConfiguration.php',
'League\\Config\\SchemaBuilderInterface' => __DIR__ . '/..' . '/league/config/src/SchemaBuilderInterface.php',
'League\\Container\\Argument\\ArgumentInterface' => __DIR__ . '/..' . '/league/container/src/Argument/ArgumentInterface.php',
'League\\Container\\Argument\\ArgumentReflectorInterface' => __DIR__ . '/..' . '/league/container/src/Argument/ArgumentReflectorInterface.php',
'League\\Container\\Argument\\ArgumentReflectorTrait' => __DIR__ . '/..' . '/league/container/src/Argument/ArgumentReflectorTrait.php',
'League\\Container\\Argument\\ArgumentResolverInterface' => __DIR__ . '/..' . '/league/container/src/Argument/ArgumentResolverInterface.php',
'League\\Container\\Argument\\ArgumentResolverTrait' => __DIR__ . '/..' . '/league/container/src/Argument/ArgumentResolverTrait.php',
'League\\Container\\Argument\\DefaultValueArgument' => __DIR__ . '/..' . '/league/container/src/Argument/DefaultValueArgument.php',
'League\\Container\\Argument\\DefaultValueInterface' => __DIR__ . '/..' . '/league/container/src/Argument/DefaultValueInterface.php',
'League\\Container\\Argument\\LiteralArgument' => __DIR__ . '/..' . '/league/container/src/Argument/LiteralArgument.php',
'League\\Container\\Argument\\LiteralArgumentInterface' => __DIR__ . '/..' . '/league/container/src/Argument/LiteralArgumentInterface.php',
'League\\Container\\Argument\\Literal\\ArrayArgument' => __DIR__ . '/..' . '/league/container/src/Argument/Literal/ArrayArgument.php',
'League\\Container\\Argument\\Literal\\BooleanArgument' => __DIR__ . '/..' . '/league/container/src/Argument/Literal/BooleanArgument.php',
'League\\Container\\Argument\\Literal\\CallableArgument' => __DIR__ . '/..' . '/league/container/src/Argument/Literal/CallableArgument.php',
'League\\Container\\Argument\\Literal\\FloatArgument' => __DIR__ . '/..' . '/league/container/src/Argument/Literal/FloatArgument.php',
'League\\Container\\Argument\\Literal\\IntegerArgument' => __DIR__ . '/..' . '/league/container/src/Argument/Literal/IntegerArgument.php',
'League\\Container\\Argument\\Literal\\ObjectArgument' => __DIR__ . '/..' . '/league/container/src/Argument/Literal/ObjectArgument.php',
'League\\Container\\Argument\\Literal\\StringArgument' => __DIR__ . '/..' . '/league/container/src/Argument/Literal/StringArgument.php',
'League\\Container\\Argument\\ResolvableArgument' => __DIR__ . '/..' . '/league/container/src/Argument/ResolvableArgument.php',
'League\\Container\\Argument\\ResolvableArgumentInterface' => __DIR__ . '/..' . '/league/container/src/Argument/ResolvableArgumentInterface.php',
'League\\Container\\Attribute\\AttributeInterface' => __DIR__ . '/..' . '/league/container/src/Attribute/AttributeInterface.php',
'League\\Container\\Attribute\\Inject' => __DIR__ . '/..' . '/league/container/src/Attribute/Inject.php',
'League\\Container\\Attribute\\Resolve' => __DIR__ . '/..' . '/league/container/src/Attribute/Resolve.php',
'League\\Container\\Container' => __DIR__ . '/..' . '/league/container/src/Container.php',
'League\\Container\\ContainerAwareInterface' => __DIR__ . '/..' . '/league/container/src/ContainerAwareInterface.php',
'League\\Container\\ContainerAwareTrait' => __DIR__ . '/..' . '/league/container/src/ContainerAwareTrait.php',
'League\\Container\\DefinitionContainerInterface' => __DIR__ . '/..' . '/league/container/src/DefinitionContainerInterface.php',
'League\\Container\\Definition\\Definition' => __DIR__ . '/..' . '/league/container/src/Definition/Definition.php',
'League\\Container\\Definition\\DefinitionAggregate' => __DIR__ . '/..' . '/league/container/src/Definition/DefinitionAggregate.php',
'League\\Container\\Definition\\DefinitionAggregateInterface' => __DIR__ . '/..' . '/league/container/src/Definition/DefinitionAggregateInterface.php',
'League\\Container\\Definition\\DefinitionInterface' => __DIR__ . '/..' . '/league/container/src/Definition/DefinitionInterface.php',
'League\\Container\\Event\\BeforeResolveEvent' => __DIR__ . '/..' . '/league/container/src/Event/BeforeResolveEvent.php',
'League\\Container\\Event\\ContainerEvent' => __DIR__ . '/..' . '/league/container/src/Event/ContainerEvent.php',
'League\\Container\\Event\\DefinitionResolvedEvent' => __DIR__ . '/..' . '/league/container/src/Event/DefinitionResolvedEvent.php',
'League\\Container\\Event\\EventAwareTrait' => __DIR__ . '/..' . '/league/container/src/Event/EventAwareTrait.php',
'League\\Container\\Event\\EventDispatcher' => __DIR__ . '/..' . '/league/container/src/Event/EventDispatcher.php',
'League\\Container\\Event\\EventFilter' => __DIR__ . '/..' . '/league/container/src/Event/EventFilter.php',
'League\\Container\\Event\\OnDefineEvent' => __DIR__ . '/..' . '/league/container/src/Event/OnDefineEvent.php',
'League\\Container\\Event\\ServiceResolvedEvent' => __DIR__ . '/..' . '/league/container/src/Event/ServiceResolvedEvent.php',
'League\\Container\\Exception\\ContainerException' => __DIR__ . '/..' . '/league/container/src/Exception/ContainerException.php',
'League\\Container\\Exception\\NotFoundException' => __DIR__ . '/..' . '/league/container/src/Exception/NotFoundException.php',
'League\\Container\\Inflector\\Inflector' => __DIR__ . '/..' . '/league/container/src/Inflector/Inflector.php',
'League\\Container\\Inflector\\InflectorAggregate' => __DIR__ . '/..' . '/league/container/src/Inflector/InflectorAggregate.php',
'League\\Container\\Inflector\\InflectorAggregateInterface' => __DIR__ . '/..' . '/league/container/src/Inflector/InflectorAggregateInterface.php',
'League\\Container\\Inflector\\InflectorInterface' => __DIR__ . '/..' . '/league/container/src/Inflector/InflectorInterface.php',
'League\\Container\\ReflectionContainer' => __DIR__ . '/..' . '/league/container/src/ReflectionContainer.php',
'League\\Container\\ServiceProvider\\AbstractServiceProvider' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/AbstractServiceProvider.php',
'League\\Container\\ServiceProvider\\BootableServiceProviderInterface' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/BootableServiceProviderInterface.php',
'League\\Container\\ServiceProvider\\ServiceProviderAggregate' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/ServiceProviderAggregate.php',
'League\\Container\\ServiceProvider\\ServiceProviderAggregateInterface' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/ServiceProviderAggregateInterface.php',
'League\\Container\\ServiceProvider\\ServiceProviderInterface' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/ServiceProviderInterface.php',
'League\\Flysystem\\CalculateChecksumFromStream' => __DIR__ . '/..' . '/league/flysystem/src/CalculateChecksumFromStream.php',
'League\\Flysystem\\ChecksumAlgoIsNotSupported' => __DIR__ . '/..' . '/league/flysystem/src/ChecksumAlgoIsNotSupported.php',
'League\\Flysystem\\ChecksumProvider' => __DIR__ . '/..' . '/league/flysystem/src/ChecksumProvider.php',
@@ -6023,76 +5737,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'PharIo\\Version\\VersionConstraintParser' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintParser.php',
'PharIo\\Version\\VersionConstraintValue' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintValue.php',
'PharIo\\Version\\VersionNumber' => __DIR__ . '/..' . '/phar-io/version/src/VersionNumber.php',
'Phinx\\Config\\Config' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/Config.php',
'Phinx\\Config\\ConfigInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php',
'Phinx\\Config\\FeatureFlags' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/FeatureFlags.php',
'Phinx\\Config\\NamespaceAwareInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php',
'Phinx\\Config\\NamespaceAwareTrait' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php',
'Phinx\\Console\\Command\\AbstractCommand' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php',
'Phinx\\Console\\Command\\Breakpoint' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php',
'Phinx\\Console\\Command\\Create' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Create.php',
'Phinx\\Console\\Command\\Init' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Init.php',
'Phinx\\Console\\Command\\ListAliases' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/ListAliases.php',
'Phinx\\Console\\Command\\Migrate' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php',
'Phinx\\Console\\Command\\Rollback' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php',
'Phinx\\Console\\Command\\SeedCreate' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php',
'Phinx\\Console\\Command\\SeedRun' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php',
'Phinx\\Console\\Command\\Status' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Status.php',
'Phinx\\Console\\Command\\Test' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/Command/Test.php',
'Phinx\\Console\\PhinxApplication' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php',
'Phinx\\Db\\Action\\Action' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/Action.php',
'Phinx\\Db\\Action\\AddColumn' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/AddColumn.php',
'Phinx\\Db\\Action\\AddForeignKey' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/AddForeignKey.php',
'Phinx\\Db\\Action\\AddIndex' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/AddIndex.php',
'Phinx\\Db\\Action\\ChangeColumn' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/ChangeColumn.php',
'Phinx\\Db\\Action\\ChangeComment' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/ChangeComment.php',
'Phinx\\Db\\Action\\ChangePrimaryKey' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/ChangePrimaryKey.php',
'Phinx\\Db\\Action\\CreateTable' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/CreateTable.php',
'Phinx\\Db\\Action\\DropForeignKey' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/DropForeignKey.php',
'Phinx\\Db\\Action\\DropIndex' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/DropIndex.php',
'Phinx\\Db\\Action\\DropTable' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/DropTable.php',
'Phinx\\Db\\Action\\RemoveColumn' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/RemoveColumn.php',
'Phinx\\Db\\Action\\RenameColumn' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/RenameColumn.php',
'Phinx\\Db\\Action\\RenameTable' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Action/RenameTable.php',
'Phinx\\Db\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AbstractAdapter.php',
'Phinx\\Db\\Adapter\\AdapterFactory' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php',
'Phinx\\Db\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php',
'Phinx\\Db\\Adapter\\AdapterWrapper' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php',
'Phinx\\Db\\Adapter\\DirectActionInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/DirectActionInterface.php',
'Phinx\\Db\\Adapter\\MysqlAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php',
'Phinx\\Db\\Adapter\\PdoAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php',
'Phinx\\Db\\Adapter\\PostgresAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php',
'Phinx\\Db\\Adapter\\ProxyAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php',
'Phinx\\Db\\Adapter\\SQLiteAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php',
'Phinx\\Db\\Adapter\\SqlServerAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php',
'Phinx\\Db\\Adapter\\TablePrefixAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php',
'Phinx\\Db\\Adapter\\TimedOutputAdapter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php',
'Phinx\\Db\\Adapter\\UnsupportedColumnTypeException' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/UnsupportedColumnTypeException.php',
'Phinx\\Db\\Adapter\\WrapperInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php',
'Phinx\\Db\\Plan\\AlterTable' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Plan/AlterTable.php',
'Phinx\\Db\\Plan\\Intent' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Plan/Intent.php',
'Phinx\\Db\\Plan\\NewTable' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Plan/NewTable.php',
'Phinx\\Db\\Plan\\Plan' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Plan/Plan.php',
'Phinx\\Db\\Plan\\Solver\\ActionSplitter' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Plan/Solver/ActionSplitter.php',
'Phinx\\Db\\Table' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table.php',
'Phinx\\Db\\Table\\Column' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/Column.php',
'Phinx\\Db\\Table\\ForeignKey' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php',
'Phinx\\Db\\Table\\Index' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/Index.php',
'Phinx\\Db\\Table\\Table' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Table/Table.php',
'Phinx\\Db\\Util\\AlterInstructions' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Db/Util/AlterInstructions.php',
'Phinx\\Migration\\AbstractMigration' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php',
'Phinx\\Migration\\AbstractTemplateCreation' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php',
'Phinx\\Migration\\CreationInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/CreationInterface.php',
'Phinx\\Migration\\IrreversibleMigrationException' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php',
'Phinx\\Migration\\Manager' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/Manager.php',
'Phinx\\Migration\\Manager\\Environment' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php',
'Phinx\\Migration\\MigrationInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Migration/MigrationInterface.php',
'Phinx\\Seed\\AbstractSeed' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php',
'Phinx\\Seed\\SeedInterface' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Seed/SeedInterface.php',
'Phinx\\Util\\Expression' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Util/Expression.php',
'Phinx\\Util\\Literal' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Util/Literal.php',
'Phinx\\Util\\Util' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Util/Util.php',
'Phinx\\Wrapper\\TextWrapper' => __DIR__ . '/..' . '/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php',
'PhpOffice\\PhpSpreadsheet\\Calculation\\ArrayEnabled' => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php',
'PhpOffice\\PhpSpreadsheet\\Calculation\\BinaryComparison' => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php',
'PhpOffice\\PhpSpreadsheet\\Calculation\\Calculation' => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php',
@@ -7833,96 +7477,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'Symfony\\Component\\Clock\\MonotonicClock' => __DIR__ . '/..' . '/symfony/clock/MonotonicClock.php',
'Symfony\\Component\\Clock\\NativeClock' => __DIR__ . '/..' . '/symfony/clock/NativeClock.php',
'Symfony\\Component\\Clock\\Test\\ClockSensitiveTrait' => __DIR__ . '/..' . '/symfony/clock/Test/ClockSensitiveTrait.php',
'Symfony\\Component\\Config\\Builder\\ClassBuilder' => __DIR__ . '/..' . '/symfony/config/Builder/ClassBuilder.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGenerator.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php',
'Symfony\\Component\\Config\\Builder\\ConfigBuilderInterface' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderInterface.php',
'Symfony\\Component\\Config\\Builder\\Method' => __DIR__ . '/..' . '/symfony/config/Builder/Method.php',
'Symfony\\Component\\Config\\Builder\\Property' => __DIR__ . '/..' . '/symfony/config/Builder/Property.php',
'Symfony\\Component\\Config\\ConfigCache' => __DIR__ . '/..' . '/symfony/config/ConfigCache.php',
'Symfony\\Component\\Config\\ConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactory.php',
'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactoryInterface.php',
'Symfony\\Component\\Config\\ConfigCacheInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheInterface.php',
'Symfony\\Component\\Config\\Definition\\ArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/ArrayNode.php',
'Symfony\\Component\\Config\\Definition\\ArrayShapeGenerator' => __DIR__ . '/..' . '/symfony/config/Definition/ArrayShapeGenerator.php',
'Symfony\\Component\\Config\\Definition\\BaseNode' => __DIR__ . '/..' . '/symfony/config/Definition/BaseNode.php',
'Symfony\\Component\\Config\\Definition\\BooleanNode' => __DIR__ . '/..' . '/symfony/config/Definition/BooleanNode.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BuilderAwareInterface.php',
'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/EnumNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ExprBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/FloatNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/MergeBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeParentInterface.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NormalizationBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NumericNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\StringNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/StringNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/TreeBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ValidationBuilder.php',
'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/VariableNodeDefinition.php',
'Symfony\\Component\\Config\\Definition\\ConfigurableInterface' => __DIR__ . '/..' . '/symfony/config/Definition/ConfigurableInterface.php',
'Symfony\\Component\\Config\\Definition\\Configuration' => __DIR__ . '/..' . '/symfony/config/Definition/Configuration.php',
'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => __DIR__ . '/..' . '/symfony/config/Definition/ConfigurationInterface.php',
'Symfony\\Component\\Config\\Definition\\Configurator\\DefinitionConfigurator' => __DIR__ . '/..' . '/symfony/config/Definition/Configurator/DefinitionConfigurator.php',
'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php',
'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php',
'Symfony\\Component\\Config\\Definition\\EnumNode' => __DIR__ . '/..' . '/symfony/config/Definition/EnumNode.php',
'Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/DuplicateKeyException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\Exception' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/Exception.php',
'Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidConfigurationException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidDefinitionException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidTypeException.php',
'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/UnsetKeyException.php',
'Symfony\\Component\\Config\\Definition\\FloatNode' => __DIR__ . '/..' . '/symfony/config/Definition/FloatNode.php',
'Symfony\\Component\\Config\\Definition\\IntegerNode' => __DIR__ . '/..' . '/symfony/config/Definition/IntegerNode.php',
'Symfony\\Component\\Config\\Definition\\Loader\\DefinitionFileLoader' => __DIR__ . '/..' . '/symfony/config/Definition/Loader/DefinitionFileLoader.php',
'Symfony\\Component\\Config\\Definition\\NodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/NodeInterface.php',
'Symfony\\Component\\Config\\Definition\\NumericNode' => __DIR__ . '/..' . '/symfony/config/Definition/NumericNode.php',
'Symfony\\Component\\Config\\Definition\\Processor' => __DIR__ . '/..' . '/symfony/config/Definition/Processor.php',
'Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypeNodeInterface.php',
'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypedArrayNode.php',
'Symfony\\Component\\Config\\Definition\\ScalarNode' => __DIR__ . '/..' . '/symfony/config/Definition/ScalarNode.php',
'Symfony\\Component\\Config\\Definition\\StringNode' => __DIR__ . '/..' . '/symfony/config/Definition/StringNode.php',
'Symfony\\Component\\Config\\Definition\\VariableNode' => __DIR__ . '/..' . '/symfony/config/Definition/VariableNode.php',
'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php',
'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLocatorFileNotFoundException.php',
'Symfony\\Component\\Config\\Exception\\LoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/LoaderLoadException.php',
'Symfony\\Component\\Config\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/config/Exception/LogicException.php',
'Symfony\\Component\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/config/FileLocator.php',
'Symfony\\Component\\Config\\FileLocatorInterface' => __DIR__ . '/..' . '/symfony/config/FileLocatorInterface.php',
'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/config/Loader/DelegatingLoader.php',
'Symfony\\Component\\Config\\Loader\\DirectoryAwareLoaderInterface' => __DIR__ . '/..' . '/symfony/config/Loader/DirectoryAwareLoaderInterface.php',
'Symfony\\Component\\Config\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/FileLoader.php',
'Symfony\\Component\\Config\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/GlobFileLoader.php',
'Symfony\\Component\\Config\\Loader\\Loader' => __DIR__ . '/..' . '/symfony/config/Loader/Loader.php',
'Symfony\\Component\\Config\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderInterface.php',
'Symfony\\Component\\Config\\Loader\\LoaderResolver' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolver.php',
'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolverInterface.php',
'Symfony\\Component\\Config\\Loader\\ParamConfigurator' => __DIR__ . '/..' . '/symfony/config/Loader/ParamConfigurator.php',
'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCache.php',
'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCacheFactory.php',
'Symfony\\Component\\Config\\ResourceCheckerInterface' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerInterface.php',
'Symfony\\Component\\Config\\Resource\\ClassExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/ClassExistenceResource.php',
'Symfony\\Component\\Config\\Resource\\ComposerResource' => __DIR__ . '/..' . '/symfony/config/Resource/ComposerResource.php',
'Symfony\\Component\\Config\\Resource\\DirectoryResource' => __DIR__ . '/..' . '/symfony/config/Resource/DirectoryResource.php',
'Symfony\\Component\\Config\\Resource\\FileExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileExistenceResource.php',
'Symfony\\Component\\Config\\Resource\\FileResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileResource.php',
'Symfony\\Component\\Config\\Resource\\GlobResource' => __DIR__ . '/..' . '/symfony/config/Resource/GlobResource.php',
'Symfony\\Component\\Config\\Resource\\ReflectionClassResource' => __DIR__ . '/..' . '/symfony/config/Resource/ReflectionClassResource.php',
'Symfony\\Component\\Config\\Resource\\ResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/ResourceInterface.php',
'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceChecker.php',
'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceInterface.php',
'Symfony\\Component\\Config\\Resource\\SkippingResourceChecker' => __DIR__ . '/..' . '/symfony/config/Resource/SkippingResourceChecker.php',
'Symfony\\Component\\Config\\Util\\Exception\\InvalidXmlException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/InvalidXmlException.php',
'Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/XmlParsingException.php',
'Symfony\\Component\\Config\\Util\\XmlUtils' => __DIR__ . '/..' . '/symfony/config/Util/XmlUtils.php',
'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php',
'Symfony\\Component\\Console\\Attribute\\Argument' => __DIR__ . '/..' . '/symfony/console/Attribute/Argument.php',
'Symfony\\Component\\Console\\Attribute\\AsCommand' => __DIR__ . '/..' . '/symfony/console/Attribute/AsCommand.php',
@@ -8131,14 +7685,6 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventSubscriberInterface.php',
'Symfony\\Component\\EventDispatcher\\GenericEvent' => __DIR__ . '/..' . '/symfony/event-dispatcher/GenericEvent.php',
'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ImmutableEventDispatcher.php',
'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php',
'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php',
'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php',
'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php',
'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php',
'Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/RuntimeException.php',
'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php',
'Symfony\\Component\\Filesystem\\Path' => __DIR__ . '/..' . '/symfony/filesystem/Path.php',
'Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php',
'Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php',
'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php',
-671
View File
@@ -593,345 +593,6 @@
],
"install-path": "../brick/math"
},
{
"name": "cakephp/chronos",
"version": "3.5.0",
"version_normalized": "3.5.0.0",
"source": {
"type": "git",
"url": "https://github.com/cakephp/chronos.git",
"reference": "e6e777b534244911566face8a5dbdbd7f7bda5a6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/chronos/zipball/e6e777b534244911566face8a5dbdbd7f7bda5a6",
"reference": "e6e777b534244911566face8a5dbdbd7f7bda5a6",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/clock": "^1.0"
},
"provide": {
"psr/clock-implementation": "1.0"
},
"require-dev": {
"cakephp/cakephp-codesniffer": "^5.0",
"phpunit/phpunit": "^10.5.58 || ^11.5.3 || ^12.1.3"
},
"time": "2026-04-10T02:50:39+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Cake\\Chronos\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Nesbitt",
"email": "brian@nesbot.com",
"homepage": "http://nesbot.com"
},
{
"name": "The CakePHP Team",
"homepage": "https://cakephp.org"
}
],
"description": "A simple API extension for DateTime.",
"homepage": "https://cakephp.org",
"keywords": [
"date",
"datetime",
"time"
],
"support": {
"issues": "https://github.com/cakephp/chronos/issues",
"source": "https://github.com/cakephp/chronos"
},
"install-path": "../cakephp/chronos"
},
{
"name": "cakephp/core",
"version": "5.3.6",
"version_normalized": "5.3.6.0",
"source": {
"type": "git",
"url": "https://github.com/cakephp/core.git",
"reference": "9c458b0e9322ec88bc4c758b33cde6a0abf49d12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/core/zipball/9c458b0e9322ec88bc4c758b33cde6a0abf49d12",
"reference": "9c458b0e9322ec88bc4c758b33cde6a0abf49d12",
"shasum": ""
},
"require": {
"cakephp/utility": "^5.3.0",
"league/container": "^5.1",
"php": ">=8.2",
"psr/container": "^1.1 || ^2.0"
},
"provide": {
"psr/container-implementation": "^2.0"
},
"suggest": {
"cakephp/cache": "To use Configure::store() and restore().",
"cakephp/event": "To use PluginApplicationInterface or plugin applications.",
"league/container": "To use Container and ServiceProvider classes"
},
"time": "2026-05-15T03:31:14+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-5.next": "5.4.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"functions.php"
],
"psr-4": {
"Cake\\Core\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/core/graphs/contributors"
}
],
"description": "CakePHP Framework Core classes",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"core",
"framework"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/core"
},
"install-path": "../cakephp/core"
},
{
"name": "cakephp/database",
"version": "5.3.6",
"version_normalized": "5.3.6.0",
"source": {
"type": "git",
"url": "https://github.com/cakephp/database.git",
"reference": "442d12bf0a1edeffc555a59de9f955cb0619c7ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/database/zipball/442d12bf0a1edeffc555a59de9f955cb0619c7ca",
"reference": "442d12bf0a1edeffc555a59de9f955cb0619c7ca",
"shasum": ""
},
"require": {
"cakephp/chronos": "^3.3",
"cakephp/core": "^5.3.0",
"cakephp/datasource": "^5.3.0",
"php": ">=8.2",
"psr/log": "^3.0"
},
"require-dev": {
"cakephp/i18n": "^5.3.0",
"cakephp/log": "^5.3.0"
},
"suggest": {
"cakephp/i18n": "If you are using locale-aware datetime formats.",
"cakephp/log": "If you want to use query logging without providing a logger yourself."
},
"time": "2026-05-21T19:38:13+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-5.next": "5.4.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Cake\\Database\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/database/graphs/contributors"
}
],
"description": "Flexible and powerful Database abstraction library with a familiar PDO-like API",
"homepage": "https://cakephp.org",
"keywords": [
"abstraction",
"cakephp",
"database",
"database abstraction",
"pdo"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/database"
},
"install-path": "../cakephp/database"
},
{
"name": "cakephp/datasource",
"version": "5.3.6",
"version_normalized": "5.3.6.0",
"source": {
"type": "git",
"url": "https://github.com/cakephp/datasource.git",
"reference": "b768f0c0bf0fca815f83c4caef14e7fbb2409bf5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/datasource/zipball/b768f0c0bf0fca815f83c4caef14e7fbb2409bf5",
"reference": "b768f0c0bf0fca815f83c4caef14e7fbb2409bf5",
"shasum": ""
},
"require": {
"cakephp/core": "^5.3.0",
"php": ">=8.2",
"psr/simple-cache": "^2.0 || ^3.0"
},
"require-dev": {
"cakephp/cache": "^5.3.0",
"cakephp/collection": "^5.3.0",
"cakephp/utility": "^5.3.0"
},
"suggest": {
"cakephp/cache": "If you decide to use Query caching.",
"cakephp/collection": "If you decide to use ResultSetInterface.",
"cakephp/utility": "If you decide to use EntityTrait."
},
"time": "2026-05-20T10:27:33+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-5.next": "5.4.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Cake\\Datasource\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/datasource/graphs/contributors"
}
],
"description": "Provides connection managing and traits for Entities and Queries that can be reused for different datastores",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"connection management",
"datasource",
"entity",
"query"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/datasource"
},
"install-path": "../cakephp/datasource"
},
{
"name": "cakephp/utility",
"version": "5.3.6",
"version_normalized": "5.3.6.0",
"source": {
"type": "git",
"url": "https://github.com/cakephp/utility.git",
"reference": "4c703a010b9d955fed44731669e35d3043425cc7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/utility/zipball/4c703a010b9d955fed44731669e35d3043425cc7",
"reference": "4c703a010b9d955fed44731669e35d3043425cc7",
"shasum": ""
},
"require": {
"cakephp/core": "^5.3.0",
"php": ">=8.2"
},
"suggest": {
"ext-intl": "To use Text::transliterate() or Text::slug()",
"lib-ICU": "To use Text::transliterate() or Text::slug()"
},
"time": "2026-05-21T19:38:13+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-5.next": "5.4.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Cake\\Utility\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/utility/graphs/contributors"
}
],
"description": "CakePHP Utility classes such as Inflector, String, Hash, and Security",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"hash",
"inflector",
"security",
"string",
"utility"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/utility"
},
"install-path": "../cakephp/utility"
},
{
"name": "carbonphp/carbon-doctrine-types",
"version": "3.2.0",
@@ -3979,93 +3640,6 @@
],
"install-path": "../league/config"
},
{
"name": "league/container",
"version": "5.2.0",
"version_normalized": "5.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/container.git",
"reference": "58accbc032f0090a9bd08326f93062c5a658b2c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/container/zipball/58accbc032f0090a9bd08326f93062c5a658b2c5",
"reference": "58accbc032f0090a9bd08326f93062c5a658b2c5",
"shasum": ""
},
"require": {
"php": "^8.1",
"psr/container": "^2.0.2",
"psr/event-dispatcher": "^1.0"
},
"provide": {
"psr/container-implementation": "^1.0"
},
"replace": {
"orno/di": "~2.0"
},
"require-dev": {
"nette/php-generator": "^4.1",
"nikic/php-parser": "^5.0",
"phpstan/phpstan": "^2.1.11",
"phpunit/phpunit": "^10.5.45|^11.5.15|^12.0",
"roave/security-advisories": "dev-latest",
"scrutinizer/ocular": "^1.9",
"squizlabs/php_codesniffer": "^3.9"
},
"time": "2026-03-19T18:52:39+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev",
"dev-2.x": "2.x-dev",
"dev-3.x": "3.x-dev",
"dev-4.x": "4.x-dev",
"dev-5.x": "5.x-dev",
"dev-master": "5.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"League\\Container\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Phil Bennett",
"email": "mail@philbennett.co.uk",
"role": "Developer"
}
],
"description": "A fast and intuitive dependency injection container.",
"homepage": "https://github.com/thephpleague/container",
"keywords": [
"container",
"dependency",
"di",
"injection",
"league",
"provider",
"service"
],
"support": {
"issues": "https://github.com/thephpleague/container/issues",
"source": "https://github.com/thephpleague/container/tree/5.2.0"
},
"funding": [
{
"url": "https://github.com/philipobenito",
"type": "github"
}
],
"install-path": "../league/container"
},
{
"name": "league/flysystem",
"version": "3.28.0",
@@ -7572,96 +7146,6 @@
],
"install-path": "../ramsey/uuid"
},
{
"name": "robmorgan/phinx",
"version": "0.16.12",
"version_normalized": "0.16.12.0",
"source": {
"type": "git",
"url": "https://github.com/cakephp/phinx.git",
"reference": "1cce44387a9146dc04c312e6a231b17fb30385cd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/phinx/zipball/1cce44387a9146dc04c312e6a231b17fb30385cd",
"reference": "1cce44387a9146dc04c312e6a231b17fb30385cd",
"shasum": ""
},
"require": {
"cakephp/database": "^5.0.2",
"composer-runtime-api": "^2.0",
"php-64bit": ">=8.1",
"psr/container": "^1.1|^2.0",
"symfony/config": "^4.0|^5.0|^6.0|^7.0|^8.0",
"symfony/console": "^6.0|^7.0|^8.0"
},
"require-dev": {
"cakephp/cakephp-codesniffer": "^5.0",
"cakephp/i18n": "^5.0",
"ext-json": "*",
"ext-pdo": "*",
"phpunit/phpunit": "^10.5",
"symfony/yaml": "^4.0|^5.0|^6.0|^7.0|^8.0"
},
"suggest": {
"ext-json": "Install if using JSON configuration format",
"ext-pdo": "PDO extension is needed",
"symfony/yaml": "Install if using YAML configuration format"
},
"time": "2026-07-03T07:27:17+00:00",
"bin": [
"bin/phinx"
],
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Phinx\\": "src/Phinx/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Morgan",
"email": "robbym@gmail.com",
"homepage": "https://robmorgan.id.au",
"role": "Lead Developer"
},
{
"name": "Woody Gilk",
"email": "woody.gilk@gmail.com",
"homepage": "https://shadowhand.me",
"role": "Developer"
},
{
"name": "Richard Quadling",
"email": "rquadling@gmail.com",
"role": "Developer"
},
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/phinx/graphs/contributors",
"role": "Developer"
}
],
"description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
"homepage": "https://phinx.org",
"keywords": [
"database",
"database migrations",
"db",
"migrations",
"phinx"
],
"support": {
"issues": "https://github.com/cakephp/phinx/issues",
"source": "https://github.com/cakephp/phinx/tree/0.16.12"
},
"install-path": "../robmorgan/phinx"
},
{
"name": "sebastian/cli-parser",
"version": "2.0.1",
@@ -9365,88 +8849,6 @@
],
"install-path": "../symfony/clock"
},
{
"name": "symfony/config",
"version": "v7.4.14",
"version_normalized": "7.4.14.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "7b665e443381ea7c4db03eb03b4bf79ea2b020eb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/7b665e443381ea7c4db03eb03b4bf79ea2b020eb",
"reference": "7b665e443381ea7c4db03eb03b4bf79ea2b020eb",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/filesystem": "^7.1|^8.0",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/finder": "<6.4",
"symfony/service-contracts": "<2.5"
},
"require-dev": {
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",
"symfony/finder": "^6.4|^7.0|^8.0",
"symfony/messenger": "^6.4|^7.0|^8.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/yaml": "^6.4|^7.0|^8.0"
},
"time": "2026-06-09T07:51:57+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Config\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/config/tree/v7.4.14"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/config"
},
{
"name": "symfony/console",
"version": "v7.1.3",
@@ -9921,79 +9323,6 @@
],
"install-path": "../symfony/event-dispatcher-contracts"
},
{
"name": "symfony/filesystem",
"version": "v7.4.11",
"version_normalized": "7.4.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/d721ea61b4a5fba8c5b6e7c1feda19efea144b50",
"reference": "d721ea61b4a5fba8c5b6e7c1feda19efea144b50",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
},
"require-dev": {
"symfony/process": "^6.4|^7.0|^8.0"
},
"time": "2026-05-11T16:38:44+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v7.4.11"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/filesystem"
},
{
"name": "symfony/finder",
"version": "v7.1.3",
+2 -91
View File
@@ -3,7 +3,7 @@
'name' => 'loshmis/vanguard',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'b19963808e5545f0b99bf0eba214b0c85b2f4b7b',
'reference' => 'baf3f6da32fd5bc669ac7360970be21407d1d384',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -82,51 +82,6 @@
'aliases' => array(),
'dev_requirement' => false,
),
'cakephp/chronos' => array(
'pretty_version' => '3.5.0',
'version' => '3.5.0.0',
'reference' => 'e6e777b534244911566face8a5dbdbd7f7bda5a6',
'type' => 'library',
'install_path' => __DIR__ . '/../cakephp/chronos',
'aliases' => array(),
'dev_requirement' => false,
),
'cakephp/core' => array(
'pretty_version' => '5.3.6',
'version' => '5.3.6.0',
'reference' => '9c458b0e9322ec88bc4c758b33cde6a0abf49d12',
'type' => 'library',
'install_path' => __DIR__ . '/../cakephp/core',
'aliases' => array(),
'dev_requirement' => false,
),
'cakephp/database' => array(
'pretty_version' => '5.3.6',
'version' => '5.3.6.0',
'reference' => '442d12bf0a1edeffc555a59de9f955cb0619c7ca',
'type' => 'library',
'install_path' => __DIR__ . '/../cakephp/database',
'aliases' => array(),
'dev_requirement' => false,
),
'cakephp/datasource' => array(
'pretty_version' => '5.3.6',
'version' => '5.3.6.0',
'reference' => 'b768f0c0bf0fca815f83c4caef14e7fbb2409bf5',
'type' => 'library',
'install_path' => __DIR__ . '/../cakephp/datasource',
'aliases' => array(),
'dev_requirement' => false,
),
'cakephp/utility' => array(
'pretty_version' => '5.3.6',
'version' => '5.3.6.0',
'reference' => '4c703a010b9d955fed44731669e35d3043425cc7',
'type' => 'library',
'install_path' => __DIR__ . '/../cakephp/utility',
'aliases' => array(),
'dev_requirement' => false,
),
'carbonphp/carbon-doctrine-types' => array(
'pretty_version' => '3.2.0',
'version' => '3.2.0.0',
@@ -694,15 +649,6 @@
'aliases' => array(),
'dev_requirement' => false,
),
'league/container' => array(
'pretty_version' => '5.2.0',
'version' => '5.2.0.0',
'reference' => '58accbc032f0090a9bd08326f93062c5a658b2c5',
'type' => 'library',
'install_path' => __DIR__ . '/../league/container',
'aliases' => array(),
'dev_requirement' => false,
),
'league/flysystem' => array(
'pretty_version' => '3.28.0',
'version' => '3.28.0.0',
@@ -742,7 +688,7 @@
'loshmis/vanguard' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'b19963808e5545f0b99bf0eba214b0c85b2f4b7b',
'reference' => 'baf3f6da32fd5bc669ac7360970be21407d1d384',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -880,12 +826,6 @@
'aliases' => array(),
'dev_requirement' => false,
),
'orno/di' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '~2.0',
),
),
'paragonie/constant_time_encoding' => array(
'pretty_version' => 'v2.7.0',
'version' => '2.7.0.0',
@@ -1085,8 +1025,6 @@
'dev_requirement' => false,
'provided' => array(
0 => '1.1|2.0',
1 => '^1.0',
2 => '^2.0',
),
),
'psr/event-dispatcher' => array(
@@ -1222,15 +1160,6 @@
0 => '4.7.6',
),
),
'robmorgan/phinx' => array(
'pretty_version' => '0.16.12',
'version' => '0.16.12.0',
'reference' => '1cce44387a9146dc04c312e6a231b17fb30385cd',
'type' => 'library',
'install_path' => __DIR__ . '/../robmorgan/phinx',
'aliases' => array(),
'dev_requirement' => false,
),
'sebastian/cli-parser' => array(
'pretty_version' => '2.0.1',
'version' => '2.0.1.0',
@@ -1462,15 +1391,6 @@
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/config' => array(
'pretty_version' => 'v7.4.14',
'version' => '7.4.14.0',
'reference' => '7b665e443381ea7c4db03eb03b4bf79ea2b020eb',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/config',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/console' => array(
'pretty_version' => 'v7.1.3',
'version' => '7.1.3.0',
@@ -1531,15 +1451,6 @@
0 => '2.0|3.0',
),
),
'symfony/filesystem' => array(
'pretty_version' => 'v7.4.11',
'version' => '7.4.11.0',
'reference' => 'd721ea61b4a5fba8c5b6e7c1feda19efea144b50',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/finder' => array(
'pretty_version' => 'v7.1.3',
'version' => '7.1.3.0',
+3 -2
View File
@@ -23,7 +23,8 @@ if ($issues) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}