Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ec5419a86 | |||
| c05091e020 | |||
| 0b470f290e | |||
| e74870c8d3 | |||
| 9001eff317 | |||
| 650676037a | |||
| 2fc34c3cf4 | |||
| 955a7ed9e9 | |||
| e6a805f1f7 | |||
| fe84d446e7 | |||
| 2ddf575191 | |||
| d73a8bb8d3 |
@@ -31,6 +31,8 @@ MAIL_USERNAME=null
|
|||||||
MAIL_PASSWORD=null
|
MAIL_PASSWORD=null
|
||||||
MAIL_ENCRYPTION=null
|
MAIL_ENCRYPTION=null
|
||||||
|
|
||||||
|
MANAGER_USER_ID=
|
||||||
|
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
PUSHER_APP_KEY=
|
PUSHER_APP_KEY=
|
||||||
PUSHER_APP_SECRET=
|
PUSHER_APP_SECRET=
|
||||||
|
|||||||
@@ -66,3 +66,6 @@ public/userarea/logsapi/commessaweb_customfields_763.json
|
|||||||
public/userarea/logsapi/commessaweb_invia_762.json
|
public/userarea/logsapi/commessaweb_invia_762.json
|
||||||
public/userarea/logsapi/commessaweb_invia_763.json
|
public/userarea/logsapi/commessaweb_invia_763.json
|
||||||
public/userarea/logsapi/last_auth_url.txt
|
public/userarea/logsapi/last_auth_url.txt
|
||||||
|
|
||||||
|
# User uploaded files
|
||||||
|
/public/userarea/files/
|
||||||
@@ -111,6 +111,14 @@ class LoginController extends Controller
|
|||||||
return redirect()->to('userarea/production_dashboard.php');
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
} elseif ($user->hasRole('User')) {
|
} elseif ($user->hasRole('User')) {
|
||||||
return redirect()->to('userarea/production_dashboard.php');
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('HR')) {
|
||||||
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('SuperUser')) {
|
||||||
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('Management')) {
|
||||||
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
|
} elseif ($user->hasRole('Quality')) {
|
||||||
|
return redirect()->to('userarea/production_dashboard.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se il ruolo non è specificato, reindirizza alla home predefinita
|
// Se il ruolo non è specificato, reindirizza alla home predefinita
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"phpmailer/phpmailer": "^6.9",
|
"phpmailer/phpmailer": "^6.9",
|
||||||
"phpoffice/phpspreadsheet": "^4.1",
|
"phpoffice/phpspreadsheet": "^4.1",
|
||||||
"proengsoft/laravel-jsvalidation": "^4.0.0",
|
"proengsoft/laravel-jsvalidation": "^4.0.0",
|
||||||
|
"robmorgan/phinx": "^0.16.11",
|
||||||
"socialiteproviders/microsoft": "^4.7",
|
"socialiteproviders/microsoft": "^4.7",
|
||||||
"spatie/laravel-query-builder": "^5.0",
|
"spatie/laravel-query-builder": "^5.0",
|
||||||
"vanguardapp/activity-log": "^6.0",
|
"vanguardapp/activity-log": "^6.0",
|
||||||
|
|||||||
Generated
+646
-2
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "9c4f1e3bc3ee2180211c055e70635aef",
|
"content-hash": "076e7721d08cfea8b06ce75dd8c6c576",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "akaunting/laravel-setting",
|
"name": "akaunting/laravel-setting",
|
||||||
@@ -251,6 +251,330 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-11-29T23:19:16+00:00"
|
"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.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/core.git",
|
||||||
|
"reference": "eb012517900ed288f580aa3487e9a09f28ea85f9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/core/zipball/eb012517900ed288f580aa3487e9a09f28ea85f9",
|
||||||
|
"reference": "eb012517900ed288f580aa3487e9a09f28ea85f9",
|
||||||
|
"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-03-31T06:25:23+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cakephp/database",
|
||||||
|
"version": "5.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/database.git",
|
||||||
|
"reference": "cf94dcb57c54a1a308fd866b038cd6995910e36e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/database/zipball/cf94dcb57c54a1a308fd866b038cd6995910e36e",
|
||||||
|
"reference": "cf94dcb57c54a1a308fd866b038cd6995910e36e",
|
||||||
|
"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-03-31T06:25:23+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cakephp/datasource",
|
||||||
|
"version": "5.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/datasource.git",
|
||||||
|
"reference": "512464eb27b19316b515ec338089b83822c9ab5a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/datasource/zipball/512464eb27b19316b515ec338089b83822c9ab5a",
|
||||||
|
"reference": "512464eb27b19316b515ec338089b83822c9ab5a",
|
||||||
|
"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-04-04T08:08:42+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cakephp/utility",
|
||||||
|
"version": "5.3.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/utility.git",
|
||||||
|
"reference": "4ac9826fe5faa1505ec5aa3c171d6b58b6ab4e99"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/utility/zipball/4ac9826fe5faa1505ec5aa3c171d6b58b6ab4e99",
|
||||||
|
"reference": "4ac9826fe5faa1505ec5aa3c171d6b58b6ab4e99",
|
||||||
|
"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-03-09T09:38:36+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "carbonphp/carbon-doctrine-types",
|
"name": "carbonphp/carbon-doctrine-types",
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
@@ -2627,6 +2951,90 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-12-11T20:36:23+00:00"
|
"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",
|
"name": "league/flysystem",
|
||||||
"version": "3.28.0",
|
"version": "3.28.0",
|
||||||
@@ -4980,6 +5388,93 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-04-27T21:32:50+00:00"
|
"time": "2024-04-27T21:32:50+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "robmorgan/phinx",
|
||||||
|
"version": "0.16.11",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cakephp/phinx.git",
|
||||||
|
"reference": "a03014fea316ba021fc0776982e5bed2d10228d4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cakephp/phinx/zipball/a03014fea316ba021fc0776982e5bed2d10228d4",
|
||||||
|
"reference": "a03014fea316ba021fc0776982e5bed2d10228d4",
|
||||||
|
"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.11"
|
||||||
|
},
|
||||||
|
"time": "2026-03-15T00:04:32+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "socialiteproviders/manager",
|
"name": "socialiteproviders/manager",
|
||||||
"version": "v4.8.1",
|
"version": "v4.8.1",
|
||||||
@@ -5312,6 +5807,85 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-05-31T14:57:53+00:00"
|
"time": "2024-05-31T14:57:53+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/config",
|
||||||
|
"version": "v7.4.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/config.git",
|
||||||
|
"reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/config/zipball/d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57",
|
||||||
|
"reference": "d91b6c7cd2a8c9a9c2b8d26c8f5ed48edf99ef57",
|
||||||
|
"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.10"
|
||||||
|
},
|
||||||
|
"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-03T14:20:49+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/console",
|
"name": "symfony/console",
|
||||||
"version": "v7.1.3",
|
"version": "v7.1.3",
|
||||||
@@ -5768,6 +6342,76 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-04-18T09:32:20+00:00"
|
"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",
|
"name": "symfony/finder",
|
||||||
"version": "v7.1.3",
|
"version": "v7.1.3",
|
||||||
@@ -11355,6 +11999,6 @@
|
|||||||
"php": "^8.2.0",
|
"php": "^8.2.0",
|
||||||
"ext-json": "*"
|
"ext-json": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class BaselineExistingDatabase extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
// Baseline migration.
|
||||||
|
// Existing database structure starts being tracked from this point.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class CreatePhinxTestTable extends AbstractMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Change Method.
|
||||||
|
*
|
||||||
|
* Write your reversible migrations using this method.
|
||||||
|
*
|
||||||
|
* More information on writing migrations is available here:
|
||||||
|
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||||
|
*
|
||||||
|
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||||
|
* with the Table class.
|
||||||
|
*/
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$table = $this->table('phinx_test_table');
|
||||||
|
|
||||||
|
$table
|
||||||
|
->addColumn('name', 'string', [
|
||||||
|
'limit' => 100,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddFunctionsToScadDeadlines extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function change(): void
|
||||||
|
{
|
||||||
|
$this->table('scad_functions', [
|
||||||
|
'id' => false,
|
||||||
|
'primary_key' => ['id'],
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'encoding' => 'utf8mb4',
|
||||||
|
])
|
||||||
|
->addColumn('id', 'integer', [
|
||||||
|
'identity' => true,
|
||||||
|
'signed' => false,
|
||||||
|
])
|
||||||
|
->addColumn('name', 'string', [
|
||||||
|
'limit' => 255,
|
||||||
|
'null' => false,
|
||||||
|
])
|
||||||
|
->addColumn('description', 'text', [
|
||||||
|
'null' => true,
|
||||||
|
])
|
||||||
|
->addColumn('status', 'string', [
|
||||||
|
'limit' => 20,
|
||||||
|
'null' => false,
|
||||||
|
'default' => 'active',
|
||||||
|
])
|
||||||
|
->addColumn('created_at', 'timestamp', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addColumn('updated_at', 'timestamp', [
|
||||||
|
'null' => false,
|
||||||
|
'default' => 'CURRENT_TIMESTAMP',
|
||||||
|
'update' => 'CURRENT_TIMESTAMP',
|
||||||
|
])
|
||||||
|
->addIndex(['name'], [
|
||||||
|
'unique' => true,
|
||||||
|
'name' => 'uniq_scad_functions_name',
|
||||||
|
])
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->table('scad_deadlines')
|
||||||
|
->addColumn('function_id', 'integer', [
|
||||||
|
'signed' => false,
|
||||||
|
'null' => true,
|
||||||
|
'after' => 'subject_id',
|
||||||
|
])
|
||||||
|
->addIndex(['function_id'], [
|
||||||
|
'name' => 'idx_scad_deadlines_function_id',
|
||||||
|
])
|
||||||
|
->addForeignKey('function_id', 'scad_functions', 'id', [
|
||||||
|
'delete' => 'SET_NULL',
|
||||||
|
'update' => 'CASCADE',
|
||||||
|
'constraint' => 'fk_scad_deadlines_function',
|
||||||
|
])
|
||||||
|
->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
if (file_exists(__DIR__ . '/.env')) {
|
||||||
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||||
|
$dotenv->safeLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'paths' => [
|
||||||
|
'migrations' => __DIR__ . '/db/migrations',
|
||||||
|
'seeds' => __DIR__ . '/db/seeds',
|
||||||
|
],
|
||||||
|
|
||||||
|
'environments' => [
|
||||||
|
'default_migration_table' => 'phinxlog',
|
||||||
|
'default_environment' => 'development',
|
||||||
|
|
||||||
|
'development' => [
|
||||||
|
'adapter' => $_ENV['DB_CONNECTION'] ?? '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',
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'version_order' => 'creation',
|
||||||
|
];
|
||||||
@@ -62,5 +62,5 @@ $photousername = basename($avatar);
|
|||||||
require_once(__DIR__ . '/../../languages/en/general.php');
|
require_once(__DIR__ . '/../../languages/en/general.php');
|
||||||
|
|
||||||
//include("generalsettings.php");
|
//include("generalsettings.php");
|
||||||
|
require_once __DIR__ . '/permissions_helper.php';
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -6,117 +6,386 @@
|
|||||||
<div>
|
<div>
|
||||||
<h4 class="logo-text"><?= htmlspecialchars('ZIBOGOMMA', ENT_QUOTES, 'UTF-8'); ?></h4>
|
<h4 class="logo-text"><?= htmlspecialchars('ZIBOGOMMA', ENT_QUOTES, 'UTF-8'); ?></h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-icon ms-auto"><i class='bx bx-arrow-back'></i>
|
<div class="toggle-icon ms-auto">
|
||||||
|
<i class='bx bx-arrow-back'></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--navigation-->
|
<!--navigation-->
|
||||||
<ul class="metismenu" id="menu">
|
<ul class="metismenu" id="menu">
|
||||||
<!-- user, admin, superuser menù -->
|
|
||||||
<?php if ((Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('User')) || (Auth::user()->hasRole('Superuser'))) : ?>
|
<?php if (userCan('production.dashboard.view')) : ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="production_dashboard.php">
|
<a href="production_dashboard.php">
|
||||||
<div class="parent-icon"><i class="bx bx-home-alt"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-home-alt"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Dashboard</div>
|
<div class="menu-title">Dashboard</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$canSeeProgramming =
|
||||||
|
userCan('production.programming.view')
|
||||||
|
|| userCan('templates.dashboard.view')
|
||||||
|
|| userCan('templates.create.view');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($canSeeProgramming) : ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class="bx bx-category"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-category"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Programmazione</div>
|
<div class="menu-title">Programmazione</div>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
|
||||||
<li> <a href="templates_dashboard.php"><i class='bx bx-radio-circle'></i><?= htmlspecialchars($dashtemplate, ENT_QUOTES, 'UTF-8'); ?></a>
|
|
||||||
</li>
|
|
||||||
<li> <a href="insert_template_xls.php"><i class='bx bx-radio-circle'></i><?= htmlspecialchars($insertnewtemplatexls, ENT_QUOTES, 'UTF-8'); ?></a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<?php if (userCan('templates.dashboard.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="templates_dashboard.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>
|
||||||
|
<?= htmlspecialchars($dashtemplate, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('templates.create.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="insert_template_xls.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>
|
||||||
|
<?= htmlspecialchars($insertnewtemplatexls, ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('production.programming.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="produzione_programmazione_drag.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>
|
||||||
|
Programmazione Produzione
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$canSeeFunctions =
|
||||||
|
userCan('masterdata.mescole.view')
|
||||||
|
|| userCan('masterdata.matrici.view')
|
||||||
|
|| userCan('masterdata.linee.view')
|
||||||
|
|| userCan('masterdata.packaging.view')
|
||||||
|
|| userCan('masterdata.suppliers.view')
|
||||||
|
|| userCan('masterdata.lookup.view')
|
||||||
|
|| userCan('masterdata.worksheets.view');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($canSeeFunctions) : ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class="bx bx-category"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-category"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Funzioni</div>
|
<div class="menu-title">Funzioni</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<?php if (userCan('masterdata.mescole.view')) : ?>
|
||||||
<a href="mescole.php"><i class='bx bx-radio-circle'></i>Mescole</a>
|
<li>
|
||||||
</li>
|
<a href="mescole.php">
|
||||||
<li>
|
<i class='bx bx-radio-circle'></i>Mescole
|
||||||
<a href="matrici.php"><i class='bx bx-radio-circle'></i>Matrici</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<?php endif; ?>
|
||||||
<a href="linee.php"><i class='bx bx-radio-circle'></i>Linee di produzione</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
<?php if (userCan('masterdata.matrici.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="matrici.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Matrici
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('masterdata.linee.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="linee.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Linee di produzione
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('masterdata.packaging.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="packaging_items.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Imballaggi
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('masterdata.suppliers.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="suppliers.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Suppliers
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('masterdata.lookup.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="lookup_values.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Setup
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('masterdata.worksheets.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="worksheets.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Fogli di lavoro
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$canSeeProduction =
|
||||||
|
userCan('production.line_view.view')
|
||||||
|
|| userCan('production.stats.view')
|
||||||
|
|| userCan('production.manager.view')
|
||||||
|
|| userCan('production.manager_stats.view')
|
||||||
|
|| userCan('warehouse.dashboard.view');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($canSeeProduction) : ?>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" class="has-arrow">
|
<a href="javascript:;" class="has-arrow">
|
||||||
<div class="parent-icon"><i class="bx bx-calendar-check"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-line-chart"></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Produzione</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<?php if (userCan('production.line_view.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="production_line_view2.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Line View
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('production.stats.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="production_stats.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Statistiche
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('production.manager.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="manager_produzione.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Manager
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('production.manager_stats.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="manager_stats.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Manager Stats
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('warehouse.dashboard.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="warehouse_dashboard.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Magazzino
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$canSeeServices =
|
||||||
|
userCan('services.status.view')
|
||||||
|
|| userCan('services.pause_reasons.view')
|
||||||
|
|| userCan('services.tools.view');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($canSeeServices) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:;" class="has-arrow">
|
||||||
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-wrench"></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Servizi</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<?php if (userCan('services.status.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="production_status.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Status
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('services.pause_reasons.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="production_pause_reasons.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Cause di Pausa
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('services.tools.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="production_tools.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Attrezzature
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$canSeeHr =
|
||||||
|
userCan('hr.employees.view')
|
||||||
|
|| userCan('hr.departments.view')
|
||||||
|
|| userCan('hr.job_roles.view')
|
||||||
|
|| userCan('hr.training_topics.view')
|
||||||
|
|| userCan('hr.trainings.view')
|
||||||
|
|| userCan('hr.skills.view');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($canSeeHr) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:;" class="has-arrow">
|
||||||
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-group"></i>
|
||||||
|
</div>
|
||||||
|
<div class="menu-title">Personale</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<?php if (userCan('hr.employees.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="employees.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Dipendenti
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('hr.departments.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="departments.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Reparti
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('hr.job_roles.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="job_roles.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Mansioni
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('hr.training_topics.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="training_topics.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Corsi di Formazione
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('hr.trainings.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="trainings.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Storico Formazione
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (userCan('hr.skills.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="skills.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Skills
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php if (userCan('deadlines.view')) : ?>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:;" class="has-arrow">
|
||||||
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-calendar-check"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">Scadenzario</div>
|
<div class="menu-title">Scadenzario</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="scadenzario/index.php"><i class='bx bx-radio-circle'></i>Lista Scadenze</a>
|
<a href="scadenzario/index.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Lista Scadenze
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="scadenzario/calendar.php"><i class='bx bx-radio-circle'></i>Calendario</a>
|
<a href="scadenzario/calendar.php">
|
||||||
|
<i class='bx bx-radio-circle'></i>Calendario
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
<li class="menu-label">Others</li>
|
|
||||||
|
|
||||||
|
|
||||||
<li>
|
<li class="menu-label">Others</li>
|
||||||
<a href="https://helpdesk.cesoft.io" target="_blank">
|
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
<li>
|
||||||
</div>
|
<a href="https://helpdesk.cesoft.io" target="_blank">
|
||||||
<div class="menu-title">Support</div>
|
<div class="parent-icon">
|
||||||
</a>
|
<i class="bx bx-support"></i>
|
||||||
</li>
|
</div>
|
||||||
<?php
|
<div class="menu-title">Support</div>
|
||||||
endif; ?>
|
</a>
|
||||||
<!-- admin, superuser menù -->
|
</li>
|
||||||
<?php if ((Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('Superuser'))) : ?>
|
|
||||||
<?php
|
|
||||||
endif; ?>
|
<?php if (userCan('users.manage')) : ?>
|
||||||
<!-- admin menù -->
|
|
||||||
<?php if (Auth::user()->hasRole('Admin')) : ?>
|
|
||||||
<li class="menu-label">Admin Menù</li>
|
<li class="menu-label">Admin Menù</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="../" target="_blank">
|
<a href="../" target="_blank">
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
<div class="parent-icon">
|
||||||
|
<i class="bx bx-user-circle"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-title">User Management</div>
|
<div class="menu-title">User Management</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li>
|
<?php endif; ?>
|
||||||
<a href="template/index.html" target="_blank">
|
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Template</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://codervent.com/rocker/documentation/index.html" target="_blank">
|
|
||||||
<div class="parent-icon"><i class="bx bx-folder"></i>
|
|
||||||
</div>
|
|
||||||
<div class="menu-title">Documentation</div>
|
|
||||||
</a>
|
|
||||||
</li> -->
|
|
||||||
<?php
|
|
||||||
endif; ?>
|
|
||||||
</ul>
|
</ul>
|
||||||
<!--end navigation-->
|
<!--end navigation-->
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (!function_exists('userCan')) {
|
||||||
|
/**
|
||||||
|
* Check if current user has a Vanguard permission.
|
||||||
|
* Uses Vanguard native method if available, otherwise falls back to DB check.
|
||||||
|
*/
|
||||||
|
function userCan($permissionName)
|
||||||
|
{
|
||||||
|
global $kindofrole;
|
||||||
|
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vanguard / Laravel-style methods, depending on installed version/customization.
|
||||||
|
if (method_exists($user, 'hasPermission')) {
|
||||||
|
return $user->hasPermission($permissionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_exists($user, 'hasPermissionTo')) {
|
||||||
|
return $user->hasPermissionTo($permissionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_exists($user, 'can')) {
|
||||||
|
return $user->can($permissionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: direct DB check using existing Vanguard tables.
|
||||||
|
static $permissions = null;
|
||||||
|
|
||||||
|
if ($permissions === null) {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT p.name
|
||||||
|
FROM auth_permissions p
|
||||||
|
INNER JOIN auth_permission_role pr ON pr.permission_id = p.id
|
||||||
|
WHERE pr.role_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([(int)$kindofrole]);
|
||||||
|
|
||||||
|
$permissions = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($permissionName, $permissions, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('visibleButtons')) {
|
||||||
|
/**
|
||||||
|
* Filter visible buttons.
|
||||||
|
*/
|
||||||
|
function visibleButtons(array $buttons)
|
||||||
|
{
|
||||||
|
return array_values(array_filter($buttons, function ($button) {
|
||||||
|
return empty($button['permission']) || userCan($button['permission']);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item d-flex align-items-center" href="../users">
|
<a class="dropdown-item d-flex align-items-center" href="user_settings.php">
|
||||||
<i class="bx bx-user fs-5"></i><span>Utente</span>
|
<i class="bx bx-user fs-5"></i><span>Utente</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,4 +1,184 @@
|
|||||||
<?php include('include/headscript.php'); ?>
|
<?php include('include/headscript.php'); ?>
|
||||||
|
<?php
|
||||||
|
$dashboardSections = [
|
||||||
|
[
|
||||||
|
'id' => 'secOperativo',
|
||||||
|
'title' => 'Operativo',
|
||||||
|
'subtitle' => 'Azioni principali di produzione e attività in scadenza',
|
||||||
|
'icon' => '🚀',
|
||||||
|
'open' => true,
|
||||||
|
'buttons' => [
|
||||||
|
[
|
||||||
|
'label' => 'Programmazione',
|
||||||
|
'icon' => '🗓️',
|
||||||
|
'class' => 'btn-programmazione',
|
||||||
|
'url' => 'produzione_programmazione_drag.php',
|
||||||
|
'permission' => 'production.programming.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Line View',
|
||||||
|
'icon' => '⚙️',
|
||||||
|
'class' => 'btn-status',
|
||||||
|
'url' => 'production_line_view2.php',
|
||||||
|
'permission' => 'production.line_view.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Statistiche',
|
||||||
|
'icon' => '📈',
|
||||||
|
'class' => 'btn-statistiche',
|
||||||
|
'url' => 'production_stats.php',
|
||||||
|
'permission' => 'production.stats.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Manager',
|
||||||
|
'icon' => '👔',
|
||||||
|
'class' => 'btn-manager',
|
||||||
|
'url' => 'manager_produzione.php',
|
||||||
|
'permission' => 'production.manager.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Manager Stats',
|
||||||
|
'icon' => '📊',
|
||||||
|
'class' => 'btn-manager-stats',
|
||||||
|
'url' => 'manager_stats.php',
|
||||||
|
'permission' => 'production.manager_stats.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Magazzino',
|
||||||
|
'icon' => '📦',
|
||||||
|
'class' => 'btn-magazzino',
|
||||||
|
'url' => 'warehouse_dashboard.php',
|
||||||
|
'permission' => 'warehouse.dashboard.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Smart-Alert',
|
||||||
|
'icon' => '⏰',
|
||||||
|
'class' => 'btn-scadenziario',
|
||||||
|
'url' => 'scadenzario/index.php',
|
||||||
|
'permission' => 'deadlines.view',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'secAnagrafiche',
|
||||||
|
'title' => 'Anagrafiche',
|
||||||
|
'subtitle' => 'Dati di base e setup di produzione',
|
||||||
|
'icon' => '🗂️',
|
||||||
|
'open' => false,
|
||||||
|
'buttons' => [
|
||||||
|
[
|
||||||
|
'label' => 'Mescole',
|
||||||
|
'icon' => '⚗️',
|
||||||
|
'class' => 'btn-mescole',
|
||||||
|
'url' => 'mescole.php',
|
||||||
|
'permission' => 'masterdata.mescole.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Elenco Profili',
|
||||||
|
'icon' => '🧩',
|
||||||
|
'class' => 'btn-matrici',
|
||||||
|
'url' => 'matrici.php',
|
||||||
|
'permission' => 'masterdata.matrici.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Linee Produzione',
|
||||||
|
'icon' => '🏭',
|
||||||
|
'class' => 'btn-linee',
|
||||||
|
'url' => 'linee.php',
|
||||||
|
'permission' => 'masterdata.linee.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Imballaggi',
|
||||||
|
'icon' => '📦',
|
||||||
|
'class' => 'btn-setup',
|
||||||
|
'url' => 'packaging_items.php',
|
||||||
|
'permission' => 'masterdata.packaging.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Suppliers',
|
||||||
|
'icon' => '🏷️',
|
||||||
|
'class' => 'btn-setup',
|
||||||
|
'url' => 'suppliers.php',
|
||||||
|
'permission' => 'masterdata.suppliers.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Setup',
|
||||||
|
'icon' => '⚙️',
|
||||||
|
'class' => 'btn-setup',
|
||||||
|
'url' => 'lookup_values.php',
|
||||||
|
'permission' => 'masterdata.lookup.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Fogli di lavoro',
|
||||||
|
'icon' => '🗒️',
|
||||||
|
'class' => 'btn-setup',
|
||||||
|
'url' => 'worksheets.php',
|
||||||
|
'permission' => 'masterdata.worksheets.view',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'secServizi',
|
||||||
|
'title' => 'Servizi',
|
||||||
|
'subtitle' => 'Status, cause pausa, attrezzature',
|
||||||
|
'icon' => '🧰',
|
||||||
|
'open' => false,
|
||||||
|
'buttons' => [
|
||||||
|
[
|
||||||
|
'label' => 'Status',
|
||||||
|
'icon' => '📋',
|
||||||
|
'class' => 'btn-setup',
|
||||||
|
'url' => 'production_status.php',
|
||||||
|
'permission' => 'services.status.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Cause di Pausa',
|
||||||
|
'icon' => '🛑',
|
||||||
|
'class' => 'btn-problem',
|
||||||
|
'url' => 'production_pause_reasons.php',
|
||||||
|
'permission' => 'services.pause_reasons.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Attrezzature',
|
||||||
|
'icon' => '🛠️',
|
||||||
|
'class' => 'btn-tools',
|
||||||
|
'url' => 'production_tools.php',
|
||||||
|
'permission' => 'services.tools.view',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'secPersonale',
|
||||||
|
'title' => 'Personale',
|
||||||
|
'subtitle' => 'Dipendenti, skill',
|
||||||
|
'icon' => '👥',
|
||||||
|
'open' => false,
|
||||||
|
'buttons' => [
|
||||||
|
[
|
||||||
|
'label' => 'Employees',
|
||||||
|
'icon' => '👥',
|
||||||
|
'class' => 'btn-employees',
|
||||||
|
'url' => 'employees.php',
|
||||||
|
'permission' => 'hr.employees.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Departments',
|
||||||
|
'icon' => '🏢',
|
||||||
|
'class' => 'btn-departments',
|
||||||
|
'url' => 'departments.php',
|
||||||
|
'permission' => 'hr.departments.view',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => 'Skills',
|
||||||
|
'icon' => '🧠',
|
||||||
|
'class' => 'btn-setup',
|
||||||
|
'url' => 'skills.php',
|
||||||
|
'permission' => 'hr.skills.view',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
?>
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="it">
|
<html lang="it">
|
||||||
|
|
||||||
@@ -7,7 +187,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<title>Dashboard Produzione - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Dashboard <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
|
||||||
<!-- Bootstrap + jQuery -->
|
<!-- Bootstrap + jQuery -->
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
@@ -310,7 +490,7 @@
|
|||||||
include(__DIR__ . '/scadenzario/include/my_deadlines_widget.php');
|
include(__DIR__ . '/scadenzario/include/my_deadlines_widget.php');
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h3 class="dashboard-title">Dashboard Produzione</h3>
|
<h3 class="dashboard-title">Dashboard</h3>
|
||||||
|
|
||||||
<!-- ===== STATISTICHE PRINCIPALI ===== -->
|
<!-- ===== STATISTICHE PRINCIPALI ===== -->
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
@@ -347,188 +527,71 @@
|
|||||||
<!-- ===== SEZIONI COLLASSABILI ===== -->
|
<!-- ===== SEZIONI COLLASSABILI ===== -->
|
||||||
<div class="sections-wrap" id="prodAccordion">
|
<div class="sections-wrap" id="prodAccordion">
|
||||||
|
|
||||||
<!-- OPERATIVO -->
|
<?php
|
||||||
<div class="section-card">
|
$hasVisibleSections = false;
|
||||||
<button type="button" class="section-header" data-bs-toggle="collapse" data-bs-target="#secOperativo" aria-expanded="true" aria-controls="secOperativo">
|
|
||||||
<div class="section-left">
|
foreach ($dashboardSections as $section):
|
||||||
<div class="section-icon">🚀</div>
|
$buttons = visibleButtons($section['buttons']);
|
||||||
<div style="min-width:0;">
|
|
||||||
<p class="section-title">Operativo</p>
|
// If no visible buttons are available, do not show the section.
|
||||||
<p class="section-subtitle">Azioni principali di produzione e attività in scadenza</p>
|
if (empty($buttons)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasVisibleSections = true;
|
||||||
|
|
||||||
|
$sectionId = htmlspecialchars($section['id'], ENT_QUOTES, 'UTF-8');
|
||||||
|
$isOpen = !empty($section['open']);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="section-card">
|
||||||
|
<button type="button"
|
||||||
|
class="section-header"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#<?= $sectionId ?>"
|
||||||
|
aria-expanded="<?= $isOpen ? 'true' : 'false' ?>"
|
||||||
|
aria-controls="<?= $sectionId ?>">
|
||||||
|
<div class="section-left">
|
||||||
|
<div class="section-icon"><?= htmlspecialchars($section['icon'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||||
|
<div style="min-width:0;">
|
||||||
|
<p class="section-title"><?= htmlspecialchars($section['title'], ENT_QUOTES, 'UTF-8') ?></p>
|
||||||
|
<p class="section-subtitle"><?= htmlspecialchars($section['subtitle'], ENT_QUOTES, 'UTF-8') ?></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="chev">⌄</div>
|
||||||
<div class="chev">⌄</div>
|
</button>
|
||||||
</button>
|
|
||||||
|
|
||||||
<div id="secOperativo" class="collapse show" data-bs-parent="#prodAccordion">
|
<div id="<?= $sectionId ?>"
|
||||||
<div class="section-body">
|
class="collapse <?= $isOpen ? 'show' : '' ?>"
|
||||||
<div class="dashboard-grid">
|
data-bs-parent="#prodAccordion">
|
||||||
<button class="dash-btn btn-programmazione" onclick="location.href='produzione_programmazione_drag.php'">
|
<div class="section-body">
|
||||||
<div class="dash-icon">🗓️</div>
|
<div class="dashboard-grid">
|
||||||
<div>Programmazione</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
|
<?php foreach ($buttons as $button): ?>
|
||||||
|
<button class="dash-btn <?= htmlspecialchars($button['class'], ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
onclick="location.href='<?= htmlspecialchars($button['url'], ENT_QUOTES, 'UTF-8') ?>'">
|
||||||
|
<div class="dash-icon"><?= htmlspecialchars($button['icon'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||||
|
<div><?= htmlspecialchars($button['label'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||||
|
</button>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
<button class="dash-btn btn-status" onclick="location.href='production_line_view2.php'">
|
|
||||||
<div class="dash-icon">⚙️</div>
|
|
||||||
<div>Line View</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-statistiche" onclick="location.href='production_stats.php'">
|
|
||||||
<div class="dash-icon">📈</div>
|
|
||||||
<div>Statistiche</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-manager" onclick="location.href='manager_produzione.php'">
|
|
||||||
<div class="dash-icon">👔</div>
|
|
||||||
<div>Manager</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-manager-stats" onclick="location.href='manager_stats.php'">
|
|
||||||
<div class="dash-icon">📊</div>
|
|
||||||
<div>Manager Stats</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-magazzino" onclick="location.href='warehouse_dashboard.php'">
|
|
||||||
<div class="dash-icon">📦</div>
|
|
||||||
<div>Magazzino</div>
|
|
||||||
|
|
||||||
</button>
|
|
||||||
<button class="dash-btn btn-scadenziario" onclick="location.href='scadenzario/index.php'">
|
|
||||||
<div class="dash-icon">⏰</div>
|
|
||||||
<div>Scadenziario</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ANAGRAFICHE -->
|
<?php endforeach; ?>
|
||||||
<div class="section-card">
|
|
||||||
<button type="button" class="section-header" data-bs-toggle="collapse" data-bs-target="#secAnagrafiche" aria-expanded="false" aria-controls="secAnagrafiche">
|
|
||||||
<div class="section-left">
|
|
||||||
<div class="section-icon">🗂️</div>
|
|
||||||
<div style="min-width:0;">
|
|
||||||
<p class="section-title">Anagrafiche</p>
|
|
||||||
<p class="section-subtitle">Dati di base e setup di produzione</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chev">⌄</div>
|
|
||||||
</button>
|
|
||||||
<div id="secAnagrafiche" class="collapse" data-bs-parent="#prodAccordion">
|
|
||||||
<div class="section-body">
|
|
||||||
<div class="dashboard-grid">
|
|
||||||
<button class="dash-btn btn-mescole" onclick="location.href='mescole.php'">
|
|
||||||
<div class="dash-icon">⚗️</div>
|
|
||||||
<div>Mescole</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-matrici" onclick="location.href='matrici.php'">
|
<?php if (!$hasVisibleSections): ?>
|
||||||
<div class="dash-icon">🧩</div>
|
<div class="section-card">
|
||||||
<div>Elenco Profili</div>
|
<div class="section-body text-center">
|
||||||
</button>
|
Nessuna sezione disponibile per il tuo profilo.
|
||||||
|
|
||||||
<button class="dash-btn btn-linee" onclick="location.href='linee.php'">
|
|
||||||
<div class="dash-icon">🏭</div>
|
|
||||||
<div>Linee Produzione</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-setup" onclick="location.href='packaging_items.php'">
|
|
||||||
<div class="dash-icon">📦</div>
|
|
||||||
<div>Imballaggi</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-setup" onclick="location.href='suppliers.php'">
|
|
||||||
<div class="dash-icon">🏷️</div>
|
|
||||||
<div>Suppliers</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-setup" onclick="location.href='lookup_values.php'">
|
|
||||||
<div class="dash-icon">⚙️</div>
|
|
||||||
<div>Setup</div>
|
|
||||||
</button>
|
|
||||||
<button class="dash-btn btn-setup" onclick="location.href='worksheets.php'">
|
|
||||||
<div class="dash-icon">🗒️</div>
|
|
||||||
<div>Fogli di lavoro</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- QUALITÀ / SERVIZI -->
|
</div>
|
||||||
<div class="section-card">
|
<!-- /sections-wrap -->
|
||||||
<button type="button" class="section-header" data-bs-toggle="collapse" data-bs-target="#secServizi" aria-expanded="false" aria-controls="secServizi">
|
|
||||||
<div class="section-left">
|
|
||||||
<div class="section-icon">🧰</div>
|
|
||||||
<div style="min-width:0;">
|
|
||||||
<p class="section-title">Servizi</p>
|
|
||||||
<p class="section-subtitle">Status, cause pausa, attrezzature</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chev">⌄</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div id="secServizi" class="collapse" data-bs-parent="#prodAccordion">
|
|
||||||
<div class="section-body">
|
|
||||||
<div class="dashboard-grid">
|
|
||||||
<button class="dash-btn btn-setup" onclick="location.href='production_status.php'">
|
|
||||||
<div class="dash-icon">📋</div>
|
|
||||||
<div>Status</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-problem" onclick="location.href='production_pause_reasons.php'">
|
|
||||||
<div class="dash-icon">🛑</div>
|
|
||||||
<div>Cause di Pausa</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-tools" onclick="location.href='production_tools.php'">
|
|
||||||
<div class="dash-icon">🛠️</div>
|
|
||||||
<div>Attrezzature</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PERSONALE -->
|
|
||||||
<div class="section-card">
|
|
||||||
<button type="button" class="section-header" data-bs-toggle="collapse" data-bs-target="#secPersonale" aria-expanded="false" aria-controls="secPersonale">
|
|
||||||
<div class="section-left">
|
|
||||||
<div class="section-icon">👥</div>
|
|
||||||
<div style="min-width:0;">
|
|
||||||
<p class="section-title">Personale</p>
|
|
||||||
<p class="section-subtitle">Dipendenti, skill</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="chev">⌄</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div id="secPersonale" class="collapse" data-bs-parent="#prodAccordion">
|
|
||||||
<div class="section-body">
|
|
||||||
<div class="dashboard-grid">
|
|
||||||
|
|
||||||
<button class="dash-btn btn-employees" onclick="location.href='employees.php'">
|
|
||||||
<div class="dash-icon">👥</div>
|
|
||||||
<div>Employees</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-departments" onclick="location.href='departments.php'">
|
|
||||||
<div class="dash-icon">🏢</div>
|
|
||||||
<div>Departments</div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="dash-btn btn-setup" onclick="location.href='skills.php'">
|
|
||||||
<div class="dash-icon">🧠</div>
|
|
||||||
<div>Skills</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div> <!-- /sections-wrap -->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,12 +4,19 @@ header('Content-Type: application/json');
|
|||||||
require_once(__DIR__ . '/../../class/db-functions.php');
|
require_once(__DIR__ . '/../../class/db-functions.php');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
$rawId = $_POST['id'] ?? $_GET['id'] ?? null;
|
||||||
|
if ($rawId === null || !is_numeric($rawId)) {
|
||||||
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
|
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = (int)$_GET['id'];
|
$id = (int)$rawId;
|
||||||
|
|
||||||
|
// Whether to create the next (recurring) deadline. Absent or '1' => create; '0' => complete only.
|
||||||
|
$createNext = ($_POST['create_next'] ?? '1') !== '0';
|
||||||
|
|
||||||
|
// Whether to carry the attachment links over to the new deadline. Default ON ("default all activate").
|
||||||
|
$copyAttachments = ($_POST['copy_attachments'] ?? '1') !== '0';
|
||||||
|
|
||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
@@ -34,11 +41,13 @@ try {
|
|||||||
->execute([$id, $currentUserId]);
|
->execute([$id, $currentUserId]);
|
||||||
|
|
||||||
$newId = null;
|
$newId = null;
|
||||||
|
$newDueDate = null;
|
||||||
|
|
||||||
// If recurring, create next deadline
|
// If recurring AND the user asked for it, create the next deadline
|
||||||
if ($deadline['recurrence_type'] !== 'once') {
|
if ($deadline['recurrence_type'] !== 'once' && $createNext) {
|
||||||
$dueDate = new DateTime($deadline['due_date']);
|
$dueDate = new DateTime($deadline['due_date']);
|
||||||
$checkDate = $deadline['check_date'] ? new DateTime($deadline['check_date']) : null;
|
$checkDate = $deadline['check_date'] ? new DateTime($deadline['check_date']) : null;
|
||||||
|
$documentDate = $deadline['document_date'] ? new DateTime($deadline['document_date']) : null;
|
||||||
|
|
||||||
switch ($deadline['recurrence_type']) {
|
switch ($deadline['recurrence_type']) {
|
||||||
case 'monthly': $interval = new DateInterval('P1M'); break;
|
case 'monthly': $interval = new DateInterval('P1M'); break;
|
||||||
@@ -57,23 +66,25 @@ try {
|
|||||||
if ($interval) {
|
if ($interval) {
|
||||||
$dueDate->add($interval);
|
$dueDate->add($interval);
|
||||||
if ($checkDate) $checkDate->add($interval);
|
if ($checkDate) $checkDate->add($interval);
|
||||||
|
if ($documentDate) $documentDate->add($interval);
|
||||||
|
|
||||||
$ins = $pdo->prepare("
|
$ins = $pdo->prepare("
|
||||||
INSERT INTO scad_deadlines
|
INSERT INTO scad_deadlines
|
||||||
(subject_id, topic, law_regulation, recurrence_type, due_date, check_date,
|
(subject_id, function_id, topic, law_regulation, recurrence_type, due_date, check_date,
|
||||||
document_date, notification_days, storage_location, notes, created_by, departments)
|
document_date, notification_days, storage_location, notes, created_by, departments)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
");
|
");
|
||||||
$ins->execute([
|
$ins->execute([
|
||||||
$deadline['subject_id'], $deadline['topic'], $deadline['law_regulation'],
|
$deadline['subject_id'], $deadline['function_id'], $deadline['topic'], $deadline['law_regulation'],
|
||||||
$deadline['recurrence_type'], $dueDate->format('Y-m-d'),
|
$deadline['recurrence_type'], $dueDate->format('Y-m-d'),
|
||||||
$checkDate ? $checkDate->format('Y-m-d') : null,
|
$checkDate ? $checkDate->format('Y-m-d') : null,
|
||||||
$deadline['document_date'],
|
$documentDate ? $documentDate->format('Y-m-d') : null,
|
||||||
$deadline['notification_days'], $deadline['storage_location'],
|
$deadline['notification_days'], $deadline['storage_location'],
|
||||||
$deadline['notes'], $deadline['created_by'], $deadline['departments']
|
$deadline['notes'], $deadline['created_by'], $deadline['departments']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$newId = $pdo->lastInsertId();
|
$newId = $pdo->lastInsertId();
|
||||||
|
$newDueDate = $dueDate;
|
||||||
|
|
||||||
// Copy employee assignments
|
// Copy employee assignments
|
||||||
$empStmt = $pdo->prepare("SELECT employee_id FROM scad_deadline_employee WHERE deadline_id = ?");
|
$empStmt = $pdo->prepare("SELECT employee_id FROM scad_deadline_employee WHERE deadline_id = ?");
|
||||||
@@ -87,6 +98,31 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Carry forward ALL attachment links from the source deadline (shared physical file, same stored_name).
|
||||||
|
// Individual links can later be removed on the new deadline without deleting the file.
|
||||||
|
if ($copyAttachments) {
|
||||||
|
$attSel = $pdo->prepare("
|
||||||
|
SELECT original_name, stored_name, mime_type, size
|
||||||
|
FROM scad_deadline_attachments
|
||||||
|
WHERE deadline_id = ?
|
||||||
|
");
|
||||||
|
$attSel->execute([$id]);
|
||||||
|
$attRows = $attSel->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($attRows) {
|
||||||
|
$attIns = $pdo->prepare("
|
||||||
|
INSERT INTO scad_deadline_attachments
|
||||||
|
(deadline_id, original_name, stored_name, mime_type, size, uploaded_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
");
|
||||||
|
$attHist = $pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, ?, 'attachment_linked', ?)");
|
||||||
|
foreach ($attRows as $a) {
|
||||||
|
$attIns->execute([$newId, $a['original_name'], $a['stored_name'], $a['mime_type'], $a['size'], $currentUserId]);
|
||||||
|
$attHist->execute([$newId, $currentUserId, $a['original_name']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// History for new
|
// History for new
|
||||||
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, ?, 'created', ?)")
|
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, ?, 'created', ?)")
|
||||||
->execute([$newId, $currentUserId, 'Creata automaticamente dalla scadenza #' . $id]);
|
->execute([$newId, $currentUserId, 'Creata automaticamente dalla scadenza #' . $id]);
|
||||||
@@ -97,7 +133,7 @@ try {
|
|||||||
|
|
||||||
$msg = 'Scadenza completata con successo.';
|
$msg = 'Scadenza completata con successo.';
|
||||||
if ($newId) {
|
if ($newId) {
|
||||||
$msg .= ' Nuova scadenza creata con data ' . $dueDate->format('d/m/Y') . '.';
|
$msg .= ' Nuova scadenza creata con data ' . $newDueDate->format('d/m/Y') . '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => $msg, 'new_id' => $newId]);
|
echo json_encode(['success' => true, 'message' => $msg, 'new_id' => $newId]);
|
||||||
|
|||||||
@@ -23,20 +23,32 @@ try {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete file
|
// Remove this link (DB record) first
|
||||||
$filePath = __DIR__ . '/../attachments/' . $att['stored_name'];
|
|
||||||
if (file_exists($filePath)) {
|
|
||||||
unlink($filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete DB record
|
|
||||||
$pdo->prepare("DELETE FROM scad_deadline_attachments WHERE id = ?")->execute([$id]);
|
$pdo->prepare("DELETE FROM scad_deadline_attachments WHERE id = ?")->execute([$id]);
|
||||||
|
|
||||||
// History
|
// The same physical file may be shared with other deadlines (carried forward on completion).
|
||||||
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, ?, 'attachment_removed', ?)")
|
// Only unlink it when no other link references the same stored file.
|
||||||
->execute([$att['deadline_id'], $currentUserId, $att['original_name']]);
|
$refStmt = $pdo->prepare("SELECT COUNT(*) FROM scad_deadline_attachments WHERE stored_name = ?");
|
||||||
|
$refStmt->execute([$att['stored_name']]);
|
||||||
|
$stillReferenced = (int)$refStmt->fetchColumn() > 0;
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Allegato eliminato.']);
|
if ($stillReferenced) {
|
||||||
|
$action = 'attachment_unlinked';
|
||||||
|
$message = 'Collegamento rimosso. Il file è conservato (usato da un\'altra scadenza).';
|
||||||
|
} else {
|
||||||
|
$filePath = __DIR__ . '/../attachments/' . $att['stored_name'];
|
||||||
|
if (file_exists($filePath)) {
|
||||||
|
unlink($filePath);
|
||||||
|
}
|
||||||
|
$action = 'attachment_removed';
|
||||||
|
$message = 'Allegato eliminato.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// History
|
||||||
|
$pdo->prepare("INSERT INTO scad_deadline_histories (deadline_id, user_id, action, notes) VALUES (?, ?, ?, ?)")
|
||||||
|
->execute([$att['deadline_id'], $currentUserId, $action, $att['original_name']]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => $message]);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
|
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
|
||||||
|
|||||||
@@ -13,10 +13,29 @@ try {
|
|||||||
$db = DBHandlerSelect::getInstance();
|
$db = DBHandlerSelect::getInstance();
|
||||||
$pdo = $db->getConnection();
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
// Collect the physical files referenced by this deadline before the FK cascade removes its links
|
||||||
|
$attStmt = $pdo->prepare("SELECT DISTINCT stored_name FROM scad_deadline_attachments WHERE deadline_id = ?");
|
||||||
|
$attStmt->execute([$id]);
|
||||||
|
$storedNames = $attStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// Deleting the deadline cascades to its attachment/employee/history rows (FK ON DELETE CASCADE)
|
||||||
$stmt = $pdo->prepare("DELETE FROM scad_deadlines WHERE id = ?");
|
$stmt = $pdo->prepare("DELETE FROM scad_deadlines WHERE id = ?");
|
||||||
$stmt->execute([$id]);
|
$stmt->execute([$id]);
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
if ($stmt->rowCount() > 0) {
|
||||||
|
// Unlink physical files no longer referenced by any other deadline (shared-file safe)
|
||||||
|
if (!empty($storedNames)) {
|
||||||
|
$refStmt = $pdo->prepare("SELECT COUNT(*) FROM scad_deadline_attachments WHERE stored_name = ?");
|
||||||
|
foreach ($storedNames as $storedName) {
|
||||||
|
$refStmt->execute([$storedName]);
|
||||||
|
if ((int)$refStmt->fetchColumn() === 0) {
|
||||||
|
$filePath = __DIR__ . '/../attachments/' . $storedName;
|
||||||
|
if (file_exists($filePath)) {
|
||||||
|
unlink($filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
echo json_encode(['success' => true, 'message' => 'Scadenza eliminata con successo.']);
|
echo json_encode(['success' => true, 'message' => 'Scadenza eliminata con successo.']);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(['success' => false, 'message' => 'Scadenza non trovata.']);
|
echo json_encode(['success' => false, 'message' => 'Scadenza non trovata.']);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ try {
|
|||||||
|
|
||||||
$id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : null;
|
$id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : null;
|
||||||
$subject_id = isset($_POST['subject_id']) && is_numeric($_POST['subject_id']) && (int)$_POST['subject_id'] > 0 ? (int)$_POST['subject_id'] : null;
|
$subject_id = isset($_POST['subject_id']) && is_numeric($_POST['subject_id']) && (int)$_POST['subject_id'] > 0 ? (int)$_POST['subject_id'] : null;
|
||||||
|
$function_id = isset($_POST['function_id']) && is_numeric($_POST['function_id']) && (int)$_POST['function_id'] > 0 ? (int)$_POST['function_id'] : null;
|
||||||
$topic = trim($_POST['topic'] ?? '');
|
$topic = trim($_POST['topic'] ?? '');
|
||||||
$law_regulation = trim($_POST['law_regulation'] ?? '') ?: null;
|
$law_regulation = trim($_POST['law_regulation'] ?? '') ?: null;
|
||||||
$recurrence_type = $_POST['recurrence_type'] ?? 'once';
|
$recurrence_type = $_POST['recurrence_type'] ?? 'once';
|
||||||
@@ -51,16 +52,26 @@ try {
|
|||||||
|
|
||||||
if ($id) {
|
if ($id) {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
UPDATE scad_deadlines SET
|
UPDATE scad_deadlines SET
|
||||||
subject_id = ?, topic = ?, law_regulation = ?, recurrence_type = ?,
|
subject_id = ?, function_id = ?, topic = ?, law_regulation = ?, recurrence_type = ?,
|
||||||
due_date = ?, check_date = ?, document_date = ?, notification_days = ?,
|
due_date = ?, check_date = ?, document_date = ?, notification_days = ?,
|
||||||
storage_location = ?, notes = ?, departments = ?
|
storage_location = ?, notes = ?, departments = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$subject_id, $topic, $law_regulation, $recurrence_type,
|
$subject_id,
|
||||||
$due_date, $check_date, $document_date, $notification_days,
|
$function_id,
|
||||||
$storage_location, $notes, $departmentsStr, $id
|
$topic,
|
||||||
|
$law_regulation,
|
||||||
|
$recurrence_type,
|
||||||
|
$due_date,
|
||||||
|
$check_date,
|
||||||
|
$document_date,
|
||||||
|
$notification_days,
|
||||||
|
$storage_location,
|
||||||
|
$notes,
|
||||||
|
$departmentsStr,
|
||||||
|
$id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Re-link employees
|
// Re-link employees
|
||||||
@@ -75,14 +86,24 @@ try {
|
|||||||
// INSERT
|
// INSERT
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO scad_deadlines
|
INSERT INTO scad_deadlines
|
||||||
(subject_id, topic, law_regulation, recurrence_type, due_date, check_date,
|
(subject_id, function_id, topic, law_regulation, recurrence_type, due_date, check_date,
|
||||||
document_date, notification_days, storage_location, notes, created_by, departments)
|
document_date, notification_days, storage_location, notes, created_by, departments)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$subject_id, $topic, $law_regulation, $recurrence_type,
|
$subject_id,
|
||||||
$due_date, $check_date, $document_date, $notification_days,
|
$function_id,
|
||||||
$storage_location, $notes, $currentUserId, $departmentsStr
|
$topic,
|
||||||
|
$law_regulation,
|
||||||
|
$recurrence_type,
|
||||||
|
$due_date,
|
||||||
|
$check_date,
|
||||||
|
$document_date,
|
||||||
|
$notification_days,
|
||||||
|
$storage_location,
|
||||||
|
$notes,
|
||||||
|
$currentUserId,
|
||||||
|
$departmentsStr
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$deadlineId = $pdo->lastInsertId();
|
$deadlineId = $pdo->lastInsertId();
|
||||||
@@ -107,7 +128,6 @@ try {
|
|||||||
'message' => $id ? 'Scadenza aggiornata con successo.' : 'Scadenza creata con successo.',
|
'message' => $id ? 'Scadenza aggiornata con successo.' : 'Scadenza creata con successo.',
|
||||||
'id' => $deadlineId
|
'id' => $deadlineId
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
if (isset($pdo) && $pdo->inTransaction()) {
|
if (isset($pdo) && $pdo->inTransaction()) {
|
||||||
$pdo->rollBack();
|
$pdo->rollBack();
|
||||||
|
|||||||
@@ -25,6 +25,17 @@ $pdo = $db->getConnection();
|
|||||||
$today = date('Y-m-d');
|
$today = date('Y-m-d');
|
||||||
$appUrl = rtrim($_ENV['APP_URL'] ?? 'http://localhost:8001', '/');
|
$appUrl = rtrim($_ENV['APP_URL'] ?? 'http://localhost:8001', '/');
|
||||||
|
|
||||||
|
// Manager email for Cc — taken from MANAGER_USER_ID → auth_users.email
|
||||||
|
$managerCcEmail = null;
|
||||||
|
if (!empty($_ENV['MANAGER_USER_ID']) && is_numeric($_ENV['MANAGER_USER_ID'])) {
|
||||||
|
$mgrStmt = $pdo->prepare("SELECT email FROM auth_users WHERE id = ?");
|
||||||
|
$mgrStmt->execute([(int)$_ENV['MANAGER_USER_ID']]);
|
||||||
|
$mgrEmail = $mgrStmt->fetchColumn();
|
||||||
|
if (!empty($mgrEmail)) {
|
||||||
|
$managerCcEmail = $mgrEmail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$sent = 0;
|
$sent = 0;
|
||||||
$skipped = 0;
|
$skipped = 0;
|
||||||
$errors = 0;
|
$errors = 0;
|
||||||
@@ -143,6 +154,11 @@ foreach ($deadlines as $dl) {
|
|||||||
);
|
);
|
||||||
$mail->addAddress($emp['email'], trim($emp['first_name'] . ' ' . $emp['last_name']));
|
$mail->addAddress($emp['email'], trim($emp['first_name'] . ' ' . $emp['last_name']));
|
||||||
|
|
||||||
|
// Cc the manager (unless they are the direct recipient)
|
||||||
|
if ($managerCcEmail && strcasecmp($managerCcEmail, $emp['email']) !== 0) {
|
||||||
|
$mail->addCC($managerCcEmail);
|
||||||
|
}
|
||||||
|
|
||||||
$detailUrl = $appUrl . '/userarea/scadenzario/detail.php?id=' . $dl['id'];
|
$detailUrl = $appUrl . '/userarea/scadenzario/detail.php?id=' . $dl['id'];
|
||||||
$topicText = (!empty($dl['subject_name']) ? $dl['subject_name'] . ' — ' : '') . $dl['topic'];
|
$topicText = (!empty($dl['subject_name']) ? $dl['subject_name'] . ' — ' : '') . $dl['topic'];
|
||||||
|
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$recurrenceLabels = ['once' => 'Una tantum', 'monthly' => 'Mensile', 'quarterly' => 'Trimestrale', 'semiannual' => 'Semestrale', 'annual' => 'Annuale', 'biennial' => 'Biennale', 'triennial' => 'Triennale', 'quadriennial' => 'Quadriennale', 'quinquennial' => 'Quinquennale', 'decennial' => 'Decennale', 'quindecennial' => 'Quindicennale'];
|
$recurrenceLabels = ['once' => 'Una tantum', 'monthly' => 'Mensile', 'quarterly' => 'Trimestrale', 'semiannual' => 'Semestrale', 'annual' => 'Annuale', 'biennial' => 'Biennale', 'triennial' => 'Triennale', 'quadriennial' => 'Quadriennale', 'quinquennial' => 'Quinquennale', 'decennial' => 'Decennale', 'quindecennial' => 'Quindicennale'];
|
||||||
$actionLabels = ['created' => 'Creata', 'updated' => 'Modificata', 'completed' => 'Completata', 'attachment_added' => 'Allegato aggiunto', 'attachment_removed' => 'Allegato rimosso', 'notification_sent' => 'Notifica inviata'];
|
$actionLabels = ['created' => 'Creata', 'updated' => 'Modificata', 'completed' => 'Completata', 'attachment_added' => 'Allegato aggiunto', 'attachment_removed' => 'Allegato rimosso', 'attachment_linked' => 'Allegato collegato', 'attachment_unlinked' => 'Collegamento rimosso', 'notification_sent' => 'Notifica inviata'];
|
||||||
$actionColors = ['created' => '#198754', 'updated' => '#5a8fd8', 'completed' => '#6f42c1', 'attachment_added' => '#e8930c', 'attachment_removed' => '#e8930c', 'notification_sent' => '#adb5bd'];
|
$actionColors = ['created' => '#198754', 'updated' => '#5a8fd8', 'completed' => '#6f42c1', 'attachment_added' => '#e8930c', 'attachment_removed' => '#e8930c', 'attachment_linked' => '#0dcaf0', 'attachment_unlinked' => '#adb5bd', 'notification_sent' => '#adb5bd'];
|
||||||
$actionIcons = ['created' => 'fa-plus', 'updated' => 'fa-pen', 'completed' => 'fa-check', 'attachment_added' => 'fa-paperclip', 'attachment_removed' => 'fa-trash', 'notification_sent' => 'fa-bell'];
|
$actionIcons = ['created' => 'fa-plus', 'updated' => 'fa-pen', 'completed' => 'fa-check', 'attachment_added' => 'fa-paperclip', 'attachment_removed' => 'fa-trash', 'attachment_linked' => 'fa-link', 'attachment_unlinked' => 'fa-link-slash', 'notification_sent' => 'fa-bell'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -85,6 +85,14 @@ if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
|||||||
<base href="<?= $baseHref ?>">
|
<base href="<?= $baseHref ?>">
|
||||||
<?php include('../cssinclude.php'); ?>
|
<?php include('../cssinclude.php'); ?>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" rel="stylesheet">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/i18n/it.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/it.js"></script>
|
||||||
|
<?php include __DIR__ . '/include/deadline_modal_css.php'; ?>
|
||||||
<title><?= $deadline ? htmlspecialchars($deadline['topic'], ENT_QUOTES, 'UTF-8') . ' — ' : '' ?>Scadenzario</title>
|
<title><?= $deadline ? htmlspecialchars($deadline['topic'], ENT_QUOTES, 'UTF-8') . ' — ' : '' ?>Scadenzario</title>
|
||||||
<script>
|
<script>
|
||||||
if (window.innerWidth > 1024) document.addEventListener('DOMContentLoaded', function() {
|
if (window.innerWidth > 1024) document.addEventListener('DOMContentLoaded', function() {
|
||||||
@@ -755,52 +763,114 @@ if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
|||||||
</div>
|
</div>
|
||||||
<?php include('../include/footer.php'); ?>
|
<?php include('../include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if ($deadline && !$isCompleted): ?>
|
||||||
|
<?php require __DIR__ . '/include/deadline_form_data.php'; ?>
|
||||||
|
<?php include __DIR__ . '/include/deadline_modal.php'; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php include('../jsinclude.php'); ?>
|
<?php include('../jsinclude.php'); ?>
|
||||||
<?php if ($deadline && !$isCompleted): ?>
|
<?php if ($deadline && !$isCompleted): ?>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
// Used by the shared modal JS to auto-open edit on "#edit"
|
||||||
|
window.SCAD_DETAIL_ID = <?= (int)$deadline['id'] ?>;
|
||||||
|
|
||||||
$('#btnModifica').on('click', function() {
|
$('#btnModifica').on('click', function() {
|
||||||
window.location.href = 'scadenzario/index.php?edit=<?= (int)$deadline['id'] ?>';
|
window.openDeadlineEdit(<?= (int)$deadline['id'] ?>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function detailSubmitComplete(createNext, copyAttachments) {
|
||||||
|
var fd = new FormData();
|
||||||
|
fd.append('id', '<?= (int)$deadline['id'] ?>');
|
||||||
|
fd.append('create_next', createNext ? '1' : '0');
|
||||||
|
fd.append('copy_attachments', copyAttachments ? '1' : '0');
|
||||||
|
|
||||||
|
fetch('scadenzario/ajax/complete_deadline.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: fd
|
||||||
|
})
|
||||||
|
.then(function(r) {
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Completata',
|
||||||
|
text: data.message,
|
||||||
|
timer: 1800,
|
||||||
|
showConfirmButton: false
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
if (data.new_id) {
|
||||||
|
window.location.href = 'scadenzario/detail.php?id=' + data.new_id + '#edit';
|
||||||
|
} else {
|
||||||
|
window.location.href = 'scadenzario/index.php';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire('Errore', data.message, 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$('#btnCompleta').on('click', function() {
|
$('#btnCompleta').on('click', function() {
|
||||||
|
var recurrence = <?= json_encode($deadline['recurrence_type'] ?? 'once') ?>;
|
||||||
|
var attCount = <?= count($attachments) ?>;
|
||||||
|
|
||||||
|
if (recurrence === 'once') {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Completare la scadenza?',
|
||||||
|
text: 'La scadenza verrà contrassegnata come completata.',
|
||||||
|
icon: 'question',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#198754',
|
||||||
|
cancelButtonText: 'Annulla',
|
||||||
|
confirmButtonText: 'Completa',
|
||||||
|
reverseButtons: true
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
detailSubmitComplete(false, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attCheckbox = attCount > 0 ?
|
||||||
|
'<div class="form-check d-flex align-items-center justify-content-center gap-2 mt-3">' +
|
||||||
|
'<input class="form-check-input" type="checkbox" id="swCopyAtt" checked>' +
|
||||||
|
'<label class="form-check-label" for="swCopyAtt">Copia gli allegati (' + attCount + ') sulla nuova scadenza</label>' +
|
||||||
|
'</div>' :
|
||||||
|
'';
|
||||||
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Completare la scadenza?',
|
title: 'Completare la scadenza?',
|
||||||
|
html: 'Vuoi creare automaticamente la prossima scadenza ricorrente?' + attCheckbox,
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
|
showDenyButton: true,
|
||||||
confirmButtonColor: '#198754',
|
confirmButtonColor: '#198754',
|
||||||
|
denyButtonColor: '#6c757d',
|
||||||
|
confirmButtonText: 'Completa e crea la prossima',
|
||||||
|
denyButtonText: 'Completa senza nuova',
|
||||||
cancelButtonText: 'Annulla',
|
cancelButtonText: 'Annulla',
|
||||||
confirmButtonText: 'Completa'
|
reverseButtons: true
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
fetch('scadenzario/ajax/complete_deadline.php?id=<?= (int)$deadline['id'] ?>')
|
var copy = attCount > 0 ? document.getElementById('swCopyAtt').checked : false;
|
||||||
.then(function(r) {
|
detailSubmitComplete(true, copy);
|
||||||
return r.json();
|
} else if (result.isDenied) {
|
||||||
})
|
detailSubmitComplete(false, false);
|
||||||
.then(function(data) {
|
|
||||||
if (data.success) {
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'success',
|
|
||||||
title: 'Completata',
|
|
||||||
text: data.message,
|
|
||||||
timer: 2500,
|
|
||||||
showConfirmButton: false
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
window.location.href = 'scadenzario/index.php';
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Swal.fire('Errore', data.message, 'error');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<?php include __DIR__ . '/include/deadline_modal_js.php'; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../../ajax/auth_check.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once(__DIR__ . '/../../../class/db-functions.php');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : 0;
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID non valido.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM scad_deadlines WHERE function_id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$inUse = (int)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($inUse > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => "Impossibile eliminare: la funzione è utilizzata in $inUse scadenz" . ($inUse === 1 ? 'a' : 'e') . '.',
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->prepare("DELETE FROM scad_functions WHERE id = ?")->execute([$id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Funzione eliminata.']);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
require_once(__DIR__ . '/../../ajax/auth_check.php');
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once(__DIR__ . '/../../../class/db-functions.php');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : null;
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '') ?: null;
|
||||||
|
|
||||||
|
if ($name === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Il nome è obbligatorio.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mb_strlen($name) > 255) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Il nome supera 255 caratteri.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM scad_functions WHERE name = ? AND id <> ?");
|
||||||
|
$stmt->execute([$name, $id]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM scad_functions WHERE name = ?");
|
||||||
|
$stmt->execute([$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Esiste già una funzione con questo nome.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE scad_functions
|
||||||
|
SET name = ?, description = ?
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$name, $description, $id]);
|
||||||
|
$savedId = $id;
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO scad_functions (name, description, status)
|
||||||
|
VALUES (?, ?, 'active')
|
||||||
|
");
|
||||||
|
$stmt->execute([$name, $description]);
|
||||||
|
$savedId = (int)$pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => $id ? 'Funzione aggiornata.' : 'Funzione creata.',
|
||||||
|
'id' => $savedId,
|
||||||
|
'name' => $name,
|
||||||
|
'description' => $description,
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,464 @@
|
|||||||
|
<?php include('../../include/headscript.php'); ?>
|
||||||
|
<?php
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$functions = $pdo->query("
|
||||||
|
SELECT f.*,
|
||||||
|
(SELECT COUNT(*) FROM scad_deadlines d WHERE d.function_id = f.id) AS deadline_count,
|
||||||
|
(SELECT COUNT(*) FROM scad_deadlines d WHERE d.function_id = f.id AND d.status <> 'completed') AS open_count
|
||||||
|
FROM scad_functions f
|
||||||
|
ORDER BY f.name ASC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<?php
|
||||||
|
$scriptDir = dirname($_SERVER['SCRIPT_NAME']);
|
||||||
|
$baseHref = dirname(dirname($scriptDir)) . '/';
|
||||||
|
?>
|
||||||
|
<base href="<?= $baseHref ?>">
|
||||||
|
<?php include('../../cssinclude.php'); ?>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<title>Scadenzario - Funzioni</title>
|
||||||
|
<script>
|
||||||
|
if (window.innerWidth > 1024) {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
document.getElementById('appWrapper').classList.add('toggled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--scad-primary: #5a8fd8;
|
||||||
|
--scad-primary-hover: #4578c0;
|
||||||
|
--scad-heading: #2c3e6b;
|
||||||
|
--scad-card-bg: linear-gradient(135deg, #f0f4ff 0%, #e8eeff 100%);
|
||||||
|
--scad-card-border: #dde4f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scad-card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scad-card .card-header {
|
||||||
|
background: var(--scad-card-bg);
|
||||||
|
border-bottom: 1px solid var(--scad-card-border);
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scad-card .card-header h5 {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--scad-heading);
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scad-card .card-body {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-scad-primary {
|
||||||
|
background: var(--scad-primary);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-scad-primary:hover {
|
||||||
|
background: var(--scad-primary-hover);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-scad-outline {
|
||||||
|
background: transparent;
|
||||||
|
border: 1.5px solid var(--scad-primary);
|
||||||
|
color: var(--scad-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.45rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-scad-outline:hover {
|
||||||
|
background: var(--scad-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-edit {
|
||||||
|
background: rgba(90, 143, 216, 0.12);
|
||||||
|
color: var(--scad-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-edit:hover {
|
||||||
|
background: var(--scad-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-delete {
|
||||||
|
background: rgba(220, 53, 69, 0.12);
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-delete:hover {
|
||||||
|
background: #dc3545;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--scad-card-border);
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
padding: 0.85rem 0.95rem;
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-card .fc-name {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--scad-heading);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-card .fc-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-card .fc-stats strong {
|
||||||
|
color: var(--scad-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state i {
|
||||||
|
font-size: 3rem;
|
||||||
|
opacity: 0.3;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 575.98px) {
|
||||||
|
.scad-card .card-header {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions .btn {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper" id="appWrapper">
|
||||||
|
<?php include('../../include/navbar.php'); ?>
|
||||||
|
<?php include('../../include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<nav aria-label="breadcrumb" class="mb-3">
|
||||||
|
<ol class="breadcrumb" style="background:transparent;padding:0;margin:0;font-size:0.85rem">
|
||||||
|
<li class="breadcrumb-item"><a href="scadenzario/index.php">Scadenzario</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Funzioni</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="card scad-card">
|
||||||
|
<div class="card-header d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||||||
|
<h5><i class="fa-solid fa-briefcase me-2"></i>Funzioni</h5>
|
||||||
|
|
||||||
|
<div class="header-actions d-flex gap-2 flex-wrap">
|
||||||
|
<a href="scadenzario/index.php" class="btn btn-scad-outline d-inline-flex align-items-center gap-2">
|
||||||
|
<i class="fa-solid fa-arrow-left"></i><span>Scadenzario</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="btn btn-scad-primary d-inline-flex align-items-center gap-2" id="btnAddFunction">
|
||||||
|
<i class="fa-solid fa-plus"></i><span>Nuova Funzione</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($functions) === 0): ?>
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="fa-solid fa-briefcase"></i>
|
||||||
|
<p>Nessuna funzione definita.<br>Clicca <strong>"Nuova Funzione"</strong> per aggiungere la prima.</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
|
<div id="functionsList">
|
||||||
|
|
||||||
|
<div class="d-md-none">
|
||||||
|
<?php foreach ($functions as $f): ?>
|
||||||
|
<div class="function-card"
|
||||||
|
data-id="<?= (int)$f['id'] ?>"
|
||||||
|
data-name="<?= htmlspecialchars($f['name'], ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-description="<?= htmlspecialchars($f['description'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-status="<?= htmlspecialchars($f['status'], ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-in-use="<?= (int)$f['deadline_count'] ?>">
|
||||||
|
|
||||||
|
<div class="fc-name"><?= htmlspecialchars($f['name'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||||
|
|
||||||
|
<?php if (!empty($f['description'])): ?>
|
||||||
|
<div class="text-muted small mt-1"><?= htmlspecialchars($f['description'], ENT_QUOTES, 'UTF-8') ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="fc-stats">
|
||||||
|
<span>Scadenze: <strong><?= (int)$f['deadline_count'] ?></strong></span>
|
||||||
|
<span>Aperte: <strong><?= (int)$f['open_count'] ?></strong></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-1 justify-content-end">
|
||||||
|
<button class="btn-action btn-action-edit btn-edit" title="Modifica">
|
||||||
|
<i class="fa-solid fa-pen"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn-action btn-action-delete btn-delete" title="Elimina">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-none d-md-block">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Descrizione</th>
|
||||||
|
<th class="text-center" style="width:120px">Scadenze</th>
|
||||||
|
<th class="text-center" style="width:120px">Aperte</th>
|
||||||
|
<th class="text-center" style="width:120px">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($functions as $f): ?>
|
||||||
|
<tr data-id="<?= (int)$f['id'] ?>"
|
||||||
|
data-name="<?= htmlspecialchars($f['name'], ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-description="<?= htmlspecialchars($f['description'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-status="<?= htmlspecialchars($f['status'], ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-in-use="<?= (int)$f['deadline_count'] ?>">
|
||||||
|
|
||||||
|
<td class="fw-semibold" style="color:var(--scad-heading)">
|
||||||
|
<?= htmlspecialchars($f['name'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-muted">
|
||||||
|
<?= htmlspecialchars($f['description'] ?? '—', ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center"><?= (int)$f['deadline_count'] ?></td>
|
||||||
|
<td class="text-center"><?= (int)$f['open_count'] ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="d-inline-flex gap-1">
|
||||||
|
<button class="btn-action btn-action-edit btn-edit" title="Modifica">
|
||||||
|
<i class="fa-solid fa-pen"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn-action btn-action-delete btn-delete" title="Elimina">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('../../include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="functionModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="functionModalTitle">Nuova Funzione</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="functionForm">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" id="functionId" name="id" value="">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="functionName" class="form-label fw-semibold">Nome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" id="functionName" name="name" maxlength="255" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="functionDescription" class="form-label fw-semibold">Descrizione</label>
|
||||||
|
<textarea class="form-control" id="functionDescription" name="description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-scad-primary">Salva</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('../../jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function() {
|
||||||
|
function openModal(data) {
|
||||||
|
const isEdit = !!data;
|
||||||
|
|
||||||
|
$('#functionModalTitle').text(isEdit ? 'Modifica Funzione' : 'Nuova Funzione');
|
||||||
|
$('#functionId').val(isEdit ? data.id : '');
|
||||||
|
$('#functionName').val(isEdit ? data.name : '');
|
||||||
|
$('#functionDescription').val(isEdit ? data.description : '');
|
||||||
|
|
||||||
|
new bootstrap.Modal('#functionModal').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#btnAddFunction').on('click', function() {
|
||||||
|
openModal(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#functionsList').on('click', '.btn-edit', function() {
|
||||||
|
const $row = $(this).closest('[data-id]');
|
||||||
|
|
||||||
|
openModal({
|
||||||
|
id: $row.data('id'),
|
||||||
|
name: $row.data('name'),
|
||||||
|
description: $row.data('description')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#functionsList').on('click', '.btn-delete', function() {
|
||||||
|
const $row = $(this).closest('[data-id]');
|
||||||
|
const inUse = parseInt($row.data('in-use') || 0, 10);
|
||||||
|
const name = $row.data('name');
|
||||||
|
|
||||||
|
if (inUse > 0) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Impossibile eliminare',
|
||||||
|
text: `La funzione "${name}" è utilizzata in ${inUse} scadenz${inUse === 1 ? 'a' : 'e'}.`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: `Eliminare "${name}"?`,
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Elimina',
|
||||||
|
cancelButtonText: 'Annulla',
|
||||||
|
confirmButtonColor: '#dc3545'
|
||||||
|
}).then(function(result) {
|
||||||
|
if (!result.isConfirmed) return;
|
||||||
|
|
||||||
|
$.post('scadenzario/functions/ajax/delete_function.php', {
|
||||||
|
id: $row.data('id')
|
||||||
|
})
|
||||||
|
.done(function(res) {
|
||||||
|
if (res.success) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Errore',
|
||||||
|
text: res.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function() {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Errore di rete'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#functionForm').on('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
id: $('#functionId').val(),
|
||||||
|
name: $('#functionName').val().trim(),
|
||||||
|
description: $('#functionDescription').val().trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!payload.name) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Nome obbligatorio'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post('scadenzario/functions/ajax/save_function.php', payload)
|
||||||
|
.done(function(res) {
|
||||||
|
if (res.success) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Errore',
|
||||||
|
text: res.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function() {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Errore di rete'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared data for the deadline modal form (used by index.php and detail.php).
|
||||||
|
* Populates $employees, $departments, $subjects. Safe to include more than once.
|
||||||
|
*/
|
||||||
|
if (!isset($pdo) || !$pdo) {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($employees)) {
|
||||||
|
$employees = $pdo->query("
|
||||||
|
SELECT e.id, e.first_name, e.last_name, e.department_id, dep.name AS department_name
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN departments dep ON dep.id = e.department_id
|
||||||
|
WHERE e.status = 'active'
|
||||||
|
ORDER BY e.first_name, e.last_name
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($departments)) {
|
||||||
|
$departments = $pdo->query("
|
||||||
|
SELECT id, name, code, color
|
||||||
|
FROM departments
|
||||||
|
WHERE is_active = 1
|
||||||
|
ORDER BY sort_order ASC, name ASC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($subjects)) {
|
||||||
|
$subjects = $pdo->query("SELECT id, name, color FROM scad_subjects ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($functions)) {
|
||||||
|
$functions = $pdo->query("
|
||||||
|
SELECT id, name
|
||||||
|
FROM scad_functions
|
||||||
|
WHERE status = 'active'
|
||||||
|
ORDER BY name ASC
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared "Nuova/Modifica Scadenza" modal markup (used by index.php and detail.php).
|
||||||
|
* Requires $subjects, $departments, $employees in scope (see deadline_form_data.php).
|
||||||
|
* The accompanying JS lives in deadline_modal_js.php.
|
||||||
|
*/
|
||||||
|
?>
|
||||||
|
<!-- Deadline Modal -->
|
||||||
|
<div class="modal fade" id="deadlineModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl modal-fullscreen-sm-down">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modalTitle">Nuova Scadenza</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||||
|
</div>
|
||||||
|
<form id="deadlineForm">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" id="dlId" name="id" value="">
|
||||||
|
|
||||||
|
<!-- Group 1: Informazioni principali -->
|
||||||
|
<div class="form-section-title">Informazioni principali</div>
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<label for="dlSubject" class="form-label fw-semibold">Argomento</label>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<select class="form-select" id="dlSubject" name="subject_id" style="flex:1">
|
||||||
|
<option value="">— Nessuno —</option>
|
||||||
|
<?php foreach ($subjects as $s): ?>
|
||||||
|
<option value="<?= (int)$s['id'] ?>" data-color="<?= htmlspecialchars($s['color'], ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($s['name'], ENT_QUOTES, 'UTF-8') ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<a href="scadenzario/subjects/index.php" target="_blank" class="btn btn-scad-outline" title="Gestisci argomenti">
|
||||||
|
<i class="fa-solid fa-gear"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<label for="dlFunction" class="form-label fw-semibold">Funzione</label>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<select class="form-select" id="dlFunction" name="function_id" style="flex:1">
|
||||||
|
<option value="">— Nessuna —</option>
|
||||||
|
<?php foreach ($functions as $fn): ?>
|
||||||
|
<option value="<?= (int)$fn['id'] ?>">
|
||||||
|
<?= htmlspecialchars($fn['name'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<a href="scadenzario/functions/index.php" target="_blank" class="btn btn-scad-outline" title="Gestisci funzioni">
|
||||||
|
<i class="fa-solid fa-gear"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<label for="dlLaw" class="form-label fw-semibold">Legge / Articolo</label>
|
||||||
|
<input type="text" class="form-control" id="dlLaw" name="law_regulation" maxlength="500" placeholder="es. D.Lgs. 81/2008, D.M. 10.03.1998...">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="dlTopic" class="form-label fw-semibold">Dettaglio <span class="text-danger">*</span></label>
|
||||||
|
<textarea class="form-control" id="dlTopic" name="topic" required maxlength="500" rows="2" placeholder="es. Verifica estintori, Autorizzazione trasporto rifiuti..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Group 2: Date e frequenza -->
|
||||||
|
<div class="form-section-title">Date e frequenza</div>
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="dlRecurrence" class="form-label fw-semibold">Periodicità</label>
|
||||||
|
<select class="form-select" id="dlRecurrence" name="recurrence_type">
|
||||||
|
<option value="once">Una tantum</option>
|
||||||
|
<option value="monthly">Mensile</option>
|
||||||
|
<option value="quarterly">Trimestrale</option>
|
||||||
|
<option value="semiannual">Semestrale</option>
|
||||||
|
<option value="annual">Annuale</option>
|
||||||
|
<option value="biennial">Biennale</option>
|
||||||
|
<option value="triennial">Triennale</option>
|
||||||
|
<option value="quadriennial">Quadriennale</option>
|
||||||
|
<option value="quinquennial">Quinquennale</option>
|
||||||
|
<option value="decennial">Decennale</option>
|
||||||
|
<option value="quindecennial">Quindicennale</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="dlDocDate" class="form-label fw-semibold">Data documento</label>
|
||||||
|
<input type="text" class="form-control js-date-it" id="dlDocDate" name="document_date" placeholder="gg/mm/aaaa" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="dlDueDate" class="form-label fw-semibold">Data scadenza <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control js-date-it" id="dlDueDate" name="due_date" placeholder="gg/mm/aaaa" autocomplete="off" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="dlCheckDate" class="form-label fw-semibold">Data ultimo controllo</label>
|
||||||
|
<input type="text" class="form-control js-date-it" id="dlCheckDate" name="check_date" placeholder="gg/mm/aaaa" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Group 3: Responsabili -->
|
||||||
|
<div class="form-section-title">Responsabili</div>
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="dlDepartments" class="form-label fw-semibold">Reparti</label>
|
||||||
|
<select class="form-select" id="dlDepartments" name="department_names[]" multiple>
|
||||||
|
<?php foreach ($departments as $dept): ?>
|
||||||
|
<option value="<?= htmlspecialchars($dept['name'], ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
<?= htmlspecialchars($dept['name'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
<?= !empty($dept['code']) ? ' (' . htmlspecialchars($dept['code'], ENT_QUOTES, 'UTF-8') . ')' : '' ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">Tutto il reparto sarà responsabile</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="dlEmployees" class="form-label fw-semibold">Singoli responsabili</label>
|
||||||
|
<select class="form-select" id="dlEmployees" name="employee_ids[]" multiple>
|
||||||
|
<?php foreach ($employees as $emp): ?>
|
||||||
|
<option value="<?= (int)$emp['id'] ?>">
|
||||||
|
<?= htmlspecialchars($emp['first_name'] . ' ' . $emp['last_name'], ENT_QUOTES, 'UTF-8') ?><?php if (!empty($emp['department_name'])): ?> (<?= htmlspecialchars($emp['department_name'], ENT_QUOTES, 'UTF-8') ?>)<?php endif; ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Group 4: Dettagli aggiuntivi -->
|
||||||
|
<div class="form-section-title">Dettagli aggiuntivi</div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<label for="dlNotifDays" class="form-label fw-semibold">Giorni preavviso</label>
|
||||||
|
<input type="number" class="form-control" id="dlNotifDays" name="notification_days" value="7" min="1" max="365">
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-8">
|
||||||
|
<label for="dlStorage" class="form-label fw-semibold">Luogo archiviazione</label>
|
||||||
|
<input type="text" class="form-control" id="dlStorage" name="storage_location" maxlength="500" placeholder="es. Armadio A3, Server/Documenti/Sicurezza...">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="dlNotes" class="form-label fw-semibold">Note</label>
|
||||||
|
<textarea class="form-control" id="dlNotes" name="notes" rows="3" placeholder="es. Scadenza 09/06/2026, Attività in appalto a Ditta specializzata..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Group 5: Allegati -->
|
||||||
|
<div class="form-section-title mt-4">Allegati</div>
|
||||||
|
<div id="attachmentsList" class="mb-3"></div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="dlFiles" class="form-label fw-semibold">Carica file</label>
|
||||||
|
<input type="file" class="form-control" id="dlFiles" multiple>
|
||||||
|
<div class="form-text">Puoi selezionare più file contemporaneamente</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-scad-primary">
|
||||||
|
<i class="fa-solid fa-check me-1"></i> Salva
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared styles for the deadline modal (deadline_modal.php).
|
||||||
|
* Relies on the --scad-* CSS variables defined on each page's :root.
|
||||||
|
*/
|
||||||
|
?>
|
||||||
|
<style>
|
||||||
|
.form-section-title {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--scad-heading);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
padding-bottom: 0.4rem;
|
||||||
|
border-bottom: 2px solid #e8eeff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#deadlineModal.modal {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#deadlineModal .modal-content,
|
||||||
|
#deadlineModal .modal-body,
|
||||||
|
#deadlineModal .modal-footer {
|
||||||
|
background: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#deadlineModal .modal-header {
|
||||||
|
background: var(--scad-card-bg);
|
||||||
|
border-bottom: 1px solid var(--scad-card-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
#deadlineModal .modal-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--scad-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attachment list in modal */
|
||||||
|
.att-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.att-item .att-name {
|
||||||
|
color: var(--scad-heading);
|
||||||
|
font-weight: 500;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.att-item .att-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.4rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.att-item .att-actions a,
|
||||||
|
.att-item .att-actions button {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.att-item .att-download {
|
||||||
|
background: #eef3ff;
|
||||||
|
color: var(--scad-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.att-item .att-download:hover {
|
||||||
|
background: var(--scad-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.att-item .att-remove {
|
||||||
|
background: #fff0f0;
|
||||||
|
color: var(--scad-red, #dc3545);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.att-item .att-remove:hover {
|
||||||
|
background: var(--scad-red, #dc3545);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Self-contained JS for the deadline modal (deadline_modal.php).
|
||||||
|
* Requires jQuery, Bootstrap, flatpickr, select2 and SweetAlert2 to be loaded first.
|
||||||
|
*
|
||||||
|
* Exposes:
|
||||||
|
* window.openDeadlineCreate() — open the modal empty (new deadline)
|
||||||
|
* window.openDeadlineEdit(id) — fetch a deadline and open the modal in edit mode
|
||||||
|
*
|
||||||
|
* Auto-open on load:
|
||||||
|
* #edit=<id> → opens edit for that id
|
||||||
|
* #edit → opens edit for window.SCAD_DETAIL_ID (used by detail.php)
|
||||||
|
*/
|
||||||
|
?>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// --- Flatpickr date fields (visible dd/mm/yyyy, submitted yyyy-mm-dd) ---
|
||||||
|
var fpOptsDate = {
|
||||||
|
dateFormat: 'Y-m-d',
|
||||||
|
altInput: true,
|
||||||
|
altFormat: 'd/m/Y',
|
||||||
|
locale: 'it',
|
||||||
|
allowInput: true
|
||||||
|
};
|
||||||
|
var fpDocDate = flatpickr('#dlDocDate', fpOptsDate);
|
||||||
|
var fpDueDate = flatpickr('#dlDueDate', fpOptsDate);
|
||||||
|
var fpCheckDate = flatpickr('#dlCheckDate', fpOptsDate);
|
||||||
|
|
||||||
|
// --- Select2 ---
|
||||||
|
var s2Opts = {
|
||||||
|
theme: 'bootstrap-5',
|
||||||
|
allowClear: true,
|
||||||
|
dropdownParent: $('#deadlineModal .modal-body'),
|
||||||
|
language: 'it',
|
||||||
|
width: '100%'
|
||||||
|
};
|
||||||
|
$('#dlSubject').select2($.extend({}, s2Opts, { placeholder: 'Seleziona argomento...' }));
|
||||||
|
$('#dlDepartments').select2($.extend({}, s2Opts, { placeholder: 'Seleziona reparti...' }));
|
||||||
|
$('#dlEmployees').select2($.extend({}, s2Opts, { placeholder: 'Seleziona persone...' }));
|
||||||
|
$('#dlFunction').select2($.extend({}, s2Opts, { placeholder: 'Seleziona funzione...' }));
|
||||||
|
|
||||||
|
// --- Auto-calc due_date from document_date + recurrence ---
|
||||||
|
var RECURRENCE_OFFSETS = {
|
||||||
|
monthly: { months: 1 },
|
||||||
|
quarterly: { months: 3 },
|
||||||
|
semiannual: { months: 6 },
|
||||||
|
annual: { years: 1 },
|
||||||
|
biennial: { years: 2 },
|
||||||
|
triennial: { years: 3 },
|
||||||
|
quadriennial: { years: 4 },
|
||||||
|
quinquennial: { years: 5 },
|
||||||
|
decennial: { years: 10 },
|
||||||
|
quindecennial: { years: 15 }
|
||||||
|
};
|
||||||
|
|
||||||
|
function computeDueDate() {
|
||||||
|
var docVal = document.getElementById('dlDocDate').value;
|
||||||
|
var recurrence = document.getElementById('dlRecurrence').value;
|
||||||
|
var offset = RECURRENCE_OFFSETS[recurrence];
|
||||||
|
if (!docVal || !offset) return;
|
||||||
|
var d = new Date(docVal + 'T00:00:00');
|
||||||
|
if (isNaN(d.getTime())) return;
|
||||||
|
if (offset.months) d.setMonth(d.getMonth() + offset.months);
|
||||||
|
if (offset.years) d.setFullYear(d.getFullYear() + offset.years);
|
||||||
|
var iso = d.getFullYear() + '-' +
|
||||||
|
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||||
|
String(d.getDate()).padStart(2, '0');
|
||||||
|
fpDueDate.setDate(iso, true, 'Y-m-d');
|
||||||
|
}
|
||||||
|
$('#dlDocDate, #dlRecurrence').on('change', computeDueDate);
|
||||||
|
|
||||||
|
// --- Modal instance ---
|
||||||
|
var modal = new bootstrap.Modal(document.getElementById('deadlineModal'));
|
||||||
|
var form = document.getElementById('deadlineForm');
|
||||||
|
|
||||||
|
// --- Render attachments list ---
|
||||||
|
function renderAttachments(attachments) {
|
||||||
|
var container = document.getElementById('attachmentsList');
|
||||||
|
if (!attachments || attachments.length === 0) {
|
||||||
|
container.innerHTML = '<div class="text-muted" style="font-size:0.85rem">Nessun allegato</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
container.innerHTML = attachments.map(function(a) {
|
||||||
|
return '<div class="att-item" data-att-id="' + a.id + '">' +
|
||||||
|
'<span class="att-name"><i class="fa-solid fa-paperclip me-1"></i>' + $('<span>').text(a.original_name).html() + '</span>' +
|
||||||
|
'<span class="att-actions">' +
|
||||||
|
'<a href="scadenzario/ajax/download_attachment.php?id=' + a.id + '" class="att-download" title="Scarica"><i class="fa-solid fa-download"></i></a>' +
|
||||||
|
'<button type="button" class="att-remove" title="Elimina" data-att-id="' + a.id + '"><i class="fa-solid fa-xmark"></i></button>' +
|
||||||
|
'</span></div>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Open modal (create) ---
|
||||||
|
window.openDeadlineCreate = function() {
|
||||||
|
form.reset();
|
||||||
|
document.getElementById('dlId').value = '';
|
||||||
|
document.getElementById('dlNotifDays').value = '7';
|
||||||
|
document.getElementById('modalTitle').textContent = 'Nuova Scadenza';
|
||||||
|
document.getElementById('dlFiles').value = '';
|
||||||
|
fpDocDate.clear();
|
||||||
|
fpDueDate.clear();
|
||||||
|
fpCheckDate.clear();
|
||||||
|
$('#dlSubject').val('').trigger('change');
|
||||||
|
$('#dlDepartments').val(null).trigger('change');
|
||||||
|
$('#dlEmployees').val(null).trigger('change');
|
||||||
|
$('#dlFunction').val('').trigger('change');
|
||||||
|
renderAttachments([]);
|
||||||
|
modal.show();
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Open modal (edit) ---
|
||||||
|
window.openDeadlineEdit = function(id) {
|
||||||
|
fetch('scadenzario/ajax/get_deadline.php?id=' + id)
|
||||||
|
.then(function(r) {
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
if (!data.success) {
|
||||||
|
Swal.fire('Errore', data.message, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var d = data.data;
|
||||||
|
document.getElementById('dlId').value = d.id;
|
||||||
|
$('#dlSubject').val(d.subject_id || '').trigger('change');
|
||||||
|
document.getElementById('dlTopic').value = d.topic || '';
|
||||||
|
$('#dlFunction').val(d.function_id || '').trigger('change');
|
||||||
|
document.getElementById('dlLaw').value = d.law_regulation || '';
|
||||||
|
document.getElementById('dlRecurrence').value = d.recurrence_type || 'once';
|
||||||
|
fpDocDate.setDate(d.document_date || null, false, 'Y-m-d');
|
||||||
|
fpDueDate.setDate(d.due_date || null, false, 'Y-m-d');
|
||||||
|
fpCheckDate.setDate(d.check_date || null, false, 'Y-m-d');
|
||||||
|
document.getElementById('dlNotifDays').value = d.notification_days || 7;
|
||||||
|
document.getElementById('dlStorage').value = d.storage_location || '';
|
||||||
|
document.getElementById('dlNotes').value = d.notes || '';
|
||||||
|
document.getElementById('dlFiles').value = '';
|
||||||
|
document.getElementById('modalTitle').textContent = 'Modifica Scadenza';
|
||||||
|
$('#dlDepartments').val(d.department_names || []).trigger('change');
|
||||||
|
if (Array.isArray(d.employee_ids)) {
|
||||||
|
$('#dlEmployees').val(d.employee_ids.map(String)).trigger('change');
|
||||||
|
} else {
|
||||||
|
$('#dlEmployees').val(null).trigger('change');
|
||||||
|
}
|
||||||
|
renderAttachments(d.attachments || []);
|
||||||
|
modal.show();
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Save ---
|
||||||
|
var isSaving = false;
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (isSaving) return;
|
||||||
|
isSaving = true;
|
||||||
|
var saveBtn = form.querySelector('[type="submit"]');
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
saveBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin me-1"></i> Salvataggio...';
|
||||||
|
var formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch('scadenzario/ajax/save_deadline.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(function(r) {
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
var deadlineId = data.id;
|
||||||
|
var fileInput = document.getElementById('dlFiles');
|
||||||
|
if (fileInput.files.length > 0) {
|
||||||
|
var fileData = new FormData();
|
||||||
|
fileData.append('deadline_id', deadlineId);
|
||||||
|
for (var i = 0; i < fileInput.files.length; i++) {
|
||||||
|
fileData.append('files[]', fileInput.files[i]);
|
||||||
|
}
|
||||||
|
return fetch('scadenzario/ajax/upload_attachment.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: fileData
|
||||||
|
})
|
||||||
|
.then(function(r) {
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(upData) {
|
||||||
|
modal.hide();
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Salvato',
|
||||||
|
text: data.message + ' ' + upData.message,
|
||||||
|
timer: 2000,
|
||||||
|
showConfirmButton: false
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
modal.hide();
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Salvato',
|
||||||
|
text: data.message,
|
||||||
|
timer: 1500,
|
||||||
|
showConfirmButton: false
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Swal.fire('Errore', data.message, 'error');
|
||||||
|
isSaving = false;
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerHTML = '<i class="fa-solid fa-check me-1"></i> Salva';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
||||||
|
isSaving = false;
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerHTML = '<i class="fa-solid fa-check me-1"></i> Salva';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Delete attachment ---
|
||||||
|
$(document).on('click', '.att-remove', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var btn = $(this);
|
||||||
|
var attId = btn.data('att-id');
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Rimuovere l\'allegato?',
|
||||||
|
text: 'Il collegamento verrà rimosso da questa scadenza. Il file resta disponibile se è usato da altre scadenze.',
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#dc3545',
|
||||||
|
cancelButtonText: 'Annulla',
|
||||||
|
confirmButtonText: 'Rimuovi',
|
||||||
|
reverseButtons: true
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
fetch('scadenzario/ajax/delete_attachment.php?id=' + attId)
|
||||||
|
.then(function(r) {
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
btn.closest('.att-item').remove();
|
||||||
|
if ($('#attachmentsList .att-item').length === 0) {
|
||||||
|
renderAttachments([]);
|
||||||
|
}
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Fatto',
|
||||||
|
text: data.message,
|
||||||
|
timer: 1800,
|
||||||
|
showConfirmButton: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire('Errore', data.message, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Auto-open from hash (#edit=ID or #edit for the current detail page) ---
|
||||||
|
var hash = window.location.hash;
|
||||||
|
var hashMatch = hash.match(/^#edit=(\d+)$/);
|
||||||
|
var autoEditId = hashMatch ? hashMatch[1] :
|
||||||
|
(hash === '#edit' && window.SCAD_DETAIL_ID ? window.SCAD_DETAIL_ID : null);
|
||||||
|
if (autoEditId) {
|
||||||
|
history.replaceState(null, '', window.location.pathname + window.location.search);
|
||||||
|
window.openDeadlineEdit(autoEditId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders two status banners for the current user:
|
* Renders two status banners for the current user:
|
||||||
* - red -> overdue deadlines (scaduta)
|
* - red -> overdue deadlines (scaduta)
|
||||||
@@ -43,49 +44,91 @@ if (!$_emp || ($_overdue === 0 && $_approaching === 0)) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<style>
|
<style>
|
||||||
.my-deadlines-widgets { display: flex; gap: 0.75rem; margin-bottom: 1rem; flex-wrap: wrap; }
|
.my-deadlines-widgets {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.my-deadlines-widgets .mdw {
|
.my-deadlines-widgets .mdw {
|
||||||
flex: 1 1 260px;
|
flex: 1 1 260px;
|
||||||
display: flex; align-items: center; gap: 0.9rem;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.9rem;
|
||||||
padding: 0.85rem 1rem;
|
padding: 0.85rem 1rem;
|
||||||
border-radius: 0.6rem;
|
border-radius: 0.6rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||||
transition: transform 0.15s, box-shadow 0.15s;
|
transition: transform 0.15s, box-shadow 0.15s;
|
||||||
}
|
}
|
||||||
.my-deadlines-widgets .mdw:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); color: #fff; }
|
|
||||||
.my-deadlines-widgets .mdw-red { background: linear-gradient(135deg, #dc3545 0%, #b02a37 100%); }
|
.my-deadlines-widgets .mdw:hover {
|
||||||
.my-deadlines-widgets .mdw-orange { background: linear-gradient(135deg, #e8930c 0%, #c77a00 100%); }
|
transform: translateY(-1px);
|
||||||
.my-deadlines-widgets .mdw-icon {
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
width: 42px; height: 42px; border-radius: 50%;
|
color: #fff;
|
||||||
display: flex; align-items: center; justify-content: center;
|
}
|
||||||
background: rgba(255,255,255,0.22); font-size: 1.2rem; flex-shrink: 0;
|
|
||||||
|
.my-deadlines-widgets .mdw-red {
|
||||||
|
background: linear-gradient(135deg, #dc3545 0%, #b02a37 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deadlines-widgets .mdw-orange {
|
||||||
|
background: linear-gradient(135deg, #e8930c 0%, #c77a00 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deadlines-widgets .mdw-icon {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.22);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deadlines-widgets .mdw-body {
|
||||||
|
flex: 1;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deadlines-widgets .mdw-count {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deadlines-widgets .mdw-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-deadlines-widgets .mdw-arrow {
|
||||||
|
opacity: 0.7;
|
||||||
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
.my-deadlines-widgets .mdw-body { flex: 1; line-height: 1.2; }
|
|
||||||
.my-deadlines-widgets .mdw-count { font-size: 1.6rem; font-weight: 700; }
|
|
||||||
.my-deadlines-widgets .mdw-label { font-size: 0.8rem; opacity: 0.95; }
|
|
||||||
.my-deadlines-widgets .mdw-arrow { opacity: 0.7; font-size: 0.9rem; }
|
|
||||||
</style>
|
</style>
|
||||||
<div class="my-deadlines-widgets">
|
<div class="my-deadlines-widgets">
|
||||||
<?php if ($_overdue > 0): ?>
|
<?php if ($_overdue > 0): ?>
|
||||||
<a class="mdw mdw-red" href="scadenzario/index.php?filter_my=1&filter_status=scaduta">
|
<a class="mdw mdw-red" href="scadenzario/index.php?filter_my=1&filter_status=scaduta">
|
||||||
<span class="mdw-icon"><i class="fa-solid fa-triangle-exclamation"></i></span>
|
<span class="mdw-icon"><i class="fa-solid fa-triangle-exclamation"></i></span>
|
||||||
<span class="mdw-body">
|
<span class="mdw-body">
|
||||||
<span class="mdw-count"><?= $_overdue ?></span>
|
<span class="mdw-count"><?= $_overdue ?></span>
|
||||||
<span class="mdw-label d-block">Scadenz<?= $_overdue === 1 ? 'a' : 'e' ?> scadut<?= $_overdue === 1 ? 'a' : 'e' ?> — <?= $_dept !== '' ? htmlspecialchars($_dept, ENT_QUOTES, 'UTF-8') : 'personali' ?></span>
|
<span class="mdw-label d-block">Task<?= $_overdue === 1 ? '' : 's' ?> scadut<?= $_overdue === 1 ? 'o' : 'i' ?> — <?= $_dept !== '' ? htmlspecialchars($_dept, ENT_QUOTES, 'UTF-8') : 'personali' ?></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="mdw-arrow"><i class="fa-solid fa-arrow-right"></i></span>
|
<span class="mdw-arrow"><i class="fa-solid fa-arrow-right"></i></span>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if ($_approaching > 0): ?>
|
<?php if ($_approaching > 0): ?>
|
||||||
<a class="mdw mdw-orange" href="scadenzario/index.php?filter_my=1&filter_status=in-scadenza">
|
<a class="mdw mdw-orange" href="scadenzario/index.php?filter_my=1&filter_status=in-scadenza">
|
||||||
<span class="mdw-icon"><i class="fa-solid fa-clock"></i></span>
|
<span class="mdw-icon"><i class="fa-solid fa-clock"></i></span>
|
||||||
<span class="mdw-body">
|
<span class="mdw-body">
|
||||||
<span class="mdw-count"><?= $_approaching ?></span>
|
<span class="mdw-count"><?= $_approaching ?></span>
|
||||||
<span class="mdw-label d-block">In scadenza a breve — <?= $_dept !== '' ? htmlspecialchars($_dept, ENT_QUOTES, 'UTF-8') : 'personali' ?></span>
|
<span class="mdw-label d-block">In scadenza a breve — <?= $_dept !== '' ? htmlspecialchars($_dept, ENT_QUOTES, 'UTF-8') : 'personali' ?></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="mdw-arrow"><i class="fa-solid fa-arrow-right"></i></span>
|
<span class="mdw-arrow"><i class="fa-solid fa-arrow-right"></i></span>
|
||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,12 +37,14 @@ $sql = "
|
|||||||
SELECT d.*,
|
SELECT d.*,
|
||||||
s.name AS subject_name,
|
s.name AS subject_name,
|
||||||
s.color AS subject_color,
|
s.color AS subject_color,
|
||||||
|
f.name AS function_name,
|
||||||
GROUP_CONCAT(DISTINCT CONCAT(e.first_name, ' ', e.last_name) ORDER BY e.first_name SEPARATOR ', ') as responsabili,
|
GROUP_CONCAT(DISTINCT CONCAT(e.first_name, ' ', e.last_name) ORDER BY e.first_name SEPARATOR ', ') as responsabili,
|
||||||
GROUP_CONCAT(DISTINCT dep.name ORDER BY dep.name SEPARATOR ', ') as reparti_persone,
|
GROUP_CONCAT(DISTINCT dep.name ORDER BY dep.name SEPARATOR ', ') as reparti_persone,
|
||||||
d.departments as reparti_assegnati,
|
d.departments as reparti_assegnati,
|
||||||
(SELECT COUNT(*) FROM scad_deadline_attachments att WHERE att.deadline_id = d.id) as attachment_count
|
(SELECT COUNT(*) FROM scad_deadline_attachments att WHERE att.deadline_id = d.id) as attachment_count
|
||||||
FROM scad_deadlines d
|
FROM scad_deadlines d
|
||||||
LEFT JOIN scad_subjects s ON s.id = d.subject_id
|
LEFT JOIN scad_subjects s ON s.id = d.subject_id
|
||||||
|
LEFT JOIN scad_functions f ON f.id = d.function_id
|
||||||
LEFT JOIN scad_deadline_employee de ON de.deadline_id = d.id
|
LEFT JOIN scad_deadline_employee de ON de.deadline_id = d.id
|
||||||
LEFT JOIN employees e ON e.id = de.employee_id
|
LEFT JOIN employees e ON e.id = de.employee_id
|
||||||
LEFT JOIN departments dep ON dep.id = e.department_id
|
LEFT JOIN departments dep ON dep.id = e.department_id
|
||||||
@@ -69,27 +71,7 @@ $stmt = $pdo->prepare($sql);
|
|||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
$deadlines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$deadlines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
$employees = $pdo->query("
|
require __DIR__ . '/include/deadline_form_data.php';
|
||||||
SELECT
|
|
||||||
e.id,
|
|
||||||
e.first_name,
|
|
||||||
e.last_name,
|
|
||||||
e.department_id,
|
|
||||||
dep.name AS department_name
|
|
||||||
FROM employees e
|
|
||||||
LEFT JOIN departments dep ON dep.id = e.department_id
|
|
||||||
WHERE e.status = 'active'
|
|
||||||
ORDER BY e.first_name, e.last_name
|
|
||||||
")->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$departments = $pdo->query("
|
|
||||||
SELECT id, name, code, color
|
|
||||||
FROM departments
|
|
||||||
WHERE is_active = 1
|
|
||||||
ORDER BY sort_order ASC, name ASC
|
|
||||||
")->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$subjects = $pdo->query("SELECT id, name, color FROM scad_subjects ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$today = date('Y-m-d');
|
$today = date('Y-m-d');
|
||||||
|
|
||||||
@@ -494,7 +476,8 @@ function getContrastTextColor($hexColor)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#deadlinesTable td:first-child {
|
#deadlinesTable td:first-child {
|
||||||
max-width: 150px;
|
max-width: 110px;
|
||||||
|
width: 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Attachment list in modal */
|
/* Attachment list in modal */
|
||||||
@@ -824,6 +807,9 @@ function getContrastTextColor($hexColor)
|
|||||||
<a href="scadenzario/subjects/index.php" class="btn btn-scad-outline d-none d-md-inline-flex align-items-center gap-2">
|
<a href="scadenzario/subjects/index.php" class="btn btn-scad-outline d-none d-md-inline-flex align-items-center gap-2">
|
||||||
<i class="fa-solid fa-tags"></i><span>Argomenti</span>
|
<i class="fa-solid fa-tags"></i><span>Argomenti</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="scadenzario/functions/index.php" class="btn btn-scad-outline d-none d-md-inline-flex align-items-center gap-2">
|
||||||
|
<i class="fa-solid fa-briefcase"></i><span>Funzioni</span>
|
||||||
|
</a>
|
||||||
<a href="scadenzario/calendar.php" class="btn btn-scad-outline d-none d-md-inline-flex align-items-center gap-2">
|
<a href="scadenzario/calendar.php" class="btn btn-scad-outline d-none d-md-inline-flex align-items-center gap-2">
|
||||||
<i class="fa-solid fa-calendar-days"></i><span>Calendario</span>
|
<i class="fa-solid fa-calendar-days"></i><span>Calendario</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -842,6 +828,7 @@ function getContrastTextColor($hexColor)
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item d-flex align-items-center gap-2" href="scadenzario/subjects/index.php"><i class="fa-solid fa-tags"></i> Argomenti</a></li>
|
<li><a class="dropdown-item d-flex align-items-center gap-2" href="scadenzario/subjects/index.php"><i class="fa-solid fa-tags"></i> Argomenti</a></li>
|
||||||
|
<li><a class="dropdown-item d-flex align-items-center gap-2" href="scadenzario/functions/index.php"><i class="fa-solid fa-briefcase"></i> Funzioni</a></li>
|
||||||
<li><a class="dropdown-item d-flex align-items-center gap-2" href="scadenzario/calendar.php"><i class="fa-solid fa-calendar-days"></i> Calendario</a></li>
|
<li><a class="dropdown-item d-flex align-items-center gap-2" href="scadenzario/calendar.php"><i class="fa-solid fa-calendar-days"></i> Calendario</a></li>
|
||||||
<li><button type="button" class="dropdown-item d-flex align-items-center gap-2" id="btnStampaMobile"><i class="fa-solid fa-print"></i> Stampa</button></li>
|
<li><button type="button" class="dropdown-item d-flex align-items-center gap-2" id="btnStampaMobile"><i class="fa-solid fa-print"></i> Stampa</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -923,7 +910,9 @@ function getContrastTextColor($hexColor)
|
|||||||
data-department="<?= htmlspecialchars($row['reparti'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
data-department="<?= htmlspecialchars($row['reparti'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
data-employees="<?= htmlspecialchars($row['responsabili'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
data-employees="<?= htmlspecialchars($row['responsabili'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
data-due-date="<?= htmlspecialchars($row['due_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
data-due-date="<?= htmlspecialchars($row['due_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
data-check-date="<?= htmlspecialchars($row['check_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
|
data-check-date="<?= htmlspecialchars($row['check_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-recurrence="<?= htmlspecialchars($row['recurrence_type'] ?? 'once', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-att-count="<?= (int)$row['attachment_count'] ?>">
|
||||||
<?php if (!empty($row['subject_name'])): ?>
|
<?php if (!empty($row['subject_name'])): ?>
|
||||||
<div class="mb-1"><?php
|
<div class="mb-1"><?php
|
||||||
$subjectBadgeBg = $row['subject_color'] ?: '#6c757d';
|
$subjectBadgeBg = $row['subject_color'] ?: '#6c757d';
|
||||||
@@ -972,11 +961,12 @@ function getContrastTextColor($hexColor)
|
|||||||
<table id="deadlinesTable" class="table table-hover align-middle mb-0" style="width:100%">
|
<table id="deadlinesTable" class="table table-hover align-middle mb-0" style="width:100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Argomento</th>
|
<th style="width:110px">Argomento</th>
|
||||||
<th>Dettaglio</th>
|
<th>Dettaglio</th>
|
||||||
<th class="d-none d-lg-table-cell">Legge/Art.</th>
|
<th class="d-none d-lg-table-cell">Legge/Art.</th>
|
||||||
<th>Scadenza</th>
|
<th>Scadenza</th>
|
||||||
<th class="d-none d-lg-table-cell">Verifica</th>
|
<th class="d-none d-lg-table-cell">Verifica</th>
|
||||||
|
<th>Funzione</th>
|
||||||
<th>Responsabili</th>
|
<th>Responsabili</th>
|
||||||
<th>Stato</th>
|
<th>Stato</th>
|
||||||
<th class="text-center" style="width:120px">Azioni</th>
|
<th class="text-center" style="width:120px">Azioni</th>
|
||||||
@@ -1000,7 +990,9 @@ function getContrastTextColor($hexColor)
|
|||||||
data-department="<?= htmlspecialchars($row['reparti'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
data-department="<?= htmlspecialchars($row['reparti'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
data-employees="<?= htmlspecialchars($row['responsabili'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
data-employees="<?= htmlspecialchars($row['responsabili'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
data-due-date="<?= htmlspecialchars($row['due_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
data-due-date="<?= htmlspecialchars($row['due_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
data-check-date="<?= htmlspecialchars($row['check_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
|
data-check-date="<?= htmlspecialchars($row['check_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-recurrence="<?= htmlspecialchars($row['recurrence_type'] ?? 'once', ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
data-att-count="<?= (int)$row['attachment_count'] ?>">
|
||||||
<td>
|
<td>
|
||||||
<?php if (!empty($row['subject_name'])): ?>
|
<?php if (!empty($row['subject_name'])): ?>
|
||||||
<?php
|
<?php
|
||||||
@@ -1014,6 +1006,7 @@ function getContrastTextColor($hexColor)
|
|||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a href="scadenzario/detail.php?id=<?= (int)$row['id'] ?>" class="fw-semibold text-decoration-none" style="color:var(--scad-heading)"><?= htmlspecialchars($row['topic'], ENT_QUOTES, 'UTF-8') ?></a>
|
<a href="scadenzario/detail.php?id=<?= (int)$row['id'] ?>" class="fw-semibold text-decoration-none" style="color:var(--scad-heading)"><?= htmlspecialchars($row['topic'], ENT_QUOTES, 'UTF-8') ?></a>
|
||||||
<?php if ((int)$row['attachment_count'] > 0): ?>
|
<?php if ((int)$row['attachment_count'] > 0): ?>
|
||||||
@@ -1023,6 +1016,17 @@ function getContrastTextColor($hexColor)
|
|||||||
<td class="d-none d-lg-table-cell text-muted"><?= htmlspecialchars($row['law_regulation'] ?? '—', ENT_QUOTES, 'UTF-8') ?></td>
|
<td class="d-none d-lg-table-cell text-muted"><?= htmlspecialchars($row['law_regulation'] ?? '—', ENT_QUOTES, 'UTF-8') ?></td>
|
||||||
<td><span class="text-nowrap"><?= $row['_dueFmt'] ?></span></td>
|
<td><span class="text-nowrap"><?= $row['_dueFmt'] ?></span></td>
|
||||||
<td class="d-none d-lg-table-cell text-muted"><?= $row['_checkFmt'] ?></td>
|
<td class="d-none d-lg-table-cell text-muted"><?= $row['_checkFmt'] ?></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($row['function_name'])): ?>
|
||||||
|
<span class="text-muted">
|
||||||
|
<i class="fa-solid fa-briefcase me-1"></i>
|
||||||
|
<?= htmlspecialchars($row['function_name'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">—</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if ($row['reparti']): ?><span class="text-muted"><i class="fa-regular fa-building me-1"></i><?= htmlspecialchars($row['reparti'], ENT_QUOTES, 'UTF-8') ?></span><?php endif; ?>
|
<?php if ($row['reparti']): ?><span class="text-muted"><i class="fa-regular fa-building me-1"></i><?= htmlspecialchars($row['reparti'], ENT_QUOTES, 'UTF-8') ?></span><?php endif; ?>
|
||||||
<?php if ($row['reparti'] && $row['responsabili']): ?><br><?php endif; ?>
|
<?php if ($row['reparti'] && $row['responsabili']): ?><br><?php endif; ?>
|
||||||
@@ -1055,143 +1059,7 @@ function getContrastTextColor($hexColor)
|
|||||||
<?php include('../include/footer.php'); ?>
|
<?php include('../include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Deadline Modal -->
|
<?php include __DIR__ . '/include/deadline_modal.php'; ?>
|
||||||
<div class="modal fade" id="deadlineModal" tabindex="-1" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-xl modal-fullscreen-sm-down">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="modalTitle">Nuova Scadenza</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
|
||||||
</div>
|
|
||||||
<form id="deadlineForm">
|
|
||||||
<div class="modal-body">
|
|
||||||
<input type="hidden" id="dlId" name="id" value="">
|
|
||||||
|
|
||||||
<!-- Group 1: Informazioni principali -->
|
|
||||||
<div class="form-section-title">Informazioni principali</div>
|
|
||||||
<div class="row g-3 mb-4">
|
|
||||||
<div class="col-12 col-md-6">
|
|
||||||
<label for="dlSubject" class="form-label fw-semibold">Argomento</label>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<select class="form-select" id="dlSubject" name="subject_id" style="flex:1">
|
|
||||||
<option value="">— Nessuno —</option>
|
|
||||||
<?php foreach ($subjects as $s): ?>
|
|
||||||
<option value="<?= (int)$s['id'] ?>" data-color="<?= htmlspecialchars($s['color'], ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($s['name'], ENT_QUOTES, 'UTF-8') ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
<a href="scadenzario/subjects/index.php" target="_blank" class="btn btn-scad-outline" title="Gestisci argomenti">
|
|
||||||
<i class="fa-solid fa-gear"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-6">
|
|
||||||
<label for="dlLaw" class="form-label fw-semibold">Legge / Articolo</label>
|
|
||||||
<input type="text" class="form-control" id="dlLaw" name="law_regulation" maxlength="500" placeholder="es. D.Lgs. 81/2008, D.M. 10.03.1998...">
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="dlTopic" class="form-label fw-semibold">Dettaglio <span class="text-danger">*</span></label>
|
|
||||||
<textarea class="form-control" id="dlTopic" name="topic" required maxlength="500" rows="2" placeholder="es. Verifica estintori, Autorizzazione trasporto rifiuti..."></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Group 2: Date e frequenza -->
|
|
||||||
<div class="form-section-title">Date e frequenza</div>
|
|
||||||
<div class="row g-3 mb-4">
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="dlRecurrence" class="form-label fw-semibold">Periodicità</label>
|
|
||||||
<select class="form-select" id="dlRecurrence" name="recurrence_type">
|
|
||||||
<option value="once">Una tantum</option>
|
|
||||||
<option value="monthly">Mensile</option>
|
|
||||||
<option value="quarterly">Trimestrale</option>
|
|
||||||
<option value="semiannual">Semestrale</option>
|
|
||||||
<option value="annual">Annuale</option>
|
|
||||||
<option value="biennial">Biennale</option>
|
|
||||||
<option value="triennial">Triennale</option>
|
|
||||||
<option value="quadriennial">Quadriennale</option>
|
|
||||||
<option value="quinquennial">Quinquennale</option>
|
|
||||||
<option value="decennial">Decennale</option>
|
|
||||||
<option value="quindecennial">Quindicennale</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="dlDocDate" class="form-label fw-semibold">Data documento</label>
|
|
||||||
<input type="date" class="form-control" id="dlDocDate" name="document_date">
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="dlDueDate" class="form-label fw-semibold">Data scadenza <span class="text-danger">*</span></label>
|
|
||||||
<input type="date" class="form-control" id="dlDueDate" name="due_date" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="dlCheckDate" class="form-label fw-semibold">Data ultimo controllo</label>
|
|
||||||
<input type="date" class="form-control" id="dlCheckDate" name="check_date">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Group 3: Responsabili -->
|
|
||||||
<div class="form-section-title">Responsabili</div>
|
|
||||||
<div class="row g-3 mb-4">
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="dlDepartments" class="form-label fw-semibold">Reparti</label>
|
|
||||||
<select class="form-select" id="dlDepartments" name="department_names[]" multiple>
|
|
||||||
<?php foreach ($departments as $dept): ?>
|
|
||||||
<option value="<?= htmlspecialchars($dept['name'], ENT_QUOTES, 'UTF-8') ?>">
|
|
||||||
<?= htmlspecialchars($dept['name'], ENT_QUOTES, 'UTF-8') ?>
|
|
||||||
<?= !empty($dept['code']) ? ' (' . htmlspecialchars($dept['code'], ENT_QUOTES, 'UTF-8') . ')' : '' ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
<div class="form-text">Tutto il reparto sarà responsabile</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="dlEmployees" class="form-label fw-semibold">Singoli responsabili</label>
|
|
||||||
<select class="form-select" id="dlEmployees" name="employee_ids[]" multiple>
|
|
||||||
<?php foreach ($employees as $emp): ?>
|
|
||||||
<option value="<?= (int)$emp['id'] ?>">
|
|
||||||
<?= htmlspecialchars($emp['first_name'] . ' ' . $emp['last_name'], ENT_QUOTES, 'UTF-8') ?><?php if (!empty($emp['department_name'])): ?> (<?= htmlspecialchars($emp['department_name'], ENT_QUOTES, 'UTF-8') ?>)<?php endif; ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Group 4: Dettagli aggiuntivi -->
|
|
||||||
<div class="form-section-title">Dettagli aggiuntivi</div>
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<label for="dlNotifDays" class="form-label fw-semibold">Giorni preavviso</label>
|
|
||||||
<input type="number" class="form-control" id="dlNotifDays" name="notification_days" value="7" min="1" max="365">
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-8">
|
|
||||||
<label for="dlStorage" class="form-label fw-semibold">Luogo archiviazione</label>
|
|
||||||
<input type="text" class="form-control" id="dlStorage" name="storage_location" maxlength="500" placeholder="es. Armadio A3, Server/Documenti/Sicurezza...">
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="dlNotes" class="form-label fw-semibold">Note</label>
|
|
||||||
<textarea class="form-control" id="dlNotes" name="notes" rows="3" placeholder="es. Scadenza 09/06/2026, Attività in appalto a Ditta specializzata..."></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Group 5: Allegati -->
|
|
||||||
<div class="form-section-title mt-4">Allegati</div>
|
|
||||||
<div id="attachmentsList" class="mb-3"></div>
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="dlFiles" class="form-label fw-semibold">Carica file</label>
|
|
||||||
<input type="file" class="form-control" id="dlFiles" multiple>
|
|
||||||
<div class="form-text">Puoi selezionare più file contemporaneamente</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
|
|
||||||
<button type="submit" class="btn btn-scad-primary">
|
|
||||||
<i class="fa-solid fa-check me-1"></i> Salva
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php include('../jsinclude.php'); ?>
|
<?php include('../jsinclude.php'); ?>
|
||||||
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
||||||
@@ -1221,84 +1089,6 @@ function getContrastTextColor($hexColor)
|
|||||||
var fpDue = flatpickr('#filterDueRange', fpOpts);
|
var fpDue = flatpickr('#filterDueRange', fpOpts);
|
||||||
var fpCheck = flatpickr('#filterCheckRange', fpOpts);
|
var fpCheck = flatpickr('#filterCheckRange', fpOpts);
|
||||||
|
|
||||||
// --- Select2 ---
|
|
||||||
$('#dlSubject').select2({
|
|
||||||
theme: 'bootstrap-5',
|
|
||||||
placeholder: 'Seleziona argomento...',
|
|
||||||
allowClear: true,
|
|
||||||
dropdownParent: $('#deadlineModal .modal-body'),
|
|
||||||
language: 'it',
|
|
||||||
width: '100%'
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#dlDepartments').select2({
|
|
||||||
theme: 'bootstrap-5',
|
|
||||||
placeholder: 'Seleziona reparti...',
|
|
||||||
allowClear: true,
|
|
||||||
dropdownParent: $('#deadlineModal .modal-body'),
|
|
||||||
language: 'it',
|
|
||||||
width: '100%'
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#dlEmployees').select2({
|
|
||||||
theme: 'bootstrap-5',
|
|
||||||
placeholder: 'Seleziona persone...',
|
|
||||||
allowClear: true,
|
|
||||||
dropdownParent: $('#deadlineModal .modal-body'),
|
|
||||||
language: 'it',
|
|
||||||
width: '100%'
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- Auto-calc due_date from document_date + recurrence ---
|
|
||||||
var RECURRENCE_OFFSETS = {
|
|
||||||
monthly: {
|
|
||||||
months: 1
|
|
||||||
},
|
|
||||||
quarterly: {
|
|
||||||
months: 3
|
|
||||||
},
|
|
||||||
semiannual: {
|
|
||||||
months: 6
|
|
||||||
},
|
|
||||||
annual: {
|
|
||||||
years: 1
|
|
||||||
},
|
|
||||||
biennial: {
|
|
||||||
years: 2
|
|
||||||
},
|
|
||||||
triennial: {
|
|
||||||
years: 3
|
|
||||||
},
|
|
||||||
quadriennial: {
|
|
||||||
years: 4
|
|
||||||
},
|
|
||||||
quinquennial: {
|
|
||||||
years: 5
|
|
||||||
},
|
|
||||||
decennial: {
|
|
||||||
years: 10
|
|
||||||
},
|
|
||||||
quindecennial: {
|
|
||||||
years: 15
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function computeDueDate() {
|
|
||||||
var docVal = document.getElementById('dlDocDate').value;
|
|
||||||
var recurrence = document.getElementById('dlRecurrence').value;
|
|
||||||
var offset = RECURRENCE_OFFSETS[recurrence];
|
|
||||||
if (!docVal || !offset) return;
|
|
||||||
var d = new Date(docVal + 'T00:00:00');
|
|
||||||
if (isNaN(d.getTime())) return;
|
|
||||||
if (offset.months) d.setMonth(d.getMonth() + offset.months);
|
|
||||||
if (offset.years) d.setFullYear(d.getFullYear() + offset.years);
|
|
||||||
var iso = d.getFullYear() + '-' +
|
|
||||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
|
||||||
String(d.getDate()).padStart(2, '0');
|
|
||||||
document.getElementById('dlDueDate').value = iso;
|
|
||||||
}
|
|
||||||
$('#dlDocDate, #dlRecurrence').on('change', computeDueDate);
|
|
||||||
|
|
||||||
// --- DataTables custom filters ---
|
// --- DataTables custom filters ---
|
||||||
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
|
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
|
||||||
if (settings.nTable.id !== 'deadlinesTable') return true;
|
if (settings.nTable.id !== 'deadlinesTable') return true;
|
||||||
@@ -1460,148 +1250,8 @@ function getContrastTextColor($hexColor)
|
|||||||
// Apply default filter on load
|
// Apply default filter on load
|
||||||
applyFiltersRefresh();
|
applyFiltersRefresh();
|
||||||
|
|
||||||
// --- Modal ---
|
|
||||||
var modal = new bootstrap.Modal(document.getElementById('deadlineModal'));
|
|
||||||
var form = document.getElementById('deadlineForm');
|
|
||||||
|
|
||||||
// Add
|
|
||||||
document.getElementById('btnAddDeadline').addEventListener('click', function() {
|
document.getElementById('btnAddDeadline').addEventListener('click', function() {
|
||||||
form.reset();
|
if (window.openDeadlineCreate) window.openDeadlineCreate();
|
||||||
document.getElementById('dlId').value = '';
|
|
||||||
document.getElementById('dlNotifDays').value = '7';
|
|
||||||
document.getElementById('modalTitle').textContent = 'Nuova Scadenza';
|
|
||||||
document.getElementById('dlFiles').value = '';
|
|
||||||
$('#dlSubject').val('').trigger('change');
|
|
||||||
$('#dlDepartments').val(null).trigger('change');
|
|
||||||
$('#dlEmployees').val(null).trigger('change');
|
|
||||||
renderAttachments([]);
|
|
||||||
modal.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save
|
|
||||||
var isSaving = false;
|
|
||||||
form.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (isSaving) return;
|
|
||||||
isSaving = true;
|
|
||||||
var saveBtn = form.querySelector('[type="submit"]');
|
|
||||||
saveBtn.disabled = true;
|
|
||||||
saveBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin me-1"></i> Salvataggio...';
|
|
||||||
var formData = new FormData(form);
|
|
||||||
|
|
||||||
fetch('scadenzario/ajax/save_deadline.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(function(r) {
|
|
||||||
return r.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
if (data.success) {
|
|
||||||
var deadlineId = data.id;
|
|
||||||
var fileInput = document.getElementById('dlFiles');
|
|
||||||
if (fileInput.files.length > 0) {
|
|
||||||
// Upload files
|
|
||||||
var fileData = new FormData();
|
|
||||||
fileData.append('deadline_id', deadlineId);
|
|
||||||
for (var i = 0; i < fileInput.files.length; i++) {
|
|
||||||
fileData.append('files[]', fileInput.files[i]);
|
|
||||||
}
|
|
||||||
return fetch('scadenzario/ajax/upload_attachment.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: fileData
|
|
||||||
})
|
|
||||||
.then(function(r) {
|
|
||||||
return r.json();
|
|
||||||
})
|
|
||||||
.then(function(upData) {
|
|
||||||
modal.hide();
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'success',
|
|
||||||
title: 'Salvato',
|
|
||||||
text: data.message + ' ' + upData.message,
|
|
||||||
timer: 2000,
|
|
||||||
showConfirmButton: false
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
modal.hide();
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'success',
|
|
||||||
title: 'Salvato',
|
|
||||||
text: data.message,
|
|
||||||
timer: 1500,
|
|
||||||
showConfirmButton: false
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Swal.fire('Errore', data.message, 'error');
|
|
||||||
isSaving = false;
|
|
||||||
saveBtn.disabled = false;
|
|
||||||
saveBtn.innerHTML = '<i class="fa-solid fa-check me-1"></i> Salva';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
|
||||||
isSaving = false;
|
|
||||||
saveBtn.disabled = false;
|
|
||||||
saveBtn.innerHTML = '<i class="fa-solid fa-check me-1"></i> Salva';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Render attachments list
|
|
||||||
function renderAttachments(attachments) {
|
|
||||||
var container = document.getElementById('attachmentsList');
|
|
||||||
if (!attachments || attachments.length === 0) {
|
|
||||||
container.innerHTML = '<div class="text-muted" style="font-size:0.85rem">Nessun allegato</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
container.innerHTML = attachments.map(function(a) {
|
|
||||||
return '<div class="att-item" data-att-id="' + a.id + '">' +
|
|
||||||
'<span class="att-name"><i class="fa-solid fa-paperclip me-1"></i>' + $('<span>').text(a.original_name).html() + '</span>' +
|
|
||||||
'<span class="att-actions">' +
|
|
||||||
'<a href="scadenzario/ajax/download_attachment.php?id=' + a.id + '" class="att-download" title="Scarica"><i class="fa-solid fa-download"></i></a>' +
|
|
||||||
'<button type="button" class="att-remove" title="Elimina" data-att-id="' + a.id + '"><i class="fa-solid fa-xmark"></i></button>' +
|
|
||||||
'</span></div>';
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete attachment
|
|
||||||
$(document).on('click', '.att-remove', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var btn = $(this);
|
|
||||||
var attId = btn.data('att-id');
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Eliminare allegato?',
|
|
||||||
icon: 'warning',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: '#dc3545',
|
|
||||||
cancelButtonText: 'Annulla',
|
|
||||||
confirmButtonText: 'Elimina'
|
|
||||||
}).then(function(result) {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
fetch('scadenzario/ajax/delete_attachment.php?id=' + attId)
|
|
||||||
.then(function(r) {
|
|
||||||
return r.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
if (data.success) {
|
|
||||||
btn.closest('.att-item').remove();
|
|
||||||
if ($('#attachmentsList .att-item').length === 0) {
|
|
||||||
renderAttachments([]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Swal.fire('Errore', data.message, 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Edit with confirmation
|
// Edit with confirmation
|
||||||
@@ -1618,91 +1268,103 @@ function getContrastTextColor($hexColor)
|
|||||||
confirmButtonText: 'Sì, modifica',
|
confirmButtonText: 'Sì, modifica',
|
||||||
reverseButtons: true
|
reverseButtons: true
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
if (!result.isConfirmed) {
|
if (result.isConfirmed && window.openDeadlineEdit) {
|
||||||
return;
|
window.openDeadlineEdit(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch('scadenzario/ajax/get_deadline.php?id=' + id)
|
|
||||||
.then(function(r) {
|
|
||||||
return r.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
if (!data.success) {
|
|
||||||
Swal.fire('Errore', data.message, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var d = data.data;
|
|
||||||
|
|
||||||
document.getElementById('dlId').value = d.id;
|
|
||||||
$('#dlSubject').val(d.subject_id || '').trigger('change');
|
|
||||||
document.getElementById('dlTopic').value = d.topic || '';
|
|
||||||
document.getElementById('dlLaw').value = d.law_regulation || '';
|
|
||||||
document.getElementById('dlRecurrence').value = d.recurrence_type || 'once';
|
|
||||||
document.getElementById('dlDocDate').value = d.document_date || '';
|
|
||||||
document.getElementById('dlDueDate').value = d.due_date || '';
|
|
||||||
document.getElementById('dlCheckDate').value = d.check_date || '';
|
|
||||||
document.getElementById('dlNotifDays').value = d.notification_days || 7;
|
|
||||||
document.getElementById('dlStorage').value = d.storage_location || '';
|
|
||||||
document.getElementById('dlNotes').value = d.notes || '';
|
|
||||||
document.getElementById('dlFiles').value = '';
|
|
||||||
|
|
||||||
document.getElementById('modalTitle').textContent = 'Modifica Scadenza';
|
|
||||||
|
|
||||||
$('#dlDepartments').val(d.department_names || []).trigger('change');
|
|
||||||
|
|
||||||
if (Array.isArray(d.employee_ids)) {
|
|
||||||
$('#dlEmployees').val(d.employee_ids.map(String)).trigger('change');
|
|
||||||
} else {
|
|
||||||
$('#dlEmployees').val(null).trigger('change');
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAttachments(d.attachments || []);
|
|
||||||
|
|
||||||
modal.show();
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Complete
|
// Complete
|
||||||
|
function submitComplete(id, createNext, copyAttachments) {
|
||||||
|
var fd = new FormData();
|
||||||
|
fd.append('id', id);
|
||||||
|
fd.append('create_next', createNext ? '1' : '0');
|
||||||
|
fd.append('copy_attachments', copyAttachments ? '1' : '0');
|
||||||
|
|
||||||
|
fetch('scadenzario/ajax/complete_deadline.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: fd
|
||||||
|
})
|
||||||
|
.then(function(r) {
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Completata',
|
||||||
|
text: data.message,
|
||||||
|
timer: 1800,
|
||||||
|
showConfirmButton: false
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
// Open the new deadline's detail page with the edit modal auto-opening
|
||||||
|
if (data.new_id) {
|
||||||
|
window.location = 'scadenzario/detail.php?id=' + data.new_id + '#edit';
|
||||||
|
} else {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire('Errore', data.message, 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(document).on('click', '.btn-complete', function() {
|
$(document).on('click', '.btn-complete', function() {
|
||||||
var el = $(this).closest('[data-id]');
|
var el = $(this).closest('[data-id]');
|
||||||
var id = el.data('id');
|
var id = el.data('id');
|
||||||
|
var recurrence = el.data('recurrence') || 'once';
|
||||||
|
var attCount = parseInt(el.data('att-count'), 10) || 0;
|
||||||
|
|
||||||
|
// Non-recurring: simple confirm, no new deadline is created
|
||||||
|
if (recurrence === 'once') {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Completare la scadenza?',
|
||||||
|
text: 'La scadenza verrà contrassegnata come completata.',
|
||||||
|
icon: 'question',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#198754',
|
||||||
|
cancelButtonText: 'Annulla',
|
||||||
|
confirmButtonText: 'Completa',
|
||||||
|
reverseButtons: true
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
submitComplete(id, false, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurring: ask whether to create the next deadline; optionally carry attachments over
|
||||||
|
var attCheckbox = attCount > 0 ?
|
||||||
|
'<div class="form-check d-flex align-items-center justify-content-center gap-2 mt-3">' +
|
||||||
|
'<input class="form-check-input" type="checkbox" id="swCopyAtt" checked>' +
|
||||||
|
'<label class="form-check-label" for="swCopyAtt">Copia gli allegati (' + attCount + ') sulla nuova scadenza</label>' +
|
||||||
|
'</div>' :
|
||||||
|
'';
|
||||||
|
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Completare la scadenza?',
|
title: 'Completare la scadenza?',
|
||||||
|
html: 'Vuoi creare automaticamente la prossima scadenza ricorrente?' + attCheckbox,
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
|
showDenyButton: true,
|
||||||
confirmButtonColor: '#198754',
|
confirmButtonColor: '#198754',
|
||||||
|
denyButtonColor: '#6c757d',
|
||||||
|
confirmButtonText: 'Completa e crea la prossima',
|
||||||
|
denyButtonText: 'Completa senza nuova',
|
||||||
cancelButtonText: 'Annulla',
|
cancelButtonText: 'Annulla',
|
||||||
confirmButtonText: 'Completa'
|
reverseButtons: true
|
||||||
}).then(function(result) {
|
}).then(function(result) {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
fetch('scadenzario/ajax/complete_deadline.php?id=' + id)
|
var copy = attCount > 0 ? document.getElementById('swCopyAtt').checked : false;
|
||||||
.then(function(r) {
|
submitComplete(id, true, copy);
|
||||||
return r.json();
|
} else if (result.isDenied) {
|
||||||
})
|
submitComplete(id, false, false);
|
||||||
.then(function(data) {
|
|
||||||
if (data.success) {
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'success',
|
|
||||||
title: 'Completata',
|
|
||||||
text: data.message,
|
|
||||||
timer: 2500,
|
|
||||||
showConfirmButton: false
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Swal.fire('Errore', data.message, 'error');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1748,38 +1410,6 @@ function getContrastTextColor($hexColor)
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-open edit modal from ?edit=ID
|
|
||||||
var urlParams = new URLSearchParams(window.location.search);
|
|
||||||
var editId = urlParams.get('edit');
|
|
||||||
if (editId) {
|
|
||||||
history.replaceState(null, '', 'scadenzario/index.php');
|
|
||||||
fetch('scadenzario/ajax/get_deadline.php?id=' + editId)
|
|
||||||
.then(function(r) {
|
|
||||||
return r.json();
|
|
||||||
})
|
|
||||||
.then(function(data) {
|
|
||||||
if (!data.success) return;
|
|
||||||
var d = data.data;
|
|
||||||
document.getElementById('dlId').value = d.id;
|
|
||||||
$('#dlSubject').val(d.subject_id || '').trigger('change');
|
|
||||||
document.getElementById('dlTopic').value = d.topic || '';
|
|
||||||
document.getElementById('dlLaw').value = d.law_regulation || '';
|
|
||||||
document.getElementById('dlRecurrence').value = d.recurrence_type || 'once';
|
|
||||||
document.getElementById('dlDocDate').value = d.document_date || '';
|
|
||||||
document.getElementById('dlDueDate').value = d.due_date || '';
|
|
||||||
document.getElementById('dlCheckDate').value = d.check_date || '';
|
|
||||||
document.getElementById('dlNotifDays').value = d.notification_days || 7;
|
|
||||||
document.getElementById('dlStorage').value = d.storage_location || '';
|
|
||||||
document.getElementById('dlNotes').value = d.notes || '';
|
|
||||||
document.getElementById('dlFiles').value = '';
|
|
||||||
document.getElementById('modalTitle').textContent = 'Modifica Scadenza';
|
|
||||||
$('#dlDepartments').val(d.department_names || []).trigger('change');
|
|
||||||
$('#dlEmployees').val(d.employee_ids.map(String)).trigger('change');
|
|
||||||
renderAttachments(d.attachments || []);
|
|
||||||
modal.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stampa
|
// Stampa
|
||||||
function doStampa() {
|
function doStampa() {
|
||||||
var params = [];
|
var params = [];
|
||||||
@@ -1802,6 +1432,7 @@ function getContrastTextColor($hexColor)
|
|||||||
if (btnStampaMobile) btnStampaMobile.addEventListener('click', doStampa);
|
if (btnStampaMobile) btnStampaMobile.addEventListener('click', doStampa);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<?php include __DIR__ . '/include/deadline_modal_js.php'; ?>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,868 @@
|
|||||||
|
<?php include('include/headscript.php'); ?>
|
||||||
|
<?php
|
||||||
|
$db = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$userId = (int)($iduserlogin ?? 0);
|
||||||
|
|
||||||
|
if ($userId <= 0) {
|
||||||
|
die('Utente non valido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($_SESSION['user_settings_csrf'])) {
|
||||||
|
$_SESSION['user_settings_csrf'] = bin2hex(random_bytes(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
$csrfToken = $_SESSION['user_settings_csrf'];
|
||||||
|
|
||||||
|
$successMessage = '';
|
||||||
|
$errorMessage = '';
|
||||||
|
|
||||||
|
// Load countries.
|
||||||
|
$countries = [];
|
||||||
|
try {
|
||||||
|
$stmtCountries = $pdo->query("
|
||||||
|
SELECT id, name, iso_3166_2
|
||||||
|
FROM auth_countries
|
||||||
|
ORDER BY name ASC
|
||||||
|
");
|
||||||
|
$countries = $stmtCountries->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$countries = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load current user.
|
||||||
|
$stmtProfileUser = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
phone,
|
||||||
|
avatar,
|
||||||
|
address,
|
||||||
|
country_id,
|
||||||
|
birthday,
|
||||||
|
role_id,
|
||||||
|
status,
|
||||||
|
last_login
|
||||||
|
FROM auth_users
|
||||||
|
WHERE id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmtProfileUser->execute([$userId]);
|
||||||
|
$profileUser = $stmtProfileUser->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$profileUser) {
|
||||||
|
die('Utente non trovato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function e($value)
|
||||||
|
{
|
||||||
|
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAvatarPath($avatar)
|
||||||
|
{
|
||||||
|
$avatar = trim((string)$avatar);
|
||||||
|
|
||||||
|
if ($avatar === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the database already contains a complete relative path, use it as it is.
|
||||||
|
if (
|
||||||
|
str_starts_with($avatar, '../') ||
|
||||||
|
str_starts_with($avatar, './') ||
|
||||||
|
str_starts_with($avatar, '/') ||
|
||||||
|
str_starts_with($avatar, 'http://') ||
|
||||||
|
str_starts_with($avatar, 'https://')
|
||||||
|
) {
|
||||||
|
return $avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the database contains only the filename, build the expected user upload path.
|
||||||
|
return '../upload/users/' . $avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAvatarInitials($profileUser)
|
||||||
|
{
|
||||||
|
$first = trim((string)($profileUser['first_name'] ?? ''));
|
||||||
|
$last = trim((string)($profileUser['last_name'] ?? ''));
|
||||||
|
$email = trim((string)($profileUser['email'] ?? ''));
|
||||||
|
|
||||||
|
$initials = '';
|
||||||
|
|
||||||
|
if ($first !== '') {
|
||||||
|
$initials .= mb_substr($first, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($last !== '') {
|
||||||
|
$initials .= mb_substr($last, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($initials === '' && $email !== '') {
|
||||||
|
$initials = mb_substr($email, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtoupper($initials ?: 'U');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$postedToken = $_POST['csrf_token'] ?? '';
|
||||||
|
|
||||||
|
if (!hash_equals($csrfToken, $postedToken)) {
|
||||||
|
$errorMessage = 'Sessione non valida. Ricarica la pagina e riprova.';
|
||||||
|
} else {
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$firstName = trim($_POST['first_name'] ?? '');
|
||||||
|
$lastName = trim($_POST['last_name'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$address = trim($_POST['address'] ?? '');
|
||||||
|
$countryId = $_POST['country_id'] !== '' ? (int)$_POST['country_id'] : null;
|
||||||
|
$birthday = trim($_POST['birthday'] ?? '');
|
||||||
|
|
||||||
|
$currentPassword = $_POST['current_password'] ?? '';
|
||||||
|
$newPassword = $_POST['new_password'] ?? '';
|
||||||
|
$confirmPassword = $_POST['confirm_password'] ?? '';
|
||||||
|
|
||||||
|
$birthdayValue = null;
|
||||||
|
$avatarToSave = $profileUser['avatar'];
|
||||||
|
|
||||||
|
if ($birthday !== '') {
|
||||||
|
$dateObj = DateTime::createFromFormat('Y-m-d', $birthday);
|
||||||
|
|
||||||
|
if (!$dateObj || $dateObj->format('Y-m-d') !== $birthday) {
|
||||||
|
$errorMessage = 'La data di nascita non è valida.';
|
||||||
|
} else {
|
||||||
|
$birthdayValue = $birthday;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errorMessage && $email === '') {
|
||||||
|
$errorMessage = 'L’email è obbligatoria.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errorMessage && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$errorMessage = 'L’email inserita non è valida.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check unique email.
|
||||||
|
if (!$errorMessage) {
|
||||||
|
$stmtCheckEmail = $pdo->prepare("
|
||||||
|
SELECT id
|
||||||
|
FROM auth_users
|
||||||
|
WHERE email = ? AND id <> ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmtCheckEmail->execute([$email, $userId]);
|
||||||
|
|
||||||
|
if ($stmtCheckEmail->fetchColumn()) {
|
||||||
|
$errorMessage = 'Questa email è già utilizzata da un altro utente.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avatar upload.
|
||||||
|
if (!$errorMessage && isset($_FILES['avatar']) && $_FILES['avatar']['error'] !== UPLOAD_ERR_NO_FILE) {
|
||||||
|
if ($_FILES['avatar']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$errorMessage = 'Errore durante il caricamento dell’avatar.';
|
||||||
|
} else {
|
||||||
|
$maxFileSize = 2 * 1024 * 1024; // 2 MB
|
||||||
|
|
||||||
|
if ($_FILES['avatar']['size'] > $maxFileSize) {
|
||||||
|
$errorMessage = 'L’avatar non può superare 2 MB.';
|
||||||
|
} else {
|
||||||
|
$tmpFile = $_FILES['avatar']['tmp_name'];
|
||||||
|
$originalName = $_FILES['avatar']['name'];
|
||||||
|
|
||||||
|
$allowedMimeTypes = [
|
||||||
|
'image/jpeg' => 'jpg',
|
||||||
|
'image/png' => 'png',
|
||||||
|
'image/webp' => 'webp',
|
||||||
|
'image/gif' => 'gif',
|
||||||
|
];
|
||||||
|
|
||||||
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||||
|
$mimeType = $finfo->file($tmpFile);
|
||||||
|
|
||||||
|
if (!array_key_exists($mimeType, $allowedMimeTypes)) {
|
||||||
|
$errorMessage = 'Formato avatar non valido. Sono consentiti JPG, PNG, WEBP o GIF.';
|
||||||
|
} else {
|
||||||
|
$uploadDir = __DIR__ . '/../upload/users/';
|
||||||
|
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$extension = $allowedMimeTypes[$mimeType];
|
||||||
|
$safeOriginalName = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', pathinfo($originalName, PATHINFO_FILENAME));
|
||||||
|
$fileName = time() . '_' . $userId . '_' . $safeOriginalName . '.' . $extension;
|
||||||
|
|
||||||
|
$destination = $uploadDir . $fileName;
|
||||||
|
|
||||||
|
if (!move_uploaded_file($tmpFile, $destination)) {
|
||||||
|
$errorMessage = 'Impossibile salvare il file avatar.';
|
||||||
|
} else {
|
||||||
|
// Path used by pages inside userarea, for example:
|
||||||
|
// <img src="../upload/users/file.jpg">
|
||||||
|
$avatarToSave = $fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$passwordToSave = null;
|
||||||
|
$wantsPasswordChange = ($currentPassword !== '' || $newPassword !== '' || $confirmPassword !== '');
|
||||||
|
|
||||||
|
if (!$errorMessage && $wantsPasswordChange) {
|
||||||
|
if ($currentPassword === '') {
|
||||||
|
$errorMessage = 'Inserisci la password attuale.';
|
||||||
|
} elseif ($newPassword === '') {
|
||||||
|
$errorMessage = 'Inserisci la nuova password.';
|
||||||
|
} elseif (strlen($newPassword) < 8) {
|
||||||
|
$errorMessage = 'La nuova password deve contenere almeno 8 caratteri.';
|
||||||
|
} elseif ($newPassword !== $confirmPassword) {
|
||||||
|
$errorMessage = 'La conferma password non corrisponde.';
|
||||||
|
} elseif (!password_verify($currentPassword, $profileUser['password'])) {
|
||||||
|
$errorMessage = 'La password attuale non è corretta.';
|
||||||
|
} else {
|
||||||
|
// Password is encrypted before saving.
|
||||||
|
$passwordToSave = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errorMessage) {
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$stmtUpdate = $pdo->prepare("
|
||||||
|
UPDATE auth_users
|
||||||
|
SET
|
||||||
|
email = :email,
|
||||||
|
first_name = :first_name,
|
||||||
|
last_name = :last_name,
|
||||||
|
phone = :phone,
|
||||||
|
avatar = :avatar,
|
||||||
|
address = :address,
|
||||||
|
country_id = :country_id,
|
||||||
|
birthday = :birthday,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmtUpdate->execute([
|
||||||
|
':email' => $email,
|
||||||
|
':first_name' => $firstName !== '' ? $firstName : null,
|
||||||
|
':last_name' => $lastName !== '' ? $lastName : null,
|
||||||
|
':phone' => $phone !== '' ? $phone : null,
|
||||||
|
':avatar' => $avatarToSave !== '' ? $avatarToSave : null,
|
||||||
|
':address' => $address !== '' ? $address : null,
|
||||||
|
':country_id' => $countryId,
|
||||||
|
':birthday' => $birthdayValue,
|
||||||
|
':id' => $userId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($passwordToSave !== null) {
|
||||||
|
$stmtPassword = $pdo->prepare("
|
||||||
|
UPDATE auth_users
|
||||||
|
SET password = ?, updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmtPassword->execute([$passwordToSave, $userId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
$successMessage = $passwordToSave !== null
|
||||||
|
? 'Profilo, avatar e password aggiornati correttamente.'
|
||||||
|
: 'Profilo aggiornato correttamente.';
|
||||||
|
|
||||||
|
// Reload updated user.
|
||||||
|
$stmtProfileUser->execute([$userId]);
|
||||||
|
$profileUser = $stmtProfileUser->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$_SESSION['user_settings_csrf'] = bin2hex(random_bytes(32));
|
||||||
|
$csrfToken = $_SESSION['user_settings_csrf'];
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
$errorMessage = 'Errore durante il salvataggio delle impostazioni.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$avatarPath = normalizeAvatarPath($profileUser['avatar'] ?? '');
|
||||||
|
?>
|
||||||
|
<!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>Impostazioni Utente <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #f3f6f8, #e8eef3);
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
color: #2b3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
padding: 1.4rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-wrap {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3.settings-title {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #2b3e50;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
padding: 16px 18px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-icon {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-heading {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-subtitle {
|
||||||
|
margin: 0;
|
||||||
|
color: #6b7a89;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 280px 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-panel {
|
||||||
|
background: linear-gradient(135deg, #f7fbff, #edf5ff);
|
||||||
|
border: 1px solid #dbeafe;
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-box {
|
||||||
|
width: 132px;
|
||||||
|
height: 132px;
|
||||||
|
border-radius: 34px;
|
||||||
|
background: linear-gradient(135deg, #cde5ff, #dff0ff);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #2b3e50;
|
||||||
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 auto 14px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-box img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
margin: 0;
|
||||||
|
color: #2b3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-email {
|
||||||
|
color: #6b7a89;
|
||||||
|
margin: 4px 0 14px 0;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-upload-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 11px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #2b3e50;
|
||||||
|
background: linear-gradient(135deg, #e5e7eb, #f3f4f6);
|
||||||
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-upload-label:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-upload-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-help {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #6b7a89;
|
||||||
|
margin-top: 10px;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-meta {
|
||||||
|
color: #6b7a89;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2b3e50;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control,
|
||||||
|
.form-select {
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #d8e0e7;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus,
|
||||||
|
.form-select:focus {
|
||||||
|
border-color: #8bbcf7;
|
||||||
|
box-shadow: 0 0 0 0.18rem rgba(139, 188, 247, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
font-size: 0.86rem;
|
||||||
|
color: #6b7a89;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-box {
|
||||||
|
background: linear-gradient(135deg, #fff7e6, #fffaf0);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid rgba(255, 184, 107, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save-settings {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 13px 24px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, #61ce5dff, #61ce5dff);
|
||||||
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save-settings:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 13px 20px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #2b3e50;
|
||||||
|
background: linear-gradient(135deg, #e5e7eb, #f3f4f6);
|
||||||
|
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
|
||||||
|
color: #2b3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readonly-note {
|
||||||
|
background: linear-gradient(135deg, #cde5ff, #dff0ff);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
color: #2b3e50;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-file-name {
|
||||||
|
font-size: 0.84rem;
|
||||||
|
color: #2b3e50;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.profile-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-panel {
|
||||||
|
max-width: 420px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.settings-body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-row {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save-settings,
|
||||||
|
.btn-back {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper toggled">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="settings-wrap">
|
||||||
|
<h3 class="settings-title">Impostazioni Utente</h3>
|
||||||
|
|
||||||
|
<?php if ($successMessage): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<?= e($successMessage); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($errorMessage): ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<?= e($errorMessage); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data" autocomplete="off">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= e($csrfToken); ?>">
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<div class="settings-header">
|
||||||
|
<div class="settings-icon">👤</div>
|
||||||
|
<div>
|
||||||
|
<p class="settings-heading">Profilo personale</p>
|
||||||
|
<p class="settings-subtitle">Dati anagrafici, contatti e avatar utente</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-body">
|
||||||
|
|
||||||
|
<div class="readonly-note">
|
||||||
|
Ruolo, stato account e impostazioni di sicurezza avanzate non sono modificabili da questa pagina.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-layout">
|
||||||
|
<div class="avatar-panel">
|
||||||
|
<div class="avatar-box" id="avatarPreviewBox">
|
||||||
|
<?php if (!empty($avatarPath)): ?>
|
||||||
|
<img src="<?= e($avatarPath); ?>" class="user-img" alt="user avatar" id="avatarPreviewImage">
|
||||||
|
<?php else: ?>
|
||||||
|
<span id="avatarInitials"><?= e(getAvatarInitials($profileUser)); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="avatar-name">
|
||||||
|
<?= e(trim(($profileUser['first_name'] ?? '') . ' ' . ($profileUser['last_name'] ?? '')) ?: 'Utente'); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="avatar-email">
|
||||||
|
<?= e($profileUser['email']); ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<label for="avatar" class="avatar-upload-label">
|
||||||
|
Carica avatar
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input type="file"
|
||||||
|
class="avatar-upload-input"
|
||||||
|
id="avatar"
|
||||||
|
name="avatar"
|
||||||
|
accept="image/jpeg,image/png,image/webp,image/gif">
|
||||||
|
|
||||||
|
<div id="selectedFileName" class="selected-file-name"></div>
|
||||||
|
|
||||||
|
<div class="avatar-help">
|
||||||
|
Formati consentiti: JPG, PNG, WEBP, GIF.<br>
|
||||||
|
Dimensione massima: 2 MB.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-meta">
|
||||||
|
Stato account: <?= e($profileUser['status']); ?>
|
||||||
|
<?php if (!empty($profileUser['last_login'])): ?>
|
||||||
|
<br>Ultimo accesso: <?= e(date('d/m/Y H:i', strtotime($profileUser['last_login']))); ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="first_name">Nome</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="first_name"
|
||||||
|
name="first_name"
|
||||||
|
value="<?= e($profileUser['first_name']); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="last_name">Cognome</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="last_name"
|
||||||
|
name="last_name"
|
||||||
|
value="<?= e($profileUser['last_name']); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="email">Email</label>
|
||||||
|
<input type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
value="<?= e($profileUser['email']); ?>"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="phone">Telefono</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="phone"
|
||||||
|
name="phone"
|
||||||
|
value="<?= e($profileUser['phone']); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="birthday">Data di nascita</label>
|
||||||
|
<input type="date"
|
||||||
|
class="form-control"
|
||||||
|
id="birthday"
|
||||||
|
name="birthday"
|
||||||
|
value="<?= e($profileUser['birthday']); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label" for="country_id">Paese</label>
|
||||||
|
<select class="form-select" id="country_id" name="country_id">
|
||||||
|
<option value="">Seleziona...</option>
|
||||||
|
<?php foreach ($countries as $country): ?>
|
||||||
|
<option value="<?= (int)$country['id']; ?>"
|
||||||
|
<?= ((int)$profileUser['country_id'] === (int)$country['id']) ? 'selected' : ''; ?>>
|
||||||
|
<?= e($country['name'] . (!empty($country['iso_3166_2']) ? ' (' . $country['iso_3166_2'] . ')' : '')); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label class="form-label" for="address">Indirizzo</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="address"
|
||||||
|
name="address"
|
||||||
|
value="<?= e($profileUser['address']); ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<div class="settings-header">
|
||||||
|
<div class="settings-icon">🔐</div>
|
||||||
|
<div>
|
||||||
|
<p class="settings-heading">Cambio password</p>
|
||||||
|
<p class="settings-subtitle">Compila questa sezione solo se vuoi modificare la password</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-body">
|
||||||
|
<div class="password-box">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" for="current_password">Password attuale</label>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="current_password"
|
||||||
|
name="current_password"
|
||||||
|
autocomplete="current-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" for="new_password">Nuova password</label>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="new_password"
|
||||||
|
name="new_password"
|
||||||
|
autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" for="confirm_password">Conferma nuova password</label>
|
||||||
|
<input type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="confirm_password"
|
||||||
|
name="confirm_password"
|
||||||
|
autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-text">
|
||||||
|
Se lasci questi campi vuoti, la password attuale rimane invariata.
|
||||||
|
La nuova password deve avere almeno 8 caratteri.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions-row">
|
||||||
|
<a href="production_dashboard.php" class="btn-back">← Torna alla dashboard</a>
|
||||||
|
<button type="submit" class="btn-save-settings">Salva impostazioni</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const avatarInput = document.getElementById('avatar');
|
||||||
|
const previewBox = document.getElementById('avatarPreviewBox');
|
||||||
|
const selectedFileName = document.getElementById('selectedFileName');
|
||||||
|
|
||||||
|
if (!avatarInput || !previewBox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarInput.addEventListener('change', function() {
|
||||||
|
const file = this.files && this.files[0] ? this.files[0] : null;
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
selectedFileName.textContent = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedFileName.textContent = file.name;
|
||||||
|
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = function(event) {
|
||||||
|
previewBox.innerHTML = '';
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = event.target.result;
|
||||||
|
img.className = 'user-img';
|
||||||
|
img.alt = 'user avatar';
|
||||||
|
|
||||||
|
previewBox.appendChild(img);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
; This file is for unifying the coding style for different editors and IDEs.
|
|
||||||
; More information at https://editorconfig.org
|
|
||||||
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_size = 4
|
|
||||||
indent_style = space
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[*.yml]
|
|
||||||
indent_size = 2
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
|
||||||
* text eol=lf
|
|
||||||
|
|
||||||
# Explicitly declare text files you want to always be normalized and converted
|
|
||||||
# to native line endings on checkout.
|
|
||||||
*.c text
|
|
||||||
*.h text
|
|
||||||
|
|
||||||
# Declare files that will always have CRLF line endings on checkout.
|
|
||||||
*.sln text eol=crlf
|
|
||||||
|
|
||||||
# Denote all files that are truly binary and should not be modified.
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.otf binary
|
|
||||||
*.eot binary
|
|
||||||
*.svg binary
|
|
||||||
*.ttf binary
|
|
||||||
*.woff binary
|
|
||||||
*.woff2 binary
|
|
||||||
|
|
||||||
*.css linguist-vendored
|
|
||||||
*.scss linguist-vendored
|
|
||||||
*.js linguist-vendored
|
|
||||||
CHANGELOG.md export-ignore
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
name: Tests
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tests:
|
|
||||||
|
|
||||||
name: PHP ${{ matrix.php }}
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php: ['7.3', '7.4', '8.0', '8.1']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Cache composer
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: ~/.composer/cache/files
|
|
||||||
key: php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php }}
|
|
||||||
extension-csv: bcmath, ctype, dom, fileinfo, intl, gd, json, mbstring, pdo, pdo_sqlite, openssl, sqlite, xml, zip
|
|
||||||
coverage: none
|
|
||||||
|
|
||||||
- name: Install composer
|
|
||||||
run: composer install --no-interaction --no-scripts --no-suggest --prefer-source
|
|
||||||
|
|
||||||
- name: Execute tests
|
|
||||||
run: vendor/bin/phpunit
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/.idea
|
|
||||||
/.history
|
|
||||||
/.vscode
|
|
||||||
/tests/databases
|
|
||||||
/vendor
|
|
||||||
.DS_Store
|
|
||||||
.phpunit.result.cache
|
|
||||||
composer.phar
|
|
||||||
composer.lock
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
preset: psr2
|
|
||||||
|
|
||||||
enabled:
|
|
||||||
- concat_with_spaces
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Andreas Lutro
|
|
||||||
|
|
||||||
Copyright (c) 2017 Akaunting
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
-186
@@ -1,186 +0,0 @@
|
|||||||
# Persistent settings package for Laravel
|
|
||||||
|
|
||||||
[](https://github.com/akaunting/laravel-setting)
|
|
||||||
[](https://styleci.io/repos/101231817)
|
|
||||||
[](LICENSE.md)
|
|
||||||
|
|
||||||
This package allows you to save settings in a more persistent way. You can use the database and/or json file to save your settings. You can also override the Laravel config.
|
|
||||||
|
|
||||||
* Driver support
|
|
||||||
* Helper function
|
|
||||||
* Blade directive
|
|
||||||
* Override config values
|
|
||||||
* Encryption
|
|
||||||
* Custom file, table and columns
|
|
||||||
* Auto save
|
|
||||||
* Extra columns
|
|
||||||
* Cache support
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### 1. Install
|
|
||||||
|
|
||||||
Run the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer require akaunting/laravel-setting
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Register (for Laravel < 5.5)
|
|
||||||
|
|
||||||
Register the service provider in `config/app.php`
|
|
||||||
|
|
||||||
```php
|
|
||||||
Akaunting\Setting\Provider::class,
|
|
||||||
```
|
|
||||||
|
|
||||||
Add alias if you want to use the facade.
|
|
||||||
|
|
||||||
```php
|
|
||||||
'Setting' => Akaunting\Setting\Facade::class,
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Publish
|
|
||||||
|
|
||||||
Publish config file.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
php artisan vendor:publish --tag=setting
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Database
|
|
||||||
|
|
||||||
Create table for database driver
|
|
||||||
|
|
||||||
```bash
|
|
||||||
php artisan migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Configure
|
|
||||||
|
|
||||||
You can change the options of your app from `config/setting.php` file
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
You can either use the helper method like `setting('foo')` or the facade `Setting::get('foo')`
|
|
||||||
|
|
||||||
### Facade
|
|
||||||
|
|
||||||
```php
|
|
||||||
Setting::get('foo', 'default');
|
|
||||||
Setting::get('nested.element');
|
|
||||||
Setting::set('foo', 'bar');
|
|
||||||
Setting::forget('foo');
|
|
||||||
$settings = Setting::all();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Helper
|
|
||||||
|
|
||||||
```php
|
|
||||||
setting('foo', 'default');
|
|
||||||
setting('nested.element');
|
|
||||||
setting(['foo' => 'bar']);
|
|
||||||
setting()->forget('foo');
|
|
||||||
$settings = setting()->all();
|
|
||||||
```
|
|
||||||
|
|
||||||
You can call the `save()` method to save the changes.
|
|
||||||
|
|
||||||
### Auto Save
|
|
||||||
|
|
||||||
If you enable the `auto_save` option in the config file, settings will be saved automatically every time the application shuts down if anything has been changed.
|
|
||||||
|
|
||||||
### Blade Directive
|
|
||||||
|
|
||||||
You can get the settings directly in your blade templates using the helper method or the blade directive like `@setting('foo')`
|
|
||||||
|
|
||||||
### Override Config Values
|
|
||||||
|
|
||||||
You can easily override default config values by adding them to the `override` option in `config/setting.php`, thereby eliminating the need to modify the default config files and also allowing you to change said values during production. Ex:
|
|
||||||
|
|
||||||
```php
|
|
||||||
'override' => [
|
|
||||||
"app.name" => "app_name",
|
|
||||||
"app.env" => "app_env",
|
|
||||||
"mail.driver" => "app_mail_driver",
|
|
||||||
"mail.host" => "app_mail_host",
|
|
||||||
],
|
|
||||||
```
|
|
||||||
|
|
||||||
The values on the left corresponds to the respective config value (Ex: config('app.name')) and the value on the right is the name of the `key` in your settings table/json file.
|
|
||||||
|
|
||||||
### Encryption
|
|
||||||
|
|
||||||
If you like to encrypt the values for a given key, you can pass the key to the `encrypted_keys` option in `config/setting.php` and the rest is automatically handled by using Laravel's built-in encryption facilities. Ex:
|
|
||||||
|
|
||||||
```php
|
|
||||||
'encrypted_keys' => [
|
|
||||||
"payment.key",
|
|
||||||
],
|
|
||||||
```
|
|
||||||
|
|
||||||
### JSON Storage
|
|
||||||
|
|
||||||
You can modify the path used on run-time using `setting()->setPath($path)`.
|
|
||||||
|
|
||||||
### Database Storage
|
|
||||||
|
|
||||||
If you want to use the database as settings storage then you should run the `php artisan migrate`. You can modify the table fields from the `create_settings_table` file in the migrations directory.
|
|
||||||
|
|
||||||
#### Extra Columns
|
|
||||||
|
|
||||||
If you want to store settings for multiple users/clients in the same database you can do so by specifying extra columns:
|
|
||||||
|
|
||||||
```php
|
|
||||||
setting()->setExtraColumns(['user_id' => Auth::user()->id]);
|
|
||||||
```
|
|
||||||
|
|
||||||
where `user_id = x` will now be added to the database query when settings are retrieved, and when new settings are saved, the `user_id` will be populated.
|
|
||||||
|
|
||||||
If you need more fine-tuned control over which data gets queried, you can use the `setConstraint` method which takes a closure with two arguments:
|
|
||||||
|
|
||||||
- `$query` is the query builder instance
|
|
||||||
- `$insert` is a boolean telling you whether the query is an insert or not. If it is an insert, you usually don't need to do anything to `$query`.
|
|
||||||
|
|
||||||
```php
|
|
||||||
setting()->setConstraint(function($query, $insert) {
|
|
||||||
if ($insert) return;
|
|
||||||
$query->where(/* ... */);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Drivers
|
|
||||||
|
|
||||||
This package uses the Laravel `Manager` class under the hood, so it's easy to add your own storage driver. All you need to do is extend the abstract `Driver` class, implement the abstract methods and call `setting()->extend`.
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyDriver extends Akaunting\Setting\Contracts\Driver
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
app('setting.manager')->extend('mydriver', function($app) {
|
|
||||||
return $app->make('MyDriver');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
Please see [Releases](../../releases) for more information what has changed recently.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Pull requests are more than welcome. You must follow the PSR coding standards.
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
If you discover any security related issues, please email security@akaunting.com instead of using the issue tracker.
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
- [Denis Duliçi](https://github.com/denisdulici)
|
|
||||||
- [All Contributors](../../contributors)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
The MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information.
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "akaunting/laravel-setting",
|
|
||||||
"description": "Persistent settings package for Laravel",
|
|
||||||
"keywords": [
|
|
||||||
"laravel",
|
|
||||||
"persistent",
|
|
||||||
"settings",
|
|
||||||
"config"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Denis Duliçi",
|
|
||||||
"email": "info@akaunting.com",
|
|
||||||
"homepage": "https://akaunting.com",
|
|
||||||
"role": "Developer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.5.9",
|
|
||||||
"laravel/framework": ">=5.3"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": ">=4.8",
|
|
||||||
"mockery/mockery": "0.9.*",
|
|
||||||
"laravel/framework": ">=5.3"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Akaunting\\Setting\\": "./src"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"src/helpers.php"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"laravel": {
|
|
||||||
"providers": [
|
|
||||||
"Akaunting\\Setting\\Provider"
|
|
||||||
],
|
|
||||||
"aliases": {
|
|
||||||
"Setting": "Akaunting\\Setting\\Facade"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minimum-stability": "dev",
|
|
||||||
"prefer-stable": true
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<phpunit backupGlobals="false"
|
|
||||||
backupStaticAttributes="false"
|
|
||||||
bootstrap="vendor/autoload.php"
|
|
||||||
colors="true"
|
|
||||||
convertErrorsToExceptions="true"
|
|
||||||
convertNoticesToExceptions="true"
|
|
||||||
convertWarningsToExceptions="true"
|
|
||||||
processIsolation="false"
|
|
||||||
stopOnFailure="false"
|
|
||||||
syntaxCheck="false"
|
|
||||||
>
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="Package Test Suite">
|
|
||||||
<directory suffix=".php">./tests/</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
</phpunit>
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Enable / Disable auto save
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Auto-save every time the application shuts down
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'auto_save' => false,
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Cache
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Options for caching. Set whether to enable cache, its key, time to live
|
|
||||||
| in seconds and whether to auto clear after save.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'cache' => [
|
|
||||||
'enabled' => false,
|
|
||||||
'key' => 'setting',
|
|
||||||
'ttl' => 3600,
|
|
||||||
'auto_clear' => true,
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Setting driver
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Select where to store the settings.
|
|
||||||
|
|
|
||||||
| Supported: "database", "json", "memory"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'driver' => 'database',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Database driver
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Options for database driver. Enter which connection to use, null means
|
|
||||||
| the default connection. Set the table and column names.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'database' => [
|
|
||||||
'connection' => null,
|
|
||||||
'table' => 'settings',
|
|
||||||
'key' => 'key',
|
|
||||||
'value' => 'value',
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| JSON driver
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Options for json driver. Enter the full path to the .json file.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'json' => [
|
|
||||||
'path' => storage_path() . '/settings.json',
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Override application config values
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| If defined, settings package will override these config values.
|
|
||||||
|
|
|
||||||
| Sample:
|
|
||||||
| "app.locale" => "settings.locale",
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'override' => [
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Fallback
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Define fallback settings to be used in case the default is null
|
|
||||||
|
|
|
||||||
| Sample:
|
|
||||||
| "currency" => "USD",
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'fallback' => [
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Required Extra Columns
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The list of columns required to be set up
|
|
||||||
|
|
|
||||||
| Sample:
|
|
||||||
| "user_id",
|
|
||||||
| "tenant_id",
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'required_extra_columns' => [
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Encryption
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Define the keys which should be crypt automatically.
|
|
||||||
|
|
|
||||||
| Sample:
|
|
||||||
| "payment.key"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'encrypted_keys' => [
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1,321 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting\Contracts;
|
|
||||||
|
|
||||||
use Akaunting\Setting\Support\Arr;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
|
|
||||||
abstract class Driver
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The settings data.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $data = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the store has changed since it was last loaded.
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $unsaved = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the settings data are loaded.
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $loaded = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Include and merge with fallbacks
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $with_fallback = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Excludes fallback data
|
|
||||||
*/
|
|
||||||
public function withoutFallback()
|
|
||||||
{
|
|
||||||
$this->with_fallback = false;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific key from the settings data.
|
|
||||||
*
|
|
||||||
* @param string|array $key
|
|
||||||
* @param mixed $default Optional default value.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function get($key, $default = null)
|
|
||||||
{
|
|
||||||
if (!$this->checkExtraColumns()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->load();
|
|
||||||
|
|
||||||
return Arr::get($this->data, $key, $default);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the fallback value if default is null.
|
|
||||||
*
|
|
||||||
* @param string|array $key
|
|
||||||
* @param mixed $default
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function getFallback($key, $default = null)
|
|
||||||
{
|
|
||||||
if (($default !== null) || is_array($key)) {
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Arr::get((array) config('setting.fallback'), $key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the given value is same as fallback.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param string $value
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isEqualToFallback($key, $value)
|
|
||||||
{
|
|
||||||
return (string) $this->getFallback($key) == (string) $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if a key exists in the settings data.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function has($key)
|
|
||||||
{
|
|
||||||
if (!$this->checkExtraColumns()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->load();
|
|
||||||
|
|
||||||
return Arr::has($this->data, $key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a specific key to a value in the settings data.
|
|
||||||
*
|
|
||||||
* @param string|array $key Key string or associative array of key => value
|
|
||||||
* @param mixed $value Optional only if the first argument is an array
|
|
||||||
*/
|
|
||||||
public function set($key, $value = null)
|
|
||||||
{
|
|
||||||
if (!$this->checkExtraColumns()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->load();
|
|
||||||
$this->unsaved = true;
|
|
||||||
|
|
||||||
if (is_array($key)) {
|
|
||||||
foreach ($key as $k => $v) {
|
|
||||||
Arr::set($this->data, $k, $v);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Arr::set($this->data, $key, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unset a key in the settings data.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*/
|
|
||||||
public function forget($key)
|
|
||||||
{
|
|
||||||
if (!$this->checkExtraColumns()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->unsaved = true;
|
|
||||||
|
|
||||||
if ($this->has($key)) {
|
|
||||||
Arr::forget($this->data, $key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unset all keys in the settings data.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function forgetAll()
|
|
||||||
{
|
|
||||||
if (!$this->checkExtraColumns()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config('setting.cache.enabled')) {
|
|
||||||
Cache::forget($this->getCacheKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->unsaved = true;
|
|
||||||
$this->data = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all settings data.
|
|
||||||
*
|
|
||||||
* @return array|bool
|
|
||||||
*/
|
|
||||||
public function all()
|
|
||||||
{
|
|
||||||
if (!$this->checkExtraColumns()) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->load();
|
|
||||||
|
|
||||||
return $this->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save any changes done to the settings data.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function save()
|
|
||||||
{
|
|
||||||
if (!$this->checkExtraColumns()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->unsaved) {
|
|
||||||
// either nothing has been changed, or data has not been loaded, so
|
|
||||||
// do nothing by returning early
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config('setting.cache.enabled') && config('setting.cache.auto_clear')) {
|
|
||||||
Cache::forget($this->getCacheKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->write($this->data);
|
|
||||||
$this->unsaved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure data is loaded.
|
|
||||||
*
|
|
||||||
* @param $force Force a reload of data. Default false.
|
|
||||||
*/
|
|
||||||
public function load($force = false)
|
|
||||||
{
|
|
||||||
if (!$this->checkExtraColumns()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->loaded && !$force) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fallback_data = $this->with_fallback ? config('setting.fallback') : [];
|
|
||||||
$driver_data = $this->readData();
|
|
||||||
|
|
||||||
$this->data = Arr::merge((array) $fallback_data, (array) $driver_data);
|
|
||||||
$this->loaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read data from driver or cache
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function readData()
|
|
||||||
{
|
|
||||||
if (config('setting.cache.enabled')) {
|
|
||||||
return $this->readDataFromCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->read();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read data from cache
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function readDataFromCache()
|
|
||||||
{
|
|
||||||
return Cache::remember($this->getCacheKey(), config('setting.cache.ttl'), function () {
|
|
||||||
return $this->read();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if extra columns are set up.
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function checkExtraColumns()
|
|
||||||
{
|
|
||||||
if (!$required_extra_columns = config('setting.required_extra_columns')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array_keys_exists($required_extra_columns, $this->getExtraColumns())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get cache key based on extra columns.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getCacheKey()
|
|
||||||
{
|
|
||||||
$key = config('setting.cache.key');
|
|
||||||
|
|
||||||
foreach ($this->getExtraColumns() as $name => $value) {
|
|
||||||
$key .= '_' . $name . '_' . $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get extra columns added to the rows.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
abstract protected function getExtraColumns();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read data from driver.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
abstract protected function read();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write data to driver.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
abstract protected function write(array $data);
|
|
||||||
}
|
|
||||||
@@ -1,372 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting\Drivers;
|
|
||||||
|
|
||||||
use Akaunting\Setting\Contracts\Driver;
|
|
||||||
use Akaunting\Setting\Support\Arr;
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Database\Connection;
|
|
||||||
use Illuminate\Support\Arr as LaravelArr;
|
|
||||||
use Illuminate\Support\Facades\Crypt;
|
|
||||||
|
|
||||||
class Database extends Driver
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The database connection instance.
|
|
||||||
*
|
|
||||||
* @var \Illuminate\Database\Connection
|
|
||||||
*/
|
|
||||||
protected $connection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The table to query from.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $table;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The key column name to query from.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The value column name to query from.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $value;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keys which should be encrypt automatically.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $encrypted_keys;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Any query constraints that should be applied.
|
|
||||||
*
|
|
||||||
* @var Closure|null
|
|
||||||
*/
|
|
||||||
protected $query_constraint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Any extra columns that should be added to the rows.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $extra_columns = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Illuminate\Database\Connection $connection
|
|
||||||
* @param string $table
|
|
||||||
*/
|
|
||||||
public function __construct(Connection $connection, $table = null, $key = null, $value = null, array $encrypted_keys = [])
|
|
||||||
{
|
|
||||||
$this->connection = $connection;
|
|
||||||
$this->table = $table ?: 'settings';
|
|
||||||
$this->key = $key ?: 'key';
|
|
||||||
$this->value = $value ?: 'value';
|
|
||||||
$this->encrypted_keys = $encrypted_keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the table to query from.
|
|
||||||
*
|
|
||||||
* @param string $table
|
|
||||||
*/
|
|
||||||
public function setTable($table)
|
|
||||||
{
|
|
||||||
$this->table = $table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the key column name to query from.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*/
|
|
||||||
public function setKey($key)
|
|
||||||
{
|
|
||||||
$this->key = $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the value column name to query from.
|
|
||||||
*
|
|
||||||
* @param string $value
|
|
||||||
*/
|
|
||||||
public function setValue($value)
|
|
||||||
{
|
|
||||||
$this->value = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the query constraint.
|
|
||||||
*
|
|
||||||
* @param Closure $callback
|
|
||||||
*/
|
|
||||||
public function setConstraint(Closure $callback)
|
|
||||||
{
|
|
||||||
$this->data = [];
|
|
||||||
$this->loaded = false;
|
|
||||||
$this->query_constraint = $callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set extra columns to be added to the rows.
|
|
||||||
*
|
|
||||||
* @param array $columns
|
|
||||||
*/
|
|
||||||
public function setExtraColumns(array $columns)
|
|
||||||
{
|
|
||||||
$this->extra_columns = $columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get extra columns added to the rows.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getExtraColumns()
|
|
||||||
{
|
|
||||||
return $this->extra_columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function forget($key)
|
|
||||||
{
|
|
||||||
parent::forget($key);
|
|
||||||
|
|
||||||
// because the database driver cannot store empty arrays, remove empty
|
|
||||||
// arrays to keep data consistent before and after saving
|
|
||||||
$segments = explode('.', $key);
|
|
||||||
array_pop($segments);
|
|
||||||
|
|
||||||
while ($segments) {
|
|
||||||
$segment = implode('.', $segments);
|
|
||||||
|
|
||||||
// non-empty array - exit out of the loop
|
|
||||||
if ($this->get($segment)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the empty array and move on to the next segment
|
|
||||||
$this->forget($segment);
|
|
||||||
array_pop($segments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function write(array $data)
|
|
||||||
{
|
|
||||||
// Get current data
|
|
||||||
$db_data = $this->newQuery()->get([$this->key, $this->value])->toArray();
|
|
||||||
|
|
||||||
$insert_data = LaravelArr::dot($data);
|
|
||||||
$update_data = [];
|
|
||||||
$delete_keys = [];
|
|
||||||
|
|
||||||
foreach ($db_data as $db_row) {
|
|
||||||
$key = $db_row->{$this->key};
|
|
||||||
$value = $db_row->{$this->value};
|
|
||||||
|
|
||||||
$is_in_insert = $is_different_in_db = $is_same_as_fallback = false;
|
|
||||||
|
|
||||||
if (isset($insert_data[$key])) {
|
|
||||||
$is_in_insert = true;
|
|
||||||
$is_different_in_db = (string) $insert_data[$key] != (string) $value;
|
|
||||||
$is_same_as_fallback = $this->isEqualToFallback($key, $insert_data[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($is_in_insert) {
|
|
||||||
if ($is_same_as_fallback) {
|
|
||||||
// Delete if new data is same as fallback
|
|
||||||
$delete_keys[] = $key;
|
|
||||||
} elseif ($is_different_in_db) {
|
|
||||||
// Update if new data is different from db
|
|
||||||
$update_data[$key] = $insert_data[$key];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Delete if current db not available in new data
|
|
||||||
$delete_keys[] = $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($insert_data[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($update_data as $key => $value) {
|
|
||||||
$value = $this->prepareValue($key, $value);
|
|
||||||
|
|
||||||
$this->newQuery()
|
|
||||||
->where($this->key, '=', $key)
|
|
||||||
->update([$this->value => $value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($insert_data) {
|
|
||||||
$this->newQuery(true)
|
|
||||||
->insert($this->prepareInsertData($insert_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($delete_keys) {
|
|
||||||
$this->newQuery()
|
|
||||||
->whereIn($this->key, $delete_keys)
|
|
||||||
->delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms settings data into an array ready to be insterted into the
|
|
||||||
* database. Call array_dot on a multidimensional array before passing it
|
|
||||||
* into this method!
|
|
||||||
*
|
|
||||||
* @param array $data Call array_dot on a multidimensional array before passing it into this method!
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function prepareInsertData(array $data)
|
|
||||||
{
|
|
||||||
$db_data = [];
|
|
||||||
|
|
||||||
if ($this->getExtraColumns()) {
|
|
||||||
foreach ($data as $key => $value) {
|
|
||||||
$value = $this->prepareValue($key, $value);
|
|
||||||
|
|
||||||
// Don't insert if same as fallback
|
|
||||||
if ($this->isEqualToFallback($key, $value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$db_data[] = array_merge(
|
|
||||||
$this->getExtraColumns(),
|
|
||||||
[$this->key => $key, $this->value => $value]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foreach ($data as $key => $value) {
|
|
||||||
$value = $this->prepareValue($key, $value);
|
|
||||||
|
|
||||||
// Don't insert if same as fallback
|
|
||||||
if ($this->isEqualToFallback($key, $value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$db_data[] = [$this->key => $key, $this->value => $value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $db_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the provided key should be encrypted or not.
|
|
||||||
* Also type casts the given value to a string so errors with booleans or integers are handeled.
|
|
||||||
* Otherwise it returns the original value.
|
|
||||||
*
|
|
||||||
* @param string $key Key to check if it's inside the encryptedValues variable.
|
|
||||||
* @param mixed $value Info: Encryption only supports strings.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function prepareValue(string $key, $value)
|
|
||||||
{
|
|
||||||
// Check if key should be encrypted
|
|
||||||
if (in_array($key, $this->encrypted_keys)) {
|
|
||||||
// Cast to string to avoid error when a user passes a boolean value
|
|
||||||
return Crypt::encryptString((string) $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the provided key should be decrypted or not.
|
|
||||||
* Otherwise it returns the original value.
|
|
||||||
*
|
|
||||||
* @param string $key Key to check if it's inside the encryptedValues variable.
|
|
||||||
* @param mixed $value Info: Encryption only supports strings.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function unpackValue(string $key, $value)
|
|
||||||
{
|
|
||||||
// Check if key should be encrypted
|
|
||||||
if (in_array($key, $this->encrypted_keys)) {
|
|
||||||
// Cast to string to avoid error when a user passes a boolean value
|
|
||||||
return Crypt::decryptString((string) $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function read()
|
|
||||||
{
|
|
||||||
return $this->parseReadData($this->newQuery()->get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse data coming from the database.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function parseReadData($data)
|
|
||||||
{
|
|
||||||
$results = [];
|
|
||||||
|
|
||||||
foreach ($data as $row) {
|
|
||||||
if (is_array($row)) {
|
|
||||||
$key = $row[$this->key];
|
|
||||||
$value = $row[$this->value];
|
|
||||||
} elseif (is_object($row)) {
|
|
||||||
$key = $row->{$this->key};
|
|
||||||
$value = $row->{$this->value};
|
|
||||||
} else {
|
|
||||||
$msg = 'Expected array or object, got ' . gettype($row);
|
|
||||||
throw new \UnexpectedValueException($msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encryption
|
|
||||||
$value = $this->unpackValue($key, $value);
|
|
||||||
|
|
||||||
Arr::set($results, $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new query builder instance.
|
|
||||||
*
|
|
||||||
* @param bool $insert
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Database\Query\Builder
|
|
||||||
*/
|
|
||||||
protected function newQuery($insert = false)
|
|
||||||
{
|
|
||||||
$query = $this->connection->table($this->table);
|
|
||||||
|
|
||||||
if (!$insert) {
|
|
||||||
foreach ($this->getExtraColumns() as $key => $value) {
|
|
||||||
$query->where($key, '=', $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->query_constraint !== null) {
|
|
||||||
$callback = $this->query_constraint;
|
|
||||||
$callback($query, $insert);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $query;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting\Drivers;
|
|
||||||
|
|
||||||
use Akaunting\Setting\Contracts\Driver;
|
|
||||||
use Illuminate\Filesystem\Filesystem;
|
|
||||||
|
|
||||||
class Json extends Driver
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param \Illuminate\Filesystem\Filesystem $files
|
|
||||||
* @param string $path
|
|
||||||
*/
|
|
||||||
public function __construct(Filesystem $files, $path = null)
|
|
||||||
{
|
|
||||||
$this->files = $files;
|
|
||||||
|
|
||||||
$this->setPath($path ?: storage_path() . '/settings.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the path for the JSON file.
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
*/
|
|
||||||
public function setPath($path)
|
|
||||||
{
|
|
||||||
// If the file does not already exist, we will attempt to create it.
|
|
||||||
if (!$this->files->exists($path)) {
|
|
||||||
$result = $this->files->put($path, '{}');
|
|
||||||
if ($result === false) {
|
|
||||||
throw new \InvalidArgumentException("Could not write to $path.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->files->isWritable($path)) {
|
|
||||||
throw new \InvalidArgumentException("$path is not writable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->path = $path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function getExtraColumns()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function read()
|
|
||||||
{
|
|
||||||
$contents = $this->files->get($this->path);
|
|
||||||
|
|
||||||
$data = json_decode($contents, true);
|
|
||||||
|
|
||||||
if ($data === null) {
|
|
||||||
throw new \RuntimeException("Invalid JSON in {$this->path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function write(array $data)
|
|
||||||
{
|
|
||||||
if ($data) {
|
|
||||||
$contents = json_encode($data);
|
|
||||||
} else {
|
|
||||||
$contents = '{}';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->files->put($this->path, $contents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting\Drivers;
|
|
||||||
|
|
||||||
use Akaunting\Setting\Contracts\Driver;
|
|
||||||
|
|
||||||
class Memory extends Driver
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param array $data
|
|
||||||
*/
|
|
||||||
public function __construct(array $data = null)
|
|
||||||
{
|
|
||||||
if ($data) {
|
|
||||||
$this->data = $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function getExtraColumns()
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function read()
|
|
||||||
{
|
|
||||||
return $this->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function write(array $data)
|
|
||||||
{
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Facade as BaseFacade;
|
|
||||||
|
|
||||||
class Facade extends BaseFacade
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the registered name of the component.
|
|
||||||
*/
|
|
||||||
public static function getFacadeAccessor()
|
|
||||||
{
|
|
||||||
return 'setting';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting;
|
|
||||||
|
|
||||||
use Akaunting\Setting\Drivers\Database;
|
|
||||||
use Akaunting\Setting\Drivers\Json;
|
|
||||||
use Akaunting\Setting\Drivers\Memory;
|
|
||||||
use Illuminate\Support\Manager as BaseManager;
|
|
||||||
|
|
||||||
class Manager extends BaseManager
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The container instance.
|
|
||||||
*
|
|
||||||
* @var \Illuminate\Contracts\Container\Container
|
|
||||||
*/
|
|
||||||
protected $container;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The application instance.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Contracts\Foundation\Application $app
|
|
||||||
*/
|
|
||||||
public function __construct($app = null)
|
|
||||||
{
|
|
||||||
$this->container = $app ?? app();
|
|
||||||
|
|
||||||
parent::__construct($this->container);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDefaultDriver()
|
|
||||||
{
|
|
||||||
return config('setting.driver');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createJsonDriver()
|
|
||||||
{
|
|
||||||
$path = config('setting.json.path');
|
|
||||||
|
|
||||||
return new Json($this->container['files'], $path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createDatabaseDriver()
|
|
||||||
{
|
|
||||||
$connection = $this->container['db']->connection(config('setting.database.connection'));
|
|
||||||
$table = config('setting.database.table');
|
|
||||||
$key = config('setting.database.key');
|
|
||||||
$value = config('setting.database.value');
|
|
||||||
$encryptedKeys = config('setting.encrypted_keys');
|
|
||||||
|
|
||||||
return new Database($connection, $table, $key, $value, $encryptedKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createMemoryDriver()
|
|
||||||
{
|
|
||||||
return new Memory();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createArrayDriver()
|
|
||||||
{
|
|
||||||
return $this->createMemoryDriver();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting\Middleware;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
|
|
||||||
class AutoSaveSetting
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Create a new save settings middleware.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->setting = app('setting');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle($request, Closure $next)
|
|
||||||
{
|
|
||||||
$response = $next($request);
|
|
||||||
|
|
||||||
$this->setting->save();
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-42
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
class CreateSettingsTable extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Set up the options.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->table = config('setting.database.table');
|
|
||||||
$this->key = config('setting.database.key');
|
|
||||||
$this->value = config('setting.database.value');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function up()
|
|
||||||
{
|
|
||||||
Schema::create($this->table, function (Blueprint $table) {
|
|
||||||
$table->increments('id');
|
|
||||||
$table->string($this->key)->index();
|
|
||||||
$table->text($this->value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function down()
|
|
||||||
{
|
|
||||||
Schema::drop($this->table);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting;
|
|
||||||
|
|
||||||
use Akaunting\Setting\Middleware\AutoSaveSetting;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
use Illuminate\View\Compilers\BladeCompiler;
|
|
||||||
|
|
||||||
class Provider extends ServiceProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Bootstrap the application services.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function boot()
|
|
||||||
{
|
|
||||||
$this->publishes([
|
|
||||||
__DIR__ . '/Config/setting.php' => config_path('setting.php'),
|
|
||||||
__DIR__ . '/Migrations/2017_08_24_000000_create_settings_table.php' => database_path('migrations/2017_08_24_000000_create_settings_table.php'),
|
|
||||||
], 'setting');
|
|
||||||
|
|
||||||
// Auto save setting
|
|
||||||
if (config('setting.auto_save')) {
|
|
||||||
$kernel = $this->app['Illuminate\Contracts\Http\Kernel'];
|
|
||||||
$kernel->pushMiddleware(AutoSaveSetting::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->override();
|
|
||||||
|
|
||||||
// Register blade directive
|
|
||||||
$this->callAfterResolving('blade.compiler', function (BladeCompiler $compiler) {
|
|
||||||
$compiler->directive('setting', function ($expression) {
|
|
||||||
return "<?php echo setting($expression); ?>";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the application services.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function register()
|
|
||||||
{
|
|
||||||
$this->app->singleton('setting.manager', function ($app) {
|
|
||||||
return new Manager($app);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->app->singleton('setting', function ($app) {
|
|
||||||
return $app['setting.manager']->driver();
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->mergeConfigFrom(__DIR__ . '/Config/setting.php', 'setting');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function override()
|
|
||||||
{
|
|
||||||
$override = config('setting.override', []);
|
|
||||||
|
|
||||||
foreach (Arr::dot($override) as $config_key => $setting_key) {
|
|
||||||
$config_key = is_string($config_key) ? $config_key : $setting_key;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (! is_null($value = setting($setting_key))) {
|
|
||||||
config([$config_key => $value]);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Akaunting\Setting\Support;
|
|
||||||
|
|
||||||
class Arr
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This class is a static class and should not be instantiated.
|
|
||||||
*/
|
|
||||||
private function __construct()
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an element from an array.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @param string $key Specify a nested element by separating keys with full stops.
|
|
||||||
* @param mixed $default If the element is not found, return this.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public static function get(array $data, $key, $default = null)
|
|
||||||
{
|
|
||||||
if ($key === null) {
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($key)) {
|
|
||||||
return static::getArray($data, $key, $default);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (explode('.', $key) as $segment) {
|
|
||||||
if (!is_array($data)) {
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!array_key_exists($segment, $data)) {
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $data[$segment];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function getArray(array $input, $keys, $default = null)
|
|
||||||
{
|
|
||||||
$output = [];
|
|
||||||
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
static::set($output, $key, static::get($input, $key, $default));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if an array has a given key.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @param string $key
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public static function has(array $data, $key)
|
|
||||||
{
|
|
||||||
foreach (explode('.', $key) as $segment) {
|
|
||||||
if (!is_array($data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!array_key_exists($segment, $data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $data[$segment];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set an element of an array.
|
|
||||||
*
|
|
||||||
* @param array $data
|
|
||||||
* @param string $key Specify a nested element by separating keys with full stops.
|
|
||||||
* @param mixed $value
|
|
||||||
*/
|
|
||||||
public static function set(array &$data, $key, $value)
|
|
||||||
{
|
|
||||||
$segments = explode('.', $key);
|
|
||||||
|
|
||||||
$key = array_pop($segments);
|
|
||||||
|
|
||||||
// iterate through all of $segments except the last one
|
|
||||||
foreach ($segments as $segment) {
|
|
||||||
if (!array_key_exists($segment, $data)) {
|
|
||||||
$data[$segment] = array();
|
|
||||||
} elseif (!is_array($data[$segment])) {
|
|
||||||
throw new \UnexpectedValueException('Non-array segment encountered');
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = &$data[$segment];
|
|
||||||
}
|
|
||||||
|
|
||||||
$data[$key] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unset an element from an array.
|
|
||||||
*
|
|
||||||
* @param array &$data
|
|
||||||
* @param string $key Specify a nested element by separating keys with full stops.
|
|
||||||
*/
|
|
||||||
public static function forget(array &$data, $key)
|
|
||||||
{
|
|
||||||
$segments = explode('.', $key);
|
|
||||||
|
|
||||||
$key = array_pop($segments);
|
|
||||||
|
|
||||||
// iterate through all of $segments except the last one
|
|
||||||
foreach ($segments as $segment) {
|
|
||||||
if (!array_key_exists($segment, $data)) {
|
|
||||||
return;
|
|
||||||
} elseif (!is_array($data[$segment])) {
|
|
||||||
throw new \UnexpectedValueException('Non-array segment encountered');
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = &$data[$segment];
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($data[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge two multidimensional arrays recursive
|
|
||||||
*
|
|
||||||
* @param array $array_1
|
|
||||||
* @param array $array_2
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function merge(array $array_1, array $array_2)
|
|
||||||
{
|
|
||||||
$merged = $array_1;
|
|
||||||
|
|
||||||
foreach ($array_2 as $key => $value) {
|
|
||||||
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
|
|
||||||
$merged[$key] = static::merge($merged[$key], $value);
|
|
||||||
} elseif (is_numeric($key)) {
|
|
||||||
if (!in_array($value, $merged)) {
|
|
||||||
$merged[] = $value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$merged[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $merged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
if (!function_exists('array_keys_exists')) {
|
|
||||||
/**
|
|
||||||
* Easily check if multiple array keys exist.
|
|
||||||
*
|
|
||||||
* @param array $keys
|
|
||||||
* @param array $arr
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
function array_keys_exists(array $keys, array $arr)
|
|
||||||
{
|
|
||||||
return !array_diff_key(array_flip($keys), $arr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!function_exists('setting')) {
|
|
||||||
/**
|
|
||||||
* Get / set the specified setting value.
|
|
||||||
*
|
|
||||||
* If an array is passed as the key, we will assume you want to set an array of values.
|
|
||||||
*
|
|
||||||
* @param array|string $key
|
|
||||||
* @param mixed $default
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
function setting($key = null, $default = null)
|
|
||||||
{
|
|
||||||
$setting = app('setting');
|
|
||||||
|
|
||||||
if (is_null($key)) {
|
|
||||||
return $setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($key)) {
|
|
||||||
$setting->set($key);
|
|
||||||
|
|
||||||
return $setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $setting->get($key, $default);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-118
@@ -1,118 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Akaunting\Setting\Drivers\Database;
|
|
||||||
|
|
||||||
abstract class AbstractFunctionalTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
abstract protected function createStore(array $data = []);
|
|
||||||
|
|
||||||
protected function assertStoreEquals($store, $expected, $message = null)
|
|
||||||
{
|
|
||||||
$this->assertEquals($expected, $store->all(), $message);
|
|
||||||
$store->save();
|
|
||||||
$store = $this->createStore();
|
|
||||||
$this->assertEquals($expected, $store->all(), $message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function assertStoreKeyEquals($store, $key, $expected, $message = null)
|
|
||||||
{
|
|
||||||
$this->assertEquals($expected, $store->get($key), $message);
|
|
||||||
$store->save();
|
|
||||||
$store = $this->createStore();
|
|
||||||
$this->assertEquals($expected, $store->get($key), $message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function store_is_initially_empty()
|
|
||||||
{
|
|
||||||
$store = $this->createStore();
|
|
||||||
$this->assertEquals([], $store->all());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function written_changes_are_saved()
|
|
||||||
{
|
|
||||||
$store = $this->createStore();
|
|
||||||
$store->set('foo', 'bar');
|
|
||||||
$this->assertStoreKeyEquals($store, 'foo', 'bar');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function nested_keys_are_nested()
|
|
||||||
{
|
|
||||||
$store = $this->createStore();
|
|
||||||
$store->set('foo.bar', 'baz');
|
|
||||||
$this->assertStoreEquals($store, ['foo' => ['bar' => 'baz']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function cannot_set_nested_key_on_non_array_member()
|
|
||||||
{
|
|
||||||
$store = $this->createStore();
|
|
||||||
$store->set('foo', 'bar');
|
|
||||||
$this->setExpectedException('UnexpectedValueException', 'Non-array segment encountered');
|
|
||||||
$store->set('foo.bar', 'baz');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function can_forget_key()
|
|
||||||
{
|
|
||||||
$store = $this->createStore();
|
|
||||||
$store->set('foo', 'bar');
|
|
||||||
$store->set('bar', 'baz');
|
|
||||||
$this->assertStoreEquals($store, ['foo' => 'bar', 'bar' => 'baz']);
|
|
||||||
|
|
||||||
$store->forget('foo');
|
|
||||||
$this->assertStoreEquals($store, ['bar' => 'baz']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function can_forget_nested_key()
|
|
||||||
{
|
|
||||||
$store = $this->createStore();
|
|
||||||
$store->set('foo.bar', 'baz');
|
|
||||||
$store->set('foo.baz', 'bar');
|
|
||||||
$store->set('bar.foo', 'baz');
|
|
||||||
$this->assertStoreEquals($store, [
|
|
||||||
'foo' => [
|
|
||||||
'bar' => 'baz',
|
|
||||||
'baz' => 'bar',
|
|
||||||
],
|
|
||||||
'bar' => [
|
|
||||||
'foo' => 'baz',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$store->forget('foo.bar');
|
|
||||||
$this->assertStoreEquals($store, [
|
|
||||||
'foo' => [
|
|
||||||
'baz' => 'bar',
|
|
||||||
],
|
|
||||||
'bar' => [
|
|
||||||
'foo' => 'baz',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$store->forget('bar.foo');
|
|
||||||
$expected = [
|
|
||||||
'foo' => [
|
|
||||||
'baz' => 'bar',
|
|
||||||
],
|
|
||||||
'bar' => [
|
|
||||||
],
|
|
||||||
];
|
|
||||||
if ($store instanceof Database) {
|
|
||||||
unset($expected['bar']);
|
|
||||||
}
|
|
||||||
$this->assertStoreEquals($store, $expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function can_forget_all()
|
|
||||||
{
|
|
||||||
$store = $this->createStore(['foo' => 'bar']);
|
|
||||||
$this->assertStoreEquals($store, ['foo' => 'bar']);
|
|
||||||
$store->forgetAll();
|
|
||||||
$this->assertStoreEquals($store, []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class DatabaseTest extends AbstractFunctionalTest
|
|
||||||
{
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->container = new \Illuminate\Container\Container();
|
|
||||||
$this->capsule = new \Illuminate\Database\Capsule\Manager($this->container);
|
|
||||||
$this->capsule->setAsGlobal();
|
|
||||||
$this->container['db'] = $this->capsule;
|
|
||||||
$this->capsule->addConnection([
|
|
||||||
'driver' => 'sqlite',
|
|
||||||
'database' => ':memory:',
|
|
||||||
'prefix' => '',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->capsule->schema()->create('settings', function ($t) {
|
|
||||||
$t->string('key', 64)->unique();
|
|
||||||
$t->string('value', 4096);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function tearDown()
|
|
||||||
{
|
|
||||||
$this->capsule->schema()->drop('settings');
|
|
||||||
unset($this->capsule);
|
|
||||||
unset($this->container);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createStore(array $data = [])
|
|
||||||
{
|
|
||||||
if ($data) {
|
|
||||||
$store = $this->createStore();
|
|
||||||
$store->set($data);
|
|
||||||
$store->save();
|
|
||||||
unset($store);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new \Akaunting\Setting\Drivers\Database(
|
|
||||||
$this->capsule->getConnection()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class JsonTest extends AbstractFunctionalTest
|
|
||||||
{
|
|
||||||
protected function createStore(array $data = null)
|
|
||||||
{
|
|
||||||
$path = dirname(__DIR__) . '/tmp/store.json';
|
|
||||||
|
|
||||||
if ($data !== null) {
|
|
||||||
if ($data) {
|
|
||||||
$json = json_encode($data);
|
|
||||||
} else {
|
|
||||||
$json = '{}';
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents($path, $json);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new \Akaunting\Setting\Drivers\Json(
|
|
||||||
new \Illuminate\Filesystem\Filesystem(),
|
|
||||||
$path
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function tearDown()
|
|
||||||
{
|
|
||||||
$path = dirname(__DIR__) . '/tmp/store.json';
|
|
||||||
unlink($path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class MemoryTest extends AbstractFunctionalTest
|
|
||||||
{
|
|
||||||
protected function assertStoreEquals($store, $expected, $message = null)
|
|
||||||
{
|
|
||||||
$this->assertEquals($expected, $store->all(), $message);
|
|
||||||
// removed persistance test assertions
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function assertStoreKeyEquals($store, $key, $expected, $message = null)
|
|
||||||
{
|
|
||||||
$this->assertEquals($expected, $store->get($key), $message);
|
|
||||||
// removed persistance test assertions
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createStore(array $data = null)
|
|
||||||
{
|
|
||||||
return new \Akaunting\Setting\Drivers\Memory($data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Akaunting\Setting\Support\Arr;
|
|
||||||
|
|
||||||
class ArrayUtilityTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider getGetData
|
|
||||||
*/
|
|
||||||
public function getReturnsCorrectValue(array $data, $key, $expected)
|
|
||||||
{
|
|
||||||
$this->assertEquals($expected, Arr::get($data, $key));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGetData()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[[], 'foo', null],
|
|
||||||
[['foo' => 'bar'], 'foo', 'bar'],
|
|
||||||
[['foo' => 'bar'], 'bar', null],
|
|
||||||
[['foo' => 'bar'], 'foo.bar', null],
|
|
||||||
[['foo' => ['bar' => 'baz']], 'foo.bar', 'baz'],
|
|
||||||
[['foo' => ['bar' => 'baz']], 'foo.baz', null],
|
|
||||||
[['foo' => ['bar' => 'baz']], 'foo', ['bar' => 'baz']],
|
|
||||||
[
|
|
||||||
['foo' => 'bar', 'bar' => 'baz'],
|
|
||||||
['foo', 'bar'],
|
|
||||||
['foo' => 'bar', 'bar' => 'baz'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
['foo' => ['bar' => 'baz'], 'bar' => 'baz'],
|
|
||||||
['foo.bar', 'bar'],
|
|
||||||
['foo' => ['bar' => 'baz'], 'bar' => 'baz'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
['foo' => ['bar' => 'baz'], 'bar' => 'baz'],
|
|
||||||
['foo.bar'],
|
|
||||||
['foo' => ['bar' => 'baz']],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
['foo' => ['bar' => 'baz'], 'bar' => 'baz'],
|
|
||||||
['foo.bar', 'baz'],
|
|
||||||
['foo' => ['bar' => 'baz'], 'baz' => null],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider getSetData
|
|
||||||
*/
|
|
||||||
public function setSetsCorrectKeyToValue(array $input, $key, $value, array $expected)
|
|
||||||
{
|
|
||||||
Arr::set($input, $key, $value);
|
|
||||||
$this->assertEquals($expected, $input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSetData()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
['foo' => 'bar'],
|
|
||||||
'foo',
|
|
||||||
'baz',
|
|
||||||
['foo' => 'baz'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[],
|
|
||||||
'foo',
|
|
||||||
'bar',
|
|
||||||
['foo' => 'bar'],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[],
|
|
||||||
'foo.bar',
|
|
||||||
'baz',
|
|
||||||
['foo' => ['bar' => 'baz']],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
['foo' => ['bar' => 'baz']],
|
|
||||||
'foo.baz',
|
|
||||||
'foo',
|
|
||||||
['foo' => ['bar' => 'baz', 'baz' => 'foo']],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
['foo' => ['bar' => 'baz']],
|
|
||||||
'foo.baz.bar',
|
|
||||||
'baz',
|
|
||||||
['foo' => ['bar' => 'baz', 'baz' => ['bar' => 'baz']]],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[],
|
|
||||||
'foo.bar.baz',
|
|
||||||
'foo',
|
|
||||||
['foo' => ['bar' => ['baz' => 'foo']]],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function setThrowsExceptionOnNonArraySegment()
|
|
||||||
{
|
|
||||||
$data = ['foo' => 'bar'];
|
|
||||||
$this->setExpectedException('UnexpectedValueException', 'Non-array segment encountered');
|
|
||||||
Arr::set($data, 'foo.bar', 'baz');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider getHasData
|
|
||||||
*/
|
|
||||||
public function hasReturnsCorrectly(array $input, $key, $expected)
|
|
||||||
{
|
|
||||||
$this->assertEquals($expected, Arr::has($input, $key));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHasData()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[[], 'foo', false],
|
|
||||||
[['foo' => 'bar'], 'foo', true],
|
|
||||||
[['foo' => 'bar'], 'bar', false],
|
|
||||||
[['foo' => 'bar'], 'foo.bar', false],
|
|
||||||
[['foo' => ['bar' => 'baz']], 'foo.bar', true],
|
|
||||||
[['foo' => ['bar' => 'baz']], 'foo.baz', false],
|
|
||||||
[['foo' => ['bar' => 'baz']], 'foo', true],
|
|
||||||
[['foo' => null], 'foo', true],
|
|
||||||
[['foo' => ['bar' => null]], 'foo.bar', true],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Mockery as m;
|
|
||||||
|
|
||||||
class DatabaseDriverTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
public function tearDown()
|
|
||||||
{
|
|
||||||
m::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function correct_data_is_inserted_and_updated()
|
|
||||||
{
|
|
||||||
$connection = $this->mockConnection();
|
|
||||||
$query = $this->mockQuery($connection);
|
|
||||||
|
|
||||||
$query->shouldReceive('get')->once()->andReturn([
|
|
||||||
['key' => 'nest.one', 'value' => 'old'],
|
|
||||||
]);
|
|
||||||
$query->shouldReceive('lists')->atMost(1)->andReturn(['nest.one']);
|
|
||||||
$query->shouldReceive('pluck')->atMost(1)->andReturn(['nest.one']);
|
|
||||||
$dbData = $this->getDbData();
|
|
||||||
unset($dbData[1]); // remove the nest.one array member
|
|
||||||
$query->shouldReceive('where')->with('key', '=', 'nest.one')->andReturn(m::self())->getMock()
|
|
||||||
->shouldReceive('update')->with(['value' => 'nestone']);
|
|
||||||
$self = $this; // 5.3 compatibility
|
|
||||||
$query->shouldReceive('insert')->once()->andReturnUsing(function ($arg) use ($dbData, $self) {
|
|
||||||
$self->assertEquals(count($dbData), count($arg));
|
|
||||||
foreach ($dbData as $key => $value) {
|
|
||||||
$self->assertContains($value, $arg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$store = $this->makeStore($connection);
|
|
||||||
$store->set('foo', 'bar');
|
|
||||||
$store->set('nest.one', 'nestone');
|
|
||||||
$store->set('nest.two', 'nesttwo');
|
|
||||||
$store->set('array', ['one', 'two']);
|
|
||||||
$store->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function extra_columns_are_queried()
|
|
||||||
{
|
|
||||||
$connection = $this->mockConnection();
|
|
||||||
$query = $this->mockQuery($connection);
|
|
||||||
$query->shouldReceive('where')->once()->with('foo', '=', 'bar')
|
|
||||||
->andReturn(m::self())->getMock()
|
|
||||||
->shouldReceive('get')->once()->andReturn([
|
|
||||||
['key' => 'foo', 'value' => 'bar'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$store = $this->makeStore($connection);
|
|
||||||
$store->setExtraColumns(['foo' => 'bar']);
|
|
||||||
$this->assertEquals('bar', $store->get('foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function extra_columns_are_inserted()
|
|
||||||
{
|
|
||||||
$connection = $this->mockConnection();
|
|
||||||
$query = $this->mockQuery($connection);
|
|
||||||
$query->shouldReceive('where')->times(2)->with('extracol', '=', 'extradata')
|
|
||||||
->andReturn(m::self());
|
|
||||||
$query->shouldReceive('get')->once()->andReturn([]);
|
|
||||||
$query->shouldReceive('lists')->atMost(1)->andReturn([]);
|
|
||||||
$query->shouldReceive('pluck')->atMost(1)->andReturn([]);
|
|
||||||
$query->shouldReceive('insert')->once()->with([
|
|
||||||
['key' => 'foo', 'value' => 'bar', 'extracol' => 'extradata'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$store = $this->makeStore($connection);
|
|
||||||
$store->setExtraColumns(['extracol' => 'extradata']);
|
|
||||||
$store->set('foo', 'bar');
|
|
||||||
$store->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getDbData()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
['key' => 'foo', 'value' => 'bar'],
|
|
||||||
['key' => 'nest.one', 'value' => 'nestone'],
|
|
||||||
['key' => 'nest.two', 'value' => 'nesttwo'],
|
|
||||||
['key' => 'array.0', 'value' => 'one'],
|
|
||||||
['key' => 'array.1', 'value' => 'two'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function mockConnection()
|
|
||||||
{
|
|
||||||
return m::mock('Illuminate\Database\Connection');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function mockQuery($connection)
|
|
||||||
{
|
|
||||||
$query = m::mock('Illuminate\Database\Query\Builder');
|
|
||||||
$connection->shouldReceive('table')->andReturn($query);
|
|
||||||
|
|
||||||
return $query;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function makeStore($connection)
|
|
||||||
{
|
|
||||||
return new Akaunting\Setting\Drivers\Database($connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Container\Container;
|
|
||||||
use Mockery as m;
|
|
||||||
|
|
||||||
class HelperTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
public static $functions;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
self::$functions = m::mock();
|
|
||||||
|
|
||||||
Container::setInstance(new Container());
|
|
||||||
|
|
||||||
$store = m::mock('Akaunting\Setting\Contracts\Driver');
|
|
||||||
|
|
||||||
app()->bind('setting', function () use ($store) {
|
|
||||||
return $store;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function helper_without_parameters_returns_store()
|
|
||||||
{
|
|
||||||
$this->assertInstanceOf('Akaunting\Setting\Contracts\Driver', setting());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function single_parameter_get_a_key_from_store()
|
|
||||||
{
|
|
||||||
app('setting')->shouldReceive('get')->with('foo', null)->once();
|
|
||||||
|
|
||||||
setting('foo');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function two_parameters_return_a_default_value()
|
|
||||||
{
|
|
||||||
app('setting')->shouldReceive('get')->with('foo', 'bar')->once();
|
|
||||||
|
|
||||||
setting('foo', 'bar');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function array_parameter_call_set_method_into_store()
|
|
||||||
{
|
|
||||||
app('setting')->shouldReceive('set')->with(['foo', 'bar'])->once();
|
|
||||||
|
|
||||||
setting(['foo', 'bar']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Mockery as m;
|
|
||||||
|
|
||||||
class JsonDriverTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
public function tearDown()
|
|
||||||
{
|
|
||||||
m::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function mockFilesystem()
|
|
||||||
{
|
|
||||||
return m::mock('Illuminate\Filesystem\Filesystem');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function makeStore($files, $path = 'fakepath')
|
|
||||||
{
|
|
||||||
return new Akaunting\Setting\Drivers\Json($files, $path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @expectedException InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function throws_exception_when_file_not_writeable()
|
|
||||||
{
|
|
||||||
$files = $this->mockFilesystem();
|
|
||||||
$files->shouldReceive('exists')->once()->with('fakepath')->andReturn(true);
|
|
||||||
$files->shouldReceive('isWritable')->once()->with('fakepath')->andReturn(false);
|
|
||||||
$store = $this->makeStore($files);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @expectedException InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function throws_exception_when_files_put_fails()
|
|
||||||
{
|
|
||||||
$files = $this->mockFilesystem();
|
|
||||||
$files->shouldReceive('exists')->once()->with('fakepath')->andReturn(false);
|
|
||||||
$files->shouldReceive('put')->once()->with('fakepath', '{}')->andReturn(false);
|
|
||||||
$store = $this->makeStore($files);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @expectedException RuntimeException
|
|
||||||
*/
|
|
||||||
public function throws_exception_when_file_contains_invalid_json()
|
|
||||||
{
|
|
||||||
$files = $this->mockFilesystem();
|
|
||||||
$files->shouldReceive('exists')->once()->with('fakepath')->andReturn(true);
|
|
||||||
$files->shouldReceive('isWritable')->once()->with('fakepath')->andReturn(true);
|
|
||||||
$files->shouldReceive('get')->once()->with('fakepath')->andReturn('[[!1!11]');
|
|
||||||
|
|
||||||
$store = $this->makeStore($files);
|
|
||||||
$store->get('foo');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
/vendor
|
|
||||||
composer.phar
|
|
||||||
composer.lock
|
|
||||||
.DS_Store
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
language: php
|
|
||||||
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
php:
|
|
||||||
- 5.5
|
|
||||||
- 5.6
|
|
||||||
- 7.0
|
|
||||||
- 7.1
|
|
||||||
- 7.2
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- travis_retry composer self-update
|
|
||||||
- travis_retry composer install --prefer-source --no-interaction --dev
|
|
||||||
|
|
||||||
script:
|
|
||||||
- composer install
|
|
||||||
- vendor/bin/phpunit
|
|
||||||
Vendored
-22
@@ -1,22 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Nguyễn Văn Ánh
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
-197
@@ -1,197 +0,0 @@
|
|||||||
No CAPTCHA reCAPTCHA
|
|
||||||
==========
|
|
||||||
|
|
||||||
[](https://travis-ci.org/anhskohbo/no-captcha)
|
|
||||||
[](https://packagist.org/packages/anhskohbo/no-captcha)
|
|
||||||
[](https://packagist.org/packages/anhskohbo/no-captcha)
|
|
||||||
[](https://packagist.org/packages/anhskohbo/no-captcha)
|
|
||||||
[](https://packagist.org/packages/anhskohbo/no-captcha)
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> For Laravel 4 use [v1](https://github.com/anhskohbo/no-captcha/tree/v1) branch.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```
|
|
||||||
composer require anhskohbo/no-captcha
|
|
||||||
```
|
|
||||||
|
|
||||||
## Laravel 5 and above
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
**_NOTE_** This package supports the auto-discovery feature of Laravel 5.5 and above, So skip these `Setup` instructions if you're using Laravel 5.5 and above.
|
|
||||||
|
|
||||||
In `app/config/app.php` add the following :
|
|
||||||
|
|
||||||
1- The ServiceProvider to the providers array :
|
|
||||||
|
|
||||||
```php
|
|
||||||
Anhskohbo\NoCaptcha\NoCaptchaServiceProvider::class,
|
|
||||||
```
|
|
||||||
|
|
||||||
2- The class alias to the aliases array :
|
|
||||||
|
|
||||||
```php
|
|
||||||
'NoCaptcha' => Anhskohbo\NoCaptcha\Facades\NoCaptcha::class,
|
|
||||||
```
|
|
||||||
|
|
||||||
3- Publish the config file
|
|
||||||
|
|
||||||
```ssh
|
|
||||||
php artisan vendor:publish --provider="Anhskohbo\NoCaptcha\NoCaptchaServiceProvider"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Add `NOCAPTCHA_SECRET` and `NOCAPTCHA_SITEKEY` in **.env** file :
|
|
||||||
|
|
||||||
```
|
|
||||||
NOCAPTCHA_SECRET=secret-key
|
|
||||||
NOCAPTCHA_SITEKEY=site-key
|
|
||||||
```
|
|
||||||
|
|
||||||
(You can obtain them from [here](https://www.google.com/recaptcha/admin))
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
#### Init js source
|
|
||||||
|
|
||||||
With default options :
|
|
||||||
|
|
||||||
```php
|
|
||||||
{!! NoCaptcha::renderJs() !!}
|
|
||||||
```
|
|
||||||
|
|
||||||
With [language support](https://developers.google.com/recaptcha/docs/language) or [onloadCallback](https://developers.google.com/recaptcha/docs/display#explicit_render) option :
|
|
||||||
|
|
||||||
```php
|
|
||||||
{!! NoCaptcha::renderJs('fr', true, 'recaptchaCallback') !!}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Display reCAPTCHA
|
|
||||||
|
|
||||||
Default widget :
|
|
||||||
|
|
||||||
```php
|
|
||||||
{!! NoCaptcha::display() !!}
|
|
||||||
```
|
|
||||||
|
|
||||||
With [custom attributes](https://developers.google.com/recaptcha/docs/display#render_param) (theme, size, callback ...) :
|
|
||||||
|
|
||||||
```php
|
|
||||||
{!! NoCaptcha::display(['data-theme' => 'dark']) !!}
|
|
||||||
```
|
|
||||||
|
|
||||||
Invisible reCAPTCHA using a [submit button](https://developers.google.com/recaptcha/docs/invisible):
|
|
||||||
|
|
||||||
```php
|
|
||||||
{!! NoCaptcha::displaySubmit('my-form-id', 'submit now!', ['data-theme' => 'dark']) !!}
|
|
||||||
```
|
|
||||||
Notice that the id of the form is required in this method to let the autogenerated
|
|
||||||
callback submit the form on a successful captcha verification.
|
|
||||||
|
|
||||||
#### Validation
|
|
||||||
|
|
||||||
Add `'g-recaptcha-response' => 'required|captcha'` to rules array :
|
|
||||||
|
|
||||||
```php
|
|
||||||
$validate = Validator::make(Input::all(), [
|
|
||||||
'g-recaptcha-response' => 'required|captcha'
|
|
||||||
]);
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Custom Validation Message
|
|
||||||
|
|
||||||
Add the following values to the `custom` array in the `validation` language file :
|
|
||||||
|
|
||||||
```php
|
|
||||||
'custom' => [
|
|
||||||
'g-recaptcha-response' => [
|
|
||||||
'required' => 'Please verify that you are not a robot.',
|
|
||||||
'captcha' => 'Captcha error! try again later or contact site admin.',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
```
|
|
||||||
|
|
||||||
Then check for captcha errors in the `Form` :
|
|
||||||
|
|
||||||
```php
|
|
||||||
@if ($errors->has('g-recaptcha-response'))
|
|
||||||
<span class="help-block">
|
|
||||||
<strong>{{ $errors->first('g-recaptcha-response') }}</strong>
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
When using the [Laravel Testing functionality](http://laravel.com/docs/5.5/testing), you will need to mock out the response for the captcha form element.
|
|
||||||
|
|
||||||
So for any form tests involving the captcha, you can do this by mocking the facade behavior:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// prevent validation error on captcha
|
|
||||||
NoCaptcha::shouldReceive('verifyResponse')
|
|
||||||
->once()
|
|
||||||
->andReturn(true);
|
|
||||||
|
|
||||||
// provide hidden input for your 'required' validation
|
|
||||||
NoCaptcha::shouldReceive('display')
|
|
||||||
->zeroOrMoreTimes()
|
|
||||||
->andReturn('<input type="hidden" name="g-recaptcha-response" value="1" />');
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then test the remainder of your form as normal.
|
|
||||||
|
|
||||||
When using HTTP tests you can add the `g-recaptcha-response` to the request body for the 'required' validation:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// prevent validation error on captcha
|
|
||||||
NoCaptcha::shouldReceive('verifyResponse')
|
|
||||||
->once()
|
|
||||||
->andReturn(true);
|
|
||||||
|
|
||||||
// POST request, with request body including g-recaptcha-response
|
|
||||||
$response = $this->json('POST', '/register', [
|
|
||||||
'g-recaptcha-response' => '1',
|
|
||||||
'name' => 'John',
|
|
||||||
'email' => 'john@example.com',
|
|
||||||
'password' => '123456',
|
|
||||||
'password_confirmation' => '123456',
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Without Laravel
|
|
||||||
|
|
||||||
Checkout example below:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
require_once "vendor/autoload.php";
|
|
||||||
|
|
||||||
$secret = 'CAPTCHA-SECRET';
|
|
||||||
$sitekey = 'CAPTCHA-SITEKEY';
|
|
||||||
$captcha = new \Anhskohbo\NoCaptcha\NoCaptcha($secret, $sitekey);
|
|
||||||
|
|
||||||
if (! empty($_POST)) {
|
|
||||||
var_dump($captcha->verifyResponse($_POST['g-recaptcha-response']));
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
|
||||||
<form action="?" method="POST">
|
|
||||||
<?php echo $captcha->display(); ?>
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php echo $captcha->renderJs(); ?>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
https://github.com/anhskohbo/no-captcha/pulls
|
|
||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "anhskohbo/no-captcha",
|
|
||||||
"description": "No CAPTCHA reCAPTCHA For Laravel.",
|
|
||||||
"keywords": [
|
|
||||||
"recaptcha",
|
|
||||||
"no-captcha",
|
|
||||||
"captcha",
|
|
||||||
"laravel",
|
|
||||||
"laravel4",
|
|
||||||
"laravel5",
|
|
||||||
"laravel6"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "anhskohbo",
|
|
||||||
"email": "anhskohbo@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.5.5",
|
|
||||||
"illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
|
|
||||||
"guzzlehttp/guzzle": "^6.2|^7.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "~4.8|^9.5.10|^10.5"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Anhskohbo\\NoCaptcha\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"laravel": {
|
|
||||||
"providers": [
|
|
||||||
"Anhskohbo\\NoCaptcha\\NoCaptchaServiceProvider"
|
|
||||||
],
|
|
||||||
"aliases": {
|
|
||||||
"NoCaptcha": "Anhskohbo\\NoCaptcha\\Facades\\NoCaptcha"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<phpunit backupGlobals="false"
|
|
||||||
backupStaticAttributes="false"
|
|
||||||
bootstrap="vendor/autoload.php"
|
|
||||||
colors="true"
|
|
||||||
convertErrorsToExceptions="true"
|
|
||||||
convertNoticesToExceptions="true"
|
|
||||||
convertWarningsToExceptions="true"
|
|
||||||
processIsolation="false"
|
|
||||||
stopOnFailure="false"
|
|
||||||
syntaxCheck="false"
|
|
||||||
>
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="Package Test Suite">
|
|
||||||
<directory suffix=".php">./tests/</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
</phpunit>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Anhskohbo\NoCaptcha\Facades;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Facade;
|
|
||||||
|
|
||||||
class NoCaptcha extends Facade
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the registered name of the component.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected static function getFacadeAccessor()
|
|
||||||
{
|
|
||||||
return 'captcha';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-246
@@ -1,246 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Anhskohbo\NoCaptcha;
|
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use GuzzleHttp\Client;
|
|
||||||
|
|
||||||
class NoCaptcha
|
|
||||||
{
|
|
||||||
const CLIENT_API = 'https://www.google.com/recaptcha/api.js';
|
|
||||||
const VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The recaptcha secret key.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $secret;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The recaptcha sitekey key.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $sitekey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \GuzzleHttp\Client
|
|
||||||
*/
|
|
||||||
protected $http;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cached verified responses.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $verifiedResponses = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NoCaptcha.
|
|
||||||
*
|
|
||||||
* @param string $secret
|
|
||||||
* @param string $sitekey
|
|
||||||
* @param array $options
|
|
||||||
*/
|
|
||||||
public function __construct($secret, $sitekey, $options = [])
|
|
||||||
{
|
|
||||||
$this->secret = $secret;
|
|
||||||
$this->sitekey = $sitekey;
|
|
||||||
$this->http = new Client($options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render HTML captcha.
|
|
||||||
*
|
|
||||||
* @param array $attributes
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function display($attributes = [])
|
|
||||||
{
|
|
||||||
$attributes = $this->prepareAttributes($attributes);
|
|
||||||
return '<div' . $this->buildAttributes($attributes) . '></div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see display()
|
|
||||||
*/
|
|
||||||
public function displayWidget($attributes = [])
|
|
||||||
{
|
|
||||||
return $this->display($attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a Invisible reCAPTCHA by embedding a callback into a form submit button.
|
|
||||||
*
|
|
||||||
* @param string $formIdentifier the html ID of the form that should be submitted.
|
|
||||||
* @param string $text the text inside the form button
|
|
||||||
* @param array $attributes array of additional html elements
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function displaySubmit($formIdentifier, $text = 'submit', $attributes = [])
|
|
||||||
{
|
|
||||||
$javascript = '';
|
|
||||||
if (!isset($attributes['data-callback'])) {
|
|
||||||
$functionName = 'onSubmit' . str_replace(['-', '=', '\'', '"', '<', '>', '`'], '', $formIdentifier);
|
|
||||||
$attributes['data-callback'] = $functionName;
|
|
||||||
$javascript = sprintf(
|
|
||||||
'<script>function %s(){document.getElementById("%s").submit();}</script>',
|
|
||||||
$functionName,
|
|
||||||
$formIdentifier
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$attributes = $this->prepareAttributes($attributes);
|
|
||||||
|
|
||||||
$button = sprintf('<button%s><span>%s</span></button>', $this->buildAttributes($attributes), $text);
|
|
||||||
|
|
||||||
return $button . $javascript;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render js source
|
|
||||||
*
|
|
||||||
* @param null $lang
|
|
||||||
* @param bool $callback
|
|
||||||
* @param string $onLoadClass
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function renderJs($lang = null, $callback = false, $onLoadClass = 'onloadCallBack')
|
|
||||||
{
|
|
||||||
return '<script src="'.$this->getJsLink($lang, $callback, $onLoadClass).'" async defer></script>'."\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify no-captcha response.
|
|
||||||
*
|
|
||||||
* @param string $response
|
|
||||||
* @param string $clientIp
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function verifyResponse($response, $clientIp = null)
|
|
||||||
{
|
|
||||||
if (empty($response)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if response already verfied before.
|
|
||||||
if (in_array($response, $this->verifiedResponses)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$verifyResponse = $this->sendRequestVerify([
|
|
||||||
'secret' => $this->secret,
|
|
||||||
'response' => $response,
|
|
||||||
'remoteip' => $clientIp,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isset($verifyResponse['success']) && $verifyResponse['success'] === true) {
|
|
||||||
// A response can only be verified once from google, so we need to
|
|
||||||
// cache it to make it work in case we want to verify it multiple times.
|
|
||||||
$this->verifiedResponses[] = $response;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify no-captcha response by Symfony Request.
|
|
||||||
*
|
|
||||||
* @param Request $request
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function verifyRequest(Request $request)
|
|
||||||
{
|
|
||||||
return $this->verifyResponse(
|
|
||||||
$request->get('g-recaptcha-response'),
|
|
||||||
$request->getClientIp()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get recaptcha js link.
|
|
||||||
*
|
|
||||||
* @param string $lang
|
|
||||||
* @param boolean $callback
|
|
||||||
* @param string $onLoadClass
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getJsLink($lang = null, $callback = false, $onLoadClass = 'onloadCallBack')
|
|
||||||
{
|
|
||||||
$client_api = static::CLIENT_API;
|
|
||||||
$params = [];
|
|
||||||
|
|
||||||
$callback ? $this->setCallBackParams($params, $onLoadClass) : false;
|
|
||||||
$lang ? $params['hl'] = $lang : null;
|
|
||||||
|
|
||||||
return $client_api . '?'. http_build_query($params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $params
|
|
||||||
* @param $onLoadClass
|
|
||||||
*/
|
|
||||||
protected function setCallBackParams(&$params, $onLoadClass)
|
|
||||||
{
|
|
||||||
$params['render'] = 'explicit';
|
|
||||||
$params['onload'] = $onLoadClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send verify request.
|
|
||||||
*
|
|
||||||
* @param array $query
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function sendRequestVerify(array $query = [])
|
|
||||||
{
|
|
||||||
$response = $this->http->request('POST', static::VERIFY_URL, [
|
|
||||||
'form_params' => $query,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return json_decode($response->getBody(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare HTML attributes and assure that the correct classes and attributes for captcha are inserted.
|
|
||||||
*
|
|
||||||
* @param array $attributes
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function prepareAttributes(array $attributes)
|
|
||||||
{
|
|
||||||
$attributes['data-sitekey'] = $this->sitekey;
|
|
||||||
if (!isset($attributes['class'])) {
|
|
||||||
$attributes['class'] = '';
|
|
||||||
}
|
|
||||||
$attributes['class'] = trim('g-recaptcha ' . $attributes['class']);
|
|
||||||
|
|
||||||
return $attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build HTML attributes.
|
|
||||||
*
|
|
||||||
* @param array $attributes
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function buildAttributes(array $attributes)
|
|
||||||
{
|
|
||||||
$html = [];
|
|
||||||
|
|
||||||
foreach ($attributes as $key => $value) {
|
|
||||||
$html[] = $key.'="'.$value.'"';
|
|
||||||
}
|
|
||||||
|
|
||||||
return count($html) ? ' '.implode(' ', $html) : '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Anhskohbo\NoCaptcha;
|
|
||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
|
|
||||||
class NoCaptchaServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Indicates if loading of the provider is deferred.
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $defer = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bootstrap the application events.
|
|
||||||
*/
|
|
||||||
public function boot()
|
|
||||||
{
|
|
||||||
$app = $this->app;
|
|
||||||
|
|
||||||
$this->bootConfig();
|
|
||||||
|
|
||||||
$app['validator']->extend('captcha', function ($attribute, $value) use ($app) {
|
|
||||||
return $app['captcha']->verifyResponse($value, $app['request']->getClientIp());
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($app->bound('form')) {
|
|
||||||
$app['form']->macro('captcha', function ($attributes = []) use ($app) {
|
|
||||||
return $app['captcha']->display($attributes, $app->getLocale());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Booting configure.
|
|
||||||
*/
|
|
||||||
protected function bootConfig()
|
|
||||||
{
|
|
||||||
$path = __DIR__.'/config/captcha.php';
|
|
||||||
|
|
||||||
$this->mergeConfigFrom($path, 'captcha');
|
|
||||||
|
|
||||||
if (function_exists('config_path')) {
|
|
||||||
$this->publishes([$path => config_path('captcha.php')]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the service provider.
|
|
||||||
*/
|
|
||||||
public function register()
|
|
||||||
{
|
|
||||||
$this->app->singleton('captcha', function ($app) {
|
|
||||||
return new NoCaptcha(
|
|
||||||
$app['config']['captcha.secret'],
|
|
||||||
$app['config']['captcha.sitekey'],
|
|
||||||
$app['config']['captcha.options']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the services provided by the provider.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function provides()
|
|
||||||
{
|
|
||||||
return ['captcha'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
'secret' => env('NOCAPTCHA_SECRET'),
|
|
||||||
'sitekey' => env('NOCAPTCHA_SITEKEY'),
|
|
||||||
'options' => [
|
|
||||||
'timeout' => 30,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Anhskohbo\NoCaptcha\NoCaptcha;
|
|
||||||
|
|
||||||
class NoCaptchaTest extends PHPUnit_Framework_TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var NoCaptcha
|
|
||||||
*/
|
|
||||||
private $captcha;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
$this->captcha = new NoCaptcha('{secret-key}', '{site-key}');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRequestShouldWorks()
|
|
||||||
{
|
|
||||||
$response = $this->captcha->verifyResponse('should_false');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testJsLink()
|
|
||||||
{
|
|
||||||
$this->assertTrue($this->captcha instanceof NoCaptcha);
|
|
||||||
|
|
||||||
$simple = '<script src="https://www.google.com/recaptcha/api.js?" async defer></script>'."\n";
|
|
||||||
$withLang = '<script src="https://www.google.com/recaptcha/api.js?hl=vi" async defer></script>'."\n";
|
|
||||||
$withCallback = '<script src="https://www.google.com/recaptcha/api.js?render=explicit&onload=myOnloadCallback" async defer></script>'."\n";
|
|
||||||
|
|
||||||
$this->assertEquals($simple, $this->captcha->renderJs());
|
|
||||||
$this->assertEquals($withLang, $this->captcha->renderJs('vi'));
|
|
||||||
$this->assertEquals($withCallback, $this->captcha->renderJs(null, true, 'myOnloadCallback'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testDisplay()
|
|
||||||
{
|
|
||||||
$this->assertTrue($this->captcha instanceof NoCaptcha);
|
|
||||||
|
|
||||||
$simple = '<div data-sitekey="{site-key}" class="g-recaptcha"></div>';
|
|
||||||
$withAttrs = '<div data-theme="light" data-sitekey="{site-key}" class="g-recaptcha"></div>';
|
|
||||||
|
|
||||||
$this->assertEquals($simple, $this->captcha->display());
|
|
||||||
$this->assertEquals($withAttrs, $this->captcha->display(['data-theme' => 'light']));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testdisplaySubmit()
|
|
||||||
{
|
|
||||||
$this->assertTrue($this->captcha instanceof NoCaptcha);
|
|
||||||
|
|
||||||
$javascript = '<script>function onSubmittest(){document.getElementById("test").submit();}</script>';
|
|
||||||
$simple = '<button data-callback="onSubmittest" data-sitekey="{site-key}" class="g-recaptcha"><span>submit</span></button>';
|
|
||||||
$withAttrs = '<button data-theme="light" class="g-recaptcha 123" data-callback="onSubmittest" data-sitekey="{site-key}"><span>submit123</span></button>';
|
|
||||||
|
|
||||||
$this->assertEquals($simple . $javascript, $this->captcha->displaySubmit('test'));
|
|
||||||
$withAttrsResult = $this->captcha->displaySubmit('test','submit123',['data-theme' => 'light', 'class' => '123']);
|
|
||||||
$this->assertEquals($withAttrs . $javascript, $withAttrsResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testdisplaySubmitWithCustomCallback()
|
|
||||||
{
|
|
||||||
$this->assertTrue($this->captcha instanceof NoCaptcha);
|
|
||||||
|
|
||||||
$withAttrs = '<button data-theme="light" class="g-recaptcha 123" data-callback="onSubmitCustomCallback" data-sitekey="{site-key}"><span>submit123</span></button>';
|
|
||||||
|
|
||||||
$withAttrsResult = $this->captcha->displaySubmit('test-custom','submit123',['data-theme' => 'light', 'class' => '123', 'data-callback' => 'onSubmitCustomCallback']);
|
|
||||||
$this->assertEquals($withAttrs, $withAttrsResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-25
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// autoload.php @generated by Composer
|
|
||||||
|
|
||||||
if (PHP_VERSION_ID < 50600) {
|
|
||||||
if (!headers_sent()) {
|
|
||||||
header('HTTP/1.1 500 Internal Server Error');
|
|
||||||
}
|
|
||||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
|
||||||
if (!ini_get('display_errors')) {
|
|
||||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
|
||||||
fwrite(STDERR, $err);
|
|
||||||
} elseif (!headers_sent()) {
|
|
||||||
echo $err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trigger_error(
|
|
||||||
$err,
|
|
||||||
E_USER_ERROR
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/composer/autoload_real.php';
|
|
||||||
|
|
||||||
return ComposerAutoloaderInitc91cd9c5b1e6a9e8573a14b799ea9342::getLoader();
|
|
||||||
Vendored
-22
@@ -1,22 +0,0 @@
|
|||||||
Copyright (c) 2017, Ben Scholzen 'DASPRiD'
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
Vendored
-57
@@ -1,57 +0,0 @@
|
|||||||
# QR Code generator
|
|
||||||
|
|
||||||
[](https://github.com/Bacon/BaconQrCode/actions/workflows/ci.yml)
|
|
||||||
[](https://codecov.io/gh/Bacon/BaconQrCode)
|
|
||||||
[](https://packagist.org/packages/bacon/bacon-qr-code)
|
|
||||||
[](https://packagist.org/packages/bacon/bacon-qr-code)
|
|
||||||
[](https://packagist.org/packages/bacon/bacon-qr-code)
|
|
||||||
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
BaconQrCode is a port of QR code portion of the ZXing library. It currently
|
|
||||||
only features the encoder part, but could later receive the decoder part as
|
|
||||||
well.
|
|
||||||
|
|
||||||
As the Reed Solomon codec implementation of the ZXing library performs quite
|
|
||||||
slow in PHP, it was exchanged with the implementation by Phil Karn.
|
|
||||||
|
|
||||||
|
|
||||||
## Example usage
|
|
||||||
```php
|
|
||||||
use BaconQrCode\Renderer\ImageRenderer;
|
|
||||||
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
|
|
||||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
|
||||||
use BaconQrCode\Writer;
|
|
||||||
|
|
||||||
$renderer = new ImageRenderer(
|
|
||||||
new RendererStyle(400),
|
|
||||||
new ImagickImageBackEnd()
|
|
||||||
);
|
|
||||||
$writer = new Writer($renderer);
|
|
||||||
$writer->writeFile('Hello World!', 'qrcode.png');
|
|
||||||
```
|
|
||||||
|
|
||||||
## Available image renderer back ends
|
|
||||||
BaconQrCode comes with multiple back ends for rendering images. Currently included are the following:
|
|
||||||
|
|
||||||
- `ImagickImageBackEnd`: renders raster images using the Imagick library
|
|
||||||
- `SvgImageBackEnd`: renders SVG files using XMLWriter
|
|
||||||
- `EpsImageBackEnd`: renders EPS files
|
|
||||||
|
|
||||||
### GDLib Renderer
|
|
||||||
GD library has so many limitations, that GD support is not added as backend, but as separated renderer.
|
|
||||||
Use `GDLibRenderer` instead of `ImageRenderer`. These are the limitations:
|
|
||||||
|
|
||||||
- Does not support gradient.
|
|
||||||
- Does not support any curves, so you QR code is always squared.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
```php
|
|
||||||
use BaconQrCode\Renderer\GDLibRenderer;
|
|
||||||
use BaconQrCode\Writer;
|
|
||||||
|
|
||||||
$renderer = new GDLibRenderer(400);
|
|
||||||
$writer = new Writer($renderer);
|
|
||||||
$writer->writeFile('Hello World!', 'qrcode.png');
|
|
||||||
```
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "bacon/bacon-qr-code",
|
|
||||||
"description": "BaconQrCode is a QR code generator for PHP.",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"homepage": "https://github.com/Bacon/BaconQrCode",
|
|
||||||
"require": {
|
|
||||||
"php": "^8.1",
|
|
||||||
"ext-iconv": "*",
|
|
||||||
"dasprid/enum": "^1.0.3"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"ext-imagick": "to generate QR code images"
|
|
||||||
},
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Ben Scholzen 'DASPRiD'",
|
|
||||||
"email": "mail@dasprids.de",
|
|
||||||
"homepage": "https://dasprids.de/",
|
|
||||||
"role": "Developer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"BaconQrCode\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload-dev": {
|
|
||||||
"psr-4": {
|
|
||||||
"BaconQrCodeTest\\": "test/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^10.5.11 || 11.0.4",
|
|
||||||
"spatie/phpunit-snapshot-assertions": "^5.1.5",
|
|
||||||
"squizlabs/php_codesniffer": "^3.9",
|
|
||||||
"phly/keep-a-changelog": "^2.12"
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"allow-plugins": {
|
|
||||||
"ocramius/package-versions": true,
|
|
||||||
"php-http/discovery": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"archive": {
|
|
||||||
"exclude": [
|
|
||||||
"/test",
|
|
||||||
"/phpunit.xml.dist"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-364
@@ -1,364 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
use BaconQrCode\Exception\InvalidArgumentException;
|
|
||||||
use SplFixedArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple, fast array of bits.
|
|
||||||
*/
|
|
||||||
final class BitArray
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Bits represented as an array of integers.
|
|
||||||
*
|
|
||||||
* @var SplFixedArray<int>
|
|
||||||
*/
|
|
||||||
private SplFixedArray $bits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new bit array with a given size.
|
|
||||||
*/
|
|
||||||
public function __construct(private int $size = 0)
|
|
||||||
{
|
|
||||||
$this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the size in bits.
|
|
||||||
*/
|
|
||||||
public function getSize() : int
|
|
||||||
{
|
|
||||||
return $this->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the size in bytes.
|
|
||||||
*/
|
|
||||||
public function getSizeInBytes() : int
|
|
||||||
{
|
|
||||||
return ($this->size + 7) >> 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the array has a minimum capacity.
|
|
||||||
*/
|
|
||||||
public function ensureCapacity(int $size) : void
|
|
||||||
{
|
|
||||||
if ($size > count($this->bits) << 5) {
|
|
||||||
$this->bits->setSize(($size + 31) >> 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a specific bit.
|
|
||||||
*/
|
|
||||||
public function get(int $i) : bool
|
|
||||||
{
|
|
||||||
return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a specific bit.
|
|
||||||
*/
|
|
||||||
public function set(int $i) : void
|
|
||||||
{
|
|
||||||
$this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flips a specific bit.
|
|
||||||
*/
|
|
||||||
public function flip(int $i) : void
|
|
||||||
{
|
|
||||||
$this->bits[$i >> 5] ^= 1 << ($i & 0x1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the next set bit position from a given position.
|
|
||||||
*/
|
|
||||||
public function getNextSet(int $from) : int
|
|
||||||
{
|
|
||||||
if ($from >= $this->size) {
|
|
||||||
return $this->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
$bitsOffset = $from >> 5;
|
|
||||||
$currentBits = $this->bits[$bitsOffset];
|
|
||||||
$bitsLength = count($this->bits);
|
|
||||||
$currentBits &= ~((1 << ($from & 0x1f)) - 1);
|
|
||||||
|
|
||||||
while (0 === $currentBits) {
|
|
||||||
if (++$bitsOffset === $bitsLength) {
|
|
||||||
return $this->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentBits = $this->bits[$bitsOffset];
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
|
|
||||||
return min($result, $this->size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the next unset bit position from a given position.
|
|
||||||
*/
|
|
||||||
public function getNextUnset(int $from) : int
|
|
||||||
{
|
|
||||||
if ($from >= $this->size) {
|
|
||||||
return $this->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
$bitsOffset = $from >> 5;
|
|
||||||
$currentBits = ~$this->bits[$bitsOffset];
|
|
||||||
$bitsLength = count($this->bits);
|
|
||||||
$currentBits &= ~((1 << ($from & 0x1f)) - 1);
|
|
||||||
|
|
||||||
while (0 === $currentBits) {
|
|
||||||
if (++$bitsOffset === $bitsLength) {
|
|
||||||
return $this->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentBits = ~$this->bits[$bitsOffset];
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
|
|
||||||
return min($result, $this->size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a bulk of bits.
|
|
||||||
*/
|
|
||||||
public function setBulk(int $i, int $newBits) : void
|
|
||||||
{
|
|
||||||
$this->bits[$i >> 5] = $newBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a range of bits.
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if end is smaller than start
|
|
||||||
*/
|
|
||||||
public function setRange(int $start, int $end) : void
|
|
||||||
{
|
|
||||||
if ($end < $start) {
|
|
||||||
throw new InvalidArgumentException('End must be greater or equal to start');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($end === $start) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
--$end;
|
|
||||||
|
|
||||||
$firstInt = $start >> 5;
|
|
||||||
$lastInt = $end >> 5;
|
|
||||||
|
|
||||||
for ($i = $firstInt; $i <= $lastInt; ++$i) {
|
|
||||||
$firstBit = $i > $firstInt ? 0 : $start & 0x1f;
|
|
||||||
$lastBit = $i < $lastInt ? 31 : $end & 0x1f;
|
|
||||||
|
|
||||||
if (0 === $firstBit && 31 === $lastBit) {
|
|
||||||
$mask = 0x7fffffff;
|
|
||||||
} else {
|
|
||||||
$mask = 0;
|
|
||||||
|
|
||||||
for ($j = $firstBit; $j < $lastBit; ++$j) {
|
|
||||||
$mask |= 1 << $j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->bits[$i] = $this->bits[$i] | $mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the bit array, unsetting every bit.
|
|
||||||
*/
|
|
||||||
public function clear() : void
|
|
||||||
{
|
|
||||||
$bitsLength = count($this->bits);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $bitsLength; ++$i) {
|
|
||||||
$this->bits[$i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a range of bits is set or not set.
|
|
||||||
|
|
||||||
* @throws InvalidArgumentException if end is smaller than start
|
|
||||||
*/
|
|
||||||
public function isRange(int $start, int $end, bool $value) : bool
|
|
||||||
{
|
|
||||||
if ($end < $start) {
|
|
||||||
throw new InvalidArgumentException('End must be greater or equal to start');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($end === $start) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
--$end;
|
|
||||||
|
|
||||||
$firstInt = $start >> 5;
|
|
||||||
$lastInt = $end >> 5;
|
|
||||||
|
|
||||||
for ($i = $firstInt; $i <= $lastInt; ++$i) {
|
|
||||||
$firstBit = $i > $firstInt ? 0 : $start & 0x1f;
|
|
||||||
$lastBit = $i < $lastInt ? 31 : $end & 0x1f;
|
|
||||||
|
|
||||||
if (0 === $firstBit && 31 === $lastBit) {
|
|
||||||
$mask = 0x7fffffff;
|
|
||||||
} else {
|
|
||||||
$mask = 0;
|
|
||||||
|
|
||||||
for ($j = $firstBit; $j <= $lastBit; ++$j) {
|
|
||||||
$mask |= 1 << $j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a bit to the array.
|
|
||||||
*/
|
|
||||||
public function appendBit(bool $bit) : void
|
|
||||||
{
|
|
||||||
$this->ensureCapacity($this->size + 1);
|
|
||||||
|
|
||||||
if ($bit) {
|
|
||||||
$this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f));
|
|
||||||
}
|
|
||||||
|
|
||||||
++$this->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a number of bits (up to 32) to the array.
|
|
||||||
|
|
||||||
* @throws InvalidArgumentException if num bits is not between 0 and 32
|
|
||||||
*/
|
|
||||||
public function appendBits(int $value, int $numBits) : void
|
|
||||||
{
|
|
||||||
if ($numBits < 0 || $numBits > 32) {
|
|
||||||
throw new InvalidArgumentException('Num bits must be between 0 and 32');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->ensureCapacity($this->size + $numBits);
|
|
||||||
|
|
||||||
for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
|
|
||||||
$this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends another bit array to this array.
|
|
||||||
*/
|
|
||||||
public function appendBitArray(self $other) : void
|
|
||||||
{
|
|
||||||
$otherSize = $other->getSize();
|
|
||||||
$this->ensureCapacity($this->size + $other->getSize());
|
|
||||||
|
|
||||||
for ($i = 0; $i < $otherSize; ++$i) {
|
|
||||||
$this->appendBit($other->get($i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes an exclusive-or comparision on the current bit array.
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if sizes don't match
|
|
||||||
*/
|
|
||||||
public function xorBits(self $other) : void
|
|
||||||
{
|
|
||||||
$bitsLength = count($this->bits);
|
|
||||||
$otherBits = $other->getBitArray();
|
|
||||||
|
|
||||||
if ($bitsLength !== count($otherBits)) {
|
|
||||||
throw new InvalidArgumentException('Sizes don\'t match');
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($i = 0; $i < $bitsLength; ++$i) {
|
|
||||||
$this->bits[$i] = $this->bits[$i] ^ $otherBits[$i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the bit array to a byte array.
|
|
||||||
*
|
|
||||||
* @return SplFixedArray<int>
|
|
||||||
*/
|
|
||||||
public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray
|
|
||||||
{
|
|
||||||
$bytes = new SplFixedArray($numBytes);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $numBytes; ++$i) {
|
|
||||||
$byte = 0;
|
|
||||||
|
|
||||||
for ($j = 0; $j < 8; ++$j) {
|
|
||||||
if ($this->get($bitOffset)) {
|
|
||||||
$byte |= 1 << (7 - $j);
|
|
||||||
}
|
|
||||||
|
|
||||||
++$bitOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
$bytes[$i] = $byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the internal bit array.
|
|
||||||
*
|
|
||||||
* @return SplFixedArray<int>
|
|
||||||
*/
|
|
||||||
public function getBitArray() : SplFixedArray
|
|
||||||
{
|
|
||||||
return $this->bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverses the array.
|
|
||||||
*/
|
|
||||||
public function reverse() : void
|
|
||||||
{
|
|
||||||
$newBits = new SplFixedArray(count($this->bits));
|
|
||||||
|
|
||||||
for ($i = 0; $i < $this->size; ++$i) {
|
|
||||||
if ($this->get($this->size - $i - 1)) {
|
|
||||||
$newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->bits = $newBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the bit array.
|
|
||||||
*/
|
|
||||||
public function __toString() : string
|
|
||||||
{
|
|
||||||
$result = '';
|
|
||||||
|
|
||||||
for ($i = 0; $i < $this->size; ++$i) {
|
|
||||||
if (0 === ($i & 0x07)) {
|
|
||||||
$result .= ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
$result .= $this->get($i) ? 'X' : '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-307
@@ -1,307 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
use BaconQrCode\Exception\InvalidArgumentException;
|
|
||||||
use SplFixedArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bit matrix.
|
|
||||||
*
|
|
||||||
* Represents a 2D matrix of bits. In function arguments below, and throughout
|
|
||||||
* the common module, x is the column position, and y is the row position. The
|
|
||||||
* ordering is always x, y. The origin is at the top-left.
|
|
||||||
*/
|
|
||||||
class BitMatrix
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Width of the bit matrix.
|
|
||||||
*/
|
|
||||||
private int $width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Height of the bit matrix.
|
|
||||||
*/
|
|
||||||
private ?int $height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Size in bits of each individual row.
|
|
||||||
*/
|
|
||||||
private int $rowSize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bits representation.
|
|
||||||
*
|
|
||||||
* @var SplFixedArray<int>
|
|
||||||
*/
|
|
||||||
private SplFixedArray $bits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws InvalidArgumentException if a dimension is smaller than zero
|
|
||||||
*/
|
|
||||||
public function __construct(int $width, ?int $height = null)
|
|
||||||
{
|
|
||||||
if (null === $height) {
|
|
||||||
$height = $width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($width < 1 || $height < 1) {
|
|
||||||
throw new InvalidArgumentException('Both dimensions must be greater than zero');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->width = $width;
|
|
||||||
$this->height = $height;
|
|
||||||
$this->rowSize = ($width + 31) >> 5;
|
|
||||||
$this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the requested bit, where true means black.
|
|
||||||
*/
|
|
||||||
public function get(int $x, int $y) : bool
|
|
||||||
{
|
|
||||||
$offset = $y * $this->rowSize + ($x >> 5);
|
|
||||||
return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the given bit to true.
|
|
||||||
*/
|
|
||||||
public function set(int $x, int $y) : void
|
|
||||||
{
|
|
||||||
$offset = $y * $this->rowSize + ($x >> 5);
|
|
||||||
$this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flips the given bit.
|
|
||||||
*/
|
|
||||||
public function flip(int $x, int $y) : void
|
|
||||||
{
|
|
||||||
$offset = $y * $this->rowSize + ($x >> 5);
|
|
||||||
$this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears all bits (set to false).
|
|
||||||
*/
|
|
||||||
public function clear() : void
|
|
||||||
{
|
|
||||||
$max = count($this->bits);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $max; ++$i) {
|
|
||||||
$this->bits[$i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a square region of the bit matrix to true.
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if left or top are negative
|
|
||||||
* @throws InvalidArgumentException if width or height are smaller than 1
|
|
||||||
* @throws InvalidArgumentException if region does not fit into the matix
|
|
||||||
*/
|
|
||||||
public function setRegion(int $left, int $top, int $width, int $height) : void
|
|
||||||
{
|
|
||||||
if ($top < 0 || $left < 0) {
|
|
||||||
throw new InvalidArgumentException('Left and top must be non-negative');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($height < 1 || $width < 1) {
|
|
||||||
throw new InvalidArgumentException('Width and height must be at least 1');
|
|
||||||
}
|
|
||||||
|
|
||||||
$right = $left + $width;
|
|
||||||
$bottom = $top + $height;
|
|
||||||
|
|
||||||
if ($bottom > $this->height || $right > $this->width) {
|
|
||||||
throw new InvalidArgumentException('The region must fit inside the matrix');
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($y = $top; $y < $bottom; ++$y) {
|
|
||||||
$offset = $y * $this->rowSize;
|
|
||||||
|
|
||||||
for ($x = $left; $x < $right; ++$x) {
|
|
||||||
$index = $offset + ($x >> 5);
|
|
||||||
$this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A fast method to retrieve one row of data from the matrix as a BitArray.
|
|
||||||
*/
|
|
||||||
public function getRow(int $y, ?BitArray $row = null) : BitArray
|
|
||||||
{
|
|
||||||
if (null === $row || $row->getSize() < $this->width) {
|
|
||||||
$row = new BitArray($this->width);
|
|
||||||
}
|
|
||||||
|
|
||||||
$offset = $y * $this->rowSize;
|
|
||||||
|
|
||||||
for ($x = 0; $x < $this->rowSize; ++$x) {
|
|
||||||
$row->setBulk($x << 5, $this->bits[$offset + $x]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $row;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a row of data from a BitArray.
|
|
||||||
*/
|
|
||||||
public function setRow(int $y, BitArray $row) : void
|
|
||||||
{
|
|
||||||
$bits = $row->getBitArray();
|
|
||||||
|
|
||||||
for ($i = 0; $i < $this->rowSize; ++$i) {
|
|
||||||
$this->bits[$y * $this->rowSize + $i] = $bits[$i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is useful in detecting the enclosing rectangle of a 'pure' barcode.
|
|
||||||
*
|
|
||||||
* @return int[]|null
|
|
||||||
*/
|
|
||||||
public function getEnclosingRectangle() : ?array
|
|
||||||
{
|
|
||||||
$left = $this->width;
|
|
||||||
$top = $this->height;
|
|
||||||
$right = -1;
|
|
||||||
$bottom = -1;
|
|
||||||
|
|
||||||
for ($y = 0; $y < $this->height; ++$y) {
|
|
||||||
for ($x32 = 0; $x32 < $this->rowSize; ++$x32) {
|
|
||||||
$bits = $this->bits[$y * $this->rowSize + $x32];
|
|
||||||
|
|
||||||
if (0 !== $bits) {
|
|
||||||
if ($y < $top) {
|
|
||||||
$top = $y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($y > $bottom) {
|
|
||||||
$bottom = $y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($x32 * 32 < $left) {
|
|
||||||
$bit = 0;
|
|
||||||
|
|
||||||
while (($bits << (31 - $bit)) === 0) {
|
|
||||||
$bit++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($x32 * 32 + $bit) < $left) {
|
|
||||||
$left = $x32 * 32 + $bit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($x32 * 32 + 31 > $right) {
|
|
||||||
$bit = 31;
|
|
||||||
|
|
||||||
while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
|
|
||||||
--$bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($x32 * 32 + $bit) > $right) {
|
|
||||||
$right = $x32 * 32 + $bit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$width = $right - $left;
|
|
||||||
$height = $bottom - $top;
|
|
||||||
|
|
||||||
if ($width < 0 || $height < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [$left, $top, $width, $height];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the most top left set bit.
|
|
||||||
*
|
|
||||||
* This is useful in detecting a corner of a 'pure' barcode.
|
|
||||||
*
|
|
||||||
* @return int[]|null
|
|
||||||
*/
|
|
||||||
public function getTopLeftOnBit() : ?array
|
|
||||||
{
|
|
||||||
$bitsOffset = 0;
|
|
||||||
|
|
||||||
while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) {
|
|
||||||
++$bitsOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($this->bits) === $bitsOffset) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$x = intdiv($bitsOffset, $this->rowSize);
|
|
||||||
$y = ($bitsOffset % $this->rowSize) << 5;
|
|
||||||
|
|
||||||
$bits = $this->bits[$bitsOffset];
|
|
||||||
$bit = 0;
|
|
||||||
|
|
||||||
while (0 === ($bits << (31 - $bit))) {
|
|
||||||
++$bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$x += $bit;
|
|
||||||
|
|
||||||
return [$x, $y];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the most bottom right set bit.
|
|
||||||
*
|
|
||||||
* This is useful in detecting a corner of a 'pure' barcode.
|
|
||||||
*
|
|
||||||
* @return int[]|null
|
|
||||||
*/
|
|
||||||
public function getBottomRightOnBit() : ?array
|
|
||||||
{
|
|
||||||
$bitsOffset = count($this->bits) - 1;
|
|
||||||
|
|
||||||
while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) {
|
|
||||||
--$bitsOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($bitsOffset < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$x = intdiv($bitsOffset, $this->rowSize);
|
|
||||||
$y = ($bitsOffset % $this->rowSize) << 5;
|
|
||||||
|
|
||||||
$bits = $this->bits[$bitsOffset];
|
|
||||||
$bit = 0;
|
|
||||||
|
|
||||||
while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
|
|
||||||
--$bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$x += $bit;
|
|
||||||
|
|
||||||
return [$x, $y];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the width of the matrix,
|
|
||||||
*/
|
|
||||||
public function getWidth() : int
|
|
||||||
{
|
|
||||||
return $this->width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the height of the matrix.
|
|
||||||
*/
|
|
||||||
public function getHeight() : int
|
|
||||||
{
|
|
||||||
return $this->height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* General bit utilities.
|
|
||||||
*
|
|
||||||
* All utility methods are based on 32-bit integers and also work on 64-bit
|
|
||||||
* systems.
|
|
||||||
*/
|
|
||||||
final class BitUtils
|
|
||||||
{
|
|
||||||
private function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs an unsigned right shift.
|
|
||||||
*
|
|
||||||
* This is the same as the unsigned right shift operator ">>>" in other
|
|
||||||
* languages.
|
|
||||||
*/
|
|
||||||
public static function unsignedRightShift(int $a, int $b) : int
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
$a >= 0
|
|
||||||
? $a >> $b
|
|
||||||
: (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the number of trailing zeros.
|
|
||||||
*/
|
|
||||||
public static function numberOfTrailingZeros(int $i) : int
|
|
||||||
{
|
|
||||||
$lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1');
|
|
||||||
return $lastPos === false ? 32 : 31 - $lastPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
use BaconQrCode\Exception\InvalidArgumentException;
|
|
||||||
use DASPRiD\Enum\AbstractEnum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 of ISO 18004.
|
|
||||||
*
|
|
||||||
* @method static self CP437()
|
|
||||||
* @method static self ISO8859_1()
|
|
||||||
* @method static self ISO8859_2()
|
|
||||||
* @method static self ISO8859_3()
|
|
||||||
* @method static self ISO8859_4()
|
|
||||||
* @method static self ISO8859_5()
|
|
||||||
* @method static self ISO8859_6()
|
|
||||||
* @method static self ISO8859_7()
|
|
||||||
* @method static self ISO8859_8()
|
|
||||||
* @method static self ISO8859_9()
|
|
||||||
* @method static self ISO8859_10()
|
|
||||||
* @method static self ISO8859_11()
|
|
||||||
* @method static self ISO8859_12()
|
|
||||||
* @method static self ISO8859_13()
|
|
||||||
* @method static self ISO8859_14()
|
|
||||||
* @method static self ISO8859_15()
|
|
||||||
* @method static self ISO8859_16()
|
|
||||||
* @method static self SJIS()
|
|
||||||
* @method static self CP1250()
|
|
||||||
* @method static self CP1251()
|
|
||||||
* @method static self CP1252()
|
|
||||||
* @method static self CP1256()
|
|
||||||
* @method static self UNICODE_BIG_UNMARKED()
|
|
||||||
* @method static self UTF8()
|
|
||||||
* @method static self ASCII()
|
|
||||||
* @method static self BIG5()
|
|
||||||
* @method static self GB18030()
|
|
||||||
* @method static self EUC_KR()
|
|
||||||
*/
|
|
||||||
final class CharacterSetEci extends AbstractEnum
|
|
||||||
{
|
|
||||||
protected const CP437 = [[0, 2]];
|
|
||||||
protected const ISO8859_1 = [[1, 3], 'ISO-8859-1'];
|
|
||||||
protected const ISO8859_2 = [[4], 'ISO-8859-2'];
|
|
||||||
protected const ISO8859_3 = [[5], 'ISO-8859-3'];
|
|
||||||
protected const ISO8859_4 = [[6], 'ISO-8859-4'];
|
|
||||||
protected const ISO8859_5 = [[7], 'ISO-8859-5'];
|
|
||||||
protected const ISO8859_6 = [[8], 'ISO-8859-6'];
|
|
||||||
protected const ISO8859_7 = [[9], 'ISO-8859-7'];
|
|
||||||
protected const ISO8859_8 = [[10], 'ISO-8859-8'];
|
|
||||||
protected const ISO8859_9 = [[11], 'ISO-8859-9'];
|
|
||||||
protected const ISO8859_10 = [[12], 'ISO-8859-10'];
|
|
||||||
protected const ISO8859_11 = [[13], 'ISO-8859-11'];
|
|
||||||
protected const ISO8859_12 = [[14], 'ISO-8859-12'];
|
|
||||||
protected const ISO8859_13 = [[15], 'ISO-8859-13'];
|
|
||||||
protected const ISO8859_14 = [[16], 'ISO-8859-14'];
|
|
||||||
protected const ISO8859_15 = [[17], 'ISO-8859-15'];
|
|
||||||
protected const ISO8859_16 = [[18], 'ISO-8859-16'];
|
|
||||||
protected const SJIS = [[20], 'Shift_JIS'];
|
|
||||||
protected const CP1250 = [[21], 'windows-1250'];
|
|
||||||
protected const CP1251 = [[22], 'windows-1251'];
|
|
||||||
protected const CP1252 = [[23], 'windows-1252'];
|
|
||||||
protected const CP1256 = [[24], 'windows-1256'];
|
|
||||||
protected const UNICODE_BIG_UNMARKED = [[25], 'UTF-16BE', 'UnicodeBig'];
|
|
||||||
protected const UTF8 = [[26], 'UTF-8'];
|
|
||||||
protected const ASCII = [[27, 170], 'US-ASCII'];
|
|
||||||
protected const BIG5 = [[28]];
|
|
||||||
protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK'];
|
|
||||||
protected const EUC_KR = [[30], 'EUC-KR'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
private array $otherEncodingNames;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<int, self>|null
|
|
||||||
*/
|
|
||||||
private static ?array $valueToEci;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string, self>|null
|
|
||||||
*/
|
|
||||||
private static ?array $nameToEci = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int[] $values
|
|
||||||
*/
|
|
||||||
public function __construct(private readonly array $values, string ...$otherEncodingNames)
|
|
||||||
{
|
|
||||||
$this->otherEncodingNames = $otherEncodingNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the primary value.
|
|
||||||
*/
|
|
||||||
public function getValue() : int
|
|
||||||
{
|
|
||||||
return $this->values[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets character set ECI by value.
|
|
||||||
*
|
|
||||||
* Returns the representing ECI of a given value, or null if it is legal but unsupported.
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if value is not between 0 and 900
|
|
||||||
*/
|
|
||||||
public static function getCharacterSetEciByValue(int $value) : ?self
|
|
||||||
{
|
|
||||||
if ($value < 0 || $value >= 900) {
|
|
||||||
throw new InvalidArgumentException('Value must be between 0 and 900');
|
|
||||||
}
|
|
||||||
|
|
||||||
$valueToEci = self::valueToEci();
|
|
||||||
|
|
||||||
if (! array_key_exists($value, $valueToEci)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $valueToEci[$value];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns character set ECI by name.
|
|
||||||
*
|
|
||||||
* Returns the representing ECI of a given name, or null if it is legal but unsupported
|
|
||||||
*/
|
|
||||||
public static function getCharacterSetEciByName(string $name) : ?self
|
|
||||||
{
|
|
||||||
$nameToEci = self::nameToEci();
|
|
||||||
$name = strtolower($name);
|
|
||||||
|
|
||||||
if (! array_key_exists($name, $nameToEci)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $nameToEci[$name];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function valueToEci() : array
|
|
||||||
{
|
|
||||||
if (null !== self::$valueToEci) {
|
|
||||||
return self::$valueToEci;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$valueToEci = [];
|
|
||||||
|
|
||||||
foreach (self::values() as $eci) {
|
|
||||||
foreach ($eci->values as $value) {
|
|
||||||
self::$valueToEci[$value] = $eci;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$valueToEci;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function nameToEci() : array
|
|
||||||
{
|
|
||||||
if (null !== self::$nameToEci) {
|
|
||||||
return self::$nameToEci;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$nameToEci = [];
|
|
||||||
|
|
||||||
foreach (self::values() as $eci) {
|
|
||||||
self::$nameToEci[strtolower($eci->name())] = $eci;
|
|
||||||
|
|
||||||
foreach ($eci->otherEncodingNames as $name) {
|
|
||||||
self::$nameToEci[strtolower($name)] = $eci;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$nameToEci;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encapsulates the parameters for one error-correction block in one symbol version.
|
|
||||||
*
|
|
||||||
* This includes the number of data codewords, and the number of times a block with these parameters is used
|
|
||||||
* consecutively in the QR code version's format.
|
|
||||||
*/
|
|
||||||
final class EcBlock
|
|
||||||
{
|
|
||||||
public function __construct(private readonly int $count, private readonly int $dataCodewords)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns how many times the block is used.
|
|
||||||
*/
|
|
||||||
public function getCount() : int
|
|
||||||
{
|
|
||||||
return $this->count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of data codewords.
|
|
||||||
*/
|
|
||||||
public function getDataCodewords() : int
|
|
||||||
{
|
|
||||||
return $this->dataCodewords;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encapsulates a set of error-correction blocks in one symbol version.
|
|
||||||
*
|
|
||||||
* Most versions will use blocks of differing sizes within one version, so, this encapsulates the parameters for each
|
|
||||||
* set of blocks. It also holds the number of error-correction codewords per block since it will be the same across all
|
|
||||||
* blocks within one version.
|
|
||||||
*/
|
|
||||||
final class EcBlocks
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* List of EC blocks.
|
|
||||||
*
|
|
||||||
* @var EcBlock[]
|
|
||||||
*/
|
|
||||||
private array $ecBlocks;
|
|
||||||
|
|
||||||
public function __construct(private readonly int $ecCodewordsPerBlock, EcBlock ...$ecBlocks)
|
|
||||||
{
|
|
||||||
$this->ecBlocks = $ecBlocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of EC codewords per block.
|
|
||||||
*/
|
|
||||||
public function getEcCodewordsPerBlock() : int
|
|
||||||
{
|
|
||||||
return $this->ecCodewordsPerBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total number of EC block appearances.
|
|
||||||
*/
|
|
||||||
public function getNumBlocks() : int
|
|
||||||
{
|
|
||||||
$total = 0;
|
|
||||||
|
|
||||||
foreach ($this->ecBlocks as $ecBlock) {
|
|
||||||
$total += $ecBlock->getCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total count of EC codewords.
|
|
||||||
*/
|
|
||||||
public function getTotalEcCodewords() : int
|
|
||||||
{
|
|
||||||
return $this->ecCodewordsPerBlock * $this->getNumBlocks();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the EC blocks included in this collection.
|
|
||||||
*
|
|
||||||
* @return EcBlock[]
|
|
||||||
*/
|
|
||||||
public function getEcBlocks() : array
|
|
||||||
{
|
|
||||||
return $this->ecBlocks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
use BaconQrCode\Exception\OutOfBoundsException;
|
|
||||||
use DASPRiD\Enum\AbstractEnum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum representing the four error correction levels.
|
|
||||||
*
|
|
||||||
* @method static self L() ~7% correction
|
|
||||||
* @method static self M() ~15% correction
|
|
||||||
* @method static self Q() ~25% correction
|
|
||||||
* @method static self H() ~30% correction
|
|
||||||
*/
|
|
||||||
final class ErrorCorrectionLevel extends AbstractEnum
|
|
||||||
{
|
|
||||||
protected const L = [0x01];
|
|
||||||
protected const M = [0x00];
|
|
||||||
protected const Q = [0x03];
|
|
||||||
protected const H = [0x02];
|
|
||||||
|
|
||||||
protected function __construct(private readonly int $bits)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws OutOfBoundsException if number of bits is invalid
|
|
||||||
*/
|
|
||||||
public static function forBits(int $bits) : self
|
|
||||||
{
|
|
||||||
switch ($bits) {
|
|
||||||
case 0:
|
|
||||||
return self::M();
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
return self::L();
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
return self::H();
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
return self::Q();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new OutOfBoundsException('Invalid number of bits');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the two bits used to encode this error correction level.
|
|
||||||
*/
|
|
||||||
public function getBits() : int
|
|
||||||
{
|
|
||||||
return $this->bits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* BaconQrCode
|
|
||||||
*
|
|
||||||
* @link http://github.com/Bacon/BaconQrCode For the canonical source repository
|
|
||||||
* @copyright 2013 Ben 'DASPRiD' Scholzen
|
|
||||||
* @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encapsulates a QR Code's format information, including the data mask used and error correction level.
|
|
||||||
*/
|
|
||||||
class FormatInformation
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Mask for format information.
|
|
||||||
*/
|
|
||||||
private const FORMAT_INFO_MASK_QR = 0x5412;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup table for decoding format information.
|
|
||||||
*
|
|
||||||
* See ISO 18004:2006, Annex C, Table C.1
|
|
||||||
*/
|
|
||||||
private const FORMAT_INFO_DECODE_LOOKUP = [
|
|
||||||
[0x5412, 0x00],
|
|
||||||
[0x5125, 0x01],
|
|
||||||
[0x5e7c, 0x02],
|
|
||||||
[0x5b4b, 0x03],
|
|
||||||
[0x45f9, 0x04],
|
|
||||||
[0x40ce, 0x05],
|
|
||||||
[0x4f97, 0x06],
|
|
||||||
[0x4aa0, 0x07],
|
|
||||||
[0x77c4, 0x08],
|
|
||||||
[0x72f3, 0x09],
|
|
||||||
[0x7daa, 0x0a],
|
|
||||||
[0x789d, 0x0b],
|
|
||||||
[0x662f, 0x0c],
|
|
||||||
[0x6318, 0x0d],
|
|
||||||
[0x6c41, 0x0e],
|
|
||||||
[0x6976, 0x0f],
|
|
||||||
[0x1689, 0x10],
|
|
||||||
[0x13be, 0x11],
|
|
||||||
[0x1ce7, 0x12],
|
|
||||||
[0x19d0, 0x13],
|
|
||||||
[0x0762, 0x14],
|
|
||||||
[0x0255, 0x15],
|
|
||||||
[0x0d0c, 0x16],
|
|
||||||
[0x083b, 0x17],
|
|
||||||
[0x355f, 0x18],
|
|
||||||
[0x3068, 0x19],
|
|
||||||
[0x3f31, 0x1a],
|
|
||||||
[0x3a06, 0x1b],
|
|
||||||
[0x24b4, 0x1c],
|
|
||||||
[0x2183, 0x1d],
|
|
||||||
[0x2eda, 0x1e],
|
|
||||||
[0x2bed, 0x1f],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Offset i holds the number of 1 bits in the binary representation of i.
|
|
||||||
*
|
|
||||||
* @var int[]
|
|
||||||
*/
|
|
||||||
private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error correction level.
|
|
||||||
*/
|
|
||||||
private ErrorCorrectionLevel $ecLevel;
|
|
||||||
|
|
||||||
private int $dataMask;
|
|
||||||
|
|
||||||
protected function __construct(int $formatInfo)
|
|
||||||
{
|
|
||||||
$this->ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3);
|
|
||||||
$this->dataMask = $formatInfo & 0x7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks how many bits are different between two integers.
|
|
||||||
*/
|
|
||||||
public static function numBitsDiffering(int $a, int $b) : int
|
|
||||||
{
|
|
||||||
$a ^= $b;
|
|
||||||
|
|
||||||
return (
|
|
||||||
self::BITS_SET_IN_HALF_BYTE[$a & 0xf]
|
|
||||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)]
|
|
||||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)]
|
|
||||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)]
|
|
||||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)]
|
|
||||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)]
|
|
||||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)]
|
|
||||||
+ self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes format information.
|
|
||||||
*/
|
|
||||||
public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
|
|
||||||
{
|
|
||||||
$formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2);
|
|
||||||
|
|
||||||
if (null !== $formatInfo) {
|
|
||||||
return $formatInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the
|
|
||||||
// pattern first.
|
|
||||||
return self::doDecodeFormatInformation(
|
|
||||||
$maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR,
|
|
||||||
$maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal method for decoding format information.
|
|
||||||
*/
|
|
||||||
private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
|
|
||||||
{
|
|
||||||
$bestDifference = PHP_INT_MAX;
|
|
||||||
$bestFormatInfo = 0;
|
|
||||||
|
|
||||||
foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) {
|
|
||||||
$targetInfo = $decodeInfo[0];
|
|
||||||
|
|
||||||
if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) {
|
|
||||||
// Found an exact match
|
|
||||||
return new self($decodeInfo[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo);
|
|
||||||
|
|
||||||
if ($bitsDifference < $bestDifference) {
|
|
||||||
$bestFormatInfo = $decodeInfo[1];
|
|
||||||
$bestDifference = $bitsDifference;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($maskedFormatInfo1 !== $maskedFormatInfo2) {
|
|
||||||
// Also try the other option
|
|
||||||
$bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo);
|
|
||||||
|
|
||||||
if ($bitsDifference < $bestDifference) {
|
|
||||||
$bestFormatInfo = $decodeInfo[1];
|
|
||||||
$bestDifference = $bitsDifference;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match.
|
|
||||||
if ($bestDifference <= 3) {
|
|
||||||
return new self($bestFormatInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the error correction level.
|
|
||||||
*/
|
|
||||||
public function getErrorCorrectionLevel() : ErrorCorrectionLevel
|
|
||||||
{
|
|
||||||
return $this->ecLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data mask.
|
|
||||||
*/
|
|
||||||
public function getDataMask() : int
|
|
||||||
{
|
|
||||||
return $this->dataMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hashes the code of the EC level.
|
|
||||||
*/
|
|
||||||
public function hashCode() : int
|
|
||||||
{
|
|
||||||
return ($this->ecLevel->getBits() << 3) | $this->dataMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies if this instance equals another one.
|
|
||||||
*/
|
|
||||||
public function equals(self $other) : bool
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
$this->ecLevel === $other->ecLevel
|
|
||||||
&& $this->dataMask === $other->dataMask
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-69
@@ -1,69 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
use DASPRiD\Enum\AbstractEnum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum representing various modes in which data can be encoded to bits.
|
|
||||||
*
|
|
||||||
* @method static self TERMINATOR()
|
|
||||||
* @method static self NUMERIC()
|
|
||||||
* @method static self ALPHANUMERIC()
|
|
||||||
* @method static self STRUCTURED_APPEND()
|
|
||||||
* @method static self BYTE()
|
|
||||||
* @method static self ECI()
|
|
||||||
* @method static self KANJI()
|
|
||||||
* @method static self FNC1_FIRST_POSITION()
|
|
||||||
* @method static self FNC1_SECOND_POSITION()
|
|
||||||
* @method static self HANZI()
|
|
||||||
*/
|
|
||||||
final class Mode extends AbstractEnum
|
|
||||||
{
|
|
||||||
protected const TERMINATOR = [[0, 0, 0], 0x00];
|
|
||||||
protected const NUMERIC = [[10, 12, 14], 0x01];
|
|
||||||
protected const ALPHANUMERIC = [[9, 11, 13], 0x02];
|
|
||||||
protected const STRUCTURED_APPEND = [[0, 0, 0], 0x03];
|
|
||||||
protected const BYTE = [[8, 16, 16], 0x04];
|
|
||||||
protected const ECI = [[0, 0, 0], 0x07];
|
|
||||||
protected const KANJI = [[8, 10, 12], 0x08];
|
|
||||||
protected const FNC1_FIRST_POSITION = [[0, 0, 0], 0x05];
|
|
||||||
protected const FNC1_SECOND_POSITION = [[0, 0, 0], 0x09];
|
|
||||||
protected const HANZI = [[8, 10, 12], 0x0d];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int[] $characterCountBitsForVersions
|
|
||||||
*/
|
|
||||||
protected function __construct(
|
|
||||||
private readonly array $characterCountBitsForVersions,
|
|
||||||
private readonly int $bits
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of bits used in a specific QR code version.
|
|
||||||
*/
|
|
||||||
public function getCharacterCountBits(Version $version) : int
|
|
||||||
{
|
|
||||||
$number = $version->getVersionNumber();
|
|
||||||
|
|
||||||
if ($number <= 9) {
|
|
||||||
$offset = 0;
|
|
||||||
} elseif ($number <= 26) {
|
|
||||||
$offset = 1;
|
|
||||||
} else {
|
|
||||||
$offset = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->characterCountBitsForVersions[$offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the four bits used to encode this mode.
|
|
||||||
*/
|
|
||||||
public function getBits() : int
|
|
||||||
{
|
|
||||||
return $this->bits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,454 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
use BaconQrCode\Exception\InvalidArgumentException;
|
|
||||||
use BaconQrCode\Exception\RuntimeException;
|
|
||||||
use SplFixedArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reed-Solomon codec for 8-bit characters.
|
|
||||||
*
|
|
||||||
* Based on libfec by Phil Karn, KA9Q.
|
|
||||||
*/
|
|
||||||
final class ReedSolomonCodec
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Symbol size in bits.
|
|
||||||
*/
|
|
||||||
private int $symbolSize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block size in symbols.
|
|
||||||
*/
|
|
||||||
private int $blockSize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First root of RS code generator polynomial, index form.
|
|
||||||
*/
|
|
||||||
private int $firstRoot;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Primitive element to generate polynomial roots, index form.
|
|
||||||
*/
|
|
||||||
private int $primitive;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prim-th root of 1, index form.
|
|
||||||
*/
|
|
||||||
private int $iPrimitive;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RS code generator polynomial degree (number of roots).
|
|
||||||
*/
|
|
||||||
private int $numRoots;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Padding bytes at front of shortened block.
|
|
||||||
*/
|
|
||||||
private int $padding;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log lookup table.
|
|
||||||
*
|
|
||||||
* @var SplFixedArray
|
|
||||||
*/
|
|
||||||
private SplFixedArray $alphaTo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Anti-Log lookup table.
|
|
||||||
*
|
|
||||||
* @var SplFixedArray
|
|
||||||
*/
|
|
||||||
private SplFixedArray $indexOf;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generator polynomial.
|
|
||||||
*
|
|
||||||
* @var SplFixedArray
|
|
||||||
*/
|
|
||||||
private SplFixedArray $generatorPoly;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws InvalidArgumentException if symbol size ist not between 0 and 8
|
|
||||||
* @throws InvalidArgumentException if first root is invalid
|
|
||||||
* @throws InvalidArgumentException if num roots is invalid
|
|
||||||
* @throws InvalidArgumentException if padding is invalid
|
|
||||||
* @throws RuntimeException if field generator polynomial is not primitive
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
int $symbolSize,
|
|
||||||
int $gfPoly,
|
|
||||||
int $firstRoot,
|
|
||||||
int $primitive,
|
|
||||||
int $numRoots,
|
|
||||||
int $padding
|
|
||||||
) {
|
|
||||||
if ($symbolSize < 0 || $symbolSize > 8) {
|
|
||||||
throw new InvalidArgumentException('Symbol size must be between 0 and 8');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) {
|
|
||||||
throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
|
|
||||||
throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) {
|
|
||||||
throw new InvalidArgumentException(
|
|
||||||
'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->symbolSize = $symbolSize;
|
|
||||||
$this->blockSize = (1 << $symbolSize) - 1;
|
|
||||||
$this->padding = $padding;
|
|
||||||
$this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
|
|
||||||
$this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
|
|
||||||
|
|
||||||
// Generate galous field lookup table
|
|
||||||
$this->indexOf[0] = $this->blockSize;
|
|
||||||
$this->alphaTo[$this->blockSize] = 0;
|
|
||||||
|
|
||||||
$sr = 1;
|
|
||||||
|
|
||||||
for ($i = 0; $i < $this->blockSize; ++$i) {
|
|
||||||
$this->indexOf[$sr] = $i;
|
|
||||||
$this->alphaTo[$i] = $sr;
|
|
||||||
|
|
||||||
$sr <<= 1;
|
|
||||||
|
|
||||||
if ($sr & (1 << $symbolSize)) {
|
|
||||||
$sr ^= $gfPoly;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sr &= $this->blockSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (1 !== $sr) {
|
|
||||||
throw new RuntimeException('Field generator polynomial is not primitive');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Form RS code generator polynomial from its roots
|
|
||||||
$this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false);
|
|
||||||
$this->firstRoot = $firstRoot;
|
|
||||||
$this->primitive = $primitive;
|
|
||||||
$this->numRoots = $numRoots;
|
|
||||||
|
|
||||||
// Find prim-th root of 1, used in decoding
|
|
||||||
for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->iPrimitive = intdiv($iPrimitive, $primitive);
|
|
||||||
|
|
||||||
$this->generatorPoly[0] = 1;
|
|
||||||
|
|
||||||
for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) {
|
|
||||||
$this->generatorPoly[$i + 1] = 1;
|
|
||||||
|
|
||||||
for ($j = $i; $j > 0; $j--) {
|
|
||||||
if ($this->generatorPoly[$j] !== 0) {
|
|
||||||
$this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[
|
|
||||||
$this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert generator poly to index form for quicker encoding
|
|
||||||
for ($i = 0; $i <= $numRoots; ++$i) {
|
|
||||||
$this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes data and writes result back into parity array.
|
|
||||||
*/
|
|
||||||
public function encode(SplFixedArray $data, SplFixedArray $parity) : void
|
|
||||||
{
|
|
||||||
for ($i = 0; $i < $this->numRoots; ++$i) {
|
|
||||||
$parity[$i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$iterations = $this->blockSize - $this->numRoots - $this->padding;
|
|
||||||
|
|
||||||
for ($i = 0; $i < $iterations; ++$i) {
|
|
||||||
$feedback = $this->indexOf[$data[$i] ^ $parity[0]];
|
|
||||||
|
|
||||||
if ($feedback !== $this->blockSize) {
|
|
||||||
// Feedback term is non-zero
|
|
||||||
$feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback);
|
|
||||||
|
|
||||||
for ($j = 1; $j < $this->numRoots; ++$j) {
|
|
||||||
$parity[$j] = $parity[$j] ^ $this->alphaTo[
|
|
||||||
$this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($j = 0; $j < $this->numRoots - 1; ++$j) {
|
|
||||||
$parity[$j] = $parity[$j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($feedback !== $this->blockSize) {
|
|
||||||
$parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
|
|
||||||
} else {
|
|
||||||
$parity[$this->numRoots - 1] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes received data.
|
|
||||||
*/
|
|
||||||
public function decode(SplFixedArray $data, ?SplFixedArray $erasures = null) : ?int
|
|
||||||
{
|
|
||||||
// This speeds up the initialization a bit.
|
|
||||||
$numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
|
|
||||||
$numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false);
|
|
||||||
|
|
||||||
$lambda = clone $numRootsPlusOne;
|
|
||||||
$b = clone $numRootsPlusOne;
|
|
||||||
$t = clone $numRootsPlusOne;
|
|
||||||
$omega = clone $numRootsPlusOne;
|
|
||||||
$root = clone $numRoots;
|
|
||||||
$loc = clone $numRoots;
|
|
||||||
|
|
||||||
$numErasures = (null !== $erasures ? count($erasures) : 0);
|
|
||||||
|
|
||||||
// Form the Syndromes; i.e., evaluate data(x) at roots of g(x)
|
|
||||||
$syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false);
|
|
||||||
|
|
||||||
for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) {
|
|
||||||
for ($j = 0; $j < $this->numRoots; ++$j) {
|
|
||||||
if ($syndromes[$j] === 0) {
|
|
||||||
$syndromes[$j] = $data[$i];
|
|
||||||
} else {
|
|
||||||
$syndromes[$j] = $data[$i] ^ $this->alphaTo[
|
|
||||||
$this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert syndromes to index form, checking for nonzero conditions
|
|
||||||
$syndromeError = 0;
|
|
||||||
|
|
||||||
for ($i = 0; $i < $this->numRoots; ++$i) {
|
|
||||||
$syndromeError |= $syndromes[$i];
|
|
||||||
$syndromes[$i] = $this->indexOf[$syndromes[$i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $syndromeError) {
|
|
||||||
// If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[]
|
|
||||||
// unmodified.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$lambda[0] = 1;
|
|
||||||
|
|
||||||
if ($numErasures > 0) {
|
|
||||||
// Init lambda to be the erasure locator polynomial
|
|
||||||
$lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))];
|
|
||||||
|
|
||||||
for ($i = 1; $i < $numErasures; ++$i) {
|
|
||||||
$u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));
|
|
||||||
|
|
||||||
for ($j = $i + 1; $j > 0; --$j) {
|
|
||||||
$tmp = $this->indexOf[$lambda[$j - 1]];
|
|
||||||
|
|
||||||
if ($tmp !== $this->blockSize) {
|
|
||||||
$lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($i = 0; $i <= $this->numRoots; ++$i) {
|
|
||||||
$b[$i] = $this->indexOf[$lambda[$i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial
|
|
||||||
$r = $numErasures;
|
|
||||||
$el = $numErasures;
|
|
||||||
|
|
||||||
while (++$r <= $this->numRoots) {
|
|
||||||
// Compute discrepancy at the r-th step in poly form
|
|
||||||
$discrepancyR = 0;
|
|
||||||
|
|
||||||
for ($i = 0; $i < $r; ++$i) {
|
|
||||||
if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) {
|
|
||||||
$discrepancyR ^= $this->alphaTo[
|
|
||||||
$this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$discrepancyR = $this->indexOf[$discrepancyR];
|
|
||||||
|
|
||||||
if ($discrepancyR === $this->blockSize) {
|
|
||||||
$tmp = $b->toArray();
|
|
||||||
array_unshift($tmp, $this->blockSize);
|
|
||||||
array_pop($tmp);
|
|
||||||
$b = SplFixedArray::fromArray($tmp, false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$t[0] = $lambda[0];
|
|
||||||
|
|
||||||
for ($i = 0; $i < $this->numRoots; ++$i) {
|
|
||||||
if ($b[$i] !== $this->blockSize) {
|
|
||||||
$t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])];
|
|
||||||
} else {
|
|
||||||
$t[$i + 1] = $lambda[$i + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (2 * $el <= $r + $numErasures - 1) {
|
|
||||||
$el = $r + $numErasures - $el;
|
|
||||||
|
|
||||||
for ($i = 0; $i <= $this->numRoots; ++$i) {
|
|
||||||
$b[$i] = (
|
|
||||||
$lambda[$i] === 0
|
|
||||||
? $this->blockSize
|
|
||||||
: $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$tmp = $b->toArray();
|
|
||||||
array_unshift($tmp, $this->blockSize);
|
|
||||||
array_pop($tmp);
|
|
||||||
$b = SplFixedArray::fromArray($tmp, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
$lambda = clone $t;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert lambda to index form and compute deg(lambda(x))
|
|
||||||
$degLambda = 0;
|
|
||||||
|
|
||||||
for ($i = 0; $i <= $this->numRoots; ++$i) {
|
|
||||||
$lambda[$i] = $this->indexOf[$lambda[$i]];
|
|
||||||
|
|
||||||
if ($lambda[$i] !== $this->blockSize) {
|
|
||||||
$degLambda = $i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find roots of the error+erasure locator polynomial by Chien search.
|
|
||||||
$reg = clone $lambda;
|
|
||||||
$reg[0] = 0;
|
|
||||||
$count = 0;
|
|
||||||
$i = 1;
|
|
||||||
|
|
||||||
for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) {
|
|
||||||
$q = 1;
|
|
||||||
|
|
||||||
for ($j = $degLambda; $j > 0; $j--) {
|
|
||||||
if ($reg[$j] !== $this->blockSize) {
|
|
||||||
$reg[$j] = $this->modNn($reg[$j] + $j);
|
|
||||||
$q ^= $this->alphaTo[$reg[$j]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($q !== 0) {
|
|
||||||
// Not a root
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store root (index-form) and error location number
|
|
||||||
$root[$count] = $i;
|
|
||||||
$loc[$count] = $k;
|
|
||||||
|
|
||||||
if (++$count === $degLambda) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($degLambda !== $count) {
|
|
||||||
// deg(lambda) unequal to number of roots: uncorrectable error detected
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find
|
|
||||||
// deg(omega).
|
|
||||||
$degOmega = $degLambda - 1;
|
|
||||||
|
|
||||||
for ($i = 0; $i <= $degOmega; ++$i) {
|
|
||||||
$tmp = 0;
|
|
||||||
|
|
||||||
for ($j = $i; $j >= 0; --$j) {
|
|
||||||
if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) {
|
|
||||||
$tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$omega[$i] = $this->indexOf[$tmp];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and
|
|
||||||
// den = lambda_pr(inv(X(l))) all in poly form.
|
|
||||||
for ($j = $count - 1; $j >= 0; --$j) {
|
|
||||||
$num1 = 0;
|
|
||||||
|
|
||||||
for ($i = $degOmega; $i >= 0; $i--) {
|
|
||||||
if ($omega[$i] !== $this->blockSize) {
|
|
||||||
$num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)];
|
|
||||||
$den = 0;
|
|
||||||
|
|
||||||
// lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i]
|
|
||||||
for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) {
|
|
||||||
if ($lambda[$i + 1] !== $this->blockSize) {
|
|
||||||
$den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply error to data
|
|
||||||
if ($num1 !== 0 && $loc[$j] >= $this->padding) {
|
|
||||||
$data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
|
|
||||||
$this->alphaTo[
|
|
||||||
$this->modNn(
|
|
||||||
$this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $erasures) {
|
|
||||||
if (count($erasures) < $count) {
|
|
||||||
$erasures->setSize($count);
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($i = 0; $i < $count; $i++) {
|
|
||||||
$erasures[$i] = $loc[$i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide.
|
|
||||||
*/
|
|
||||||
private function modNn(int $x) : int
|
|
||||||
{
|
|
||||||
while ($x >= $this->blockSize) {
|
|
||||||
$x -= $this->blockSize;
|
|
||||||
$x = ($x >> $this->symbolSize) + ($x & $this->blockSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-592
@@ -1,592 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Common;
|
|
||||||
|
|
||||||
use BaconQrCode\Exception\InvalidArgumentException;
|
|
||||||
use SplFixedArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version representation.
|
|
||||||
*/
|
|
||||||
final class Version
|
|
||||||
{
|
|
||||||
private const VERSION_DECODE_INFO = [
|
|
||||||
0x07c94,
|
|
||||||
0x085bc,
|
|
||||||
0x09a99,
|
|
||||||
0x0a4d3,
|
|
||||||
0x0bbf6,
|
|
||||||
0x0c762,
|
|
||||||
0x0d847,
|
|
||||||
0x0e60d,
|
|
||||||
0x0f928,
|
|
||||||
0x10b78,
|
|
||||||
0x1145d,
|
|
||||||
0x12a17,
|
|
||||||
0x13532,
|
|
||||||
0x149a6,
|
|
||||||
0x15683,
|
|
||||||
0x168c9,
|
|
||||||
0x177ec,
|
|
||||||
0x18ec4,
|
|
||||||
0x191e1,
|
|
||||||
0x1afab,
|
|
||||||
0x1b08e,
|
|
||||||
0x1cc1a,
|
|
||||||
0x1d33f,
|
|
||||||
0x1ed75,
|
|
||||||
0x1f250,
|
|
||||||
0x209d5,
|
|
||||||
0x216f0,
|
|
||||||
0x228ba,
|
|
||||||
0x2379f,
|
|
||||||
0x24b0b,
|
|
||||||
0x2542e,
|
|
||||||
0x26a64,
|
|
||||||
0x27541,
|
|
||||||
0x28c69,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version number of this version.
|
|
||||||
*/
|
|
||||||
private int $versionNumber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alignment pattern centers.
|
|
||||||
*
|
|
||||||
* @var SplFixedArray|array
|
|
||||||
*/
|
|
||||||
private SplFixedArray|array $alignmentPatternCenters;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error correction blocks.
|
|
||||||
*
|
|
||||||
* @var EcBlocks[]
|
|
||||||
*/
|
|
||||||
private array $ecBlocks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total number of codewords.
|
|
||||||
*/
|
|
||||||
private null|int|float $totalCodewords;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cached version instances.
|
|
||||||
*
|
|
||||||
* @var array<int, self>|null
|
|
||||||
*/
|
|
||||||
private static ?array $versions = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int[] $alignmentPatternCenters
|
|
||||||
*/
|
|
||||||
private function __construct(
|
|
||||||
int $versionNumber,
|
|
||||||
array $alignmentPatternCenters,
|
|
||||||
EcBlocks ...$ecBlocks
|
|
||||||
) {
|
|
||||||
$this->versionNumber = $versionNumber;
|
|
||||||
$this->alignmentPatternCenters = $alignmentPatternCenters;
|
|
||||||
$this->ecBlocks = $ecBlocks;
|
|
||||||
|
|
||||||
$totalCodewords = 0;
|
|
||||||
$ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock();
|
|
||||||
|
|
||||||
foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) {
|
|
||||||
$totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->totalCodewords = $totalCodewords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the version number.
|
|
||||||
*/
|
|
||||||
public function getVersionNumber() : int
|
|
||||||
{
|
|
||||||
return $this->versionNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the alignment pattern centers.
|
|
||||||
*
|
|
||||||
* @return int[]
|
|
||||||
*/
|
|
||||||
public function getAlignmentPatternCenters() : array
|
|
||||||
{
|
|
||||||
return $this->alignmentPatternCenters;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total number of codewords.
|
|
||||||
*/
|
|
||||||
public function getTotalCodewords() : int
|
|
||||||
{
|
|
||||||
return $this->totalCodewords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the dimension for the current version.
|
|
||||||
*/
|
|
||||||
public function getDimensionForVersion() : int
|
|
||||||
{
|
|
||||||
return 17 + 4 * $this->versionNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of EC blocks for a specific EC level.
|
|
||||||
*/
|
|
||||||
public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks
|
|
||||||
{
|
|
||||||
return $this->ecBlocks[$ecLevel->ordinal()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a provisional version number for a specific dimension.
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if dimension is not 1 mod 4
|
|
||||||
*/
|
|
||||||
public static function getProvisionalVersionForDimension(int $dimension) : self
|
|
||||||
{
|
|
||||||
if (1 !== $dimension % 4) {
|
|
||||||
throw new InvalidArgumentException('Dimension is not 1 mod 4');
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::getVersionForNumber(intdiv($dimension - 17, 4));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a version instance for a specific version number.
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if version number is out of range
|
|
||||||
*/
|
|
||||||
public static function getVersionForNumber(int $versionNumber) : self
|
|
||||||
{
|
|
||||||
if ($versionNumber < 1 || $versionNumber > 40) {
|
|
||||||
throw new InvalidArgumentException('Version number must be between 1 and 40');
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::versions()[$versionNumber - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes version information from an integer and returns the version.
|
|
||||||
*/
|
|
||||||
public static function decodeVersionInformation(int $versionBits) : ?self
|
|
||||||
{
|
|
||||||
$bestDifference = PHP_INT_MAX;
|
|
||||||
$bestVersion = 0;
|
|
||||||
|
|
||||||
foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) {
|
|
||||||
if ($targetVersion === $versionBits) {
|
|
||||||
return self::getVersionForNumber($i + 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
$bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion);
|
|
||||||
|
|
||||||
if ($bitsDifference < $bestDifference) {
|
|
||||||
$bestVersion = $i + 7;
|
|
||||||
$bestDifference = $bitsDifference;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($bestDifference <= 3) {
|
|
||||||
return self::getVersionForNumber($bestVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the function pattern for the current version.
|
|
||||||
*/
|
|
||||||
public function buildFunctionPattern() : BitMatrix
|
|
||||||
{
|
|
||||||
$dimension = $this->getDimensionForVersion();
|
|
||||||
$bitMatrix = new BitMatrix($dimension);
|
|
||||||
|
|
||||||
// Top left finder pattern + separator + format
|
|
||||||
$bitMatrix->setRegion(0, 0, 9, 9);
|
|
||||||
// Top right finder pattern + separator + format
|
|
||||||
$bitMatrix->setRegion($dimension - 8, 0, 8, 9);
|
|
||||||
// Bottom left finder pattern + separator + format
|
|
||||||
$bitMatrix->setRegion(0, $dimension - 8, 9, 8);
|
|
||||||
|
|
||||||
// Alignment patterns
|
|
||||||
$max = count($this->alignmentPatternCenters);
|
|
||||||
|
|
||||||
for ($x = 0; $x < $max; ++$x) {
|
|
||||||
$i = $this->alignmentPatternCenters[$x] - 2;
|
|
||||||
|
|
||||||
for ($y = 0; $y < $max; ++$y) {
|
|
||||||
if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) {
|
|
||||||
// No alignment patterns near the three finder paterns
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vertical timing pattern
|
|
||||||
$bitMatrix->setRegion(6, 9, 1, $dimension - 17);
|
|
||||||
// Horizontal timing pattern
|
|
||||||
$bitMatrix->setRegion(9, 6, $dimension - 17, 1);
|
|
||||||
|
|
||||||
if ($this->versionNumber > 6) {
|
|
||||||
// Version info, top right
|
|
||||||
$bitMatrix->setRegion($dimension - 11, 0, 3, 6);
|
|
||||||
// Version info, bottom left
|
|
||||||
$bitMatrix->setRegion(0, $dimension - 11, 6, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $bitMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation for the version.
|
|
||||||
*/
|
|
||||||
public function __toString() : string
|
|
||||||
{
|
|
||||||
return (string) $this->versionNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build and cache a specific version.
|
|
||||||
*
|
|
||||||
* See ISO 18004:2006 6.5.1 Table 9.
|
|
||||||
*
|
|
||||||
* @return array<int, self>
|
|
||||||
*/
|
|
||||||
private static function versions() : array
|
|
||||||
{
|
|
||||||
if (null !== self::$versions) {
|
|
||||||
return self::$versions;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$versions = [
|
|
||||||
new self(
|
|
||||||
1,
|
|
||||||
[],
|
|
||||||
new EcBlocks(7, new EcBlock(1, 19)),
|
|
||||||
new EcBlocks(10, new EcBlock(1, 16)),
|
|
||||||
new EcBlocks(13, new EcBlock(1, 13)),
|
|
||||||
new EcBlocks(17, new EcBlock(1, 9))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
2,
|
|
||||||
[6, 18],
|
|
||||||
new EcBlocks(10, new EcBlock(1, 34)),
|
|
||||||
new EcBlocks(16, new EcBlock(1, 28)),
|
|
||||||
new EcBlocks(22, new EcBlock(1, 22)),
|
|
||||||
new EcBlocks(28, new EcBlock(1, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
3,
|
|
||||||
[6, 22],
|
|
||||||
new EcBlocks(15, new EcBlock(1, 55)),
|
|
||||||
new EcBlocks(26, new EcBlock(1, 44)),
|
|
||||||
new EcBlocks(18, new EcBlock(2, 17)),
|
|
||||||
new EcBlocks(22, new EcBlock(2, 13))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
4,
|
|
||||||
[6, 26],
|
|
||||||
new EcBlocks(20, new EcBlock(1, 80)),
|
|
||||||
new EcBlocks(18, new EcBlock(2, 32)),
|
|
||||||
new EcBlocks(26, new EcBlock(2, 24)),
|
|
||||||
new EcBlocks(16, new EcBlock(4, 9))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
5,
|
|
||||||
[6, 30],
|
|
||||||
new EcBlocks(26, new EcBlock(1, 108)),
|
|
||||||
new EcBlocks(24, new EcBlock(2, 43)),
|
|
||||||
new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)),
|
|
||||||
new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
6,
|
|
||||||
[6, 34],
|
|
||||||
new EcBlocks(18, new EcBlock(2, 68)),
|
|
||||||
new EcBlocks(16, new EcBlock(4, 27)),
|
|
||||||
new EcBlocks(24, new EcBlock(4, 19)),
|
|
||||||
new EcBlocks(28, new EcBlock(4, 15))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
7,
|
|
||||||
[6, 22, 38],
|
|
||||||
new EcBlocks(20, new EcBlock(2, 78)),
|
|
||||||
new EcBlocks(18, new EcBlock(4, 31)),
|
|
||||||
new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)),
|
|
||||||
new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
8,
|
|
||||||
[6, 24, 42],
|
|
||||||
new EcBlocks(24, new EcBlock(2, 97)),
|
|
||||||
new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)),
|
|
||||||
new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)),
|
|
||||||
new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
9,
|
|
||||||
[6, 26, 46],
|
|
||||||
new EcBlocks(30, new EcBlock(2, 116)),
|
|
||||||
new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)),
|
|
||||||
new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)),
|
|
||||||
new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
10,
|
|
||||||
[6, 28, 50],
|
|
||||||
new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)),
|
|
||||||
new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)),
|
|
||||||
new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)),
|
|
||||||
new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
11,
|
|
||||||
[6, 30, 54],
|
|
||||||
new EcBlocks(20, new EcBlock(4, 81)),
|
|
||||||
new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)),
|
|
||||||
new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)),
|
|
||||||
new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
12,
|
|
||||||
[6, 32, 58],
|
|
||||||
new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)),
|
|
||||||
new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)),
|
|
||||||
new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)),
|
|
||||||
new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
13,
|
|
||||||
[6, 34, 62],
|
|
||||||
new EcBlocks(26, new EcBlock(4, 107)),
|
|
||||||
new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)),
|
|
||||||
new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)),
|
|
||||||
new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
14,
|
|
||||||
[6, 26, 46, 66],
|
|
||||||
new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)),
|
|
||||||
new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)),
|
|
||||||
new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)),
|
|
||||||
new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
15,
|
|
||||||
[6, 26, 48, 70],
|
|
||||||
new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)),
|
|
||||||
new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)),
|
|
||||||
new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)),
|
|
||||||
new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
16,
|
|
||||||
[6, 26, 50, 74],
|
|
||||||
new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)),
|
|
||||||
new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)),
|
|
||||||
new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)),
|
|
||||||
new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
17,
|
|
||||||
[6, 30, 54, 78],
|
|
||||||
new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)),
|
|
||||||
new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)),
|
|
||||||
new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)),
|
|
||||||
new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
18,
|
|
||||||
[6, 30, 56, 82],
|
|
||||||
new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)),
|
|
||||||
new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)),
|
|
||||||
new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)),
|
|
||||||
new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
19,
|
|
||||||
[6, 30, 58, 86],
|
|
||||||
new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)),
|
|
||||||
new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)),
|
|
||||||
new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)),
|
|
||||||
new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
20,
|
|
||||||
[6, 34, 62, 90],
|
|
||||||
new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)),
|
|
||||||
new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)),
|
|
||||||
new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)),
|
|
||||||
new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
21,
|
|
||||||
[6, 28, 50, 72, 94],
|
|
||||||
new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)),
|
|
||||||
new EcBlocks(26, new EcBlock(17, 42)),
|
|
||||||
new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)),
|
|
||||||
new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
22,
|
|
||||||
[6, 26, 50, 74, 98],
|
|
||||||
new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)),
|
|
||||||
new EcBlocks(28, new EcBlock(17, 46)),
|
|
||||||
new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)),
|
|
||||||
new EcBlocks(24, new EcBlock(34, 13))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
23,
|
|
||||||
[6, 30, 54, 78, 102],
|
|
||||||
new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)),
|
|
||||||
new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)),
|
|
||||||
new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
24,
|
|
||||||
[6, 28, 54, 80, 106],
|
|
||||||
new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)),
|
|
||||||
new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)),
|
|
||||||
new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
25,
|
|
||||||
[6, 32, 58, 84, 110],
|
|
||||||
new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)),
|
|
||||||
new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)),
|
|
||||||
new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
26,
|
|
||||||
[6, 30, 58, 86, 114],
|
|
||||||
new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)),
|
|
||||||
new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)),
|
|
||||||
new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)),
|
|
||||||
new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
27,
|
|
||||||
[6, 34, 62, 90, 118],
|
|
||||||
new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)),
|
|
||||||
new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)),
|
|
||||||
new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)),
|
|
||||||
new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
28,
|
|
||||||
[6, 26, 50, 74, 98, 122],
|
|
||||||
new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)),
|
|
||||||
new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)),
|
|
||||||
new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
29,
|
|
||||||
[6, 30, 54, 78, 102, 126],
|
|
||||||
new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)),
|
|
||||||
new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)),
|
|
||||||
new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)),
|
|
||||||
new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
30,
|
|
||||||
[6, 26, 52, 78, 104, 130],
|
|
||||||
new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)),
|
|
||||||
new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)),
|
|
||||||
new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
31,
|
|
||||||
[6, 30, 56, 82, 108, 134],
|
|
||||||
new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)),
|
|
||||||
new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)),
|
|
||||||
new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
32,
|
|
||||||
[6, 34, 60, 86, 112, 138],
|
|
||||||
new EcBlocks(30, new EcBlock(17, 115)),
|
|
||||||
new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)),
|
|
||||||
new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
33,
|
|
||||||
[6, 30, 58, 86, 114, 142],
|
|
||||||
new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)),
|
|
||||||
new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)),
|
|
||||||
new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
34,
|
|
||||||
[6, 34, 62, 90, 118, 146],
|
|
||||||
new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)),
|
|
||||||
new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)),
|
|
||||||
new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
35,
|
|
||||||
[6, 30, 54, 78, 102, 126, 150],
|
|
||||||
new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)),
|
|
||||||
new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)),
|
|
||||||
new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
36,
|
|
||||||
[6, 24, 50, 76, 102, 128, 154],
|
|
||||||
new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)),
|
|
||||||
new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)),
|
|
||||||
new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
37,
|
|
||||||
[6, 28, 54, 80, 106, 132, 158],
|
|
||||||
new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)),
|
|
||||||
new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)),
|
|
||||||
new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
38,
|
|
||||||
[6, 32, 58, 84, 110, 136, 162],
|
|
||||||
new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)),
|
|
||||||
new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)),
|
|
||||||
new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
39,
|
|
||||||
[6, 26, 54, 82, 110, 138, 166],
|
|
||||||
new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)),
|
|
||||||
new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)),
|
|
||||||
new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16))
|
|
||||||
),
|
|
||||||
new self(
|
|
||||||
40,
|
|
||||||
[6, 30, 58, 86, 114, 142, 170],
|
|
||||||
new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)),
|
|
||||||
new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)),
|
|
||||||
new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)),
|
|
||||||
new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16))
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Encoder;
|
|
||||||
|
|
||||||
use SplFixedArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block pair.
|
|
||||||
*/
|
|
||||||
final class BlockPair
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Creates a new block pair.
|
|
||||||
*
|
|
||||||
* @param SplFixedArray<int> $dataBytes Data bytes in the block.
|
|
||||||
* @param SplFixedArray<int> $errorCorrectionBytes Error correction bytes in the block.
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
private readonly SplFixedArray $dataBytes,
|
|
||||||
private readonly SplFixedArray $errorCorrectionBytes
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the data bytes.
|
|
||||||
*
|
|
||||||
* @return SplFixedArray<int>
|
|
||||||
*/
|
|
||||||
public function getDataBytes() : SplFixedArray
|
|
||||||
{
|
|
||||||
return $this->dataBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the error correction bytes.
|
|
||||||
*
|
|
||||||
* @return SplFixedArray<int>
|
|
||||||
*/
|
|
||||||
public function getErrorCorrectionBytes() : SplFixedArray
|
|
||||||
{
|
|
||||||
return $this->errorCorrectionBytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Encoder;
|
|
||||||
|
|
||||||
use SplFixedArray;
|
|
||||||
use Traversable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Byte matrix.
|
|
||||||
*/
|
|
||||||
final class ByteMatrix
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Bytes in the matrix, represented as array.
|
|
||||||
*
|
|
||||||
* @var SplFixedArray<SplFixedArray<int>>
|
|
||||||
*/
|
|
||||||
private SplFixedArray $bytes;
|
|
||||||
|
|
||||||
public function __construct(private readonly int $width, private readonly int $height)
|
|
||||||
{
|
|
||||||
$this->bytes = new SplFixedArray($height);
|
|
||||||
|
|
||||||
for ($y = 0; $y < $height; ++$y) {
|
|
||||||
$this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the width of the matrix.
|
|
||||||
*/
|
|
||||||
public function getWidth() : int
|
|
||||||
{
|
|
||||||
return $this->width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the height of the matrix.
|
|
||||||
*/
|
|
||||||
public function getHeight() : int
|
|
||||||
{
|
|
||||||
return $this->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the internal representation of the matrix.
|
|
||||||
*
|
|
||||||
* @return SplFixedArray<SplFixedArray<int>>
|
|
||||||
*/
|
|
||||||
public function getArray() : SplFixedArray
|
|
||||||
{
|
|
||||||
return $this->bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Traversable<int>
|
|
||||||
*/
|
|
||||||
public function getBytes() : Traversable
|
|
||||||
{
|
|
||||||
foreach ($this->bytes as $row) {
|
|
||||||
foreach ($row as $byte) {
|
|
||||||
yield $byte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the byte for a specific position.
|
|
||||||
*/
|
|
||||||
public function get(int $x, int $y) : int
|
|
||||||
{
|
|
||||||
return $this->bytes[$y][$x];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the byte for a specific position.
|
|
||||||
*/
|
|
||||||
public function set(int $x, int $y, int $value) : void
|
|
||||||
{
|
|
||||||
$this->bytes[$y][$x] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the matrix with a specific value.
|
|
||||||
*/
|
|
||||||
public function clear(int $value) : void
|
|
||||||
{
|
|
||||||
for ($y = 0; $y < $this->height; ++$y) {
|
|
||||||
for ($x = 0; $x < $this->width; ++$x) {
|
|
||||||
$this->bytes[$y][$x] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __clone()
|
|
||||||
{
|
|
||||||
$this->bytes = clone $this->bytes;
|
|
||||||
|
|
||||||
foreach ($this->bytes as $index => $row) {
|
|
||||||
$this->bytes[$index] = clone $row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the matrix.
|
|
||||||
*/
|
|
||||||
public function __toString() : string
|
|
||||||
{
|
|
||||||
$result = '';
|
|
||||||
|
|
||||||
for ($y = 0; $y < $this->height; $y++) {
|
|
||||||
for ($x = 0; $x < $this->width; $x++) {
|
|
||||||
switch ($this->bytes[$y][$x]) {
|
|
||||||
case 0:
|
|
||||||
$result .= ' 0';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
$result .= ' 1';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$result .= ' ';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$result .= "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-679
@@ -1,679 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Encoder;
|
|
||||||
|
|
||||||
use BaconQrCode\Common\BitArray;
|
|
||||||
use BaconQrCode\Common\CharacterSetEci;
|
|
||||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
|
||||||
use BaconQrCode\Common\Mode;
|
|
||||||
use BaconQrCode\Common\ReedSolomonCodec;
|
|
||||||
use BaconQrCode\Common\Version;
|
|
||||||
use BaconQrCode\Exception\WriterException;
|
|
||||||
use SplFixedArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encoder.
|
|
||||||
*/
|
|
||||||
final class Encoder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Default byte encoding.
|
|
||||||
*/
|
|
||||||
public const DEFAULT_BYTE_MODE_ENCODING = 'ISO-8859-1';
|
|
||||||
|
|
||||||
/** @deprecated use DEFAULT_BYTE_MODE_ENCODING */
|
|
||||||
public const DEFAULT_BYTE_MODE_ECODING = self::DEFAULT_BYTE_MODE_ENCODING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The original table is defined in the table 5 of JISX0510:2004 (p.19).
|
|
||||||
*/
|
|
||||||
private const ALPHANUMERIC_TABLE = [
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
|
|
||||||
36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
|
|
||||||
-1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
|
|
||||||
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Codec cache.
|
|
||||||
*
|
|
||||||
* @var array<string,ReedSolomonCodec>
|
|
||||||
*/
|
|
||||||
private static array $codecs = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes "content" with the error correction level "ecLevel".
|
|
||||||
*/
|
|
||||||
public static function encode(
|
|
||||||
string $content,
|
|
||||||
ErrorCorrectionLevel $ecLevel,
|
|
||||||
string $encoding = self::DEFAULT_BYTE_MODE_ENCODING,
|
|
||||||
?Version $forcedVersion = null,
|
|
||||||
// Barcode scanner might not be able to read the encoded message of the QR code with the prefix ECI of UTF-8
|
|
||||||
bool $prefixEci = true
|
|
||||||
) : QrCode {
|
|
||||||
// Pick an encoding mode appropriate for the content. Note that this
|
|
||||||
// will not attempt to use multiple modes / segments even if that were
|
|
||||||
// more efficient. Would be nice.
|
|
||||||
$mode = self::chooseMode($content, $encoding);
|
|
||||||
|
|
||||||
// This will store the header information, like mode and length, as well
|
|
||||||
// as "header" segments like an ECI segment.
|
|
||||||
$headerBits = new BitArray();
|
|
||||||
|
|
||||||
// Append ECI segment if applicable
|
|
||||||
if ($prefixEci && Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ENCODING !== $encoding) {
|
|
||||||
$eci = CharacterSetEci::getCharacterSetEciByName($encoding);
|
|
||||||
|
|
||||||
if (null !== $eci) {
|
|
||||||
self::appendEci($eci, $headerBits);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// (With ECI in place,) Write the mode marker
|
|
||||||
self::appendModeInfo($mode, $headerBits);
|
|
||||||
|
|
||||||
// Collect data within the main segment, separately, to count its size
|
|
||||||
// if needed. Don't add it to main payload yet.
|
|
||||||
$dataBits = new BitArray();
|
|
||||||
self::appendBytes($content, $mode, $dataBits, $encoding);
|
|
||||||
|
|
||||||
// Hard part: need to know version to know how many bits length takes.
|
|
||||||
// But need to know how many bits it takes to know version. First we
|
|
||||||
// take a guess at version by assuming version will be the minimum, 1:
|
|
||||||
$provisionalBitsNeeded = $headerBits->getSize()
|
|
||||||
+ $mode->getCharacterCountBits(Version::getVersionForNumber(1))
|
|
||||||
+ $dataBits->getSize();
|
|
||||||
$provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel);
|
|
||||||
|
|
||||||
// Use that guess to calculate the right version. I am still not sure
|
|
||||||
// this works in 100% of cases.
|
|
||||||
$bitsNeeded = $headerBits->getSize()
|
|
||||||
+ $mode->getCharacterCountBits($provisionalVersion)
|
|
||||||
+ $dataBits->getSize();
|
|
||||||
$version = self::chooseVersion($bitsNeeded, $ecLevel);
|
|
||||||
|
|
||||||
if (null !== $forcedVersion) {
|
|
||||||
// Forced version check
|
|
||||||
if ($version->getVersionNumber() <= $forcedVersion->getVersionNumber()) {
|
|
||||||
// Calculated minimum version is same or equal as forced version
|
|
||||||
$version = $forcedVersion;
|
|
||||||
} else {
|
|
||||||
throw new WriterException(
|
|
||||||
'Invalid version! Calculated version: '
|
|
||||||
. $version->getVersionNumber()
|
|
||||||
. ', requested version: '
|
|
||||||
. $forcedVersion->getVersionNumber()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$headerAndDataBits = new BitArray();
|
|
||||||
$headerAndDataBits->appendBitArray($headerBits);
|
|
||||||
|
|
||||||
// Find "length" of main segment and write it.
|
|
||||||
$numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content));
|
|
||||||
self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits);
|
|
||||||
|
|
||||||
// Put data together into the overall payload.
|
|
||||||
$headerAndDataBits->appendBitArray($dataBits);
|
|
||||||
$ecBlocks = $version->getEcBlocksForLevel($ecLevel);
|
|
||||||
$numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords();
|
|
||||||
|
|
||||||
// Terminate the bits properly.
|
|
||||||
self::terminateBits($numDataBytes, $headerAndDataBits);
|
|
||||||
|
|
||||||
// Interleave data bits with error correction code.
|
|
||||||
$finalBits = self::interleaveWithEcBytes(
|
|
||||||
$headerAndDataBits,
|
|
||||||
$version->getTotalCodewords(),
|
|
||||||
$numDataBytes,
|
|
||||||
$ecBlocks->getNumBlocks()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Choose the mask pattern.
|
|
||||||
$dimension = $version->getDimensionForVersion();
|
|
||||||
$matrix = new ByteMatrix($dimension, $dimension);
|
|
||||||
$maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix);
|
|
||||||
|
|
||||||
// Build the matrix.
|
|
||||||
MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix);
|
|
||||||
|
|
||||||
return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the alphanumeric code for a byte.
|
|
||||||
*/
|
|
||||||
private static function getAlphanumericCode(int $code) : int
|
|
||||||
{
|
|
||||||
if (isset(self::ALPHANUMERIC_TABLE[$code])) {
|
|
||||||
return self::ALPHANUMERIC_TABLE[$code];
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chooses the best mode for a given content.
|
|
||||||
*/
|
|
||||||
private static function chooseMode(string $content, ?string $encoding = null) : Mode
|
|
||||||
{
|
|
||||||
if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) {
|
|
||||||
return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE();
|
|
||||||
}
|
|
||||||
|
|
||||||
$hasNumeric = false;
|
|
||||||
$hasAlphanumeric = false;
|
|
||||||
$contentLength = strlen($content);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $contentLength; ++$i) {
|
|
||||||
$char = $content[$i];
|
|
||||||
|
|
||||||
if (ctype_digit($char)) {
|
|
||||||
$hasNumeric = true;
|
|
||||||
} elseif (-1 !== self::getAlphanumericCode(ord($char))) {
|
|
||||||
$hasAlphanumeric = true;
|
|
||||||
} else {
|
|
||||||
return Mode::BYTE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($hasAlphanumeric) {
|
|
||||||
return Mode::ALPHANUMERIC();
|
|
||||||
} elseif ($hasNumeric) {
|
|
||||||
return Mode::NUMERIC();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Mode::BYTE();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the mask penalty for a matrix.
|
|
||||||
*/
|
|
||||||
private static function calculateMaskPenalty(ByteMatrix $matrix) : int
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
MaskUtil::applyMaskPenaltyRule1($matrix)
|
|
||||||
+ MaskUtil::applyMaskPenaltyRule2($matrix)
|
|
||||||
+ MaskUtil::applyMaskPenaltyRule3($matrix)
|
|
||||||
+ MaskUtil::applyMaskPenaltyRule4($matrix)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if content only consists of double-byte kanji characters.
|
|
||||||
*/
|
|
||||||
private static function isOnlyDoubleByteKanji(string $content) : bool
|
|
||||||
{
|
|
||||||
$bytes = @iconv('utf-8', 'SHIFT-JIS', $content);
|
|
||||||
|
|
||||||
if (false === $bytes) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$length = strlen($bytes);
|
|
||||||
|
|
||||||
if (0 !== $length % 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($i = 0; $i < $length; $i += 2) {
|
|
||||||
$byte = ord($bytes[$i]) & 0xff;
|
|
||||||
|
|
||||||
if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chooses the best mask pattern for a matrix.
|
|
||||||
*/
|
|
||||||
private static function chooseMaskPattern(
|
|
||||||
BitArray $bits,
|
|
||||||
ErrorCorrectionLevel $ecLevel,
|
|
||||||
Version $version,
|
|
||||||
ByteMatrix $matrix
|
|
||||||
) : int {
|
|
||||||
$minPenalty = PHP_INT_MAX;
|
|
||||||
$bestMaskPattern = -1;
|
|
||||||
|
|
||||||
for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) {
|
|
||||||
MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix);
|
|
||||||
$penalty = self::calculateMaskPenalty($matrix);
|
|
||||||
|
|
||||||
if ($penalty < $minPenalty) {
|
|
||||||
$minPenalty = $penalty;
|
|
||||||
$bestMaskPattern = $maskPattern;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $bestMaskPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chooses the best version for the input.
|
|
||||||
*
|
|
||||||
* @throws WriterException if data is too big
|
|
||||||
*/
|
|
||||||
private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version
|
|
||||||
{
|
|
||||||
for ($versionNum = 1; $versionNum <= 40; ++$versionNum) {
|
|
||||||
$version = Version::getVersionForNumber($versionNum);
|
|
||||||
$numBytes = $version->getTotalCodewords();
|
|
||||||
|
|
||||||
$ecBlocks = $version->getEcBlocksForLevel($ecLevel);
|
|
||||||
$numEcBytes = $ecBlocks->getTotalEcCodewords();
|
|
||||||
|
|
||||||
$numDataBytes = $numBytes - $numEcBytes;
|
|
||||||
$totalInputBytes = intdiv($numInputBits + 8, 8);
|
|
||||||
|
|
||||||
if ($numDataBytes >= $totalInputBytes) {
|
|
||||||
return $version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new WriterException('Data too big');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminates the bits in a bit array.
|
|
||||||
*
|
|
||||||
* @throws WriterException if data bits cannot fit in the QR code
|
|
||||||
* @throws WriterException if bits size does not equal the capacity
|
|
||||||
*/
|
|
||||||
private static function terminateBits(int $numDataBytes, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$capacity = $numDataBytes << 3;
|
|
||||||
|
|
||||||
if ($bits->getSize() > $capacity) {
|
|
||||||
throw new WriterException('Data bits cannot fit in the QR code');
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) {
|
|
||||||
$bits->appendBit(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
$numBitsInLastByte = $bits->getSize() & 0x7;
|
|
||||||
|
|
||||||
if ($numBitsInLastByte > 0) {
|
|
||||||
for ($i = $numBitsInLastByte; $i < 8; ++$i) {
|
|
||||||
$bits->appendBit(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$numPaddingBytes = $numDataBytes - $bits->getSizeInBytes();
|
|
||||||
|
|
||||||
for ($i = 0; $i < $numPaddingBytes; ++$i) {
|
|
||||||
$bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($bits->getSize() !== $capacity) {
|
|
||||||
throw new WriterException('Bits size does not equal capacity');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets number of data- and EC bytes for a block ID.
|
|
||||||
*
|
|
||||||
* @return int[]
|
|
||||||
* @throws WriterException if block ID is too large
|
|
||||||
* @throws WriterException if EC bytes mismatch
|
|
||||||
* @throws WriterException if RS blocks mismatch
|
|
||||||
* @throws WriterException if total bytes mismatch
|
|
||||||
*/
|
|
||||||
private static function getNumDataBytesAndNumEcBytesForBlockId(
|
|
||||||
int $numTotalBytes,
|
|
||||||
int $numDataBytes,
|
|
||||||
int $numRsBlocks,
|
|
||||||
int $blockId
|
|
||||||
) : array {
|
|
||||||
if ($blockId >= $numRsBlocks) {
|
|
||||||
throw new WriterException('Block ID too large');
|
|
||||||
}
|
|
||||||
|
|
||||||
$numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks;
|
|
||||||
$numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2;
|
|
||||||
$numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks);
|
|
||||||
$numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1;
|
|
||||||
$numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks);
|
|
||||||
$numDataBytesInGroup2 = $numDataBytesInGroup1 + 1;
|
|
||||||
$numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1;
|
|
||||||
$numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2;
|
|
||||||
|
|
||||||
if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) {
|
|
||||||
throw new WriterException('EC bytes mismatch');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) {
|
|
||||||
throw new WriterException('RS blocks mismatch');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($numTotalBytes !==
|
|
||||||
(($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1)
|
|
||||||
+ (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2)
|
|
||||||
) {
|
|
||||||
throw new WriterException('Total bytes mismatch');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($blockId < $numRsBlocksInGroup1) {
|
|
||||||
return [$numDataBytesInGroup1, $numEcBytesInGroup1];
|
|
||||||
} else {
|
|
||||||
return [$numDataBytesInGroup2, $numEcBytesInGroup2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interleaves data with EC bytes.
|
|
||||||
*
|
|
||||||
* @throws WriterException if number of bits and data bytes does not match
|
|
||||||
* @throws WriterException if data bytes does not match offset
|
|
||||||
* @throws WriterException if an interleaving error occurs
|
|
||||||
*/
|
|
||||||
private static function interleaveWithEcBytes(
|
|
||||||
BitArray $bits,
|
|
||||||
int $numTotalBytes,
|
|
||||||
int $numDataBytes,
|
|
||||||
int $numRsBlocks
|
|
||||||
) : BitArray {
|
|
||||||
if ($bits->getSizeInBytes() !== $numDataBytes) {
|
|
||||||
throw new WriterException('Number of bits and data bytes does not match');
|
|
||||||
}
|
|
||||||
|
|
||||||
$dataBytesOffset = 0;
|
|
||||||
$maxNumDataBytes = 0;
|
|
||||||
$maxNumEcBytes = 0;
|
|
||||||
|
|
||||||
$blocks = new SplFixedArray($numRsBlocks);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $numRsBlocks; ++$i) {
|
|
||||||
list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId(
|
|
||||||
$numTotalBytes,
|
|
||||||
$numDataBytes,
|
|
||||||
$numRsBlocks,
|
|
||||||
$i
|
|
||||||
);
|
|
||||||
|
|
||||||
$size = $numDataBytesInBlock;
|
|
||||||
$dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size);
|
|
||||||
$ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock);
|
|
||||||
$blocks[$i] = new BlockPair($dataBytes, $ecBytes);
|
|
||||||
|
|
||||||
$maxNumDataBytes = max($maxNumDataBytes, $size);
|
|
||||||
$maxNumEcBytes = max($maxNumEcBytes, count($ecBytes));
|
|
||||||
$dataBytesOffset += $numDataBytesInBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($numDataBytes !== $dataBytesOffset) {
|
|
||||||
throw new WriterException('Data bytes does not match offset');
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = new BitArray();
|
|
||||||
|
|
||||||
for ($i = 0; $i < $maxNumDataBytes; ++$i) {
|
|
||||||
foreach ($blocks as $block) {
|
|
||||||
$dataBytes = $block->getDataBytes();
|
|
||||||
|
|
||||||
if ($i < count($dataBytes)) {
|
|
||||||
$result->appendBits($dataBytes[$i], 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($i = 0; $i < $maxNumEcBytes; ++$i) {
|
|
||||||
foreach ($blocks as $block) {
|
|
||||||
$ecBytes = $block->getErrorCorrectionBytes();
|
|
||||||
|
|
||||||
if ($i < count($ecBytes)) {
|
|
||||||
$result->appendBits($ecBytes[$i], 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($numTotalBytes !== $result->getSizeInBytes()) {
|
|
||||||
throw new WriterException(
|
|
||||||
'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates EC bytes for given data.
|
|
||||||
*
|
|
||||||
* @param SplFixedArray<int> $dataBytes
|
|
||||||
* @return SplFixedArray<int>
|
|
||||||
*/
|
|
||||||
private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray
|
|
||||||
{
|
|
||||||
$numDataBytes = count($dataBytes);
|
|
||||||
$toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $numDataBytes; $i++) {
|
|
||||||
$toEncode[$i] = $dataBytes[$i] & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ecBytes = new SplFixedArray($numEcBytesInBlock);
|
|
||||||
$codec = self::getCodec($numDataBytes, $numEcBytesInBlock);
|
|
||||||
$codec->encode($toEncode, $ecBytes);
|
|
||||||
|
|
||||||
return $ecBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an RS codec and caches it.
|
|
||||||
*/
|
|
||||||
private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec
|
|
||||||
{
|
|
||||||
$cacheId = $numDataBytes . '-' . $numEcBytesInBlock;
|
|
||||||
|
|
||||||
if (isset(self::$codecs[$cacheId])) {
|
|
||||||
return self::$codecs[$cacheId];
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$codecs[$cacheId] = new ReedSolomonCodec(
|
|
||||||
8,
|
|
||||||
0x11d,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
$numEcBytesInBlock,
|
|
||||||
255 - $numDataBytes - $numEcBytesInBlock
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends mode information to a bit array.
|
|
||||||
*/
|
|
||||||
private static function appendModeInfo(Mode $mode, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$bits->appendBits($mode->getBits(), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends length information to a bit array.
|
|
||||||
*
|
|
||||||
* @throws WriterException if num letters is bigger than expected
|
|
||||||
*/
|
|
||||||
private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$numBits = $mode->getCharacterCountBits($version);
|
|
||||||
|
|
||||||
if ($numLetters >= (1 << $numBits)) {
|
|
||||||
throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
$bits->appendBits($numLetters, $numBits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends bytes to a bit array in a specific mode.
|
|
||||||
*
|
|
||||||
* @throws WriterException if an invalid mode was supplied
|
|
||||||
*/
|
|
||||||
private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void
|
|
||||||
{
|
|
||||||
switch ($mode) {
|
|
||||||
case Mode::NUMERIC():
|
|
||||||
self::appendNumericBytes($content, $bits);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Mode::ALPHANUMERIC():
|
|
||||||
self::appendAlphanumericBytes($content, $bits);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Mode::BYTE():
|
|
||||||
self::append8BitBytes($content, $bits, $encoding);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Mode::KANJI():
|
|
||||||
self::appendKanjiBytes($content, $bits);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new WriterException('Invalid mode: ' . $mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends numeric bytes to a bit array.
|
|
||||||
*/
|
|
||||||
private static function appendNumericBytes(string $content, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$length = strlen($content);
|
|
||||||
$i = 0;
|
|
||||||
|
|
||||||
while ($i < $length) {
|
|
||||||
$num1 = (int) $content[$i];
|
|
||||||
|
|
||||||
if ($i + 2 < $length) {
|
|
||||||
// Encode three numeric letters in ten bits.
|
|
||||||
$num2 = (int) $content[$i + 1];
|
|
||||||
$num3 = (int) $content[$i + 2];
|
|
||||||
$bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10);
|
|
||||||
$i += 3;
|
|
||||||
} elseif ($i + 1 < $length) {
|
|
||||||
// Encode two numeric letters in seven bits.
|
|
||||||
$num2 = (int) $content[$i + 1];
|
|
||||||
$bits->appendBits($num1 * 10 + $num2, 7);
|
|
||||||
$i += 2;
|
|
||||||
} else {
|
|
||||||
// Encode one numeric letter in four bits.
|
|
||||||
$bits->appendBits($num1, 4);
|
|
||||||
++$i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends alpha-numeric bytes to a bit array.
|
|
||||||
*
|
|
||||||
* @throws WriterException if an invalid alphanumeric code was found
|
|
||||||
*/
|
|
||||||
private static function appendAlphanumericBytes(string $content, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$length = strlen($content);
|
|
||||||
$i = 0;
|
|
||||||
|
|
||||||
while ($i < $length) {
|
|
||||||
$code1 = self::getAlphanumericCode(ord($content[$i]));
|
|
||||||
|
|
||||||
if (-1 === $code1) {
|
|
||||||
throw new WriterException('Invalid alphanumeric code');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($i + 1 < $length) {
|
|
||||||
$code2 = self::getAlphanumericCode(ord($content[$i + 1]));
|
|
||||||
|
|
||||||
if (-1 === $code2) {
|
|
||||||
throw new WriterException('Invalid alphanumeric code');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode two alphanumeric letters in 11 bits.
|
|
||||||
$bits->appendBits($code1 * 45 + $code2, 11);
|
|
||||||
$i += 2;
|
|
||||||
} else {
|
|
||||||
// Encode one alphanumeric letter in six bits.
|
|
||||||
$bits->appendBits($code1, 6);
|
|
||||||
++$i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends regular 8-bit bytes to a bit array.
|
|
||||||
*
|
|
||||||
* @throws WriterException if content cannot be encoded to target encoding
|
|
||||||
*/
|
|
||||||
private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void
|
|
||||||
{
|
|
||||||
$bytes = @iconv('utf-8', $encoding, $content);
|
|
||||||
|
|
||||||
if (false === $bytes) {
|
|
||||||
throw new WriterException('Could not encode content to ' . $encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
$length = strlen($bytes);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $length; $i++) {
|
|
||||||
$bits->appendBits(ord($bytes[$i]), 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends KANJI bytes to a bit array.
|
|
||||||
*
|
|
||||||
* @throws WriterException if content does not seem to be encoded in SHIFT-JIS
|
|
||||||
* @throws WriterException if an invalid byte sequence occurs
|
|
||||||
*/
|
|
||||||
private static function appendKanjiBytes(string $content, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$bytes = @iconv('utf-8', 'SHIFT-JIS', $content);
|
|
||||||
|
|
||||||
if (false === $bytes) {
|
|
||||||
throw new WriterException('Content could not be converted to SHIFT-JIS');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen($bytes) % 2 > 0) {
|
|
||||||
// We just do a simple length check here. The for loop will check
|
|
||||||
// individual characters.
|
|
||||||
throw new WriterException('Content does not seem to be encoded in SHIFT-JIS');
|
|
||||||
}
|
|
||||||
|
|
||||||
$length = strlen($bytes);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $length; $i += 2) {
|
|
||||||
$byte1 = ord($bytes[$i]) & 0xff;
|
|
||||||
$byte2 = ord($bytes[$i + 1]) & 0xff;
|
|
||||||
$code = ($byte1 << 8) | $byte2;
|
|
||||||
|
|
||||||
if ($code >= 0x8140 && $code <= 0x9ffc) {
|
|
||||||
$subtracted = $code - 0x8140;
|
|
||||||
} elseif ($code >= 0xe040 && $code <= 0xebbf) {
|
|
||||||
$subtracted = $code - 0xc140;
|
|
||||||
} else {
|
|
||||||
throw new WriterException('Invalid byte sequence');
|
|
||||||
}
|
|
||||||
|
|
||||||
$encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff);
|
|
||||||
|
|
||||||
$bits->appendBits($encoded, 13);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends ECI information to a bit array.
|
|
||||||
*/
|
|
||||||
private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$mode = Mode::ECI();
|
|
||||||
$bits->appendBits($mode->getBits(), 4);
|
|
||||||
$bits->appendBits($eci->getValue(), 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-271
@@ -1,271 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Encoder;
|
|
||||||
|
|
||||||
use BaconQrCode\Common\BitUtils;
|
|
||||||
use BaconQrCode\Exception\InvalidArgumentException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mask utility.
|
|
||||||
*/
|
|
||||||
final class MaskUtil
|
|
||||||
{
|
|
||||||
/**#@+
|
|
||||||
* Penalty weights from section 6.8.2.1
|
|
||||||
*/
|
|
||||||
public const N1 = 3;
|
|
||||||
public const N2 = 3;
|
|
||||||
public const N3 = 40;
|
|
||||||
public const N4 = 10;
|
|
||||||
/**#@-*/
|
|
||||||
|
|
||||||
private function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies mask penalty rule 1 and returns the penalty.
|
|
||||||
*
|
|
||||||
* Finds repetitive cells with the same color and gives penalty to them.
|
|
||||||
* Example: 00000 or 11111.
|
|
||||||
*/
|
|
||||||
public static function applyMaskPenaltyRule1(ByteMatrix $matrix) : int
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
self::applyMaskPenaltyRule1Internal($matrix, true)
|
|
||||||
+ self::applyMaskPenaltyRule1Internal($matrix, false)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies mask penalty rule 2 and returns the penalty.
|
|
||||||
*
|
|
||||||
* Finds 2x2 blocks with the same color and gives penalty to them. This is
|
|
||||||
* actually equivalent to the spec's rule, which is to find MxN blocks and
|
|
||||||
* give a penalty proportional to (M-1)x(N-1), because this is the number of
|
|
||||||
* 2x2 blocks inside such a block.
|
|
||||||
*/
|
|
||||||
public static function applyMaskPenaltyRule2(ByteMatrix $matrix) : int
|
|
||||||
{
|
|
||||||
$penalty = 0;
|
|
||||||
$array = $matrix->getArray();
|
|
||||||
$width = $matrix->getWidth();
|
|
||||||
$height = $matrix->getHeight();
|
|
||||||
|
|
||||||
for ($y = 0; $y < $height - 1; ++$y) {
|
|
||||||
for ($x = 0; $x < $width - 1; ++$x) {
|
|
||||||
$value = $array[$y][$x];
|
|
||||||
|
|
||||||
if ($value === $array[$y][$x + 1]
|
|
||||||
&& $value === $array[$y + 1][$x]
|
|
||||||
&& $value === $array[$y + 1][$x + 1]
|
|
||||||
) {
|
|
||||||
++$penalty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::N2 * $penalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies mask penalty rule 3 and returns the penalty.
|
|
||||||
*
|
|
||||||
* Finds consecutive cells of 00001011101 or 10111010000, and gives penalty
|
|
||||||
* to them. If we find patterns like 000010111010000, we give penalties
|
|
||||||
* twice (i.e. 40 * 2).
|
|
||||||
*/
|
|
||||||
public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int
|
|
||||||
{
|
|
||||||
$penalty = 0;
|
|
||||||
$array = $matrix->getArray();
|
|
||||||
$width = $matrix->getWidth();
|
|
||||||
$height = $matrix->getHeight();
|
|
||||||
|
|
||||||
for ($y = 0; $y < $height; ++$y) {
|
|
||||||
for ($x = 0; $x < $width; ++$x) {
|
|
||||||
if ($x + 6 < $width
|
|
||||||
&& 1 === $array[$y][$x]
|
|
||||||
&& 0 === $array[$y][$x + 1]
|
|
||||||
&& 1 === $array[$y][$x + 2]
|
|
||||||
&& 1 === $array[$y][$x + 3]
|
|
||||||
&& 1 === $array[$y][$x + 4]
|
|
||||||
&& 0 === $array[$y][$x + 5]
|
|
||||||
&& 1 === $array[$y][$x + 6]
|
|
||||||
&& (
|
|
||||||
(
|
|
||||||
$x + 10 < $width
|
|
||||||
&& 0 === $array[$y][$x + 7]
|
|
||||||
&& 0 === $array[$y][$x + 8]
|
|
||||||
&& 0 === $array[$y][$x + 9]
|
|
||||||
&& 0 === $array[$y][$x + 10]
|
|
||||||
)
|
|
||||||
|| (
|
|
||||||
$x - 4 >= 0
|
|
||||||
&& 0 === $array[$y][$x - 1]
|
|
||||||
&& 0 === $array[$y][$x - 2]
|
|
||||||
&& 0 === $array[$y][$x - 3]
|
|
||||||
&& 0 === $array[$y][$x - 4]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
$penalty += self::N3;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($y + 6 < $height
|
|
||||||
&& 1 === $array[$y][$x]
|
|
||||||
&& 0 === $array[$y + 1][$x]
|
|
||||||
&& 1 === $array[$y + 2][$x]
|
|
||||||
&& 1 === $array[$y + 3][$x]
|
|
||||||
&& 1 === $array[$y + 4][$x]
|
|
||||||
&& 0 === $array[$y + 5][$x]
|
|
||||||
&& 1 === $array[$y + 6][$x]
|
|
||||||
&& (
|
|
||||||
(
|
|
||||||
$y + 10 < $height
|
|
||||||
&& 0 === $array[$y + 7][$x]
|
|
||||||
&& 0 === $array[$y + 8][$x]
|
|
||||||
&& 0 === $array[$y + 9][$x]
|
|
||||||
&& 0 === $array[$y + 10][$x]
|
|
||||||
)
|
|
||||||
|| (
|
|
||||||
$y - 4 >= 0
|
|
||||||
&& 0 === $array[$y - 1][$x]
|
|
||||||
&& 0 === $array[$y - 2][$x]
|
|
||||||
&& 0 === $array[$y - 3][$x]
|
|
||||||
&& 0 === $array[$y - 4][$x]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
$penalty += self::N3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $penalty;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies mask penalty rule 4 and returns the penalty.
|
|
||||||
*
|
|
||||||
* Calculates the ratio of dark cells and gives penalty if the ratio is far
|
|
||||||
* from 50%. It gives 10 penalty for 5% distance.
|
|
||||||
*/
|
|
||||||
public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int
|
|
||||||
{
|
|
||||||
$numDarkCells = 0;
|
|
||||||
|
|
||||||
$array = $matrix->getArray();
|
|
||||||
$width = $matrix->getWidth();
|
|
||||||
$height = $matrix->getHeight();
|
|
||||||
|
|
||||||
for ($y = 0; $y < $height; ++$y) {
|
|
||||||
$arrayY = $array[$y];
|
|
||||||
|
|
||||||
for ($x = 0; $x < $width; ++$x) {
|
|
||||||
if (1 === $arrayY[$x]) {
|
|
||||||
++$numDarkCells;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$numTotalCells = $height * $width;
|
|
||||||
$darkRatio = $numDarkCells / $numTotalCells;
|
|
||||||
$fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20);
|
|
||||||
|
|
||||||
return $fixedPercentVariances * self::N4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the mask bit for "getMaskPattern" at "x" and "y".
|
|
||||||
*
|
|
||||||
* See 8.8 of JISX0510:2004 for mask pattern conditions.
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException if an invalid mask pattern was supplied
|
|
||||||
*/
|
|
||||||
public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool
|
|
||||||
{
|
|
||||||
switch ($maskPattern) {
|
|
||||||
case 0:
|
|
||||||
$intermediate = ($y + $x) & 0x1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
$intermediate = $y & 0x1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
$intermediate = $x % 3;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
$intermediate = ($y + $x) % 3;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
$intermediate = (BitUtils::unsignedRightShift($y, 1) + (int) ($x / 3)) & 0x1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
$temp = $y * $x;
|
|
||||||
$intermediate = ($temp & 0x1) + ($temp % 3);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
$temp = $y * $x;
|
|
||||||
$intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 7:
|
|
||||||
$temp = $y * $x;
|
|
||||||
$intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0 == $intermediate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function for applyMaskPenaltyRule1.
|
|
||||||
*
|
|
||||||
* We need this for doing this calculation in both vertical and horizontal
|
|
||||||
* orders respectively.
|
|
||||||
*/
|
|
||||||
private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int
|
|
||||||
{
|
|
||||||
$penalty = 0;
|
|
||||||
$iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth();
|
|
||||||
$jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight();
|
|
||||||
$array = $matrix->getArray();
|
|
||||||
|
|
||||||
for ($i = 0; $i < $iLimit; ++$i) {
|
|
||||||
$numSameBitCells = 0;
|
|
||||||
$prevBit = -1;
|
|
||||||
|
|
||||||
for ($j = 0; $j < $jLimit; $j++) {
|
|
||||||
$bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i];
|
|
||||||
|
|
||||||
if ($bit === $prevBit) {
|
|
||||||
++$numSameBitCells;
|
|
||||||
} else {
|
|
||||||
if ($numSameBitCells >= 5) {
|
|
||||||
$penalty += self::N1 + ($numSameBitCells - 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
$numSameBitCells = 1;
|
|
||||||
$prevBit = $bit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($numSameBitCells >= 5) {
|
|
||||||
$penalty += self::N1 + ($numSameBitCells - 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $penalty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Encoder;
|
|
||||||
|
|
||||||
use BaconQrCode\Common\BitArray;
|
|
||||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
|
||||||
use BaconQrCode\Common\Version;
|
|
||||||
use BaconQrCode\Exception\RuntimeException;
|
|
||||||
use BaconQrCode\Exception\WriterException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matrix utility.
|
|
||||||
*/
|
|
||||||
final class MatrixUtil
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Position detection pattern.
|
|
||||||
*/
|
|
||||||
private const POSITION_DETECTION_PATTERN = [
|
|
||||||
[1, 1, 1, 1, 1, 1, 1],
|
|
||||||
[1, 0, 0, 0, 0, 0, 1],
|
|
||||||
[1, 0, 1, 1, 1, 0, 1],
|
|
||||||
[1, 0, 1, 1, 1, 0, 1],
|
|
||||||
[1, 0, 1, 1, 1, 0, 1],
|
|
||||||
[1, 0, 0, 0, 0, 0, 1],
|
|
||||||
[1, 1, 1, 1, 1, 1, 1],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Position adjustment pattern.
|
|
||||||
*/
|
|
||||||
private const POSITION_ADJUSTMENT_PATTERN = [
|
|
||||||
[1, 1, 1, 1, 1],
|
|
||||||
[1, 0, 0, 0, 1],
|
|
||||||
[1, 0, 1, 0, 1],
|
|
||||||
[1, 0, 0, 0, 1],
|
|
||||||
[1, 1, 1, 1, 1],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coordinates for position adjustment patterns for each version.
|
|
||||||
*/
|
|
||||||
private const POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = [
|
|
||||||
[null, null, null, null, null, null, null], // Version 1
|
|
||||||
[ 6, 18, null, null, null, null, null], // Version 2
|
|
||||||
[ 6, 22, null, null, null, null, null], // Version 3
|
|
||||||
[ 6, 26, null, null, null, null, null], // Version 4
|
|
||||||
[ 6, 30, null, null, null, null, null], // Version 5
|
|
||||||
[ 6, 34, null, null, null, null, null], // Version 6
|
|
||||||
[ 6, 22, 38, null, null, null, null], // Version 7
|
|
||||||
[ 6, 24, 42, null, null, null, null], // Version 8
|
|
||||||
[ 6, 26, 46, null, null, null, null], // Version 9
|
|
||||||
[ 6, 28, 50, null, null, null, null], // Version 10
|
|
||||||
[ 6, 30, 54, null, null, null, null], // Version 11
|
|
||||||
[ 6, 32, 58, null, null, null, null], // Version 12
|
|
||||||
[ 6, 34, 62, null, null, null, null], // Version 13
|
|
||||||
[ 6, 26, 46, 66, null, null, null], // Version 14
|
|
||||||
[ 6, 26, 48, 70, null, null, null], // Version 15
|
|
||||||
[ 6, 26, 50, 74, null, null, null], // Version 16
|
|
||||||
[ 6, 30, 54, 78, null, null, null], // Version 17
|
|
||||||
[ 6, 30, 56, 82, null, null, null], // Version 18
|
|
||||||
[ 6, 30, 58, 86, null, null, null], // Version 19
|
|
||||||
[ 6, 34, 62, 90, null, null, null], // Version 20
|
|
||||||
[ 6, 28, 50, 72, 94, null, null], // Version 21
|
|
||||||
[ 6, 26, 50, 74, 98, null, null], // Version 22
|
|
||||||
[ 6, 30, 54, 78, 102, null, null], // Version 23
|
|
||||||
[ 6, 28, 54, 80, 106, null, null], // Version 24
|
|
||||||
[ 6, 32, 58, 84, 110, null, null], // Version 25
|
|
||||||
[ 6, 30, 58, 86, 114, null, null], // Version 26
|
|
||||||
[ 6, 34, 62, 90, 118, null, null], // Version 27
|
|
||||||
[ 6, 26, 50, 74, 98, 122, null], // Version 28
|
|
||||||
[ 6, 30, 54, 78, 102, 126, null], // Version 29
|
|
||||||
[ 6, 26, 52, 78, 104, 130, null], // Version 30
|
|
||||||
[ 6, 30, 56, 82, 108, 134, null], // Version 31
|
|
||||||
[ 6, 34, 60, 86, 112, 138, null], // Version 32
|
|
||||||
[ 6, 30, 58, 86, 114, 142, null], // Version 33
|
|
||||||
[ 6, 34, 62, 90, 118, 146, null], // Version 34
|
|
||||||
[ 6, 30, 54, 78, 102, 126, 150], // Version 35
|
|
||||||
[ 6, 24, 50, 76, 102, 128, 154], // Version 36
|
|
||||||
[ 6, 28, 54, 80, 106, 132, 158], // Version 37
|
|
||||||
[ 6, 32, 58, 84, 110, 136, 162], // Version 38
|
|
||||||
[ 6, 26, 54, 82, 110, 138, 166], // Version 39
|
|
||||||
[ 6, 30, 58, 86, 114, 142, 170], // Version 40
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type information coordinates.
|
|
||||||
*/
|
|
||||||
private const TYPE_INFO_COORDINATES = [
|
|
||||||
[8, 0],
|
|
||||||
[8, 1],
|
|
||||||
[8, 2],
|
|
||||||
[8, 3],
|
|
||||||
[8, 4],
|
|
||||||
[8, 5],
|
|
||||||
[8, 7],
|
|
||||||
[8, 8],
|
|
||||||
[7, 8],
|
|
||||||
[5, 8],
|
|
||||||
[4, 8],
|
|
||||||
[3, 8],
|
|
||||||
[2, 8],
|
|
||||||
[1, 8],
|
|
||||||
[0, 8],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version information polynomial.
|
|
||||||
*/
|
|
||||||
private const VERSION_INFO_POLY = 0x1f25;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type information polynomial.
|
|
||||||
*/
|
|
||||||
private const TYPE_INFO_POLY = 0x537;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type information mask pattern.
|
|
||||||
*/
|
|
||||||
private const TYPE_INFO_MASK_PATTERN = 0x5412;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears a given matrix.
|
|
||||||
*/
|
|
||||||
public static function clearMatrix(ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
$matrix->clear(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a complete matrix.
|
|
||||||
*/
|
|
||||||
public static function buildMatrix(
|
|
||||||
BitArray $dataBits,
|
|
||||||
ErrorCorrectionLevel $level,
|
|
||||||
Version $version,
|
|
||||||
int $maskPattern,
|
|
||||||
ByteMatrix $matrix
|
|
||||||
) : void {
|
|
||||||
self::clearMatrix($matrix);
|
|
||||||
self::embedBasicPatterns($version, $matrix);
|
|
||||||
self::embedTypeInfo($level, $maskPattern, $matrix);
|
|
||||||
self::maybeEmbedVersionInfo($version, $matrix);
|
|
||||||
self::embedDataBits($dataBits, $maskPattern, $matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the position detection patterns from a matrix.
|
|
||||||
*
|
|
||||||
* This can be useful if you need to render those patterns separately.
|
|
||||||
*/
|
|
||||||
public static function removePositionDetectionPatterns(ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
$pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);
|
|
||||||
|
|
||||||
self::removePositionDetectionPattern(0, 0, $matrix);
|
|
||||||
self::removePositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
|
|
||||||
self::removePositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds type information into a matrix.
|
|
||||||
*/
|
|
||||||
private static function embedTypeInfo(ErrorCorrectionLevel $level, int $maskPattern, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
$typeInfoBits = new BitArray();
|
|
||||||
self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits);
|
|
||||||
|
|
||||||
$typeInfoBitsSize = $typeInfoBits->getSize();
|
|
||||||
|
|
||||||
for ($i = 0; $i < $typeInfoBitsSize; ++$i) {
|
|
||||||
$bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i);
|
|
||||||
|
|
||||||
$x1 = self::TYPE_INFO_COORDINATES[$i][0];
|
|
||||||
$y1 = self::TYPE_INFO_COORDINATES[$i][1];
|
|
||||||
|
|
||||||
$matrix->set($x1, $y1, (int) $bit);
|
|
||||||
|
|
||||||
if ($i < 8) {
|
|
||||||
$x2 = $matrix->getWidth() - $i - 1;
|
|
||||||
$y2 = 8;
|
|
||||||
} else {
|
|
||||||
$x2 = 8;
|
|
||||||
$y2 = $matrix->getHeight() - 7 + ($i - 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
$matrix->set($x2, $y2, (int) $bit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates type information bits and appends them to a bit array.
|
|
||||||
*
|
|
||||||
* @throws RuntimeException if bit array resulted in invalid size
|
|
||||||
*/
|
|
||||||
private static function makeTypeInfoBits(ErrorCorrectionLevel $level, int $maskPattern, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$typeInfo = ($level->getBits() << 3) | $maskPattern;
|
|
||||||
$bits->appendBits($typeInfo, 5);
|
|
||||||
|
|
||||||
$bchCode = self::calculateBchCode($typeInfo, self::TYPE_INFO_POLY);
|
|
||||||
$bits->appendBits($bchCode, 10);
|
|
||||||
|
|
||||||
$maskBits = new BitArray();
|
|
||||||
$maskBits->appendBits(self::TYPE_INFO_MASK_PATTERN, 15);
|
|
||||||
$bits->xorBits($maskBits);
|
|
||||||
|
|
||||||
if (15 !== $bits->getSize()) {
|
|
||||||
throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds version information if required.
|
|
||||||
*/
|
|
||||||
private static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
if ($version->getVersionNumber() < 7) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$versionInfoBits = new BitArray();
|
|
||||||
self::makeVersionInfoBits($version, $versionInfoBits);
|
|
||||||
|
|
||||||
$bitIndex = 6 * 3 - 1;
|
|
||||||
|
|
||||||
for ($i = 0; $i < 6; ++$i) {
|
|
||||||
for ($j = 0; $j < 3; ++$j) {
|
|
||||||
$bit = $versionInfoBits->get($bitIndex);
|
|
||||||
--$bitIndex;
|
|
||||||
|
|
||||||
$matrix->set($i, $matrix->getHeight() - 11 + $j, (int) $bit);
|
|
||||||
$matrix->set($matrix->getHeight() - 11 + $j, $i, (int) $bit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates version information bits and appends them to a bit array.
|
|
||||||
*
|
|
||||||
* @throws RuntimeException if bit array resulted in invalid size
|
|
||||||
*/
|
|
||||||
private static function makeVersionInfoBits(Version $version, BitArray $bits) : void
|
|
||||||
{
|
|
||||||
$bits->appendBits($version->getVersionNumber(), 6);
|
|
||||||
|
|
||||||
$bchCode = self::calculateBchCode($version->getVersionNumber(), self::VERSION_INFO_POLY);
|
|
||||||
$bits->appendBits($bchCode, 12);
|
|
||||||
|
|
||||||
if (18 !== $bits->getSize()) {
|
|
||||||
throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the BCH code for a value and a polynomial.
|
|
||||||
*/
|
|
||||||
private static function calculateBchCode(int $value, int $poly) : int
|
|
||||||
{
|
|
||||||
$msbSetInPoly = self::findMsbSet($poly);
|
|
||||||
$value <<= $msbSetInPoly - 1;
|
|
||||||
|
|
||||||
while (self::findMsbSet($value) >= $msbSetInPoly) {
|
|
||||||
$value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds and MSB set.
|
|
||||||
*/
|
|
||||||
private static function findMsbSet(int $value) : int
|
|
||||||
{
|
|
||||||
$numDigits = 0;
|
|
||||||
|
|
||||||
while (0 !== $value) {
|
|
||||||
$value >>= 1;
|
|
||||||
++$numDigits;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $numDigits;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds basic patterns into a matrix.
|
|
||||||
*/
|
|
||||||
private static function embedBasicPatterns(Version $version, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
self::embedPositionDetectionPatternsAndSeparators($matrix);
|
|
||||||
self::embedDarkDotAtLeftBottomCorner($matrix);
|
|
||||||
self::maybeEmbedPositionAdjustmentPatterns($version, $matrix);
|
|
||||||
self::embedTimingPatterns($matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds position detection patterns and separators into a byte matrix.
|
|
||||||
*/
|
|
||||||
private static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
$pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);
|
|
||||||
|
|
||||||
self::embedPositionDetectionPattern(0, 0, $matrix);
|
|
||||||
self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
|
|
||||||
self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
|
|
||||||
|
|
||||||
$hspWidth = 8;
|
|
||||||
|
|
||||||
self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix);
|
|
||||||
self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix);
|
|
||||||
self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix);
|
|
||||||
|
|
||||||
$vspSize = 7;
|
|
||||||
|
|
||||||
self::embedVerticalSeparationPattern($vspSize, 0, $matrix);
|
|
||||||
self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix);
|
|
||||||
self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds a single position detection pattern into a byte matrix.
|
|
||||||
*/
|
|
||||||
private static function embedPositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
for ($y = 0; $y < 7; ++$y) {
|
|
||||||
for ($x = 0; $x < 7; ++$x) {
|
|
||||||
$matrix->set($xStart + $x, $yStart + $y, self::POSITION_DETECTION_PATTERN[$y][$x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function removePositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
for ($y = 0; $y < 7; ++$y) {
|
|
||||||
for ($x = 0; $x < 7; ++$x) {
|
|
||||||
$matrix->set($xStart + $x, $yStart + $y, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds a single horizontal separation pattern.
|
|
||||||
*
|
|
||||||
* @throws RuntimeException if a byte was already set
|
|
||||||
*/
|
|
||||||
private static function embedHorizontalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
for ($x = 0; $x < 8; $x++) {
|
|
||||||
if (-1 !== $matrix->get($xStart + $x, $yStart)) {
|
|
||||||
throw new RuntimeException('Byte already set');
|
|
||||||
}
|
|
||||||
|
|
||||||
$matrix->set($xStart + $x, $yStart, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds a single vertical separation pattern.
|
|
||||||
*
|
|
||||||
* @throws RuntimeException if a byte was already set
|
|
||||||
*/
|
|
||||||
private static function embedVerticalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
for ($y = 0; $y < 7; $y++) {
|
|
||||||
if (-1 !== $matrix->get($xStart, $yStart + $y)) {
|
|
||||||
throw new RuntimeException('Byte already set');
|
|
||||||
}
|
|
||||||
|
|
||||||
$matrix->set($xStart, $yStart + $y, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds a dot at the left bottom corner.
|
|
||||||
*
|
|
||||||
* @throws RuntimeException if a byte was already set to 0
|
|
||||||
*/
|
|
||||||
private static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
if (0 === $matrix->get(8, $matrix->getHeight() - 8)) {
|
|
||||||
throw new RuntimeException('Byte already set to 0');
|
|
||||||
}
|
|
||||||
|
|
||||||
$matrix->set(8, $matrix->getHeight() - 8, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds position adjustment patterns if required.
|
|
||||||
*/
|
|
||||||
private static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
if ($version->getVersionNumber() < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$index = $version->getVersionNumber() - 1;
|
|
||||||
|
|
||||||
$coordinates = self::POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[$index];
|
|
||||||
$numCoordinates = count($coordinates);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $numCoordinates; ++$i) {
|
|
||||||
for ($j = 0; $j < $numCoordinates; ++$j) {
|
|
||||||
$y = $coordinates[$i];
|
|
||||||
$x = $coordinates[$j];
|
|
||||||
|
|
||||||
if (null === $x || null === $y) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-1 === $matrix->get($x, $y)) {
|
|
||||||
self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds a single position adjustment pattern.
|
|
||||||
*/
|
|
||||||
private static function embedPositionAdjustmentPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
for ($y = 0; $y < 5; $y++) {
|
|
||||||
for ($x = 0; $x < 5; $x++) {
|
|
||||||
$matrix->set($xStart + $x, $yStart + $y, self::POSITION_ADJUSTMENT_PATTERN[$y][$x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds timing patterns into a matrix.
|
|
||||||
*/
|
|
||||||
private static function embedTimingPatterns(ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
$matrixWidth = $matrix->getWidth();
|
|
||||||
|
|
||||||
for ($i = 8; $i < $matrixWidth - 8; ++$i) {
|
|
||||||
$bit = ($i + 1) % 2;
|
|
||||||
|
|
||||||
if (-1 === $matrix->get($i, 6)) {
|
|
||||||
$matrix->set($i, 6, $bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-1 === $matrix->get(6, $i)) {
|
|
||||||
$matrix->set(6, $i, $bit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds "dataBits" using "getMaskPattern".
|
|
||||||
*
|
|
||||||
* For debugging purposes, it skips masking process if "getMaskPattern" is -1. See 8.7 of JISX0510:2004 (p.38) for
|
|
||||||
* how to embed data bits.
|
|
||||||
*
|
|
||||||
* @throws WriterException if not all bits could be consumed
|
|
||||||
*/
|
|
||||||
private static function embedDataBits(BitArray $dataBits, int $maskPattern, ByteMatrix $matrix) : void
|
|
||||||
{
|
|
||||||
$bitIndex = 0;
|
|
||||||
$direction = -1;
|
|
||||||
|
|
||||||
// Start from the right bottom cell.
|
|
||||||
$x = $matrix->getWidth() - 1;
|
|
||||||
$y = $matrix->getHeight() - 1;
|
|
||||||
|
|
||||||
while ($x > 0) {
|
|
||||||
// Skip vertical timing pattern.
|
|
||||||
if (6 === $x) {
|
|
||||||
--$x;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ($y >= 0 && $y < $matrix->getHeight()) {
|
|
||||||
for ($i = 0; $i < 2; $i++) {
|
|
||||||
$xx = $x - $i;
|
|
||||||
|
|
||||||
// Skip the cell if it's not empty.
|
|
||||||
if (-1 !== $matrix->get($xx, $y)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($bitIndex < $dataBits->getSize()) {
|
|
||||||
$bit = $dataBits->get($bitIndex);
|
|
||||||
++$bitIndex;
|
|
||||||
} else {
|
|
||||||
// Padding bit. If there is no bit left, we'll fill the
|
|
||||||
// left cells with 0, as described in 8.4.9 of
|
|
||||||
// JISX0510:2004 (p. 24).
|
|
||||||
$bit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip masking if maskPattern is -1.
|
|
||||||
if (-1 !== $maskPattern && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) {
|
|
||||||
$bit = ! $bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$matrix->set($xx, $y, (int) $bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
$y += $direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
$direction = -$direction;
|
|
||||||
$y += $direction;
|
|
||||||
$x -= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All bits should be consumed
|
|
||||||
if ($dataBits->getSize() !== $bitIndex) {
|
|
||||||
throw new WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-108
@@ -1,108 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Encoder;
|
|
||||||
|
|
||||||
use BaconQrCode\Common\ErrorCorrectionLevel;
|
|
||||||
use BaconQrCode\Common\Mode;
|
|
||||||
use BaconQrCode\Common\Version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* QR code.
|
|
||||||
*/
|
|
||||||
final class QrCode
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Number of possible mask patterns.
|
|
||||||
*/
|
|
||||||
public const NUM_MASK_PATTERNS = 8;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mask pattern of the QR code.
|
|
||||||
*/
|
|
||||||
private int $maskPattern = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matrix of the QR code.
|
|
||||||
*/
|
|
||||||
private ByteMatrix $matrix;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
private readonly Mode $mode,
|
|
||||||
private readonly ErrorCorrectionLevel $errorCorrectionLevel,
|
|
||||||
private readonly Version $version,
|
|
||||||
int $maskPattern,
|
|
||||||
ByteMatrix $matrix
|
|
||||||
) {
|
|
||||||
$this->maskPattern = $maskPattern;
|
|
||||||
$this->matrix = $matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the mode.
|
|
||||||
*/
|
|
||||||
public function getMode() : Mode
|
|
||||||
{
|
|
||||||
return $this->mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the EC level.
|
|
||||||
*/
|
|
||||||
public function getErrorCorrectionLevel() : ErrorCorrectionLevel
|
|
||||||
{
|
|
||||||
return $this->errorCorrectionLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the version.
|
|
||||||
*/
|
|
||||||
public function getVersion() : Version
|
|
||||||
{
|
|
||||||
return $this->version;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the mask pattern.
|
|
||||||
*/
|
|
||||||
public function getMaskPattern() : int
|
|
||||||
{
|
|
||||||
return $this->maskPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMatrix(): ByteMatrix
|
|
||||||
{
|
|
||||||
return $this->matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates whether a mask pattern is valid.
|
|
||||||
*/
|
|
||||||
public static function isValidMaskPattern(int $maskPattern) : bool
|
|
||||||
{
|
|
||||||
return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the QR code.
|
|
||||||
*/
|
|
||||||
public function __toString() : string
|
|
||||||
{
|
|
||||||
$result = "<<\n"
|
|
||||||
. ' mode: ' . $this->mode . "\n"
|
|
||||||
. ' ecLevel: ' . $this->errorCorrectionLevel . "\n"
|
|
||||||
. ' version: ' . $this->version . "\n"
|
|
||||||
. ' maskPattern: ' . $this->maskPattern . "\n";
|
|
||||||
|
|
||||||
if ($this->matrix === null) {
|
|
||||||
$result .= " matrix: null\n";
|
|
||||||
} else {
|
|
||||||
$result .= " matrix:\n";
|
|
||||||
$result .= $this->matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result .= ">>\n";
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Exception;
|
|
||||||
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
interface ExceptionInterface extends Throwable
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Exception;
|
|
||||||
|
|
||||||
final class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Exception;
|
|
||||||
|
|
||||||
final class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Exception;
|
|
||||||
|
|
||||||
final class RuntimeException extends \RuntimeException implements ExceptionInterface
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Exception;
|
|
||||||
|
|
||||||
final class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Exception;
|
|
||||||
|
|
||||||
final class WriterException extends \RuntimeException implements ExceptionInterface
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types = 1);
|
|
||||||
|
|
||||||
namespace BaconQrCode\Renderer\Color;
|
|
||||||
|
|
||||||
use BaconQrCode\Exception;
|
|
||||||
|
|
||||||
final class Alpha implements ColorInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param int $alpha the alpha value, 0 to 100
|
|
||||||
*/
|
|
||||||
public function __construct(private readonly int $alpha, private readonly ColorInterface $baseColor)
|
|
||||||
{
|
|
||||||
if ($alpha < 0 || $alpha > 100) {
|
|
||||||
throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAlpha() : int
|
|
||||||
{
|
|
||||||
return $this->alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBaseColor() : ColorInterface
|
|
||||||
{
|
|
||||||
return $this->baseColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toRgb() : Rgb
|
|
||||||
{
|
|
||||||
return $this->baseColor->toRgb();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toCmyk() : Cmyk
|
|
||||||
{
|
|
||||||
return $this->baseColor->toCmyk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toGray() : Gray
|
|
||||||
{
|
|
||||||
return $this->baseColor->toGray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user