diff --git a/assets/css/app.css b/assets/css/app.css index a49b28c5..595dca29 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -832,4 +832,9 @@ div.dataTables_wrapper div.dataTables_info { .darkmode--activated img { mix-blend-mode: difference; +} + +.scanner-video { + max-width: 500px; + max-height: 250px; } \ No newline at end of file diff --git a/assets/ts_src/event_listeners.ts b/assets/ts_src/event_listeners.ts index 25abcbc8..d4deeed8 100644 --- a/assets/ts_src/event_listeners.ts +++ b/assets/ts_src/event_listeners.ts @@ -23,6 +23,7 @@ import "marked"; import * as marked from "marked"; import "qrcode"; import {parse} from "marked"; +import * as ZXing from "@zxing/library"; /************************************ * @@ -568,6 +569,91 @@ $(document).on("ajaxUI:reload", function() { }) }); +//Reuse codereader between multiple requests +const codeReader = new ZXing.BrowserMultiFormatReader(); + +//Init barcode scanner +$(document).on("ajaxUI:start ajaxUI:reload", function() { + + //Skip if we are not on scanner page... + if (!document.getElementById('scan_dialog_form')) { + + codeReader.reset(); + + return; + } + + + let selectedDeviceId; + + + //Save it for later, so we can reset it + console.log('ZXing code reader initialized'); + codeReader.listVideoInputDevices() + .then((videoInputDevices) => { + if (videoInputDevices.length >= 1) { + const sourceSelect = document.getElementById('sourceSelect'); + + + videoInputDevices.forEach((element) => { + const sourceOption = document.createElement('option'); + sourceOption.text = element.label; + sourceOption.value = element.deviceId; + sourceSelect.appendChild(sourceOption); + }); + + //Try to retrieve last selected webcam... + let last_cam_id = localStorage.getItem('scanner_last_cam_id'); + if (!!last_cam_id) { + //selectedDeviceId = localStorage.getItem('scanner_last_cam_id'); + $(sourceSelect).val(last_cam_id); + } else { + selectedDeviceId = videoInputDevices[0].deviceId; + } + + sourceSelect.onchange = () => { + //@ts-ignore + selectedDeviceId = sourceSelect.value; + localStorage.setItem('scanner_last_cam_id', selectedDeviceId); + changeHandler(); + }; + + document.getElementById('sourceSelectPanel').classList.remove('d-none'); + document.getElementById('video').classList.remove('d-none'); + document.getElementById('scanner-warning').classList.add('d-none'); + } + + + let changeHandler = () => { + codeReader.reset(); + codeReader.decodeFromVideoDevice(selectedDeviceId, 'video', (result, err) => { + if (result) { + //@ts-ignore + document.getElementById('scan_dialog_input').value = result.text; + //Submit form + //@ts-ignore + document.getElementById('scan_dialog_form').submit(); + } + if (err && !(err instanceof ZXing.NotFoundException)) { + console.error(err); + //document.getElementById('result').textContent = err + } + }); + console.log(`Started continous decode from camera with id ${selectedDeviceId}`) + }; + + //Register Change Src Button + //document.getElementById('changeSrcBtn').addEventListener('click', changeHandler); + + //Try to start logging automatically. + changeHandler(); + + }) + .catch((err) => { + console.error(err) + }) +}); + //Need for proper body padding, with every navbar height $(window).resize(function () { diff --git a/composer.json b/composer.json index b1854b2f..4cbd0449 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,8 @@ "beberlei/doctrineextensions": "^1.2", "doctrine/annotations": "^1.6", "doctrine/doctrine-bundle": "^2.0", + "dompdf/dompdf": "^0.8.5", + "erusev/parsedown": "^1.7", "florianv/swap": "^4.0", "friendsofsymfony/ckeditor-bundle": "^2.0", "gregwar/captcha-bundle": "^2.1.0", @@ -50,8 +52,10 @@ "symfony/web-link": "4.4.*", "symfony/webpack-encore-bundle": "^1.1", "symfony/yaml": "4.4.*", + "tecnickcom/tc-lib-barcode": "^1.15", "twig/cssinliner-extra": "^3.0", "twig/extra-bundle": "^3.0", + "twig/html-extra": "^3.0", "twig/inky-extra": "^3.0", "twig/intl-extra": "^3.0", "twig/markdown-extra": "^3.0", diff --git a/composer.lock b/composer.lock index f4a111f1..f80038fa 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "aef1b0fadcee2305438721839abb1400", + "content-hash": "08c388456fd013cb56d78205a1d8002c", "packages": [ { "name": "beberlei/assert", @@ -124,16 +124,16 @@ }, { "name": "doctrine/annotations", - "version": "1.10.1", + "version": "1.10.2", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "5eb79f3dbdffed6544e1fc287572c0f462bd29bb" + "reference": "b9d758e831c70751155c698c2f7df4665314a1cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5eb79f3dbdffed6544e1fc287572c0f462bd29bb", - "reference": "5eb79f3dbdffed6544e1fc287572c0f462bd29bb", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/b9d758e831c70751155c698c2f7df4665314a1cb", + "reference": "b9d758e831c70751155c698c2f7df4665314a1cb", "shasum": "" }, "require": { @@ -189,7 +189,7 @@ "docblock", "parser" ], - "time": "2020-04-02T12:33:25+00:00" + "time": "2020-04-20T09:18:32+00:00" }, { "name": "doctrine/cache", @@ -428,16 +428,16 @@ }, { "name": "doctrine/dbal", - "version": "v2.10.1", + "version": "2.10.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8" + "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", - "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/aab745e7b6b2de3b47019da81e7225e14dcfdac8", + "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8", "shasum": "" }, "require": { @@ -449,9 +449,11 @@ "require-dev": { "doctrine/coding-standard": "^6.0", "jetbrains/phpstorm-stubs": "^2019.1", - "phpstan/phpstan": "^0.11.3", + "nikic/php-parser": "^4.4", + "phpstan/phpstan": "^0.12", "phpunit/phpunit": "^8.4.1", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0" + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "vimeo/psalm": "^3.11" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -516,20 +518,20 @@ "sqlserver", "sqlsrv" ], - "time": "2020-01-04T12:56:21+00:00" + "time": "2020-04-20T17:19:26+00:00" }, { "name": "doctrine/doctrine-bundle", - "version": "2.0.7", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "6926771140ee87a823c3b2c72602de9dda4490d3" + "reference": "b0e0deb6e700438401ede433a15a6372d2285202" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/6926771140ee87a823c3b2c72602de9dda4490d3", - "reference": "6926771140ee87a823c3b2c72602de9dda4490d3", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/b0e0deb6e700438401ede433a15a6372d2285202", + "reference": "b0e0deb6e700438401ede433a15a6372d2285202", "shasum": "" }, "require": { @@ -608,7 +610,7 @@ "orm", "persistence" ], - "time": "2020-01-18T11:56:15+00:00" + "time": "2020-04-23T10:52:09+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", @@ -756,33 +758,38 @@ }, { "name": "doctrine/inflector", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" + "reference": "ab5de36233a1995f9c776c741b803eb8207aebef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/ab5de36233a1995f9c776c741b803eb8207aebef", + "reference": "ab5de36233a1995f9c776c741b803eb8207aebef", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.2" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector", + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -811,15 +818,21 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", "keywords": [ "inflection", - "pluralize", - "singularize", - "string" + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" ], - "time": "2019-10-30T19:59:35+00:00" + "time": "2020-05-06T11:01:57+00:00" }, { "name": "doctrine/instantiator", @@ -1266,6 +1279,72 @@ ], "time": "2020-03-27T11:06:43+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v0.8.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "6782abfc090b132134cd6cea0ec6d76f0fce2c56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/6782abfc090b132134cd6cea0ec6d76f0fce2c56", + "reference": "6782abfc090b132134cd6cea0ec6d76f0fce2c56", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "phenx/php-font-lib": "^0.5.1", + "phenx/php-svg-lib": "^0.3.3", + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.7-dev" + } + }, + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + }, + { + "name": "Brian Sweeney", + "email": "eclecticgeek@gmail.com" + }, + { + "name": "Gabriel Bull", + "email": "me@gabrielbull.com" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "time": "2020-02-20T03:52:51+00:00" + }, { "name": "egulias/email-validator", "version": "2.1.17", @@ -1324,6 +1403,52 @@ ], "time": "2020-02-13T22:36:52+00:00" }, + { + "name": "erusev/parsedown", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "time": "2019-12-30T22:54:17+00:00" + }, { "name": "florianv/exchanger", "version": "2.4.0", @@ -1578,16 +1703,16 @@ }, { "name": "gregwar/captcha-bundle", - "version": "v2.1.2", + "version": "v2.1.3", "source": { "type": "git", "url": "https://github.com/Gregwar/CaptchaBundle.git", - "reference": "cdbe566accded9086e6c91af45920505ae871c2f" + "reference": "df6915eb5e23c25f32f2a2ad3ed07b275a0f005f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Gregwar/CaptchaBundle/zipball/cdbe566accded9086e6c91af45920505ae871c2f", - "reference": "cdbe566accded9086e6c91af45920505ae871c2f", + "url": "https://api.github.com/repos/Gregwar/CaptchaBundle/zipball/df6915eb5e23c25f32f2a2ad3ed07b275a0f005f", + "reference": "df6915eb5e23c25f32f2a2ad3ed07b275a0f005f", "shasum": "" }, "require": { @@ -1635,7 +1760,7 @@ "symfony", "visual" ], - "time": "2020-03-02T10:00:37+00:00" + "time": "2020-04-28T08:45:36+00:00" }, { "name": "imagine/imagine", @@ -2092,16 +2217,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.3.0", + "version": "v4.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc" + "reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc", - "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120", + "reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120", "shasum": "" }, "require": { @@ -2140,7 +2265,7 @@ "parser", "php" ], - "time": "2019-11-08T13:50:10+00:00" + "time": "2020-04-10T16:34:50+00:00" }, { "name": "nikolaposa/version", @@ -2623,6 +2748,83 @@ ], "time": "2018-07-02T15:55:56+00:00" }, + { + "name": "phenx/php-font-lib", + "version": "0.5.2", + "source": { + "type": "git", + "url": "https://github.com/PhenX/php-font-lib.git", + "reference": "ca6ad461f032145fff5971b5985e5af9e7fa88d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/ca6ad461f032145fff5971b5985e5af9e7fa88d8", + "reference": "ca6ad461f032145fff5971b5985e5af9e7fa88d8", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5 || ^6 || ^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/PhenX/php-font-lib", + "time": "2020-03-08T15:31:32+00:00" + }, + { + "name": "phenx/php-svg-lib", + "version": "v0.3.3", + "source": { + "type": "git", + "url": "https://github.com/PhenX/php-svg-lib.git", + "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhenX/php-svg-lib/zipball/5fa61b65e612ce1ae15f69b3d223cb14ecc60e32", + "reference": "5fa61b65e612ce1ae15f69b3d223cb14ecc60e32", + "shasum": "" + }, + "require": { + "sabberworm/php-css-parser": "^8.3" + }, + "require-dev": { + "phpunit/phpunit": "^5.5|^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/PhenX/php-svg-lib", + "time": "2019-09-11T20:02:13+00:00" + }, { "name": "php-http/discovery", "version": "1.7.4", @@ -3064,24 +3266,21 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", "shasum": "" }, "require": { "php": ">=7.1" }, - "require-dev": { - "phpunit/phpunit": "~6" - }, "type": "library", "extra": { "branch-alias": { @@ -3112,7 +3311,7 @@ "reflection", "static analysis" ], - "time": "2018-08-07T13:53:10+00:00" + "time": "2020-04-27T09:25:28+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -3706,16 +3905,16 @@ }, { "name": "s9e/text-formatter", - "version": "2.4.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "a9644df49d31c2fcadf65cc2e6b5816c75a390ae" + "reference": "1f92145ebeba84dc3736c608d2e56cdf1c3e31fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/a9644df49d31c2fcadf65cc2e6b5816c75a390ae", - "reference": "a9644df49d31c2fcadf65cc2e6b5816c75a390ae", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/1f92145ebeba84dc3736c608d2e56cdf1c3e31fa", + "reference": "1f92145ebeba84dc3736c608d2e56cdf1c3e31fa", "shasum": "" }, "require": { @@ -3741,7 +3940,7 @@ }, "type": "library", "extra": { - "version": "2.4.0" + "version": "2.5.0" }, "autoload": { "psr-4": { @@ -3771,20 +3970,65 @@ "parser", "shortcodes" ], - "time": "2020-03-31T16:44:09+00:00" + "time": "2020-04-29T16:11:43+00:00" }, { - "name": "scheb/two-factor-bundle", - "version": "v4.14.0", + "name": "sabberworm/php-css-parser", + "version": "8.3.0", "source": { "type": "git", - "url": "https://github.com/scheb/two-factor-bundle.git", - "reference": "6393d304ee51a703711e5f5cc3e76f04ce4e238c" + "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/two-factor-bundle/zipball/6393d304ee51a703711e5f5cc3e76f04ce4e238c", - "reference": "6393d304ee51a703711e5f5cc3e76f04ce4e238c", + "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "codacy/coverage": "^1.4", + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-0": { + "Sabberworm\\CSS": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "time": "2019-02-22T07:42:52+00:00" + }, + { + "name": "scheb/two-factor-bundle", + "version": "v4.16.0", + "source": { + "type": "git", + "url": "https://github.com/scheb/two-factor-bundle.git", + "reference": "44ae7d1394296ed54aa621dd96516786ade2eeed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scheb/two-factor-bundle/zipball/44ae7d1394296ed54aa621dd96516786ade2eeed", + "reference": "44ae7d1394296ed54aa621dd96516786ade2eeed", "shasum": "" }, "require": { @@ -3803,13 +4047,14 @@ "symfony/twig-bundle": "^3.4|^4.0|^5.0" }, "require-dev": { - "doctrine/lexer": "^1.0.1", - "doctrine/orm": "^2.6", + "doctrine/persistence": "^1.3", "escapestudios/symfony2-coding-standard": "^3.9", - "phpunit/phpunit": "^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", "squizlabs/php_codesniffer": "^3.5", "swiftmailer/swiftmailer": "^6.0", - "symfony/yaml": "^3.4|^4.0|^5.0" + "symfony/polyfill-php80": "^1.15", + "symfony/yaml": "^3.4|^4.0|^5.0", + "vimeo/psalm": "^3.11" }, "type": "symfony-bundle", "autoload": { @@ -3839,20 +4084,20 @@ "two-factor", "two-step" ], - "time": "2020-02-15T13:01:16+00:00" + "time": "2020-05-08T19:32:30+00:00" }, { "name": "sensio/framework-extra-bundle", - "version": "v5.5.4", + "version": "v5.5.5", "source": { "type": "git", "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git", - "reference": "d0585d4825a87a5030ca8cd34adb4a17e1066c17" + "reference": "c76bb1c5c67840ecb6d9be8e9d8d7036e375e317" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/d0585d4825a87a5030ca8cd34adb4a17e1066c17", - "reference": "d0585d4825a87a5030ca8cd34adb4a17e1066c17", + "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/c76bb1c5c67840ecb6d9be8e9d8d7036e375e317", + "reference": "c76bb1c5c67840ecb6d9be8e9d8d7036e375e317", "shasum": "" }, "require": { @@ -3912,7 +4157,7 @@ "annotations", "controllers" ], - "time": "2020-04-06T12:20:39+00:00" + "time": "2020-05-06T12:12:33+00:00" }, { "name": "sensiolabs/security-checker", @@ -4110,16 +4355,16 @@ }, { "name": "symfony/asset", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "e3574559efcb51601022fa46fd88dd13a8febdc2" + "reference": "fc8eff5841b549fbd66f89e1fd7cfb6a823ee512" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/e3574559efcb51601022fa46fd88dd13a8febdc2", - "reference": "e3574559efcb51601022fa46fd88dd13a8febdc2", + "url": "https://api.github.com/repos/symfony/asset/zipball/fc8eff5841b549fbd66f89e1fd7cfb6a823ee512", + "reference": "fc8eff5841b549fbd66f89e1fd7cfb6a823ee512", "shasum": "" }, "require": { @@ -4162,20 +4407,20 @@ ], "description": "Symfony Asset Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T14:39:55+00:00" }, { "name": "symfony/cache", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "f777b570291aebe51081b9827e05f3a747665e87" + "reference": "ba0aa1738d04df338c0fabdbecf9cf5fddcdb63f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/f777b570291aebe51081b9827e05f3a747665e87", - "reference": "f777b570291aebe51081b9827e05f3a747665e87", + "url": "https://api.github.com/repos/symfony/cache/zipball/ba0aa1738d04df338c0fabdbecf9cf5fddcdb63f", + "reference": "ba0aa1738d04df338c0fabdbecf9cf5fddcdb63f", "shasum": "" }, "require": { @@ -4241,7 +4486,7 @@ "caching", "psr6" ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-28T17:55:16+00:00" }, { "name": "symfony/cache-contracts", @@ -4303,16 +4548,16 @@ }, { "name": "symfony/config", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "3f4a3de1af498ed0ea653d4dc2317794144e6ca4" + "reference": "8ba41fe053683e1e6e3f6fa21f07ea5c4dd9e4c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/3f4a3de1af498ed0ea653d4dc2317794144e6ca4", - "reference": "3f4a3de1af498ed0ea653d4dc2317794144e6ca4", + "url": "https://api.github.com/repos/symfony/config/zipball/8ba41fe053683e1e6e3f6fa21f07ea5c4dd9e4c0", + "reference": "8ba41fe053683e1e6e3f6fa21f07ea5c4dd9e4c0", "shasum": "" }, "require": { @@ -4363,11 +4608,11 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-15T15:56:18+00:00" }, { "name": "symfony/console", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -4514,7 +4759,7 @@ }, { "name": "symfony/css-selector", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -4567,7 +4812,7 @@ }, { "name": "symfony/debug", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -4623,16 +4868,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "755b18859be26b90f4bf63753432d3387458bf31" + "reference": "9d0c2807962f7f12264ab459f48fb541dbd386bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/755b18859be26b90f4bf63753432d3387458bf31", - "reference": "755b18859be26b90f4bf63753432d3387458bf31", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9d0c2807962f7f12264ab459f48fb541dbd386bd", + "reference": "9d0c2807962f7f12264ab459f48fb541dbd386bd", "shasum": "" }, "require": { @@ -4692,20 +4937,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2020-03-30T10:09:30+00:00" + "time": "2020-04-16T16:36:56+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "7889c9df9fe4d95042c2f6e79901c9e6517343d9" + "reference": "642cb1000331b8dc5568587f60aeb299070f9a55" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/7889c9df9fe4d95042c2f6e79901c9e6517343d9", - "reference": "7889c9df9fe4d95042c2f6e79901c9e6517343d9", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/642cb1000331b8dc5568587f60aeb299070f9a55", + "reference": "642cb1000331b8dc5568587f60aeb299070f9a55", "shasum": "" }, "require": { @@ -4786,11 +5031,11 @@ ], "description": "Symfony Doctrine Bridge", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T16:45:36+00:00" }, { "name": "symfony/dotenv", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", @@ -4847,7 +5092,7 @@ }, { "name": "symfony/error-handler", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", @@ -4903,7 +5148,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -5031,16 +5276,16 @@ }, { "name": "symfony/expression-language", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "c4171e39e6cfc72e2bedb44922310d7d2bd2ab91" + "reference": "38010d8d1eb425b74f25b87c366c4d97e4b06a89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/c4171e39e6cfc72e2bedb44922310d7d2bd2ab91", - "reference": "c4171e39e6cfc72e2bedb44922310d7d2bd2ab91", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/38010d8d1eb425b74f25b87c366c4d97e4b06a89", + "reference": "38010d8d1eb425b74f25b87c366c4d97e4b06a89", "shasum": "" }, "require": { @@ -5078,20 +5323,20 @@ ], "description": "Symfony ExpressionLanguage Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-15T15:55:41+00:00" }, { "name": "symfony/filesystem", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "fe297193bf2e6866ed900ed2d5869362768df6a7" + "reference": "a3ebf3bfd8a98a147c010a568add5a8aa4edea0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fe297193bf2e6866ed900ed2d5869362768df6a7", - "reference": "fe297193bf2e6866ed900ed2d5869362768df6a7", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/a3ebf3bfd8a98a147c010a568add5a8aa4edea0f", + "reference": "a3ebf3bfd8a98a147c010a568add5a8aa4edea0f", "shasum": "" }, "require": { @@ -5128,11 +5373,11 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T14:39:55+00:00" }, { "name": "symfony/finder", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -5181,16 +5426,16 @@ }, { "name": "symfony/flex", - "version": "v1.6.2", + "version": "v1.6.3", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "e4f5a2653ca503782a31486198bd1dd1c9a47f83" + "reference": "89999fdaad52cab14637709f2d2ce25835a051e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/e4f5a2653ca503782a31486198bd1dd1c9a47f83", - "reference": "e4f5a2653ca503782a31486198bd1dd1c9a47f83", + "url": "https://api.github.com/repos/symfony/flex/zipball/89999fdaad52cab14637709f2d2ce25835a051e6", + "reference": "89999fdaad52cab14637709f2d2ce25835a051e6", "shasum": "" }, "require": { @@ -5226,20 +5471,20 @@ } ], "description": "Composer plugin for Symfony", - "time": "2020-01-30T12:06:45+00:00" + "time": "2020-05-09T12:10:32+00:00" }, { "name": "symfony/form", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "6dfd2d0f47b9a4abee73807f7172e2b8a0006571" + "reference": "505299904397a7c6d515a7c03cdbc1b4a1d4a21f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/6dfd2d0f47b9a4abee73807f7172e2b8a0006571", - "reference": "6dfd2d0f47b9a4abee73807f7172e2b8a0006571", + "url": "https://api.github.com/repos/symfony/form/zipball/505299904397a7c6d515a7c03cdbc1b4a1d4a21f", + "reference": "505299904397a7c6d515a7c03cdbc1b4a1d4a21f", "shasum": "" }, "require": { @@ -5310,20 +5555,20 @@ ], "description": "Symfony Form Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-28T17:55:16+00:00" }, { "name": "symfony/framework-bundle", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "80cdda836cfbe3ccb2bdd4a974f632473f0807a6" + "reference": "fdacdf191a71aef94e05b64319868f4d06fe509c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/80cdda836cfbe3ccb2bdd4a974f632473f0807a6", - "reference": "80cdda836cfbe3ccb2bdd4a974f632473f0807a6", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/fdacdf191a71aef94e05b64319868f4d06fe509c", + "reference": "fdacdf191a71aef94e05b64319868f4d06fe509c", "shasum": "" }, "require": { @@ -5441,20 +5686,20 @@ ], "description": "Symfony FrameworkBundle", "homepage": "https://symfony.com", - "time": "2020-03-30T11:41:10+00:00" + "time": "2020-04-23T20:17:53+00:00" }, { "name": "symfony/http-client", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "9a8f5c968dc68d58044f8e9ff39d03074489b55d" + "reference": "88d1745f4095727b8bf0574a0f414331f4ec229c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/9a8f5c968dc68d58044f8e9ff39d03074489b55d", - "reference": "9a8f5c968dc68d58044f8e9ff39d03074489b55d", + "url": "https://api.github.com/repos/symfony/http-client/zipball/88d1745f4095727b8bf0574a0f414331f4ec229c", + "reference": "88d1745f4095727b8bf0574a0f414331f4ec229c", "shasum": "" }, "require": { @@ -5509,7 +5754,7 @@ ], "description": "Symfony HttpClient component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T16:14:02+00:00" }, { "name": "symfony/http-client-contracts", @@ -5570,16 +5815,16 @@ }, { "name": "symfony/http-foundation", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b" + "reference": "ec5bd254c223786f5fa2bb49a1e705c1b8e7cee2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62f92509c9abfd1f73e17b8cf1b72c0bdac6611b", - "reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ec5bd254c223786f5fa2bb49a1e705c1b8e7cee2", + "reference": "ec5bd254c223786f5fa2bb49a1e705c1b8e7cee2", "shasum": "" }, "require": { @@ -5621,20 +5866,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2020-03-30T14:07:33+00:00" + "time": "2020-04-18T20:40:08+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f356a489e51856b99908005eb7f2c51a1dfc95dc" + "reference": "1799a6c01f0db5851f399151abdb5d6393fec277" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f356a489e51856b99908005eb7f2c51a1dfc95dc", - "reference": "f356a489e51856b99908005eb7f2c51a1dfc95dc", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1799a6c01f0db5851f399151abdb5d6393fec277", + "reference": "1799a6c01f0db5851f399151abdb5d6393fec277", "shasum": "" }, "require": { @@ -5711,11 +5956,11 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2020-03-30T14:59:15+00:00" + "time": "2020-04-28T18:47:42+00:00" }, { "name": "symfony/inflector", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/inflector.git", @@ -5773,16 +6018,16 @@ }, { "name": "symfony/intl", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "63238a53b1cf0cd3e2b0b22cabc7c0b6f3fd4562" + "reference": "040f10fde20ae35e8623771ba8a733508c87aa6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/63238a53b1cf0cd3e2b0b22cabc7c0b6f3fd4562", - "reference": "63238a53b1cf0cd3e2b0b22cabc7c0b6f3fd4562", + "url": "https://api.github.com/repos/symfony/intl/zipball/040f10fde20ae35e8623771ba8a733508c87aa6a", + "reference": "040f10fde20ae35e8623771ba8a733508c87aa6a", "shasum": "" }, "require": { @@ -5844,20 +6089,20 @@ "l10n", "localization" ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T14:39:55+00:00" }, { "name": "symfony/mailer", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "449856e70ccb1c91ebd75d6fb287ffe21be9fafe" + "reference": "939553797698f6702fb00bdc2870bfa23f976473" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/449856e70ccb1c91ebd75d6fb287ffe21be9fafe", - "reference": "449856e70ccb1c91ebd75d6fb287ffe21be9fafe", + "url": "https://api.github.com/repos/symfony/mailer/zipball/939553797698f6702fb00bdc2870bfa23f976473", + "reference": "939553797698f6702fb00bdc2870bfa23f976473", "shasum": "" }, "require": { @@ -5912,20 +6157,20 @@ ], "description": "Symfony Mailer Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-23T12:41:43+00:00" }, { "name": "symfony/mime", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "6dde9dc70155e91b850b1d009d1f841c54bc4aba" + "reference": "7a583ffb6c7dd5aabb5db920817a3cc39261c517" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/6dde9dc70155e91b850b1d009d1f841c54bc4aba", - "reference": "6dde9dc70155e91b850b1d009d1f841c54bc4aba", + "url": "https://api.github.com/repos/symfony/mime/zipball/7a583ffb6c7dd5aabb5db920817a3cc39261c517", + "reference": "7a583ffb6c7dd5aabb5db920817a3cc39261c517", "shasum": "" }, "require": { @@ -5974,20 +6219,20 @@ "mime", "mime-type" ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-16T14:49:30+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "2df4a774d99ae6e87c8b67891430f935312be412" + "reference": "224355f29abfb8b00a4c5fb22bdaff5c47e82105" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/2df4a774d99ae6e87c8b67891430f935312be412", - "reference": "2df4a774d99ae6e87c8b67891430f935312be412", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/224355f29abfb8b00a4c5fb22bdaff5c47e82105", + "reference": "224355f29abfb8b00a4c5fb22bdaff5c47e82105", "shasum": "" }, "require": { @@ -6041,7 +6286,7 @@ ], "description": "Symfony Monolog Bridge", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T16:14:02+00:00" }, { "name": "symfony/monolog-bundle", @@ -6108,16 +6353,16 @@ }, { "name": "symfony/options-resolver", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "9072131b5e6e21203db3249c7db26b52897bc73e" + "reference": "ade3d89dd3b875b83c8cff2980c9bb0daf6ef297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/9072131b5e6e21203db3249c7db26b52897bc73e", - "reference": "9072131b5e6e21203db3249c7db26b52897bc73e", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ade3d89dd3b875b83c8cff2980c9bb0daf6ef297", + "reference": "ade3d89dd3b875b83c8cff2980c9bb0daf6ef297", "shasum": "" }, "require": { @@ -6158,7 +6403,7 @@ "configuration", "options" ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-06T10:16:26+00:00" }, { "name": "symfony/orm-pack", @@ -6189,16 +6434,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" + "reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1aab00e39cebaef4d8652497f46c15c1b7e45294", + "reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294", "shasum": "" }, "require": { @@ -6210,7 +6455,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.16-dev" } }, "autoload": { @@ -6243,20 +6488,20 @@ "polyfill", "portable" ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-08T16:50:20+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "9c281272735eb66476e0fa7381e03fb0d4b60197" + "reference": "0913a9ab67d6042966287c8ad996927b6c710330" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/9c281272735eb66476e0fa7381e03fb0d4b60197", - "reference": "9c281272735eb66476e0fa7381e03fb0d4b60197", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/0913a9ab67d6042966287c8ad996927b6c710330", + "reference": "0913a9ab67d6042966287c8ad996927b6c710330", "shasum": "" }, "require": { @@ -6269,7 +6514,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.16-dev" } }, "autoload": { @@ -6301,20 +6546,20 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-08T16:50:20+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + "reference": "ab0af41deab94ec8dceb3d1fb408bdd038eba4dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ab0af41deab94ec8dceb3d1fb408bdd038eba4dc", + "reference": "ab0af41deab94ec8dceb3d1fb408bdd038eba4dc", "shasum": "" }, "require": { @@ -6328,7 +6573,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.16-dev" } }, "autoload": { @@ -6363,20 +6608,20 @@ "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-08T16:50:20+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "reference": "a54881ec0ab3b2005c406aed0023c062879031e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a54881ec0ab3b2005c406aed0023c062879031e7", + "reference": "a54881ec0ab3b2005c406aed0023c062879031e7", "shasum": "" }, "require": { @@ -6388,7 +6633,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.16-dev" } }, "autoload": { @@ -6422,20 +6667,20 @@ "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-08T16:50:20+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "37b0976c78b94856543260ce09b460a7bc852747" + "reference": "42fda6d7380e5c940d7f68341ccae89d5ab9963b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", - "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/42fda6d7380e5c940d7f68341ccae89d5ab9963b", + "reference": "42fda6d7380e5c940d7f68341ccae89d5ab9963b", "shasum": "" }, "require": { @@ -6444,7 +6689,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.16-dev" } }, "autoload": { @@ -6477,20 +6722,20 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-08T17:28:34+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" + "reference": "7e95fe59d12169fcf4041487e4bf34fca37ee0ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/7e95fe59d12169fcf4041487e4bf34fca37ee0ed", + "reference": "7e95fe59d12169fcf4041487e4bf34fca37ee0ed", "shasum": "" }, "require": { @@ -6499,7 +6744,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.16-dev" } }, "autoload": { @@ -6535,20 +6780,20 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-02T14:56:09+00:00" }, { "name": "symfony/process", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3" + "reference": "4b6a9a4013baa65d409153cbb5a895bf093dc7f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3e40e87a20eaf83a1db825e1fa5097ae89042db3", - "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3", + "url": "https://api.github.com/repos/symfony/process/zipball/4b6a9a4013baa65d409153cbb5a895bf093dc7f4", + "reference": "4b6a9a4013baa65d409153cbb5a895bf093dc7f4", "shasum": "" }, "require": { @@ -6584,20 +6829,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-15T15:56:18+00:00" }, { "name": "symfony/property-access", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "75cbf0f388d82685ce06515951397bc1370901d7" + "reference": "f6a51bd76a3a5c36c57221a4f491b9cf02663672" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/75cbf0f388d82685ce06515951397bc1370901d7", - "reference": "75cbf0f388d82685ce06515951397bc1370901d7", + "url": "https://api.github.com/repos/symfony/property-access/zipball/f6a51bd76a3a5c36c57221a4f491b9cf02663672", + "reference": "f6a51bd76a3a5c36c57221a4f491b9cf02663672", "shasum": "" }, "require": { @@ -6651,20 +6896,20 @@ "property path", "reflection" ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-15T15:55:41+00:00" }, { "name": "symfony/property-info", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "b6baecd501adec01a9d68f9c90b83659656065af" + "reference": "ab5bb41dee66b4f7b4e0f615772b07d8f466e218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/b6baecd501adec01a9d68f9c90b83659656065af", - "reference": "b6baecd501adec01a9d68f9c90b83659656065af", + "url": "https://api.github.com/repos/symfony/property-info/zipball/ab5bb41dee66b4f7b4e0f615772b07d8f466e218", + "reference": "ab5bb41dee66b4f7b4e0f615772b07d8f466e218", "shasum": "" }, "require": { @@ -6727,20 +6972,20 @@ "type", "validator" ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-06T10:16:26+00:00" }, { "name": "symfony/routing", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0f562fa613e288d7dbae6c63abbc9b33ed75a8f8" + "reference": "67b4e1f99c050cbc310b8f3d0dbdc4b0212c052c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0f562fa613e288d7dbae6c63abbc9b33ed75a8f8", - "reference": "0f562fa613e288d7dbae6c63abbc9b33ed75a8f8", + "url": "https://api.github.com/repos/symfony/routing/zipball/67b4e1f99c050cbc310b8f3d0dbdc4b0212c052c", + "reference": "67b4e1f99c050cbc310b8f3d0dbdc4b0212c052c", "shasum": "" }, "require": { @@ -6803,20 +7048,20 @@ "uri", "url" ], - "time": "2020-03-30T11:41:10+00:00" + "time": "2020-04-21T19:59:53+00:00" }, { "name": "symfony/security-bundle", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "1c317cd29a75e4806479241ffd31d8035e243420" + "reference": "dd1641ab03f2dd62e7aa0de8efd80cee20d585ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/1c317cd29a75e4806479241ffd31d8035e243420", - "reference": "1c317cd29a75e4806479241ffd31d8035e243420", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/dd1641ab03f2dd62e7aa0de8efd80cee20d585ff", + "reference": "dd1641ab03f2dd62e7aa0de8efd80cee20d585ff", "shasum": "" }, "require": { @@ -6886,20 +7131,20 @@ ], "description": "Symfony SecurityBundle", "homepage": "https://symfony.com", - "time": "2020-03-30T11:41:10+00:00" + "time": "2020-04-12T22:16:27+00:00" }, { "name": "symfony/security-core", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "e99ad8bcd5d1202a1cff7b3e0e76d9077d81cbe6" + "reference": "fc84e9481e5bd9d80f70c0d8151601211377a5dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/e99ad8bcd5d1202a1cff7b3e0e76d9077d81cbe6", - "reference": "e99ad8bcd5d1202a1cff7b3e0e76d9077d81cbe6", + "url": "https://api.github.com/repos/symfony/security-core/zipball/fc84e9481e5bd9d80f70c0d8151601211377a5dc", + "reference": "fc84e9481e5bd9d80f70c0d8151601211377a5dc", "shasum": "" }, "require": { @@ -6959,11 +7204,11 @@ ], "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", - "time": "2020-03-30T11:51:53+00:00" + "time": "2020-04-21T21:19:23+00:00" }, { "name": "symfony/security-csrf", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", @@ -7022,16 +7267,16 @@ }, { "name": "symfony/security-guard", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/security-guard.git", - "reference": "606a741712d8adb49aee9b59d57010724db06797" + "reference": "d2ba618ed2a52f37dd74fb2c52a14388beddd5fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-guard/zipball/606a741712d8adb49aee9b59d57010724db06797", - "reference": "606a741712d8adb49aee9b59d57010724db06797", + "url": "https://api.github.com/repos/symfony/security-guard/zipball/d2ba618ed2a52f37dd74fb2c52a14388beddd5fc", + "reference": "d2ba618ed2a52f37dd74fb2c52a14388beddd5fc", "shasum": "" }, "require": { @@ -7072,20 +7317,20 @@ ], "description": "Symfony Security Component - Guard", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-15T15:55:41+00:00" }, { "name": "symfony/security-http", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "b413064160255c31077bb082d25b7bd89275971b" + "reference": "055a4f4fe58ab19515fa573919bf7ebd114f4aa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/b413064160255c31077bb082d25b7bd89275971b", - "reference": "b413064160255c31077bb082d25b7bd89275971b", + "url": "https://api.github.com/repos/symfony/security-http/zipball/055a4f4fe58ab19515fa573919bf7ebd114f4aa7", + "reference": "055a4f4fe58ab19515fa573919bf7ebd114f4aa7", "shasum": "" }, "require": { @@ -7093,7 +7338,7 @@ "symfony/http-foundation": "^3.4.40|^4.4.7|^5.0.7", "symfony/http-kernel": "^4.4", "symfony/property-access": "^3.4|^4.0|^5.0", - "symfony/security-core": "^4.4.7" + "symfony/security-core": "^4.4.8" }, "conflict": { "symfony/event-dispatcher": ">=5", @@ -7138,20 +7383,20 @@ ], "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", - "time": "2020-03-30T11:51:53+00:00" + "time": "2020-04-03T17:46:33+00:00" }, { "name": "symfony/serializer", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "2a508a535f2323defb325cf28301064fcbb061b9" + "reference": "067b6a064ffc53b48d3854c7b408b1ea26017a50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/2a508a535f2323defb325cf28301064fcbb061b9", - "reference": "2a508a535f2323defb325cf28301064fcbb061b9", + "url": "https://api.github.com/repos/symfony/serializer/zipball/067b6a064ffc53b48d3854c7b408b1ea26017a50", + "reference": "067b6a064ffc53b48d3854c7b408b1ea26017a50", "shasum": "" }, "require": { @@ -7220,7 +7465,7 @@ ], "description": "Symfony Serializer Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T16:14:02+00:00" }, { "name": "symfony/serializer-pack", @@ -7312,7 +7557,7 @@ }, { "name": "symfony/stopwatch", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -7362,7 +7607,7 @@ }, { "name": "symfony/templating", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/templating.git", @@ -7418,16 +7663,16 @@ }, { "name": "symfony/translation", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "4e54d336f2eca5facad449d0b0118bb449375b76" + "reference": "8272bbd2b7e220ef812eba2a2b30068a5c64b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4e54d336f2eca5facad449d0b0118bb449375b76", - "reference": "4e54d336f2eca5facad449d0b0118bb449375b76", + "url": "https://api.github.com/repos/symfony/translation/zipball/8272bbd2b7e220ef812eba2a2b30068a5c64b191", + "reference": "8272bbd2b7e220ef812eba2a2b30068a5c64b191", "shasum": "" }, "require": { @@ -7490,7 +7735,7 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T16:45:36+00:00" }, { "name": "symfony/translation-contracts", @@ -7551,16 +7796,16 @@ }, { "name": "symfony/twig-bridge", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "bef4da6724c5a89bb3408d3bc785be7cd5b9efed" + "reference": "d64035d0d6b3dbeed3a6839e3833779aaecf3513" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/bef4da6724c5a89bb3408d3bc785be7cd5b9efed", - "reference": "bef4da6724c5a89bb3408d3bc785be7cd5b9efed", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d64035d0d6b3dbeed3a6839e3833779aaecf3513", + "reference": "d64035d0d6b3dbeed3a6839e3833779aaecf3513", "shasum": "" }, "require": { @@ -7650,20 +7895,20 @@ ], "description": "Symfony Twig Bridge", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-14T09:16:32+00:00" }, { "name": "symfony/twig-bundle", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "44e3e82867bf4dcf52732dd7e0c83826f9da1095" + "reference": "79046e5189c5f4da923f395ccc11db930953c990" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/44e3e82867bf4dcf52732dd7e0c83826f9da1095", - "reference": "44e3e82867bf4dcf52732dd7e0c83826f9da1095", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/79046e5189c5f4da923f395ccc11db930953c990", + "reference": "79046e5189c5f4da923f395ccc11db930953c990", "shasum": "" }, "require": { @@ -7725,20 +7970,20 @@ ], "description": "Symfony TwigBundle", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-15T15:55:41+00:00" }, { "name": "symfony/validator", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "2bf1de9d5cac5e5ebc159203c53dcf5b2058d340" + "reference": "1780dff34d756f924ed7bb4f1cd94a7f9685eb69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/2bf1de9d5cac5e5ebc159203c53dcf5b2058d340", - "reference": "2bf1de9d5cac5e5ebc159203c53dcf5b2058d340", + "url": "https://api.github.com/repos/symfony/validator/zipball/1780dff34d756f924ed7bb4f1cd94a7f9685eb69", + "reference": "1780dff34d756f924ed7bb4f1cd94a7f9685eb69", "shasum": "" }, "require": { @@ -7818,20 +8063,20 @@ ], "description": "Symfony Validator Component", "homepage": "https://symfony.com", - "time": "2020-03-30T11:41:10+00:00" + "time": "2020-04-28T18:23:58+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5a0c2d93006131a36cf6f767d10e2ca8333b0d4a" + "reference": "c587e04ce5d1aa62d534a038f574d9a709e814cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5a0c2d93006131a36cf6f767d10e2ca8333b0d4a", - "reference": "5a0c2d93006131a36cf6f767d10e2ca8333b0d4a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c587e04ce5d1aa62d534a038f574d9a709e814cf", + "reference": "c587e04ce5d1aa62d534a038f574d9a709e814cf", "shasum": "" }, "require": { @@ -7894,20 +8139,20 @@ "debug", "dump" ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-12T16:14:02+00:00" }, { "name": "symfony/var-exporter", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "6e4939b084defee0ab60a21e6a02e3a198afd91f" + "reference": "6e95bdca4a4604da6c148729972d4b627a034b13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/6e4939b084defee0ab60a21e6a02e3a198afd91f", - "reference": "6e4939b084defee0ab60a21e6a02e3a198afd91f", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/6e95bdca4a4604da6c148729972d4b627a034b13", + "reference": "6e95bdca4a4604da6c148729972d4b627a034b13", "shasum": "" }, "require": { @@ -7954,11 +8199,11 @@ "instantiate", "serialize" ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-15T15:55:41+00:00" }, { "name": "symfony/web-link", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", @@ -8087,16 +8332,16 @@ }, { "name": "symfony/yaml", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ef166890d821518106da3560086bfcbeb4fadfec" + "reference": "b385dce1c0e9f839b384af90188638819433e252" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec", - "reference": "ef166890d821518106da3560086bfcbeb4fadfec", + "url": "https://api.github.com/repos/symfony/yaml/zipball/b385dce1c0e9f839b384af90188638819433e252", + "reference": "b385dce1c0e9f839b384af90188638819433e252", "shasum": "" }, "require": { @@ -8142,20 +8387,173 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2020-03-30T11:41:10+00:00" + "time": "2020-04-28T17:55:16+00:00" }, { - "name": "thecodingmachine/safe", - "version": "v1.1", + "name": "tecnickcom/tc-lib-barcode", + "version": "1.15.20", "source": { "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "f440677bad66c0ef42fa3f875bf05718028af5d3" + "url": "https://github.com/tecnickcom/tc-lib-barcode.git", + "reference": "dd8de5620ec436d61cc8535e11f2879146ebc16b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/f440677bad66c0ef42fa3f875bf05718028af5d3", - "reference": "f440677bad66c0ef42fa3f875bf05718028af5d3", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/dd8de5620ec436d61cc8535e11f2879146ebc16b", + "reference": "dd8de5620ec436d61cc8535e11f2879146ebc16b", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "ext-date": "*", + "ext-gd": "*", + "ext-pcre": "*", + "php": ">=5.3", + "tecnickcom/tc-lib-color": "^1.12.15" + }, + "require-dev": { + "apigen/apigen": "^2.8.1 || ^4.1.2", + "bartlett/php-compatinfo": "^4.5.2 || ^5.0.10 || ^5.0.12", + "pdepend/pdepend": "^2.5.2", + "phploc/phploc": "^2.1 || ^3.0 || ^4.0", + "phpmd/phpmd": "^2.6.0", + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.1", + "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", + "squizlabs/php_codesniffer": "^2.8.0 || ^3.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Barcode\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to generate linear and bidimensional barcodes", + "homepage": "http://www.tecnick.com", + "keywords": [ + "3 of 9", + "ANSI MH10.8M-1983", + "CBC", + "CODABAR", + "CODE 11", + "CODE 128 A B C", + "CODE 39", + "CODE 93", + "EAN 13", + "EAN 8", + "ECC200", + "Intelligent Mail Barcode", + "Interleaved 2 of 5", + "KIX", + "Klant", + "MSI", + "Onecode", + "PHARMACODE", + "PHARMACODE TWO-TRACKS", + "POSTNET", + "RMS4CC", + "Standard 2 of 5", + "UPC-A", + "UPC-E", + "USD-3", + "USPS-B-3200", + "USS-93", + "barcode", + "datamatrix", + "pdf417", + "planet", + "qr-code", + "royal mail", + "tc-lib-barcode", + "upc" + ], + "time": "2020-01-02T16:01:18+00:00" + }, + { + "name": "tecnickcom/tc-lib-color", + "version": "1.12.15", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-color.git", + "reference": "2f4860cbac4d58c210b6bec4c5806906278962c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/2f4860cbac4d58c210b6bec4c5806906278962c1", + "reference": "2f4860cbac4d58c210b6bec4c5806906278962c1", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": ">=5.3" + }, + "require-dev": { + "apigen/apigen": "^2.8.1 || ^4.1.2", + "bartlett/php-compatinfo": "^4.5.2 || ^5.0.10 || ^5.0.12", + "pdepend/pdepend": "^2.5.2", + "phploc/phploc": "^2.1 || ^3.0 || ^4.0", + "phpmd/phpmd": "^2.6.0", + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.1", + "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", + "squizlabs/php_codesniffer": "^2.8.0 || ^3.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Color\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to manipulate various color representations", + "homepage": "http://www.tecnick.com", + "keywords": [ + "cmyk", + "color", + "colors", + "colour", + "colours", + "hsl", + "hsla", + "javascript", + "rgb", + "rgba", + "tc-lib-color", + "web" + ], + "time": "2020-01-02T16:01:17+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "04f9ffae372a9816d4472dfb7bcf6126b623a9df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/04f9ffae372a9816d4472dfb7bcf6126b623a9df", + "reference": "04f9ffae372a9816d4472dfb7bcf6126b623a9df", "shasum": "" }, "require": { @@ -8239,6 +8637,7 @@ "generated/ps.php", "generated/pspell.php", "generated/readline.php", + "generated/rpminfo.php", "generated/rrd.php", "generated/sem.php", "generated/session.php", @@ -8274,7 +8673,7 @@ "MIT" ], "description": "PHP core functions that throw exceptions instead of returning FALSE on error", - "time": "2020-03-24T13:59:42+00:00" + "time": "2020-05-04T15:25:36+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8438,6 +8837,59 @@ ], "time": "2020-01-01T17:11:09+00:00" }, + { + "name": "twig/html-extra", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/twigphp/html-extra.git", + "reference": "0a0340aaba8c09e1c899d81fce6d45a1d6e448d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/html-extra/zipball/0a0340aaba8c09e1c899d81fce6d45a1d6e448d5", + "reference": "0a0340aaba8c09e1c899d81fce6d45a1d6e448d5", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/mime": "^4.3|^5.0", + "twig/twig": "^2.4|^3.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\Extra\\Html\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for HTML", + "homepage": "https://twig.symfony.com", + "keywords": [ + "html", + "twig" + ], + "time": "2020-01-01T17:11:09+00:00" + }, { "name": "twig/inky-extra", "version": "v3.0.3", @@ -8667,16 +9119,16 @@ }, { "name": "webmozart/assert", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", "shasum": "" }, "require": { @@ -8684,7 +9136,7 @@ "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "vimeo/psalm": "<3.9.1" }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" @@ -8711,7 +9163,7 @@ "check", "validate" ], - "time": "2020-02-14T12:15:55+00:00" + "time": "2020-04-18T12:12:48+00:00" }, { "name": "yubico/u2flib-server", @@ -8867,16 +9319,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v2.4.2", + "version": "v2.4.4", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "feca077369a47263b22156b3c6389e55f3809f24" + "reference": "1e58d53e4af390efc7813e36cd215bd82cba4b06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/feca077369a47263b22156b3c6389e55f3809f24", - "reference": "feca077369a47263b22156b3c6389e55f3809f24", + "url": "https://api.github.com/repos/amphp/amp/zipball/1e58d53e4af390efc7813e36cd215bd82cba4b06", + "reference": "1e58d53e4af390efc7813e36cd215bd82cba4b06", "shasum": "" }, "require": { @@ -8889,7 +9341,7 @@ "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^6.0.9 | ^7", "react/promise": "^2", - "vimeo/psalm": "^3.9@dev" + "vimeo/psalm": "^3.11@dev" }, "type": "library", "extra": { @@ -8941,7 +9393,7 @@ "non-blocking", "promise" ], - "time": "2020-04-04T15:05:26+00:00" + "time": "2020-04-30T04:54:50+00:00" }, { "name": "amphp/byte-stream", @@ -9236,22 +9688,23 @@ }, { "name": "doctrine/doctrine-fixtures-bundle", - "version": "3.3.0", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineFixturesBundle.git", - "reference": "8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70" + "reference": "39defca57ee0949e1475c46177b30b6d1b732e8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70", - "reference": "8f07fcfdac7f3591f3c4bf13a50cbae05f65ed70", + "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/39defca57ee0949e1475c46177b30b6d1b732e8f", + "reference": "39defca57ee0949e1475c46177b30b6d1b732e8f", "shasum": "" }, "require": { "doctrine/data-fixtures": "^1.3", "doctrine/doctrine-bundle": "^1.11|^2.0", "doctrine/orm": "^2.6.0", + "doctrine/persistence": "^1.3", "php": "^7.1", "symfony/config": "^3.4|^4.3|^5.0", "symfony/console": "^3.4|^4.3|^5.0", @@ -9299,7 +9752,7 @@ "Fixture", "persistence" ], - "time": "2019-11-13T15:46:58+00:00" + "time": "2020-04-02T16:40:37+00:00" }, { "name": "ekino/phpstan-banned-code", @@ -9453,16 +9906,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.16.1", + "version": "v2.16.3", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02" + "reference": "83baf823a33a1cbd5416c8626935cf3f843c10b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c8afb599858876e95e8ebfcd97812d383fa23f02", - "reference": "c8afb599858876e95e8ebfcd97812d383fa23f02", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/83baf823a33a1cbd5416c8626935cf3f843c10b0", + "reference": "83baf823a33a1cbd5416c8626935cf3f843c10b0", "shasum": "" }, "require": { @@ -9498,6 +9951,7 @@ "symfony/yaml": "^3.0 || ^4.0 || ^5.0" }, "suggest": { + "ext-dom": "For handling output formats in XML", "ext-mbstring": "For handling non-UTF8 characters in cache signature.", "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", @@ -9520,6 +9974,7 @@ "tests/Test/IntegrationCaseFactory.php", "tests/Test/IntegrationCaseFactoryInterface.php", "tests/Test/InternalIntegrationCaseFactory.php", + "tests/Test/IsIdenticalConstraint.php", "tests/TestCase.php" ] }, @@ -9538,7 +9993,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2019-11-25T22:10:32+00:00" + "time": "2020-04-15T18:51:10+00:00" }, { "name": "jean85/pretty-package-versions", @@ -9593,16 +10048,16 @@ }, { "name": "netresearch/jsonmapper", - "version": "v1.6.0", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "0d4d1b48d682a93b6bfedf60b88c7750e9cb0b06" + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/0d4d1b48d682a93b6bfedf60b88c7750e9cb0b06", - "reference": "0d4d1b48d682a93b6bfedf60b88c7750e9cb0b06", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e", "shasum": "" }, "require": { @@ -9613,8 +10068,8 @@ "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4", - "squizlabs/php_codesniffer": "~1.5" + "phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4 || ~7.0", + "squizlabs/php_codesniffer": "~3.5" }, "type": "library", "autoload": { @@ -9629,159 +10084,13 @@ "authors": [ { "name": "Christian Weiske", - "role": "Developer", "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/" + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" } ], "description": "Map nested JSON structures onto PHP classes", - "time": "2019-08-15T19:41:25+00:00" - }, - { - "name": "nette/application", - "version": "v3.0.4", - "source": { - "type": "git", - "url": "https://github.com/nette/application.git", - "reference": "da24ae52e65fe52fa6d26f2181a56ec48958ccb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/application/zipball/da24ae52e65fe52fa6d26f2181a56ec48958ccb0", - "reference": "da24ae52e65fe52fa6d26f2181a56ec48958ccb0", - "shasum": "" - }, - "require": { - "nette/component-model": "^3.0", - "nette/http": "^3.0.2", - "nette/routing": "~3.0.0", - "nette/utils": "^3.1", - "php": ">=7.1" - }, - "conflict": { - "latte/latte": "<2.6", - "nette/di": "<3.0-stable", - "nette/forms": "<3.0", - "tracy/tracy": "<2.5" - }, - "require-dev": { - "latte/latte": "^2.6", - "mockery/mockery": "^1.0", - "nette/di": "^v3.0", - "nette/forms": "^3.0", - "nette/robot-loader": "^3.2", - "nette/security": "^3.0", - "nette/tester": "^2.3.1", - "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.6" - }, - "suggest": { - "latte/latte": "Allows using Latte in templates", - "nette/forms": "Allows to use Nette\\Application\\UI\\Form" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🏆 Nette Application: a full-stack component-based MVC kernel for PHP that helps you write powerful and modern web applications. Write less, have cleaner code and your work will bring you joy.", - "homepage": "https://nette.org", - "keywords": [ - "Forms", - "component-based", - "control", - "framework", - "mvc", - "mvp", - "nette", - "presenter", - "routing", - "seo" - ], - "time": "2020-01-22T21:39:02+00:00" - }, - { - "name": "nette/component-model", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/nette/component-model.git", - "reference": "3153441f3d64bbdac300e75b8e5dde36590f7e01" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/component-model/zipball/3153441f3d64bbdac300e75b8e5dde36590f7e01", - "reference": "3153441f3d64bbdac300e75b8e5dde36590f7e01", - "shasum": "" - }, - "require": { - "nette/utils": "^2.5 || ^3.0", - "php": ">=7.1" - }, - "conflict": { - "nette/application": "<2.4", - "nette/nette": "<2.2" - }, - "require-dev": { - "nette/tester": "^2.0", - "tracy/tracy": "^2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "⚛ Nette Component Model", - "homepage": "https://nette.org", - "keywords": [ - "components", - "nette" - ], - "time": "2019-02-20T07:13:15+00:00" + "time": "2020-04-16T18:48:43+00:00" }, { "name": "nette/finder", @@ -9846,80 +10155,6 @@ ], "time": "2020-01-03T20:35:40+00:00" }, - { - "name": "nette/http", - "version": "v3.0.4", - "source": { - "type": "git", - "url": "https://github.com/nette/http.git", - "reference": "e4d8d360c66c8af9512ca13ab629d312af2b3ce3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/http/zipball/e4d8d360c66c8af9512ca13ab629d312af2b3ce3", - "reference": "e4d8d360c66c8af9512ca13ab629d312af2b3ce3", - "shasum": "" - }, - "require": { - "nette/utils": "^3.1", - "php": ">=7.1" - }, - "conflict": { - "nette/di": "<3.0.3" - }, - "require-dev": { - "nette/di": "^3.0", - "nette/security": "^3.0", - "nette/tester": "^2.0", - "phpstan/phpstan": "^0.12", - "tracy/tracy": "^2.4" - }, - "suggest": { - "ext-fileinfo": "to detect type of uploaded files", - "nette/security": "allows use Nette\\Http\\UserStorage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "🌐 Nette Http: abstraction for HTTP request, response and session. Provides careful data sanitization and utility for URL and cookies manipulation.", - "homepage": "https://nette.org", - "keywords": [ - "cookies", - "http", - "nette", - "proxy", - "request", - "response", - "security", - "session", - "url" - ], - "time": "2020-03-31T13:31:41+00:00" - }, { "name": "nette/robot-loader", "version": "v3.2.3", @@ -9983,63 +10218,6 @@ ], "time": "2020-02-28T13:10:07+00:00" }, - { - "name": "nette/routing", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/nette/routing.git", - "reference": "603c697f3df7ed214795d4e8e8c58fbf981232b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nette/routing/zipball/603c697f3df7ed214795d4e8e8c58fbf981232b1", - "reference": "603c697f3df7ed214795d4e8e8c58fbf981232b1", - "shasum": "" - }, - "require": { - "nette/http": "^3.0", - "nette/utils": "^3.0", - "php": ">=7.1" - }, - "require-dev": { - "nette/tester": "^2.0", - "tracy/tracy": "^2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" - ], - "authors": [ - { - "name": "David Grudl", - "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" - } - ], - "description": "Nette Routing: two-ways URL conversion", - "homepage": "https://nette.org", - "keywords": [ - "nette" - ], - "time": "2019-02-13T15:57:18+00:00" - }, { "name": "nette/utils", "version": "v3.1.1", @@ -10265,16 +10443,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "0.4.3", + "version": "0.4.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "928179efc5368145a8b03cb20d58cb3f3136afae" + "reference": "d8d9d4645379e677466d407034436bb155b11c65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/928179efc5368145a8b03cb20d58cb3f3136afae", - "reference": "928179efc5368145a8b03cb20d58cb3f3136afae", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/d8d9d4645379e677466d407034436bb155b11c65", + "reference": "d8d9d4645379e677466d407034436bb155b11c65", "shasum": "" }, "require": { @@ -10286,7 +10464,7 @@ "jakub-onderka/php-parallel-lint": "^0.9.2", "phing/phing": "^2.16.0", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", + "phpstan/phpstan": "^0.12.19", "phpstan/phpstan-strict-rules": "^0.12", "phpunit/phpunit": "^6.3", "slevomat/coding-standard": "^4.7.2", @@ -10310,25 +10488,28 @@ "MIT" ], "description": "PHPDoc parser with support for nullable, intersection and generic types", - "time": "2020-01-25T20:42:48+00:00" + "time": "2020-04-13T16:28:46+00:00" }, { "name": "phpstan/phpstan", - "version": "0.12.18", + "version": "0.12.23", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "1ce27fe29c8660a27926127d350d53d80c4d4286" + "reference": "71e529efced79e055fa8318b692e7f7d03ea4e75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ce27fe29c8660a27926127d350d53d80c4d4286", - "reference": "1ce27fe29c8660a27926127d350d53d80c4d4286", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/71e529efced79e055fa8318b692e7f7d03ea4e75", + "reference": "71e529efced79e055fa8318b692e7f7d03ea4e75", "shasum": "" }, "require": { "php": "^7.1" }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, "bin": [ "phpstan", "phpstan.phar" @@ -10349,20 +10530,20 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2020-03-22T16:51:47+00:00" + "time": "2020-05-05T12:55:44+00:00" }, { "name": "phpstan/phpstan-doctrine", - "version": "0.12.10", + "version": "0.12.12", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-doctrine.git", - "reference": "601f343b05875074454ca72702204592f8844f7d" + "reference": "842047b2496a46dca2c826d9d6ae37ed08593677" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/601f343b05875074454ca72702204592f8844f7d", - "reference": "601f343b05875074454ca72702204592f8844f7d", + "url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/842047b2496a46dca2c826d9d6ae37ed08593677", + "reference": "842047b2496a46dca2c826d9d6ae37ed08593677", "shasum": "" }, "require": { @@ -10413,20 +10594,20 @@ "MIT" ], "description": "Doctrine extensions for PHPStan", - "time": "2020-03-13T13:03:08+00:00" + "time": "2020-05-08T08:31:05+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "0.12.5", + "version": "0.12.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "063c8289357d42cceba956589bfb8b57b5ac4d40" + "reference": "ba69dcd8e57c1a8580bf190e0554bea0fc37fe2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/063c8289357d42cceba956589bfb8b57b5ac4d40", - "reference": "063c8289357d42cceba956589bfb8b57b5ac4d40", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/ba69dcd8e57c1a8580bf190e0554bea0fc37fe2f", + "reference": "ba69dcd8e57c1a8580bf190e0554bea0fc37fe2f", "shasum": "" }, "require": { @@ -10483,31 +10664,32 @@ } ], "description": "Symfony Framework extensions and rules for PHPStan", - "time": "2020-04-09T07:41:44+00:00" + "time": "2020-04-15T20:26:41+00:00" }, { "name": "psalm/plugin-symfony", - "version": "v1.1.3", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/psalm/psalm-plugin-symfony.git", - "reference": "c2b2dd4ae70bf269023e22efd15b7b31f7efe51d" + "reference": "95e6128cfae20f4cc0d03ab47510cbe882824132" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/c2b2dd4ae70bf269023e22efd15b7b31f7efe51d", - "reference": "c2b2dd4ae70bf269023e22efd15b7b31f7efe51d", + "url": "https://api.github.com/repos/psalm/psalm-plugin-symfony/zipball/95e6128cfae20f4cc0d03ab47510cbe882824132", + "reference": "95e6128cfae20f4cc0d03ab47510cbe882824132", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.1", "symfony/framework-bundle": "^3.0 || ^4.0 || ^5.0", - "vimeo/psalm": "^3.7" + "vimeo/psalm": "^3.11" }, "require-dev": { "codeception/base": "^2.5", "phpunit/phpunit": "~7.5", + "symfony/console": "*", "weirdan/codeception-psalm-module": "^0.2.2" }, "type": "psalm-plugin", @@ -10532,7 +10714,7 @@ } ], "description": "Psalm Plugin for Symfony", - "time": "2020-03-15T10:38:16+00:00" + "time": "2020-04-27T05:38:20+00:00" }, { "name": "roave/security-advisories", @@ -10540,12 +10722,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "0f73cf4b4b9227eb8845723bc2a8869bc4dd6e8f" + "reference": "ec1a75b10126327b351fdea7c2b9bfb94e2f6f35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0f73cf4b4b9227eb8845723bc2a8869bc4dd6e8f", - "reference": "0f73cf4b4b9227eb8845723bc2a8869bc4dd6e8f", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ec1a75b10126327b351fdea7c2b9bfb94e2f6f35", + "reference": "ec1a75b10126327b351fdea7c2b9bfb94e2f6f35", "shasum": "" }, "conflict": { @@ -10558,6 +10740,7 @@ "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", "aws/aws-sdk-php": ">=3,<3.2.1", "bagisto/bagisto": "<0.1.5", + "barrelstrength/sprout-base-email": "<3.9", "bolt/bolt": "<3.6.10", "brightlocal/phpwhois": "<=4.2.5", "buddypress/buddypress": "<5.1.2", @@ -10591,6 +10774,8 @@ "endroid/qr-code-bundle": "<3.4.2", "enshrined/svg-sanitize": "<0.13.1", "erusev/parsedown": "<1.7.2", + "ezsystems/demobundle": ">=5.4,<5.4.6.1", + "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1", "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1", "ezsystems/ezplatform": ">=1.7,<1.7.9.1|>=1.13,<1.13.5.1|>=2.5,<2.5.4", "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6", @@ -10633,6 +10818,7 @@ "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", + "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", "onelogin/php-saml": "<2.10.4", "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", "openid/php-openid": "<2.3", @@ -10666,7 +10852,7 @@ "serluck/phpwhois": "<=4.2.6", "shopware/shopware": "<5.3.7", "silverstripe/admin": ">=1.0.3,<1.0.4|>=1.1,<1.1.1", - "silverstripe/assets": ">=1,<1.3.5|>=1.4,<1.4.4", + "silverstripe/assets": ">=1,<1.4.7|>=1.5,<1.5.2", "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", @@ -10679,7 +10865,7 @@ "silverstripe/userforms": "<3", "simple-updates/phpwhois": "<=1", "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", - "simplesamlphp/simplesamlphp": "<1.18.4", + "simplesamlphp/simplesamlphp": "<1.18.6", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "simplito/elliptic-php": "<1.0.6", "slim/slim": "<2.6", @@ -10687,7 +10873,7 @@ "socalnick/scn-social-auth": "<1.15.2", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", - "ssddanbrown/bookstack": "<0.25.3", + "ssddanbrown/bookstack": "<0.29.2", "stormpath/sdk": ">=0,<9.9.99", "studio-42/elfinder": "<2.1.49", "swiftmailer/swiftmailer": ">=4,<5.4.5", @@ -10796,7 +10982,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2020-03-31T14:30:16+00:00" + "time": "2020-05-09T00:00:21+00:00" }, { "name": "sebastian/diff", @@ -10856,31 +11042,32 @@ }, { "name": "slevomat/coding-standard", - "version": "6.2.0", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "c4bf9cad66da885cc843cc24d708661d9d8fbb95" + "reference": "b905a82255749de847fd4de607c7a4c8163f058d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/c4bf9cad66da885cc843cc24d708661d9d8fbb95", - "reference": "c4bf9cad66da885cc843cc24d708661d9d8fbb95", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b905a82255749de847fd4de607c7a4c8163f058d", + "reference": "b905a82255749de847fd4de607c7a4c8163f058d", "shasum": "" }, "require": { "php": "^7.1", - "phpstan/phpdoc-parser": "0.4.0 - 0.4.3", - "squizlabs/php_codesniffer": "^3.5.4" + "phpstan/phpdoc-parser": "0.4.0 - 0.4.4", + "squizlabs/php_codesniffer": "^3.5.5" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "0.6.2", "phing/phing": "2.16.3", - "php-parallel-lint/php-parallel-lint": "1.1.0", - "phpstan/phpstan": "0.12.18", - "phpstan/phpstan-phpunit": "0.12.6", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.19", + "phpstan/phpstan-deprecation-rules": "0.12.2", + "phpstan/phpstan-phpunit": "0.12.8", "phpstan/phpstan-strict-rules": "0.12.2", - "phpunit/phpunit": "7.5.20|8.5.2|9.0.1" + "phpunit/phpunit": "7.5.20|8.5.2|9.1.2" }, "type": "phpcodesniffer-standard", "extra": { @@ -10898,20 +11085,20 @@ "MIT" ], "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "time": "2020-03-28T22:04:31+00:00" + "time": "2020-04-28T07:15:08+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.5.4", + "version": "3.5.5", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "dceec07328401de6211037abbb18bda423677e26" + "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dceec07328401de6211037abbb18bda423677e26", - "reference": "dceec07328401de6211037abbb18bda423677e26", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/73e2e7f57d958e7228fce50dc0c61f58f017f9f6", + "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6", "shasum": "" }, "require": { @@ -10949,11 +11136,11 @@ "phpcs", "standards" ], - "time": "2020-01-30T22:20:29+00:00" + "time": "2020-04-17T01:09:41+00:00" }, { "name": "symfony/browser-kit", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", @@ -11012,7 +11199,7 @@ }, { "name": "symfony/debug-bundle", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", @@ -11107,7 +11294,7 @@ }, { "name": "symfony/dom-crawler", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", @@ -11168,22 +11355,22 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "31396f2e61803f0e2debbb43ba5aa21acbc6e15a" + "reference": "0b5fd0e13eedc88727b47a11edbcf68bc6b797ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/31396f2e61803f0e2debbb43ba5aa21acbc6e15a", - "reference": "31396f2e61803f0e2debbb43ba5aa21acbc6e15a", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/0b5fd0e13eedc88727b47a11edbcf68bc6b797ed", + "reference": "0b5fd0e13eedc88727b47a11edbcf68bc6b797ed", "shasum": "" }, "require": { - "doctrine/inflector": "^1.2", + "doctrine/inflector": "^1.2 || ^2.0", "nikic/php-parser": "^4.0", - "php": "^7.0.8", + "php": "^7.1.3", "symfony/config": "^3.4|^4.0|^5.0", "symfony/console": "^3.4|^4.0|^5.0", "symfony/dependency-injection": "^3.4|^4.0|^5.0", @@ -11232,27 +11419,27 @@ "scaffold", "scaffolding" ], - "time": "2020-04-05T10:50:59+00:00" + "time": "2020-05-08T13:53:05+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v5.0.7", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "0258b43a94972abf1ee99ce2221359f8ac2a17fd" + "reference": "00b8da18a52fa842b7a39613fb0a63720a354e74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0258b43a94972abf1ee99ce2221359f8ac2a17fd", - "reference": "0258b43a94972abf1ee99ce2221359f8ac2a17fd", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/00b8da18a52fa842b7a39613fb0a63720a354e74", + "reference": "00b8da18a52fa842b7a39613fb0a63720a354e74", "shasum": "" }, "require": { "php": ">=5.5.9" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0" + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0|9.1.2" }, "suggest": { "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" @@ -11297,7 +11484,66 @@ ], "description": "Symfony PHPUnit Bridge", "homepage": "https://symfony.com", - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-04-28T17:58:55+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "6cc55bd2a085dbe05b4122c1987a82897b8da419" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6cc55bd2a085dbe05b4122c1987a82897b8da419", + "reference": "6cc55bd2a085dbe05b4122c1987a82897b8da419", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.16-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2020-05-02T14:56:09+00:00" }, { "name": "symfony/profiler-pack", @@ -11357,16 +11603,16 @@ }, { "name": "symfony/web-profiler-bundle", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "4c432f5c21c700270819daacf95323302fa8f004" + "reference": "aaeaa6a620e0187ea3107bdd4030a8b284f7e89d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/4c432f5c21c700270819daacf95323302fa8f004", - "reference": "4c432f5c21c700270819daacf95323302fa8f004", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/aaeaa6a620e0187ea3107bdd4030a8b284f7e89d", + "reference": "aaeaa6a620e0187ea3107bdd4030a8b284f7e89d", "shasum": "" }, "require": { @@ -11419,36 +11665,36 @@ ], "description": "Symfony WebProfilerBundle", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-04-28T17:55:16+00:00" }, { "name": "symplify/auto-bind-parameter", - "version": "v7.2.12", + "version": "v7.3.10", "source": { "type": "git", "url": "https://github.com/symplify/auto-bind-parameter.git", - "reference": "e07e2d6228b8211321098647166c6c48fcd58498" + "reference": "722aaf2b75be2f26bf147add53000f6c2e06b70d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/auto-bind-parameter/zipball/e07e2d6228b8211321098647166c6c48fcd58498", - "reference": "e07e2d6228b8211321098647166c6c48fcd58498", + "url": "https://api.github.com/repos/symplify/auto-bind-parameter/zipball/722aaf2b75be2f26bf147add53000f6c2e06b70d", + "reference": "722aaf2b75be2f26bf147add53000f6c2e06b70d", "shasum": "" }, "require": { "nette/utils": "^3.0", "php": "^7.2", "symfony/dependency-injection": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symplify/package-builder": "^7.2.12" + "symfony/http-kernel": "^4.4|^5.0" }, "require-dev": { - "phpunit/phpunit": "^8.5|^9.0" + "phpunit/phpunit": "^8.5|^9.0", + "symplify/package-builder": "^7.3.10" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -11461,28 +11707,27 @@ "MIT" ], "description": "Auto bind parameters for your Symfony applications", - "time": "2020-04-09T23:55:34+00:00" + "time": "2020-05-07T21:45:46+00:00" }, { "name": "symplify/autowire-array-parameter", - "version": "v7.2.12", + "version": "v7.3.10", "source": { "type": "git", "url": "https://github.com/symplify/autowire-array-parameter.git", - "reference": "8a0b518432dfa8d9d5a6b3848683609683f0696b" + "reference": "6bb61f924e1a1542c062063885b6c881ce39a909" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/autowire-array-parameter/zipball/8a0b518432dfa8d9d5a6b3848683609683f0696b", - "reference": "8a0b518432dfa8d9d5a6b3848683609683f0696b", + "url": "https://api.github.com/repos/symplify/autowire-array-parameter/zipball/6bb61f924e1a1542c062063885b6c881ce39a909", + "reference": "6bb61f924e1a1542c062063885b6c881ce39a909", "shasum": "" }, "require": { - "nette/application": "^3.0", "nette/utils": "^3.0", "php": "^7.2", "symfony/dependency-injection": "^4.4|^5.0", - "symplify/package-builder": "^7.2.12" + "symplify/package-builder": "^7.3.10" }, "require-dev": { "phpunit/phpunit": "^8.5|^9.0" @@ -11490,7 +11735,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -11503,49 +11748,55 @@ "MIT" ], "description": "Autowire array parameters for your Symfony applications", - "time": "2020-04-09T23:55:34+00:00" + "time": "2020-05-07T21:45:46+00:00" }, { "name": "symplify/coding-standard", - "version": "v7.2.12", + "version": "v7.3.10", "source": { "type": "git", "url": "https://github.com/symplify/coding-standard.git", - "reference": "900e96cf4dc7315c038f6f8d565c9bc83d9fd649" + "reference": "eb1583770cd09b2aa79f7017ed4a0477b3ba6eb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/coding-standard/zipball/900e96cf4dc7315c038f6f8d565c9bc83d9fd649", - "reference": "900e96cf4dc7315c038f6f8d565c9bc83d9fd649", + "url": "https://api.github.com/repos/symplify/coding-standard/zipball/eb1583770cd09b2aa79f7017ed4a0477b3ba6eb5", + "reference": "eb1583770cd09b2aa79f7017ed4a0477b3ba6eb5", "shasum": "" }, "require": { "friendsofphp/php-cs-fixer": "^2.16", "nette/finder": "^2.5", "nette/utils": "^3.0", + "nikic/php-parser": "^4.4", "php": "^7.2", "phpstan/phpdoc-parser": "^0.4", + "phpstan/phpstan": "^0.12.23", + "slevomat/coding-standard": "^6.3.2", "squizlabs/php_codesniffer": "^3.5", - "symplify/autowire-array-parameter": "^7.2.12", - "symplify/package-builder": "^7.2.12", - "symplify/smart-file-system": "^7.2.12" + "symplify/autowire-array-parameter": "^7.3.10", + "symplify/package-builder": "^7.3.10", + "symplify/phpstan-extensions": "^7.3.10", + "symplify/smart-file-system": "^7.3.10" }, "require-dev": { "nette/application": "^3.0", + "nette/bootstrap": "^3.0", "phpunit/phpunit": "^8.5|^9.0", - "symplify/easy-coding-standard-tester": "^7.2.12", - "symplify/package-builder": "^7.2.12" + "symplify/easy-coding-standard-tester": "^7.3.10", + "symplify/package-builder": "^7.3.10" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { "psr-4": { "Symplify\\CodingStandard\\": "src", - "Symplify\\CodingStandard\\TokenRunner\\": "packages/token-runner/src" + "Symplify\\CodingStandard\\CognitiveComplexity\\": "packages/cognitive-complexity/src", + "Symplify\\CodingStandard\\ObjectCalisthenics\\": "packages/object-calisthenics/src" } }, "notification-url": "https://packagist.org/downloads/", @@ -11553,20 +11804,64 @@ "MIT" ], "description": "Set of Symplify rules for PHP_CodeSniffer and PHP CS Fixer.", - "time": "2020-04-09T23:55:34+00:00" + "time": "2020-05-07T21:45:46+00:00" }, { - "name": "symplify/easy-coding-standard", - "version": "v7.2.12", + "name": "symplify/console-color-diff", + "version": "v7.3.10", "source": { "type": "git", - "url": "https://github.com/symplify/easy-coding-standard.git", - "reference": "8b62588f3f7c6c2605d3d7693297ebed9b4956e5" + "url": "https://github.com/symplify/console-color-diff.git", + "reference": "c3de6ca7896300fd3daaf743ab7e1a92d85df484" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/8b62588f3f7c6c2605d3d7693297ebed9b4956e5", - "reference": "8b62588f3f7c6c2605d3d7693297ebed9b4956e5", + "url": "https://api.github.com/repos/symplify/console-color-diff/zipball/c3de6ca7896300fd3daaf743ab7e1a92d85df484", + "reference": "c3de6ca7896300fd3daaf743ab7e1a92d85df484", + "shasum": "" + }, + "require": { + "nette/utils": "^3.0", + "php": "^7.2", + "sebastian/diff": "^3.0|^4.0", + "symfony/console": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/http-kernel": "^4.4|^5.0", + "symplify/package-builder": "^7.3.10" + }, + "require-dev": { + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symplify\\ConsoleColorDiff\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Package to print diffs in console with colors", + "time": "2020-05-07T21:45:46+00:00" + }, + { + "name": "symplify/easy-coding-standard", + "version": "v7.3.10", + "source": { + "type": "git", + "url": "https://github.com/symplify/easy-coding-standard.git", + "reference": "d2b0e44786345163000329958eafe8c0f1312b4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/easy-coding-standard/zipball/d2b0e44786345163000329958eafe8c0f1312b4c", + "reference": "d2b0e44786345163000329958eafe8c0f1312b4c", "shasum": "" }, "require": { @@ -11578,7 +11873,7 @@ "ocramius/package-versions": "^1.4", "php": "^7.2", "psr/simple-cache": "^1.0", - "slevomat/coding-standard": "^6.0", + "slevomat/coding-standard": "^6.3.2", "squizlabs/php_codesniffer": "^3.5", "symfony/cache": "^4.4|^5.0", "symfony/config": "^4.4|^5.0", @@ -11587,19 +11882,17 @@ "symfony/finder": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", "symfony/yaml": "^4.4|^5.0", - "symplify/auto-bind-parameter": "^7.2.12", - "symplify/autowire-array-parameter": "^7.2.12", - "symplify/coding-standard": "^7.2.12", - "symplify/package-builder": "^7.2.12", - "symplify/set-config-resolver": "^7.2.12", - "symplify/smart-file-system": "^7.2.12" - }, - "replace": { - "symfony/polyfill-php70": "*" + "symplify/auto-bind-parameter": "^7.3.10", + "symplify/autowire-array-parameter": "^7.3.10", + "symplify/coding-standard": "^7.3.10", + "symplify/console-color-diff": "^7.3.10", + "symplify/package-builder": "^7.3.10", + "symplify/set-config-resolver": "^7.3.10", + "symplify/smart-file-system": "^7.3.10" }, "require-dev": { "phpunit/phpunit": "^8.5|^9.0", - "symplify/easy-coding-standard-tester": "^7.2.12" + "symplify/easy-coding-standard-tester": "^7.3.10" }, "bin": [ "bin/ecs" @@ -11607,7 +11900,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -11624,20 +11917,20 @@ "MIT" ], "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", - "time": "2020-04-09T23:55:34+00:00" + "time": "2020-05-07T21:45:46+00:00" }, { "name": "symplify/package-builder", - "version": "v7.2.12", + "version": "v7.3.10", "source": { "type": "git", "url": "https://github.com/symplify/package-builder.git", - "reference": "95f3ac3cccc6e247d71c5830879c25c8207b0297" + "reference": "a584e97915d1ddc1d1b3de0dece2377657617de7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/package-builder/zipball/95f3ac3cccc6e247d71c5830879c25c8207b0297", - "reference": "95f3ac3cccc6e247d71c5830879c25c8207b0297", + "url": "https://api.github.com/repos/symplify/package-builder/zipball/a584e97915d1ddc1d1b3de0dece2377657617de7", + "reference": "a584e97915d1ddc1d1b3de0dece2377657617de7", "shasum": "" }, "require": { @@ -11657,7 +11950,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -11670,20 +11963,61 @@ "MIT" ], "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", - "time": "2020-04-07T13:55:45+00:00" + "time": "2020-05-04T10:14:30+00:00" }, { - "name": "symplify/set-config-resolver", - "version": "v7.2.12", + "name": "symplify/phpstan-extensions", + "version": "v7.3.10", "source": { "type": "git", - "url": "https://github.com/symplify/set-config-resolver.git", - "reference": "275e882c5cb175bbb9520c652b9d25a23a678d30" + "url": "https://github.com/symplify/phpstan-extensions.git", + "reference": "4f7a4c0cf0f460af38544731f0df8ab842847bcb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/set-config-resolver/zipball/275e882c5cb175bbb9520c652b9d25a23a678d30", - "reference": "275e882c5cb175bbb9520c652b9d25a23a678d30", + "url": "https://api.github.com/repos/symplify/phpstan-extensions/zipball/4f7a4c0cf0f460af38544731f0df8ab842847bcb", + "reference": "4f7a4c0cf0f460af38544731f0df8ab842847bcb", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^2.16", + "nette/utils": "^3.0", + "php": "^7.2", + "phpstan/phpstan": "^0.12.23", + "symplify/coding-standard": "^7.3.10", + "symplify/package-builder": "^7.3.10", + "symplify/smart-file-system": "^7.3.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symplify\\PHPStanExtensions\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "'stats' and 'ignore' error format, solid Symfony SplFileInfo and other useful extensions for PHPStan.", + "time": "2020-05-07T21:45:46+00:00" + }, + { + "name": "symplify/set-config-resolver", + "version": "v7.3.10", + "source": { + "type": "git", + "url": "https://github.com/symplify/set-config-resolver.git", + "reference": "5443b43175cdb6927692517d9166a1f1c4ea37ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symplify/set-config-resolver/zipball/5443b43175cdb6927692517d9166a1f1c4ea37ad", + "reference": "5443b43175cdb6927692517d9166a1f1c4ea37ad", "shasum": "" }, "require": { @@ -11692,7 +12026,7 @@ "symfony/console": "^4.4|^5.0", "symfony/filesystem": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", - "symplify/smart-file-system": "^7.2.12" + "symplify/smart-file-system": "^7.3.10" }, "require-dev": { "phpunit/phpunit": "^8.5|^9.0" @@ -11700,7 +12034,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -11713,20 +12047,20 @@ "MIT" ], "description": "Resolve config and sets from configs and cli opptions for CLI applications", - "time": "2020-04-09T23:55:34+00:00" + "time": "2020-05-07T21:45:46+00:00" }, { "name": "symplify/smart-file-system", - "version": "v7.2.12", + "version": "v7.3.10", "source": { "type": "git", "url": "https://github.com/symplify/smart-file-system.git", - "reference": "c7496682de3aeb006ef360431ba5d04ed262f802" + "reference": "27087a6eec9cd9fb603cdb4551f28dd8c7b0a2fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symplify/smart-file-system/zipball/c7496682de3aeb006ef360431ba5d04ed262f802", - "reference": "c7496682de3aeb006ef360431ba5d04ed262f802", + "url": "https://api.github.com/repos/symplify/smart-file-system/zipball/27087a6eec9cd9fb603cdb4551f28dd8c7b0a2fd", + "reference": "27087a6eec9cd9fb603cdb4551f28dd8c7b0a2fd", "shasum": "" }, "require": { @@ -11737,13 +12071,13 @@ }, "require-dev": { "nette/finder": "^2.5", - "phpstan/phpstan": "^0.12.10", + "phpstan/phpstan": "^0.12.23", "phpunit/phpunit": "^8.5|^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -11756,20 +12090,20 @@ "MIT" ], "description": "Sanitized FileInfo with safe getRealPath() and other handy methods", - "time": "2020-03-18T23:14:48+00:00" + "time": "2020-05-07T17:06:47+00:00" }, { "name": "vimeo/psalm", - "version": "3.10.1", + "version": "3.11.2", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "eeed5ecccc10131397f0eb7ee6da810c0be3a7fc" + "reference": "d470903722cfcbc1cd04744c5491d3e6d13ec3d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/eeed5ecccc10131397f0eb7ee6da810c0be3a7fc", - "reference": "eeed5ecccc10131397f0eb7ee6da810c0be3a7fc", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d470903722cfcbc1cd04744c5491d3e6d13ec3d9", + "reference": "d470903722cfcbc1cd04744c5491d3e6d13ec3d9", "shasum": "" }, "require": { @@ -11784,7 +12118,7 @@ "ext-tokenizer": "*", "felixfbecker/advanced-json-rpc": "^3.0.3", "felixfbecker/language-server-protocol": "^1.4", - "netresearch/jsonmapper": "^1.0", + "netresearch/jsonmapper": "^1.0 || ^2.0", "nikic/php-parser": "^4.3", "ocramius/package-versions": "^1.2", "openlss/lib-array2xml": "^1.0", @@ -11798,13 +12132,15 @@ "psalm/psalm": "self.version" }, "require-dev": { + "amphp/amp": "^2.4.2", "bamarni/composer-bin-plugin": "^1.2", "brianium/paratest": "^4.0.0", "ext-curl": "*", + "php-coveralls/php-coveralls": "^2.2", "phpmyadmin/sql-parser": "5.1.0", "phpspec/prophecy": ">=1.9.0", - "phpunit/phpunit": "^7.5.16 || ^8.5", - "psalm/plugin-phpunit": "^0.9", + "phpunit/phpunit": "^7.5.16 || ^8.5 || ^9.0", + "psalm/plugin-phpunit": "^0.10", "slevomat/coding-standard": "^5.0", "squizlabs/php_codesniffer": "^3.5", "symfony/process": "^4.3" @@ -11852,7 +12188,7 @@ "inspection", "php" ], - "time": "2020-03-23T11:40:30+00:00" + "time": "2020-04-13T12:47:11+00:00" }, { "name": "webmozart/glob", diff --git a/config/packages/fos_ckeditor.yaml b/config/packages/fos_ckeditor.yaml index 2d61863b..11103ccd 100644 --- a/config/packages/fos_ckeditor.yaml +++ b/config/packages/fos_ckeditor.yaml @@ -17,7 +17,19 @@ fos_ck_editor: extraPlugins: "markdown" height: 60 toolbar: description_toolbar - + label_config: + height: 100 + enterMode: 2 + toolbar: label_toolbar + extraPlugins: ["partdb_label", "showprotected"] + allowedContent: true + font_names: > + DejaVu Sans Mono/DejaVu Sans Mono; + DejaVu Sans/DejaVu Sans; + DejaVu Serif/DejaVu Serif; + Helvetica/Helvetica, Arial, sans-serif; + Times New Roman/Times New Roman, Times, serif; + Courier New/Courier New, Courier, monospace; plugins: bbcode: @@ -29,10 +41,25 @@ fos_ck_editor: specialchar: path: "ckeditor/plugins/specialchar" filename: "plugin.js" + partdb_label: + path: "ckeditor/plugins/partdb_label/" + filename: "plugin.js" + showprotected: + path: "ckeditor/plugins/showprotected/" + filename: "plugin.js" toolbars: configs: + label_toolbar: + - [ 'Bold', 'Italic', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] + - ['JustifyLeft', 'JustifyCenter', 'JustifyRight'] + - ["SpecialChar"] + - ["Source"] + - "/" + - ['Format', 'FontSize', 'Font'] + - ['Table', 'HorizontalRule'] + - ['Placeholders'] description_toolbar: - [ 'Bold', 'Italic', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] - ["SpecialChar"] diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 8203eb8f..9a114dc0 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -15,3 +15,4 @@ twig: allow_email_pw_reset: '%allow_email_pw_reset%' locale_menu: '%locale_menu%' attachment_manager: '@App\Services\Attachments\AttachmentManager' + label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper' diff --git a/config/permissions.yaml b/config/permissions.yaml index bd5b8181..d1d0f94a 100644 --- a/config/permissions.yaml +++ b/config/permissions.yaml @@ -291,6 +291,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co timetravel: label: "perm.tools.timeTravel" bit: 14 + label_scanner: + label: "perm.tools.label_scanner" + bit: 16 groups: label: "perm.groups" @@ -466,11 +469,33 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co edit_options: label: "perm.self.edit_options" bit: 2 - delete_profiles: - label: "perm.self.delete_profiles" - bit: 4 + alsoSet: ['create_labels'] + read_profiles: + label: "perm.self.read_profiles" + bit: 10 edit_profiles: label: "perm.self.edit_profiles" bit: 6 + alsoSet: ['read_profiles'] + create_profiles: + label: "perm.self.create_profiles" + bit: 8 + alsoSet: ['read_profiles', 'edit_profiles'] + delete_profiles: + label: "perm.self.delete_profiles" + bit: 4 + alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles'] + use_twig: + label: "perm.labels.use_twig" + bit: 12 + alsoSet: ['create_labels', 'edit_options'] + show_history: + label: "perm.show_history" + bit: 14 + alsoSet: ['read_profiles'] + revert_element: + label: "perm.revert_elements" + bit: 16 + alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles'] diff --git a/config/routes.yaml b/config/routes.yaml index a8c925b7..6de247bf 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -3,6 +3,11 @@ # controller: App\Controller\DefaultController::index # Redirect every url without an locale to the locale of the user/the global base locale + +scan_qr: + path: /scan/{type}/{id} + controller: App\Controller\ScanController:scanQRCode + redirector: path: /{url} requirements: diff --git a/config/services.yaml b/config/services.yaml index 24cacd4e..fdf9519b 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -35,6 +35,11 @@ services: bool $gpdr_compliance : '%gpdr_compliance%' bool $kernel_debug: '%kernel.debug%' string $kernel_cache_dir: '%kernel.cache_dir%' + string $partdb_title: '%partdb_title%' + + _instanceof: + App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface: + tags: ['app.label_placeholder_provider'] # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name @@ -163,6 +168,15 @@ services: $code_length: 8 $code_count: 15 + App\Services\LabelSystem\LabelTextReplacer: + arguments: + $providers: !tagged_iterator 'app.label_placeholder_provider' + App\Services\TranslationExtractor\PermissionExtractor: tags: - - { name: 'translation.extractor', alias: 'permissionExtractor'} \ No newline at end of file + - { name: 'translation.extractor', alias: 'permissionExtractor'} + + # PartLotProvider must be invoked before all other providers, so it can override %%NAME%% placeholder + App\Services\LabelSystem\PlaceholderProviders\PartLotProvider: + tags: + - { name: 'app.label_placeholder_provider', priority: 10} \ No newline at end of file diff --git a/package.json b/package.json index 5f7776e9..41e6746d 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@types/jquery.form": "^3.26.30", "@types/marked": "^0.7.2", "@types/typeahead": "^0.11.32", + "@zxing/library": "^0.16.3", "bootbox": "^5.4.0", "bootstrap-fileinput": "^5.0.1", "bootstrap-select": "^1.13.8", diff --git a/public/ckeditor/plugins/partdb_label/icons/hidpi/placeholder.png b/public/ckeditor/plugins/partdb_label/icons/hidpi/placeholder.png new file mode 100644 index 00000000..5289e6da Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/icons/hidpi/placeholder.png differ diff --git a/public/ckeditor/plugins/partdb_label/icons/placeholder.png b/public/ckeditor/plugins/partdb_label/icons/placeholder.png new file mode 100644 index 00000000..5641ad1d Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/icons/placeholder.png differ diff --git a/public/ckeditor/plugins/partdb_label/lang/de.js b/public/ckeditor/plugins/partdb_label/lang/de.js new file mode 100644 index 00000000..2a5818ba --- /dev/null +++ b/public/ckeditor/plugins/partdb_label/lang/de.js @@ -0,0 +1,69 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +CKEDITOR.plugins.setLang( 'partdb_label', 'de', { + title: 'Platzhalter einfügen', + label: 'Platzhalter', + 'section.global': 'Global', + 'section.part': 'Bauteil', + 'section.part_lot': 'Bauteilbestand', + 'section.storelocation': 'Lagerort', + 'part.id': 'Datenbank ID', + 'part.name': 'Bauteilename', + 'part.category': 'Kategorie', + 'part.category_full': 'Kategorie (Ganzer Pfad)', + 'part.manufacturer': 'Hersteller', + 'part.manufacturer_full': 'Hersteller (Ganzer Pfad)', + 'part.footprint': 'Footprint', + 'part.footprint_full': 'Footprint (Ganzer Pfad)', + 'part.mass': 'Gewicht', + 'part.tags': 'Tags', + 'part.mpn': 'Herstellernummer (MPN)', + 'part.status': 'Herstellungsstatus', + 'part.description': 'Beschreibung', + 'part.description_t': 'Beschreibung (Text)', + 'part.comment': 'Kommentar', + 'part.comment_t': 'Kommentar (Text)', + 'part.last_modified': 'Änderungsdatum', + 'part.creation_date': 'Erstellungsdatum', + 'global.username': 'Benutzername', + 'global.username_full': 'Benutzername (inklusive Name)', + 'global.datetime': 'Datum & Uhrzeit', + 'global.date': 'Datum', + 'global.time': 'Uhrzeit', + 'global.install_name': 'Installationsname', + 'global.type': 'Zieltyp', + 'lot.id': 'Lot ID', + 'lot.name': 'Lot Name', + 'lot.comment': 'Lot Kommentar', + 'lot.expiration_date': 'Ablaufdatum', + 'lot.amount': 'Bestandsmenge', + 'lot.location': 'Lagerort', + 'lot.location_full': 'Lagerort (Ganzer Pfad)', + + 'storelocation.id': 'Lagerort ID', + 'storelocation.name': 'Name', + 'storelocation.full_path': 'Ganzer Pfad', + 'storelocation.parent_name': 'Name des Übergeordneten Elements', + 'storelocation.parent_full_path': 'Ganzer Pfad des Übergeordneten Elements', + 'storelocation.comment': 'Kommentar', + 'storelocation.comment_t': 'Kommentar (Text)', + 'storelocation.last_modified': 'Änderungsdatum', + 'storelocation.creation_date': 'Erstellungsdatum', +} ); \ No newline at end of file diff --git a/public/ckeditor/plugins/partdb_label/lang/en.js b/public/ckeditor/plugins/partdb_label/lang/en.js new file mode 100644 index 00000000..b26e9748 --- /dev/null +++ b/public/ckeditor/plugins/partdb_label/lang/en.js @@ -0,0 +1,69 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +CKEDITOR.plugins.setLang( 'partdb_label', 'en', { + title: 'Insert Placeholders', + label: 'Placeholders', + 'section.global': 'Globals', + 'section.part': 'Part', + 'section.part_lot': 'Part lot', + 'section.storelocation': 'Storage location', + 'part.id': 'Database ID', + 'part.name': 'Part name', + 'part.category': 'Category', + 'part.category_full': 'Category (Full path)', + 'part.manufacturer': 'Manufacturer', + 'part.manufacturer_full': 'Manufacturer (Full path)', + 'part.footprint': 'Footprint', + 'part.footprint_full': 'Footprint (Full path)', + 'part.mass': 'Mass', + 'part.tags': 'Tags', + 'part.mpn': 'Manufacturer Product Number (MPN)', + 'part.status': 'Manufacturing status', + 'part.description': 'Description', + 'part.description_t': 'Description (Text)', + 'part.comment': 'Comment', + 'part.comment_t': 'Comment (Text)', + 'part.last_modified': 'Last modified datetime', + 'part.creation_date': 'Creation datetime', + 'global.username': 'Username', + 'global.username_full': 'Username (including name)', + 'global.datetime': 'Current datetime', + 'global.date': 'Current date', + 'global.time': 'Current time', + 'global.install_name': 'Instance name', + 'global.type': 'Target type', + 'lot.id': 'Lot ID', + 'lot.name': 'Lot name', + 'lot.comment': 'Lot comment', + 'lot.expiration_date': 'Expiration date', + 'lot.amount': 'Lot amount', + 'lot.location': 'Storage location', + 'lot.location_full': 'Storage location (Full path)', + + 'storelocation.id': 'Location ID', + 'storelocation.name': 'Name', + 'storelocation.full_path': 'Full path', + 'storelocation.parent_name': 'Parent name', + 'storelocation.parent_full_path': 'Parent full path', + 'storelocation.comment': 'Comment', + 'storelocation.comment_t': 'Comment (Text)', + 'storelocation.last_modified': 'Last modified datetime', + 'storelocation.creation_date': 'Createion datetime', +} ); \ No newline at end of file diff --git a/public/ckeditor/plugins/partdb_label/plugin.js b/public/ckeditor/plugins/partdb_label/plugin.js new file mode 100644 index 00000000..c039fed1 --- /dev/null +++ b/public/ckeditor/plugins/partdb_label/plugin.js @@ -0,0 +1,209 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* + * Placeholder logic inspired by CKEDITOR placeholder plugin (https://github.com/ckeditor/ckeditor4/blob/master/plugins/placeholder/plugin.js) + */ + +const PLACEHOLDERS = { + part: { + label: 'section.part', + entries: [ + ['[[ID]]', 'part.id'], + ['[[NAME]]', 'part.name'], + ['[[CATEGORY]]', 'part.category'], + ['[[CATEGORY_FULL]]', 'part.category_full'], + ['[[MANUFACTURER]]', 'part.manufacturer'], + ['[[MANUFACTURER_FULL]]', 'part.manufacturer_full'], + ['[[FOOTPRINT]]', 'part.footprint'], + ['[[FOOTPRINT_FULL]]', 'part.footprint'], + ['[[MASS]]', 'part.mass'], + ['[[MPN]]', 'part.mpn'], + ['[[TAGS]]', 'part.tags'], + ['[[M_STATUS]]', 'part.status'], + ['[[DESCRIPTION]]', 'part.description'], + ['[[DESCRIPTION_T]]', 'part.description_t'], + ['[[COMMENT]]', 'part.comment'], + ['[[COMMENT_T]]', 'part.comment_t'], + ['[[LAST_MODIFIED]]', 'part.last_modified'], + ['[[CREATION_DATE]]', 'part.creation_date'], + ] + }, + part_lot: { + label: 'section.part_lot', + entries: [ + ['[[LOT_ID]]', 'lot.id'], + ['[[LOT_NAME]]', 'lot.name'], + ['[[LOT_COMMENT]]', 'lot.comment'], + ['[[EXPIRATION_DATE]]', 'lot.expiration_date'], + ['[[AMOUNT]]', 'lot.amount'], + ['[[LOCATION]]', 'lot.location'], + ['[[LOCATION_FULL]]', 'lot.location_full'], + ] + }, + storelocation: { + label: 'section.storelocation', + entries: [ + ['[[ID]]', 'storelocation.id'], + ['[[NAME]]', 'storelocation.name'], + ['[[FULL_PATH]]', 'storelocation.full_path'], + ['[[PARENT]]', 'storelocation.parent_name'], + ['[[PARENT_FULL_PATH]]', 'storelocation.parent_full_path'], + ['[[COMMENT]]', 'storelocation.comment'], + ['[[COMMENT_T]]', 'storelocation.comment_t'], + ['[[LAST_MODIFIED]]', 'storelocation.last_modified'], + ['[[CREATION_DATE]]', 'storelocation.creation_date'], + ] + }, + global: { + label: 'section.global', + entries: [ + ['[[USERNAME]]', 'global.username'], + ['[[USERNAME_FULL]]', 'global.username_full'], + ['[[DATETIME]]', 'global.datetime'], + ['[[DATE]]', 'global.date'], + ['[[TIME]]', 'global.time'], + ['[[INSTALL_NAME]]', 'global.install_name'], + ['[[TYPE]]', 'global.type'] + ], + }, +}; + +function findLabelForPlaceholder(search) +{ + for (var group in PLACEHOLDERS) { + var entries = PLACEHOLDERS[group].entries; + for (var placeholder in entries) { + if (entries[placeholder][0] == search) { + return entries[placeholder][1]; + } + } + } +} + +//Dont escape text inside of twig blocks +CKEDITOR.config.protectedSource.push(/\{\{[\s\S]*?\}\}/g); +CKEDITOR.config.protectedSource.push(/\{\%[\s\S]*?%\}/g); + +CKEDITOR.plugins.add('partdb_label', { + hidpi: true, + icons: 'placeholder', + lang: ['en', 'de'], + init: function (editor) { + var config = editor.config, + lang = editor.lang.partdb_label; + + var pluginDirectory = this.path; + editor.addContentsCss( pluginDirectory + 'styles/style.css' ); + + // Put ur init code here. + editor.widgets.add( 'placeholder', { + // Widget code. + pathName: lang.label, + // We need to have wrapping element, otherwise there are issues in + // add dialog. + template: '[[]]', + + downcast: function() { + return new CKEDITOR.htmlParser.text( '[[' + this.data.name + ']]' ); + }, + + init: function() { + // Note that placeholder markup characters are stripped for the name. + this.setData( 'name', this.element.getText().slice( 2, -2 ) ); + }, + + data: function() { + this.element.setText( '[[' + this.data.name + ']]' ); + var title = findLabelForPlaceholder( '[[' + this.data.name + ']]'); + if (lang[title]) { + title = lang[title]; + } + this.element.setAttribute('title', title); + }, + + getLabel: function() { + return this.editor.lang.widget.label.replace( /%1/, this.data.name + ' ' + this.pathName ); + } + } ); + + editor.ui.addRichCombo('Placeholders', { + label: lang.label, + title: lang.title, + allowedContent: 'abbr[title]', + panel: { + css: [ CKEDITOR.skin.getPath( 'editor' ) ].concat( config.contentsCss ), + multiSelect: false, + attributes: { 'aria-label': lang.title } + }, + init: function () { + for (var group in PLACEHOLDERS) { + var localized_group = PLACEHOLDERS[group].label; + if (lang[localized_group]) { + localized_group = lang[localized_group]; + } + this.startGroup(localized_group); + var entries = PLACEHOLDERS[group].entries; + for (var placeholder in entries) { + var localized_placeholder = entries[placeholder][1]; + if (lang[localized_placeholder]) { + localized_placeholder = lang[localized_placeholder]; + } + this.add(entries[placeholder][0], localized_placeholder, entries[placeholder][0]) + } + } + }, + onClick: function(value) { + editor.focus(); + editor.fire('saveSnapshot'); + editor.insertText(value); + } + }); + }, + afterInit: function( editor ) { + var placeholderReplaceRegex = /\[\[([^\[\]])+\]\]/g; + + editor.dataProcessor.dataFilter.addRules({ + text: function (text, node) { + var dtd = node.parent && CKEDITOR.dtd[node.parent.name]; + + // Skip the case when placeholder is in elements like or <textarea> + // but upcast placeholder in custom elements (no DTD). + if (dtd && !dtd.span) + return; + + return text.replace(placeholderReplaceRegex, function (match) { + // Creating widget code. + var widgetWrapper = null, + innerElement = new CKEDITOR.htmlParser.element('span', { + 'class': 'cke_placeholder' + }); + + // Adds placeholder identifier as innertext. + innerElement.add(new CKEDITOR.htmlParser.text(match)); + widgetWrapper = editor.widgets.wrapElement(innerElement, 'placeholder'); + + // Return outerhtml of widget wrapper so it will be placed + // as replacement. + return widgetWrapper.getOuterHtml(); + }); + } + }); + } +}); \ No newline at end of file diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-Bold.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-Bold.woff new file mode 100644 index 00000000..77ea1108 Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-Bold.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-BoldOblique.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-BoldOblique.woff new file mode 100644 index 00000000..501b77e4 Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-BoldOblique.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-Oblique.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-Oblique.woff new file mode 100644 index 00000000..d01f9098 Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSans-Oblique.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSans.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSans.woff new file mode 100644 index 00000000..0c0b50cb Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSans.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-Bold.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-Bold.woff new file mode 100644 index 00000000..abb18d0c Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-Bold.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-BoldOblique.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-BoldOblique.woff new file mode 100644 index 00000000..abb1c2b3 Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-BoldOblique.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-Oblique.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-Oblique.woff new file mode 100644 index 00000000..6493cb63 Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono-Oblique.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono.woff new file mode 100644 index 00000000..629c3523 Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-Bold.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-Bold.woff new file mode 100644 index 00000000..0ec61b37 Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-Bold.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-BoldItalic.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-BoldItalic.woff new file mode 100644 index 00000000..45ce687b Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-BoldItalic.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-Italic.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-Italic.woff new file mode 100644 index 00000000..51ee3548 Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif-Italic.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif.woff b/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif.woff new file mode 100644 index 00000000..abf7b5cb Binary files /dev/null and b/public/ckeditor/plugins/partdb_label/styles/DejaVuSerif.woff differ diff --git a/public/ckeditor/plugins/partdb_label/styles/style.css b/public/ckeditor/plugins/partdb_label/styles/style.css new file mode 100644 index 00000000..9d4e566c --- /dev/null +++ b/public/ckeditor/plugins/partdb_label/styles/style.css @@ -0,0 +1,117 @@ +/* + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + + +@font-face { + font-family: "DejaVu Sans Mono"; + font-style: normal; + font-weight: normal; + src: local(DejaVu Sans Mono), local(DejaVuSansMono), + url(DejaVuSansMono.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Sans Mono"; + font-style: normal; + font-weight: bold; + src: local(DejaVu Sans Mono Bold), local(DejaVuSansMono-Bold), + url(DejaVuSansMono-Bold.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Sans Mono"; + font-style: oblique; + font-weight: bold; + src: local(DejaVu Sans Mono Bold Oblique), local(DejaVuSansMono-BoldOblique), + url(DejaVuSansMono-BoldOblique.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Sans Mono"; + font-style: oblique; + font-weight: normal; + src: local(DejaVu Sans Mono Oblique), local(DejaVuSansMono-Oblique), + url(DejaVuSansMono-Oblique.woff) format("woff"); +} + +@font-face { + font-family: "DejaVu Sans"; + font-style: normal; + font-weight: normal; + src: local(DejaVu Sans), local(DejaVuSans), + url(DejaVuSans.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Sans"; + font-style: normal; + font-weight: bold; + src: local(DejaVu Sans Bold), local(DejaVuSans-Bold), + url(DejaVuSans-Bold.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Sans"; + font-style: oblique; + font-weight: bold; + src: local(DejaVu Sans Bold Oblique), local(DejaVuSans-BoldOblique), + url(DejaVuSans-BoldOblique.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Sans"; + font-style: oblique; + font-weight: normal; + src: local(DejaVu Sans Oblique), local(DejaVuSans-Oblique), + url(DejaVuSans-Oblique.woff) format("woff"); +} + +@font-face { + font-family: "DejaVu Serif"; + font-style: normal; + font-weight: normal; + src: local(DejaVu Serif), local(DejaVuSerif), + url(DejaVuSerif.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Serif"; + font-style: normal; + font-weight: bold; + src: local(DejaVu Serif Bold), local(DejaVuSerif-Bold), + url(DejaVuSerif-Bold.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Serif"; + font-style: italic; + font-weight: bold; + src: local(DejaVu Serif Bold Italic), local(DejaVuSerif-BoldItalic), + url(DejaVuSerif-BoldItalic.woff) format("woff"); +} +@font-face { + font-family: "DejaVu Serif"; + font-style: italic; + font-weight: normal; + src: local(DejaVu Serif Italic), local(DejaVuSerif-Italic), + url(DejaVuSerif-Italic.woff) format("woff"); +} + + +.cke_placeholder { + background-color:#ff0 +} + +.cke_editable { + font-family: "DejaVu Sans Mono"; + font-size: 9pt; + line-height: 1.5; +} diff --git a/public/ckeditor/plugins/showprotected/dialogs/protected.js b/public/ckeditor/plugins/showprotected/dialogs/protected.js new file mode 100644 index 00000000..9c7ede3d --- /dev/null +++ b/public/ckeditor/plugins/showprotected/dialogs/protected.js @@ -0,0 +1,52 @@ + +CKEDITOR.dialog.add( 'showProtectedDialog', function( editor ) { + + return { + title: 'Edit Protected Source', + minWidth: 300, + minHeight: 60, + onOk: function() { + var newSourceValue = this.getContentElement( 'info', 'txtProtectedSource' ).getValue(); + + var encodedSourceValue = CKEDITOR.plugins.showprotected.encodeProtectedSource( newSourceValue ); + + this._.selectedElement.setAttribute('data-cke-realelement', encodedSourceValue); + this._.selectedElement.setAttribute('title', newSourceValue); + this._.selectedElement.setAttribute('alt', newSourceValue); + }, + + onHide: function() { + delete this._.selectedElement; + }, + + onShow: function() { + this._.selectedElement = editor.getSelection().getSelectedElement(); + + var decodedSourceValue = CKEDITOR.plugins.showprotected.decodeProtectedSource( this._.selectedElement.getAttribute('data-cke-realelement') ); + + this.setValueOf( 'info', 'txtProtectedSource', decodedSourceValue ); + }, + contents: [ + { + id: 'info', + label: 'Edit Protected Source', + accessKey: 'I', + elements: [ + { + type: 'text', + id: 'txtProtectedSource', + label: 'Value', + required: true, + validate: function() { + if ( !this.getValue() ) { + alert( 'The value cannot be empty' ); + return false; + } + return true; + } + } + ] + } + ] + }; +} ); \ No newline at end of file diff --git a/public/ckeditor/plugins/showprotected/images/code.gif b/public/ckeditor/plugins/showprotected/images/code.gif new file mode 100644 index 00000000..912517b8 Binary files /dev/null and b/public/ckeditor/plugins/showprotected/images/code.gif differ diff --git a/public/ckeditor/plugins/showprotected/plugin.js b/public/ckeditor/plugins/showprotected/plugin.js new file mode 100644 index 00000000..a3e88132 --- /dev/null +++ b/public/ckeditor/plugins/showprotected/plugin.js @@ -0,0 +1,105 @@ +/* + * "showprotected" CKEditor plugin + * + * Created by Matthew Lieder (https://github.com/IGx89) + * + * Licensed under the MIT, GPL, LGPL and MPL licenses + * + * Icon courtesy of famfamfam: http://www.famfamfam.com/lab/icons/mini/ + */ + +// TODO: configuration settings +// TODO: show the actual text inline, not just an icon? +// TODO: improve copy/paste behavior (tooltip is wrong after paste) + +CKEDITOR.plugins.add( 'showprotected', { + requires: 'dialog,fakeobjects', + onLoad: function() { + // Add the CSS styles for protected source placeholders. + var iconPath = CKEDITOR.getUrl( this.path + 'images' + '/code.gif' ), + baseStyle = 'background:url(' + iconPath + ') no-repeat %1 center;border:1px dotted #00f;background-size:16px;'; + + var template = '.%2 img.cke_protected' + + '{' + + baseStyle + + 'width:16px;' + + 'min-height:15px;' + + // The default line-height on IE. + 'height:1.15em;' + + // Opera works better with "middle" (even if not perfect) + 'vertical-align:' + ( CKEDITOR.env.opera ? 'middle' : 'text-bottom' ) + ';' + + '}'; + + // Styles with contents direction awareness. + function cssWithDir( dir ) { + return template.replace( /%1/g, dir == 'rtl' ? 'right' : 'left' ).replace( /%2/g, 'cke_contents_' + dir ); + } + + CKEDITOR.addCss( cssWithDir( 'ltr' ) + cssWithDir( 'rtl' ) ); + }, + + init: function( editor ) { + CKEDITOR.dialog.add( 'showProtectedDialog', this.path + 'dialogs/protected.js' ); + + editor.on( 'doubleclick', function( evt ) { + var element = evt.data.element; + + if ( element.is( 'img' ) && element.hasClass( 'cke_protected' ) ) { + evt.data.dialog = 'showProtectedDialog'; + } + } ); + }, + + afterInit: function( editor ) { + // Register a filter to displaying placeholders after mode change. + + var dataProcessor = editor.dataProcessor, + dataFilter = dataProcessor && dataProcessor.dataFilter; + + if ( dataFilter ) { + dataFilter.addRules( { + comment: function( commentText, commentElement ) { + if(commentText.indexOf(CKEDITOR.plugins.showprotected.protectedSourceMarker) == 0) { + commentElement.attributes = []; + var fakeElement = editor.createFakeParserElement( commentElement, 'cke_protected', 'protected' ); + + var cleanedCommentText = CKEDITOR.plugins.showprotected.decodeProtectedSource( commentText ); + fakeElement.attributes.title = fakeElement.attributes.alt = cleanedCommentText; + + return fakeElement; + } + + return null; + } + } ); + } + } +} ); + +/** + * Set of showprotected plugin's helpers. + * + * @class + * @singleton + */ +CKEDITOR.plugins.showprotected = { + + protectedSourceMarker: '{cke_protected}', + + decodeProtectedSource: function( protectedSource ) { + if(protectedSource.indexOf('%3C!--') == 0) { + return decodeURIComponent(protectedSource).replace( /<!--\{cke_protected\}([\s\S]+?)-->/g, function( match, data ) { + return decodeURIComponent( data ); + } ); + } else { + return decodeURIComponent(protectedSource.substr(CKEDITOR.plugins.showprotected.protectedSourceMarker.length)); + } + }, + + encodeProtectedSource: function( protectedSource ) { + return '<!--' + CKEDITOR.plugins.showprotected.protectedSourceMarker + + encodeURIComponent( protectedSource ).replace( /--/g, '%2D%2D' ) + + '-->'; + } + +}; \ No newline at end of file diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index 43fb8e6e..366842eb 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -45,6 +45,8 @@ namespace App\Controller\AdminPages; use App\DataTables\LogDataTable; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\LabelSystem\LabelProfile; +use App\Entity\Parts\PartLot; use App\Entity\UserSystem\User; use App\Events\SecurityEvent; use App\Events\SecurityEvents; @@ -54,6 +56,8 @@ use App\Form\AdminPages\MassCreationForm; use App\Services\Attachments\AttachmentSubmitHandler; use App\Services\EntityExporter; use App\Services\EntityImporter; +use App\Services\LabelSystem\Barcodes\BarcodeExampleElementsGenerator; +use App\Services\LabelSystem\LabelGenerator; use App\Services\LogSystem\EventCommentHelper; use App\Services\LogSystem\HistoryHelper; use App\Services\LogSystem\TimeTravel; @@ -92,11 +96,14 @@ abstract class BaseAdminController extends AbstractController protected $dataTableFactory; /** @var EventDispatcher */ protected $eventDispatcher; + protected $labelGenerator; + protected $barcodeExampleGenerator; public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder, AttachmentSubmitHandler $attachmentSubmitHandler, EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel, - DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher) + DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, BarcodeExampleElementsGenerator $barcodeExampleGenerator, + LabelGenerator $labelGenerator) { if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) { throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!'); @@ -118,6 +125,8 @@ abstract class BaseAdminController extends AbstractController $this->timeTravel = $timeTravel; $this->dataTableFactory = $dataTableFactory; $this->eventDispatcher = $eventDispatcher; + $this->barcodeExampleGenerator = $barcodeExampleGenerator; + $this->labelGenerator = $labelGenerator; } protected function _edit(AbstractNamedDBElement $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response @@ -156,11 +165,22 @@ abstract class BaseAdminController extends AbstractController $table = null; } - $form = $this->createForm($this->form_class, $entity, [ + $form_options = [ 'attachment_class' => $this->attachment_class, 'parameter_class' => $this->parameter_class, 'disabled' => null !== $timeTravel_timestamp ? true : null, - ]); + ]; + + //Disable editing of options, if user is not allowed to use twig... + if ( + $entity instanceof LabelProfile + && $entity->getOptions()->getLinesMode() === 'twig' + && !$this->isGranted('@labels.use_twig') + ) { + $form_options['disable_options'] = true; + } + + $form = $this->createForm($this->form_class, $entity, $form_options); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { @@ -210,11 +230,18 @@ abstract class BaseAdminController extends AbstractController $this->addFlash('error', 'entity.edit_flash.invalid'); } + //Show preview for LabelProfile if needed. + if ($entity instanceof LabelProfile) { + $example = $this->barcodeExampleGenerator->getElement($entity->getOptions()->getSupportedElement()); + $pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example); + } + return $this->render($this->twig_template, [ 'entity' => $entity, 'form' => $form->createView(), 'route_base' => $this->route_base, 'datatable' => $table, + 'pdf_data' => $pdf_data ?? null, 'timeTravel' => $timeTravel_timestamp, ]); } diff --git a/src/Controller/AdminPages/LabelProfileController.php b/src/Controller/AdminPages/LabelProfileController.php new file mode 100644 index 00000000..a1b64058 --- /dev/null +++ b/src/Controller/AdminPages/LabelProfileController.php @@ -0,0 +1,126 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +declare(strict_types=1); + +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +namespace App\Controller\AdminPages; + +use App\Entity\Attachments\AttachmentType; +use App\Entity\Attachments\AttachmentTypeAttachment; +use App\Entity\Attachments\LabelAttachment; +use App\Entity\LabelSystem\LabelProfile; +use App\Entity\Parameters\AttachmentTypeParameter; +use App\Form\AdminPages\AttachmentTypeAdminForm; +use App\Form\AdminPages\BaseEntityAdminForm; +use App\Form\AdminPages\LabelProfileAdminForm; +use App\Services\EntityExporter; +use App\Services\EntityImporter; +use App\Services\StructuralElementRecursionHelper; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; + +/** + * @Route("/label_profile") + */ +class LabelProfileController extends BaseAdminController +{ + protected $entity_class = LabelProfile::class; + protected $twig_template = 'AdminPages/LabelProfileAdmin.html.twig'; + protected $form_class = LabelProfileAdminForm::class; + protected $route_base = 'label_profile'; + protected $attachment_class = LabelAttachment::class; + protected $parameter_class = 'not_used'; + + /** + * @Route("/{id}", name="label_profile_delete", methods={"DELETE"}) + * + * @return RedirectResponse + */ + public function delete(Request $request, LabelProfile $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse + { + return $this->_delete($request, $entity, $recursionHelper); + } + + /** + * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="label_profile_edit") + * @Route("/{id}", requirements={"id"="\d+"}) + * + * @return Response + */ + public function edit(LabelProfile $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response + { + return $this->_edit($entity, $request, $em, $timestamp); + } + + /** + * @Route("/new", name="label_profile_new") + * @Route("/{id}/clone", name="label_profile_clone") + * @Route("/") + * + * @return Response + */ + public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AttachmentType $entity = null): Response + { + return $this->_new($request, $em, $importer, $entity); + } + + /** + * @Route("/export", name="label_profile_export_all") + * + * @return Response + */ + public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response + { + return $this->_exportAll($em, $exporter, $request); + } + + /** + * @Route("/{id}/export", name="label_profile_export") + * + * @return Response + */ + public function exportEntity(LabelProfile $entity, EntityExporter $exporter, Request $request): Response + { + return $this->_exportEntity($entity, $exporter, $request); + } +} diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php new file mode 100644 index 00000000..29a564f5 --- /dev/null +++ b/src/Controller/LabelController.php @@ -0,0 +1,167 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Controller; + + +use App\Entity\Base\AbstractDBElement; +use App\Entity\Contracts\NamedElementInterface; +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\LabelSystem\LabelProfile; +use App\Entity\Parts\Part; +use App\Exceptions\TwigModeException; +use App\Form\LabelOptionsType; +use App\Form\LabelSystem\LabelDialogType; +use App\Helpers\LabelResponse; +use App\Repository\DBElementRepository; +use App\Services\ElementTypeNameGenerator; +use App\Services\LabelSystem\LabelGenerator; +use App\Services\Misc\RangeParser; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\SubmitButton; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Contracts\Translation\TranslatorInterface; + +/** + * @Route("/label") + * @package App\Controller + */ +class LabelController extends AbstractController +{ + protected $labelGenerator; + protected $em; + protected $elementTypeNameGenerator; + protected $rangeParser; + protected $translator; + + public function __construct(LabelGenerator $labelGenerator, EntityManagerInterface $em, ElementTypeNameGenerator $elementTypeNameGenerator, + RangeParser $rangeParser, TranslatorInterface $translator) + { + $this->labelGenerator = $labelGenerator; + $this->em = $em; + $this->elementTypeNameGenerator = $elementTypeNameGenerator; + $this->rangeParser = $rangeParser; + $this->translator = $translator; + } + + /** + * @Route("/dialog", name="label_dialog") + * @Route("/{profile}/dialog", name="label_dialog_profile") + */ + public function generator(Request $request, ?LabelProfile $profile = null) + { + $this->denyAccessUnlessGranted('@labels.create_labels'); + + //If we inherit a LabelProfile, the user need to have access to it... + if ($profile !== null) { + $this->denyAccessUnlessGranted('read', $profile); + } + + if ($profile) { + $label_options = $profile->getOptions(); + } else { + $label_options = new LabelOptions(); + } + + //We have to disable the options, if twig mode is selected and user is not allowed to use it. + $disable_options = $label_options->getLinesMode() === 'twig' && !$this->isGranted("@labels.use_twig"); + + $form = $this->createForm(LabelDialogType::class, null, [ + 'disable_options' => $disable_options, + ]); + + //Try to parse given target_type and target_id + $target_type = $request->query->get('target_type', null); + $target_id = $request->query->get('target_id', null); + $generate = $request->query->getBoolean('generate', false); + + if ($profile === null && is_string($target_type)) { + $label_options->setSupportedElement($target_type); + } + if (is_string($target_id)) { + $form['target_id']->setData($target_id); + } + + + $form['options']->setData($label_options); + $form->handleRequest($request); + + /** @var LabelOptions $form_options */ + $form_options = $form['options']->getData(); + + $pdf_data = null; + $filename = 'invalid.pdf'; + + //Generate PDF either when the form is submitted and valid, or the form was not submit yet, and generate is set + if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile !== null)) { + $target_id = (string) $form->get('target_id')->getData(); + $targets = $this->findObjects($form_options->getSupportedElement(), $target_id); + if (!empty($targets)) { + try { + $pdf_data = $this->labelGenerator->generateLabel($form_options, $targets); + $filename = $this->getLabelName($targets[0], $profile); + } catch (TwigModeException $exception) { + $form->get('options')->get('lines')->addError(new FormError($exception->getMessage())); + } + + } else { + //$this->addFlash('warning', 'label_generator.no_entities_found'); + $form->get('target_id')->addError( + new FormError($this->translator->trans('label_generator.no_entities_found')) + ); + } + } + + return $this->render('LabelSystem/dialog.html.twig', [ + 'form' => $form->createView(), + 'pdf_data' => $pdf_data, + 'filename' => $filename, + 'profile' => $profile, + ]); + } + + protected function getLabelName(AbstractDBElement $element, ?LabelProfile $profile = null): string + { + $ret = 'label_' . $this->elementTypeNameGenerator->getLocalizedTypeLabel($element); + $ret .= $element->getID(); + + return $ret . '.pdf'; + } + + protected function findObjects(string $type, string $ids): array + { + if(!isset(LabelGenerator::CLASS_SUPPORT_MAPPING[$type])) { + throw new \InvalidArgumentException('The given type is not known and can not be mapped to a class!'); + } + + $id_array = $this->rangeParser->parse($ids); + + /** @var DBElementRepository $repo */ + $repo = $this->em->getRepository(LabelGenerator::CLASS_SUPPORT_MAPPING[$type]); + return $repo->getElementsFromIDArray($id_array); + } +} \ No newline at end of file diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php new file mode 100644 index 00000000..3d4ce484 --- /dev/null +++ b/src/Controller/ScanController.php @@ -0,0 +1,93 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Controller; + + +use App\Form\LabelSystem\ScanDialogType; +use App\Services\LabelSystem\Barcodes\BarcodeRedirector; +use App\Services\LabelSystem\Barcodes\BarcodeNormalizer; +use Doctrine\ORM\EntityNotFoundException; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Form\FormError; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; + +/** + * @Route("/scan") + * @package App\Controller + */ +class ScanController extends AbstractController +{ + protected $barcodeParser; + protected $barcodeNormalizer; + + public function __construct(BarcodeRedirector $barcodeParser, BarcodeNormalizer $barcodeNormalizer) + { + $this->barcodeParser = $barcodeParser; + $this->barcodeNormalizer = $barcodeNormalizer; + } + + /** + * @Route("", name="scan_dialog") + */ + public function dialog(Request $request): Response + { + $this->denyAccessUnlessGranted('@tools.label_scanner'); + + $form = $this->createForm(ScanDialogType::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $input = $form['input']->getData(); + try { + [$type, $id] = $this->barcodeNormalizer->normalizeBarcodeContent($input); + try { + return $this->redirect($this->barcodeParser->getRedirectURL($type, $id)); + } catch (EntityNotFoundException $exception) { + $this->addFlash('success', 'scan.qr_not_found'); + } + } catch (\InvalidArgumentException $exception) { + $this->addFlash('error', 'scan.format_unknown'); + } + } + + return $this->render('LabelSystem/Scanner/dialog.html.twig', [ + 'form' => $form->createView(), + ]); + } + + /** + * The route definition for this action is done in routes.yaml, as it does not use the _locale prefix as the other routes + * @param string $type + * @param int $id + */ + public function scanQRCode(string $type, int $id): Response + { + try { + $this->addFlash('success', 'scan.qr_success'); + return $this->redirect($this->barcodeParser->getRedirectURL($type, $id)); + } catch (EntityNotFoundException $exception) { + $this->addFlash('success', 'scan.qr_not_found'); + return $this->redirectToRoute('homepage'); + } + } +} \ No newline at end of file diff --git a/src/DataFixtures/GroupFixtures.php b/src/DataFixtures/GroupFixtures.php index 9707ab6a..5ab20e71 100644 --- a/src/DataFixtures/GroupFixtures.php +++ b/src/DataFixtures/GroupFixtures.php @@ -82,8 +82,8 @@ class GroupFixtures extends Fixture 'suppliers' => 5461, 'manufacturers' => 5461, 'attachment_types' => 1365, - 'tools' => 1365, - 'labels' => 21, + 'tools' => 87381, + 'labels' => 87381, 'parts_category' => 5, 'parts_minamount' => 5, 'parts_lots' => 85, @@ -125,8 +125,8 @@ class GroupFixtures extends Fixture 'suppliers' => 1705, 'manufacturers' => 1705, 'attachment_types' => 681, - 'tools' => 1366, - 'labels' => 165, + 'tools' => 87382, + 'labels' => 173737, 'parts_category' => 9, 'parts_minamount' => 9, 'parts_lots' => 169, @@ -168,8 +168,8 @@ class GroupFixtures extends Fixture 'suppliers' => 5461, 'manufacturers' => 5461, 'attachment_types' => 1365, - 'tools' => 1365, - 'labels' => 85, + 'tools' => 87381, + 'labels' => 91477, 'parts_category' => 5, 'parts_minamount' => 5, 'parts_lots' => 85, diff --git a/src/DataFixtures/LabelProfileFixtures.php b/src/DataFixtures/LabelProfileFixtures.php new file mode 100644 index 00000000..fa1a72b4 --- /dev/null +++ b/src/DataFixtures/LabelProfileFixtures.php @@ -0,0 +1,96 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\DataFixtures; + + +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\LabelSystem\LabelProfile; +use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ObjectManager; + +class LabelProfileFixtures extends Fixture +{ + + protected $em; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->em = $entityManager; + } + + public function load(ObjectManager $manager) + { + $this->em->getConnection()->exec("ALTER TABLE `label_profiles` AUTO_INCREMENT = 1;"); + + $profile1 = new LabelProfile(); + $profile1->setName('Profile 1'); + $profile1->setShowInDropdown(true); + + $option1 = new LabelOptions(); + $option1->setLines("[[NAME]]\n[[DESCRIPION]]"); + $option1->setBarcodeType('none'); + $option1->setSupportedElement('part'); + $profile1->setOptions($option1); + + $manager->persist($profile1); + + $profile2 = new LabelProfile(); + $profile2->setName('Profile 2'); + $profile2->setShowInDropdown(false); + + $option2 = new LabelOptions(); + $option2->setLines("[[NAME]]\n[[DESCRIPION]]"); + $option2->setBarcodeType('qr'); + $option2->setSupportedElement('part'); + $profile2->setOptions($option2); + + $manager->persist($profile2); + + $profile3 = new LabelProfile(); + $profile3->setName('Profile 3'); + $profile3->setShowInDropdown(true); + + $option3 = new LabelOptions(); + $option3->setLines("[[NAME]]\n[[DESCRIPION]]"); + $option3->setBarcodeType('code128'); + $option3->setSupportedElement('part_lot'); + $profile3->setOptions($option3); + + $manager->persist($profile3); + + + $profile4 = new LabelProfile(); + $profile4->setName('Profile 4'); + $profile4->setShowInDropdown(true); + + $option4 = new LabelOptions(); + $option4->setLines("{{ element.name }}"); + $option4->setBarcodeType('code39'); + $option4->setSupportedElement('part'); + $option4->setLinesMode('twig'); + $profile4->setOptions($option4); + + $manager->persist($profile4); + + $manager->flush(); + } +} \ No newline at end of file diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index ff6cc8cd..ab77d7dd 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -43,7 +43,7 @@ use LogicException; * "Footprint" = "FootprintAttachment", "Manufacturer" = "ManufacturerAttachment", * "Currency" = "CurrencyAttachment", "Group" = "GroupAttachment", * "MeasurementUnit" = "MeasurementUnitAttachment", "Storelocation" = "StorelocationAttachment", - * "Supplier" = "SupplierAttachment", "User" = "UserAttachment" + * "Supplier" = "SupplierAttachment", "User" = "UserAttachment", "LabelProfile" = "LabelAttachment", * }) * @ORM\EntityListeners({"App\EntityListeners\AttachmentDeleteListener"}) */ diff --git a/src/Entity/Attachments/LabelAttachment.php b/src/Entity/Attachments/LabelAttachment.php new file mode 100644 index 00000000..65d31f42 --- /dev/null +++ b/src/Entity/Attachments/LabelAttachment.php @@ -0,0 +1,44 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Entity\Attachments; + + +use App\Entity\LabelSystem\LabelProfile; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; + +/** + * A attachment attached to a user element. + * + * @ORM\Entity() + * @UniqueEntity({"name", "attachment_type", "element"}) + */ +class LabelAttachment extends Attachment +{ + public const ALLOWED_ELEMENT_CLASS = LabelProfile::class; + + /** + * @var LabelProfile the element this attachment is associated with + * @ORM\ManyToOne(targetEntity="App\Entity\LabelSystem\LabelProfile", inversedBy="attachments") + * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). + */ + protected $element; +} \ No newline at end of file diff --git a/src/Entity/LabelSystem/LabelOptions.php b/src/Entity/LabelSystem/LabelOptions.php new file mode 100644 index 00000000..8907169e --- /dev/null +++ b/src/Entity/LabelSystem/LabelOptions.php @@ -0,0 +1,240 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Entity\LabelSystem; + +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * @ORM\Embeddable() + */ +class LabelOptions +{ + public const BARCODE_TYPES = ['none', /*'ean8',*/ 'qr', 'code39', 'datamatrix', 'code93', 'code128']; + public const SUPPORTED_ELEMENTS = ['part', 'part_lot', 'storelocation']; + public const PICTURE_TYPES = ['none', 'element_picture', 'main_attachment']; + + public const LINES_MODES = ['html', 'twig']; + + /** + * @var float The page size of the label in mm + * @Assert\Positive() + * @ORM\Column(type="float") + */ + protected $width = 50.0; + + /** + * @var float The page size of the label in mm + * @Assert\Positive() + * @ORM\Column(type="float") + */ + protected $height = 30.0; + + /** + * @var string The type of the barcode that should be used in the label (e.g. 'qr') + * @Assert\Choice(choices=LabelOptions::BARCODE_TYPES) + * @ORM\Column(type="string") + */ + protected $barcode_type = 'none'; + + /** + * @var string What image should be shown along the + * @Assert\Choice(choices=LabelOptions::PICTURE_TYPES) + * @ORM\Column(type="string") + */ + protected $picture_type = 'none'; + + /** + * @var string + * @Assert\Choice(choices=LabelOptions::SUPPORTED_ELEMENTS) + * @ORM\Column(type="string") + */ + protected $supported_element = 'part'; + + /** + * @var string Any additional CSS for the label. + * @ORM\Column(type="text") + */ + protected $additional_css = ''; + + /** @var string The mode that will be used to interpret the lines. + * @Assert\Choice(choices=LabelOptions::LINES_MODES) + * @ORM\Column(type="string") + */ + protected $lines_mode = 'html'; + + /** + * @var string + * @ORM\Column(type="text") + */ + protected $lines = ''; + + /** + * @return float + */ + public function getWidth(): float + { + return $this->width; + } + + /** + * @param float $width + * @return LabelOptions + */ + public function setWidth(float $width): LabelOptions + { + $this->width = $width; + return $this; + } + + /** + * @return float + */ + public function getHeight(): float + { + return $this->height; + } + + /** + * @param float $height + * @return LabelOptions + */ + public function setHeight(float $height): LabelOptions + { + $this->height = $height; + return $this; + } + + /** + * @return string + */ + public function getBarcodeType(): string + { + return $this->barcode_type; + } + + /** + * @param string $barcode_type + * @return LabelOptions + */ + public function setBarcodeType(string $barcode_type): LabelOptions + { + $this->barcode_type = $barcode_type; + return $this; + } + + /** + * @return string + */ + public function getPictureType(): string + { + return $this->picture_type; + } + + /** + * @param string $picture_type + * @return LabelOptions + */ + public function setPictureType(string $picture_type): LabelOptions + { + $this->picture_type = $picture_type; + return $this; + } + + /** + * @return string + */ + public function getSupportedElement(): string + { + return $this->supported_element; + } + + /** + * @param string $supported_element + * @return LabelOptions + */ + public function setSupportedElement(string $supported_element): LabelOptions + { + $this->supported_element = $supported_element; + return $this; + } + + /** + * @return string + */ + public function getLines(): string + { + return $this->lines; + } + + /** + * @param string $lines + * @return LabelOptions + */ + public function setLines(string $lines): LabelOptions + { + $this->lines = $lines; + return $this; + } + + /** + * Gets additional CSS (it will simply be attached + * @return string + */ + public function getAdditionalCss(): string + { + return $this->additional_css; + } + + /** + * + * @param string $additional_css + * @return LabelOptions + */ + public function setAdditionalCss(string $additional_css): LabelOptions + { + $this->additional_css = $additional_css; + return $this; + } + + /** + * @return string + */ + public function getLinesMode(): string + { + return $this->lines_mode; + } + + /** + * @param string $lines_mode + * @return LabelOptions + */ + public function setLinesMode(string $lines_mode): LabelOptions + { + $this->lines_mode = $lines_mode; + return $this; + } + + + + + +} \ No newline at end of file diff --git a/src/Entity/LabelSystem/LabelProfile.php b/src/Entity/LabelSystem/LabelProfile.php new file mode 100644 index 00000000..0311e64b --- /dev/null +++ b/src/Entity/LabelSystem/LabelProfile.php @@ -0,0 +1,126 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Entity\LabelSystem; + +use App\Entity\Attachments\AttachmentContainingDBElement; +use App\Entity\Attachments\LabelAttachment; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * @ORM\Entity(repositoryClass="App\Repository\LabelProfileRepository") + * @ORM\Table(name="label_profiles") + * @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"}) + * @UniqueEntity({"name", "options.supported_element"}) + * @package App\Entity\LabelSystem + */ +class LabelProfile extends AttachmentContainingDBElement +{ + /** + * @var Collection<int, LabelAttachment> + * @ORM\OneToMany(targetEntity="App\Entity\Attachments\LabelAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) + * @ORM\OrderBy({"name" = "ASC"}) + */ + protected $attachments; + + /** + * @var LabelOptions + * @ORM\Embedded(class="LabelOptions") + * @Assert\Valid() + */ + protected $options; + + /** + * @var string The comment info for this element + * @ORM\Column(type="text") + */ + protected $comment = ''; + + /** + * @var bool Determines, if this label profile should be shown in the dropdown quick menu. + * @ORM\Column(type="boolean") + */ + protected $show_in_dropdown = true; + + public function __construct() + { + parent::__construct(); + $this->options = new LabelOptions(); + } + + public function getOptions(): LabelOptions + { + return $this->options; + } + + public function setOptions(LabelOptions $labelOptions): LabelProfile + { + $this->options = $labelOptions; + return $this; + } + + /** + * Get the comment of the element. + * @return string the comment + */ + public function getComment(): ?string + { + return $this->comment; + } + + public function setComment(string $new_comment): string + { + $this->comment = $new_comment; + return $this; + } + + /** + * Returns true, if this label profile should be shown in label generator quick menu. + * @return bool + */ + public function isShowInDropdown(): bool + { + return $this->show_in_dropdown; + } + + /** + * Sets the show in dropdown menu. + * @param bool $show_in_dropdown + * @return LabelProfile + */ + public function setShowInDropdown(bool $show_in_dropdown): LabelProfile + { + $this->show_in_dropdown = $show_in_dropdown; + return $this; + } + + + + /** + * @inheritDoc + */ + public function getIDString(): string + { + return 'LP'.sprintf('%09d', $this->getID()); + } +} \ No newline at end of file diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index deb39a6b..829dad48 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -47,6 +47,7 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; use App\Entity\Devices\Device; use App\Entity\Devices\DevicePart; +use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; @@ -117,6 +118,7 @@ abstract class AbstractLogEntry extends AbstractDBElement protected const TARGET_TYPE_PRICEDETAIL = 16; protected const TARGET_TYPE_MEASUREMENTUNIT = 17; protected const TARGET_TYPE_PARAMETER = 18; + protected const TARGET_TYPE_LABEL_PROFILE = 19; /** @var array This const is used to convert the numeric level to a PSR-3 compatible log level */ protected const LEVEL_ID_TO_STRING = [ @@ -149,6 +151,7 @@ abstract class AbstractLogEntry extends AbstractDBElement self::TARGET_TYPE_PRICEDETAIL => Pricedetail::class, self::TARGET_TYPE_MEASUREMENTUNIT => MeasurementUnit::class, self::TARGET_TYPE_PARAMETER => AbstractParameter::class, + self::TARGET_TYPE_LABEL_PROFILE => LabelProfile::class, ]; /** @var User The user which has caused this log entry @@ -163,7 +166,7 @@ abstract class AbstractLogEntry extends AbstractDBElement protected $timestamp; /** @var int The priority level of the associated level. 0 is highest, 7 lowest - * @ORM\Column(type="integer", name="level", columnDefinition="TINYINT") + * @ORM\Column(type="integer", name="level", columnDefinition="TINYINT(4) NOT NULL") */ protected $level; diff --git a/src/EntityListeners/TreeCacheInvalidationListener.php b/src/EntityListeners/TreeCacheInvalidationListener.php index 514f34ae..22877421 100644 --- a/src/EntityListeners/TreeCacheInvalidationListener.php +++ b/src/EntityListeners/TreeCacheInvalidationListener.php @@ -44,6 +44,7 @@ namespace App\EntityListeners; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\LabelSystem\LabelProfile; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Services\UserCacheKeyGenerator; @@ -71,7 +72,7 @@ class TreeCacheInvalidationListener public function invalidate(AbstractDBElement $element, LifecycleEventArgs $event): void { //If an element was changed, then invalidate all cached trees with this element class - if ($element instanceof AbstractStructuralDBElement) { + if ($element instanceof AbstractStructuralDBElement || $element instanceof LabelProfile) { $secure_class_name = str_replace('\\', '_', get_class($element)); $this->cache->invalidateTags([$secure_class_name]); } diff --git a/src/Exceptions/TwigModeException.php b/src/Exceptions/TwigModeException.php new file mode 100644 index 00000000..67e54059 --- /dev/null +++ b/src/Exceptions/TwigModeException.php @@ -0,0 +1,33 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Exceptions; + + +use Throwable; +use Twig\Error\Error; + +class TwigModeException extends \RuntimeException +{ + public function __construct(Error $previous = null) + { + parent::__construct($previous->getMessage(), 0, $previous); + } +} \ No newline at end of file diff --git a/src/Form/AdminPages/BaseEntityAdminForm.php b/src/Form/AdminPages/BaseEntityAdminForm.php index 402de5b4..4fdb7c55 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -45,6 +45,8 @@ namespace App\Form\AdminPages; use App\Entity\Attachments\Attachment; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\LabelSystem\LabelProfile; use App\Form\AttachmentFormType; use App\Form\ParameterType; use App\Form\Type\MasterPictureAttachmentType; @@ -94,36 +96,50 @@ class BaseEntityAdminForm extends AbstractType 'placeholder' => 'part.name.placeholder', ], 'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity), - ]) - - ->add('parent', StructuralEntityType::class, [ - 'class' => get_class($entity), - 'required' => false, - 'label' => 'parent.label', - 'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'move', $entity), - ]) - - ->add('not_selectable', CheckboxType::class, [ - 'required' => false, - 'label' => 'entity.edit.not_selectable', - 'help' => 'entity.edit.not_selectable.help', - 'label_attr' => [ - 'class' => 'checkbox-custom', - ], - 'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity), - ]) - - ->add('comment', CKEditorType::class, [ - 'required' => false, - 'empty_data' => '', - 'label' => 'comment.label', - 'attr' => [ - 'rows' => 4, - ], - 'help' => 'bbcode.hint', - 'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]); + if ($entity instanceof AbstractStructuralDBElement) { + $builder->add( + 'parent', + StructuralEntityType::class, + [ + 'class' => get_class($entity), + 'required' => false, + 'label' => 'parent.label', + 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'move', $entity), + ] + ) + ->add( + 'not_selectable', + CheckboxType::class, + [ + 'required' => false, + 'label' => 'entity.edit.not_selectable', + 'help' => 'entity.edit.not_selectable.help', + 'label_attr' => [ + 'class' => 'checkbox-custom', + ], + 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), + ] + ); + } + if ($entity instanceof AbstractStructuralDBElement || $entity instanceof LabelProfile) { + $builder->add( + 'comment', + CKEditorType::class, + [ + 'required' => false, + 'empty_data' => '', + 'label' => 'comment.label', + 'attr' => [ + 'rows' => 4, + ], + 'help' => 'bbcode.hint', + 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), + ] + ); + } + $this->additionalFormElements($builder, $options, $entity); //Attachment section @@ -154,19 +170,25 @@ class BaseEntityAdminForm extends AbstractType 'empty_data' => null, ]); - $builder->add('parameters', CollectionType::class, [ - 'entry_type' => ParameterType::class, - 'allow_add' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity), - 'allow_delete' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity), - 'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity), - 'reindex_enable' => true, - 'label' => false, - 'by_reference' => false, - 'prototype_data' => new $options['parameter_class'](), - 'entry_options' => [ - 'data_class' => $options['parameter_class'], - ], - ]); + if ($entity instanceof AbstractStructuralDBElement) { + $builder->add( + 'parameters', + CollectionType::class, + [ + 'entry_type' => ParameterType::class, + 'allow_add' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity), + 'allow_delete' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity), + 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), + 'reindex_enable' => true, + 'label' => false, + 'by_reference' => false, + 'prototype_data' => new $options['parameter_class'](), + 'entry_options' => [ + 'data_class' => $options['parameter_class'], + ], + ] + ); + } //Buttons $builder->add('save', SubmitType::class, [ diff --git a/src/Form/AdminPages/LabelProfileAdminForm.php b/src/Form/AdminPages/LabelProfileAdminForm.php new file mode 100644 index 00000000..b77aa502 --- /dev/null +++ b/src/Form/AdminPages/LabelProfileAdminForm.php @@ -0,0 +1,55 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Form\AdminPages; + + +use App\Entity\Base\AbstractNamedDBElement; +use App\Entity\LabelSystem\LabelProfile; +use App\Form\LabelOptionsType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class LabelProfileAdminForm extends BaseEntityAdminForm +{ + protected function additionalFormElements(FormBuilderInterface $builder, array $options, AbstractNamedDBElement $entity): void + { + + $builder->add('show_in_dropdown', CheckboxType::class, [ + 'required' => false, + 'label' => 'label_profile.showInDropdown', + 'label_attr' => [ + 'class' => 'checkbox-custom', + ], + ]); + $builder->add('options', LabelOptionsType::class, [ + 'label' => false, + 'disabled' => $options['disable_options'], + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + parent::configureOptions($resolver); + $resolver->setDefault('data_class', LabelProfile::class); + $resolver->setDefault('disable_options', false); + } +} \ No newline at end of file diff --git a/src/Form/LabelOptionsType.php b/src/Form/LabelOptionsType.php new file mode 100644 index 00000000..9f35957e --- /dev/null +++ b/src/Form/LabelOptionsType.php @@ -0,0 +1,142 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Form; + + +use App\Entity\LabelSystem\LabelOptions; +use FOS\CKEditorBundle\Form\Type\CKEditorType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Security; + +class LabelOptionsType extends AbstractType +{ + private $security; + + public function __construct(Security $security) + { + $this->security = $security; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('width', NumberType::class, [ + 'label' => 'label_options.page_size.label', + 'html5' => true, + 'attr' => [ + 'placeholder' => 'label_options.width.placeholder', + 'min' => 0, + 'step' => 'any', + ] + ]); + $builder->add('height', NumberType::class, [ + 'label' => false, + 'html5' => true, + 'attr' => [ + 'placeholder' => 'label_options.height.placeholder', + 'min' => 0, + 'step' => 'any', + ] + ]); + + $builder->add('supported_element', ChoiceType::class, [ + 'label' => 'label_options.supported_elements.label', + 'choices' => [ + 'part.label' => 'part', + 'part_lot.label' => 'part_lot', + 'storelocation.label' => 'storelocation', + ] + ]); + + $builder->add('barcode_type', ChoiceType::class, [ + 'label' => 'label_options.barcode_type.label', + 'empty_data' => 'none', + 'choices' => [ + 'label_options.barcode_type.none' => 'none', + 'label_options.barcode_type.qr' => 'qr', + 'label_options.barcode_type.code128' => 'code128', + 'label_options.barcode_type.code39' => 'code39', + 'label_options.barcode_type.code93' => 'code93', + 'label_options.barcode_type.datamatrix' => 'datamatrix', + ], + 'group_by' => function ($choice, $key, $value) { + if (in_array($choice, ['qr', 'datamatrix'])) { + return 'label_options.barcode_type.2D'; + } + if (in_array($choice, ['code39', 'code93', 'code128'])) { + return 'label_options.barcode_type.1D'; + } + + return null; + }, + 'attr' => [ + 'class' => 'selectpicker', + 'data-live-search' => true, + ], + + ]); + + $builder->add('lines', CKEditorType::class, [ + 'label' => 'label_profile.lines.label', + 'empty_data' => '', + 'attr' => [ + 'rows' => 4, + ], + 'config_name' => 'label_config', + ]); + + $builder->add('additional_css', TextareaType::class, [ + 'label' => 'label_options.additional_css.label', + 'empty_data' => '', + 'attr' => [ + 'rows' => 4, + ], + 'required' => false, + ]); + + $builder->add('lines_mode', ChoiceType::class, [ + 'label' => 'label_options.lines_mode.label', + 'choices' => [ + 'label_options.lines_mode.html' => 'html', + 'label.options.lines_mode.twig' => 'twig', + ], + 'help' => 'label_options.lines_mode.help', + 'help_html' => true, + 'expanded' => true, + 'attr' => [ + 'class' => 'pt-2' + ], + 'label_attr' => [ + 'class' => 'radio-custom radio-inline' + ], + 'disabled' => !$this->security->isGranted('@labels.use_twig') + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('data_class', LabelOptions::class); + } +} \ No newline at end of file diff --git a/src/Form/LabelSystem/LabelDialogType.php b/src/Form/LabelSystem/LabelDialogType.php new file mode 100644 index 00000000..60f392c0 --- /dev/null +++ b/src/Form/LabelSystem/LabelDialogType.php @@ -0,0 +1,70 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Form\LabelSystem; + + +use App\Form\LabelOptionsType; +use App\Validator\Constraints\Misc\ValidRange; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Security; + +class LabelDialogType extends AbstractType +{ + protected $security; + + public function __construct(Security $security) + { + $this->security = $security; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('target_id', TextType::class, [ + 'required' => true, + 'label' => 'label_generator.target_id.label', + 'help' => 'label_generator.target_id.range_hint', + 'constraints' => [ + new ValidRange(), + ], + ]); + + $builder->add('options', LabelOptionsType::class, [ + 'label' => false, + 'disabled' => !$this->security->isGranted('@labels.edit_options') || $options['disable_options'], + + ]); + $builder->add('update', SubmitType::class, [ + 'label' => 'label_generator.update', + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + $resolver->setDefault('mapped', false); + $resolver->setDefault('disable_options', false); + } +} \ No newline at end of file diff --git a/src/Form/LabelSystem/ScanDialogType.php b/src/Form/LabelSystem/ScanDialogType.php new file mode 100644 index 00000000..fc269203 --- /dev/null +++ b/src/Form/LabelSystem/ScanDialogType.php @@ -0,0 +1,51 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Form\LabelSystem; + + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class ScanDialogType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('input', TextType::class, [ + 'label' => 'scan_dialog.input', + 'attr' => [ + 'autofocus' => true, + 'id' => 'scan_dialog_input', + ], + ]); + + $builder->add('submit', SubmitType::class, [ + 'label' => 'scan_dialog.submit' + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('mapped', false); + } +} \ No newline at end of file diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index 4fb55433..9958efb4 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -178,6 +178,7 @@ class UserAdminForm extends AbstractType 'attr' => [ 'class' => 'selectpicker', ], + 'choice_translation_domain' => false, 'placeholder' => 'user_settings.theme.placeholder', 'label' => 'user.theme.label', 'disabled' => ! $this->security->isGranted('change_user_settings', $entity), diff --git a/src/Helpers/LabelResponse.php b/src/Helpers/LabelResponse.php new file mode 100644 index 00000000..510ff0b1 --- /dev/null +++ b/src/Helpers/LabelResponse.php @@ -0,0 +1,112 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Helpers; + + +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class LabelResponse extends Response +{ + public function __construct($content = '', int $status = 200, array $headers = []) + { + parent::__construct($content, $status, $headers); + } + + public function setContent($content) + { + parent::setContent($content); + + $this->setAutoEtag(); + $this->setAutoLastModified(); + return $this; + } + + public function prepare(Request $request) + { + parent::prepare($request); + + $this->headers->set('Content-Type','application/pdf'); + + + if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + $this->ensureIEOverSSLCompatibility($request); + + return $this; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + */ + public function setAutoLastModified() + { + $this->setLastModified(new \DateTime()); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + */ + public function setAutoEtag() + { + $this->setEtag(base64_encode(hash('sha256', $this->content, true))); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition($disposition, $filename, $filenameFallback = '') + { + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + +} \ No newline at end of file diff --git a/src/Migrations/Version20200502161750.php b/src/Migrations/Version20200502161750.php new file mode 100644 index 00000000..74be880a --- /dev/null +++ b/src/Migrations/Version20200502161750.php @@ -0,0 +1,38 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20200502161750 extends AbstractMigration +{ + public function getDescription() : string + { + return ''; + } + + public function up(Schema $schema) : void + { + // this up() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('CREATE TABLE label_profiles (id INT AUTO_INCREMENT NOT NULL, id_preview_attachement INT DEFAULT NULL, comment LONGTEXT NOT NULL, show_in_dropdown TINYINT(1) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, options_width DOUBLE PRECISION NOT NULL, options_height DOUBLE PRECISION NOT NULL, options_barcode_type VARCHAR(255) NOT NULL, options_picture_type VARCHAR(255) NOT NULL, options_supported_element VARCHAR(255) NOT NULL, options_additional_css LONGTEXT NOT NULL, options_lines_mode VARCHAR(255) NOT NULL, options_lines LONGTEXT NOT NULL, INDEX IDX_C93E9CF56DEDCEC2 (id_preview_attachement), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE label_profiles ADD CONSTRAINT FK_C93E9CF56DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES `attachments` (id)'); + $this->addSql('ALTER TABLE log CHANGE level level TINYINT(4) NOT NULL'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('DROP TABLE label_profiles'); + $this->addSql('ALTER TABLE log CHANGE level level TINYINT(1) DEFAULT NULL'); + } +} diff --git a/src/Repository/DBElementRepository.php b/src/Repository/DBElementRepository.php index 41d8c058..0c84f1e5 100644 --- a/src/Repository/DBElementRepository.php +++ b/src/Repository/DBElementRepository.php @@ -50,6 +50,22 @@ class DBElementRepository extends EntityRepository $this->setField($element, 'id', $new_id); } + /** + * Find all elements that match a list of IDs. + * @param array $ids + * @return AbstractDBElement[] + */ + public function getElementsFromIDArray(array $ids): array + { + $qb = $this->createQueryBuilder('element'); + $q = $qb->select('element') + ->where('element.id IN (?1)') + ->setParameter(1, $ids) + ->getQuery(); + + return $q->getResult(); + } + protected function setField(AbstractDBElement $element, string $field, int $new_value): void { $reflection = new \ReflectionClass(get_class($element)); diff --git a/src/Repository/LabelProfileRepository.php b/src/Repository/LabelProfileRepository.php new file mode 100644 index 00000000..fad62359 --- /dev/null +++ b/src/Repository/LabelProfileRepository.php @@ -0,0 +1,89 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Repository; + +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\LabelSystem\LabelProfile; +use App\Helpers\Trees\TreeViewNode; +use Symfony\Contracts\Translation\TranslatorInterface; + +class LabelProfileRepository extends NamedDBElementRepository +{ + + /** + * Find the profiles that are shown in the dropdown for the given type. + * You should maybe use the cached version of this in LabelProfileDropdownHelper + * @param string $type + * @return array + */ + public function getDropdownProfiles(string $type): array + { + if (!in_array($type, LabelOptions::SUPPORTED_ELEMENTS)) { + throw new \InvalidArgumentException('Invalid supported_element type given.'); + } + return $this->findBy(['options.supported_element' => $type, 'show_in_dropdown' => true], ['name' => 'ASC']); + } + + /** + * Gets a tree of TreeViewNode elements. The root elements has $parent as parent. + * The treeview is generic, that means the href are null and ID values are set. + * + * @return TreeViewNode[] + */ + public function getGenericNodeTree(): array + { + $result = []; + + foreach (LabelOptions::SUPPORTED_ELEMENTS as $type) { + $type_children = []; + $entities = $this->findForSupportedElement($type); + foreach ($entities as $entity) { + /** @var LabelProfile $entity */ + $node = new TreeViewNode($entity->getName(), null, null); + $node->setId($entity->getID()); + $type_children[] = $node; + } + + if (!empty($type_children)) { + //Use default label e.g. 'part_label'. $$ marks that it will be translated in TreeViewGenerator + $tmp = new TreeViewNode('$$' . $type . '.label', null, $type_children); + + $result[] = $tmp; + } + } + + return $result; + } + + /** + * Find all LabelProfiles that can be used with the given type + * @param string $type See LabelOptions::SUPPORTED_ELEMENTS for valid values. + * @param array $order_by The way the results should be sorted. By default ordered by + * @return array + */ + public function findForSupportedElement(string $type, array $order_by = ['name' => 'ASC']): array + { + if (!in_array($type, LabelOptions::SUPPORTED_ELEMENTS)) { + throw new \InvalidArgumentException('Invalid supported_element type given.'); + } + return $this->findBy(['options.supported_element' => $type], $order_by); + } +} \ No newline at end of file diff --git a/src/Security/Voter/LabelProfileVoter.php b/src/Security/Voter/LabelProfileVoter.php new file mode 100644 index 00000000..d8abc7ea --- /dev/null +++ b/src/Security/Voter/LabelProfileVoter.php @@ -0,0 +1,62 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Security\Voter; + + +use App\Entity\LabelSystem\LabelProfile; +use App\Entity\UserSystem\User; + +class LabelProfileVoter extends ExtendedVoter +{ + + protected const MAPPING = [ + 'read' => 'read_profiles', + 'create' => 'create_profiles', + 'edit' => 'edit_profiles', + 'delete' => 'delete_profiles', + 'show_history' => 'show_history', + 'revert_element' => 'revert_element', + ]; + + /** + * @inheritDoc + */ + protected function voteOnUser($attribute, $subject, User $user): bool + { + return $this->resolver->inherit($user, 'labels', self::MAPPING[$attribute]) ?? false; + } + + /** + * @inheritDoc + */ + protected function supports($attribute, $subject) + { + if ($subject instanceof LabelProfile) { + if (!isset(self::MAPPING[$attribute])) { + return false; + } + + return $this->resolver->isValidOperation('labels', self::MAPPING[$attribute]); + } + + return false; + } +} \ No newline at end of file diff --git a/src/Services/ElementTypeNameGenerator.php b/src/Services/ElementTypeNameGenerator.php index 56ac883b..9d4d3f4d 100644 --- a/src/Services/ElementTypeNameGenerator.php +++ b/src/Services/ElementTypeNameGenerator.php @@ -46,6 +46,7 @@ use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; use App\Entity\Contracts\NamedElementInterface; use App\Entity\Devices\Device; +use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; @@ -92,6 +93,7 @@ class ElementTypeNameGenerator Group::class => $this->translator->trans('group.label'), User::class => $this->translator->trans('user.label'), AbstractParameter::class => $this->translator->trans('parameter.label'), + LabelProfile::class => $this->translator->trans('label_profile.label'), ]; } diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 076926e0..96ac6ff1 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -47,6 +47,7 @@ use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\PartAttachment; use App\Entity\Base\AbstractDBElement; use App\Entity\Devices\Device; +use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; @@ -133,7 +134,6 @@ class EntityURLGenerator { $map = [ Part::class => 'part_info', - //As long we does not have own things for it use edit page AttachmentType::class => 'attachment_type_edit', Category::class => 'category_edit', @@ -146,6 +146,7 @@ class EntityURLGenerator Currency::class => 'currency_edit', MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', + LabelProfile::class => 'label_profile_edit', ]; try { @@ -241,6 +242,7 @@ class EntityURLGenerator Currency::class => 'currency_edit', MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', + LabelProfile::class => 'label_profile_edit' ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -270,6 +272,7 @@ class EntityURLGenerator Currency::class => 'currency_edit', MeasurementUnit::class => 'measurement_unit_edit', Group::class => 'group_edit', + LabelProfile::class => 'label_profile_edit' ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -299,6 +302,7 @@ class EntityURLGenerator Currency::class => 'currency_new', MeasurementUnit::class => 'measurement_unit_new', Group::class => 'group_new', + LabelProfile::class => 'label_profile_new' ]; return $this->urlGenerator->generate($this->mapToController($map, $entity)); @@ -329,6 +333,7 @@ class EntityURLGenerator Currency::class => 'currency_clone', MeasurementUnit::class => 'measurement_unit_clone', Group::class => 'group_clone', + LabelProfile::class => 'label_profile_clone' ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); @@ -371,6 +376,7 @@ class EntityURLGenerator Currency::class => 'currency_delete', MeasurementUnit::class => 'measurement_unit_delete', Group::class => 'group_delete', + LabelProfile::class => 'label_profile_delete' ]; return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]); diff --git a/src/Services/LabelSystem/BarcodeGenerator.php b/src/Services/LabelSystem/BarcodeGenerator.php new file mode 100644 index 00000000..70f68d27 --- /dev/null +++ b/src/Services/LabelSystem/BarcodeGenerator.php @@ -0,0 +1,84 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem; + +use App\Entity\LabelSystem\LabelOptions; +use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; +use Com\Tecnick\Barcode\Barcode; + +final class BarcodeGenerator +{ + private $barcodeContentGenerator; + + public function __construct(BarcodeContentGenerator $barcodeContentGenerator) + { + $this->barcodeContentGenerator = $barcodeContentGenerator; + } + + public function generateSVG(LabelOptions $options, object $target): ?string + { + $barcode = new Barcode(); + + switch ($options->getBarcodeType()) { + case 'qr': + $type = 'QRCODE'; + break; + case 'datamatrix': + $type = 'DATAMATRIX'; + break; + case 'code39': + $type = 'C39'; + break; + case 'code93': + $type = 'C93'; + break; + case 'code128': + $type = 'C128A'; + break; + case 'none': + return null; + default: + throw new \InvalidArgumentException('Unknown label type!'); + } + + $bobj = $barcode->getBarcodeObj($type, $this->getContent($options, $target)); + + return $bobj->getSvgCode(); + } + + public function getContent(LabelOptions $options, object $target): ?string + { + switch ($options->getBarcodeType()) { + case 'qr': + case 'datamatrix': + return $this->barcodeContentGenerator->getURLContent($target); + case 'code39': + case 'code93': + case 'code128': + return $this->barcodeContentGenerator->get1DBarcodeContent($target); + case 'none': + return null; + default: + throw new \InvalidArgumentException('Unknown label type!'); + } + } + +} \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php new file mode 100644 index 00000000..90838d9a --- /dev/null +++ b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php @@ -0,0 +1,98 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\Barcodes; + + +use App\Entity\Base\AbstractDBElement; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; +use App\Entity\Parts\Supplier; +use App\Exceptions\EntityNotSupportedException; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +final class BarcodeContentGenerator +{ + private $urlGenerator; + + public const PREFIX_MAP = [ + Part::class => 'P', + PartLot::class => 'L', + Storelocation::class => 'S' + ]; + + private const URL_MAP = [ + Part::class => 'part', + PartLot::class => 'lot', + Storelocation::class => 'location', + ]; + + public function __construct(UrlGeneratorInterface $urlGenerator) + { + $this->urlGenerator = $urlGenerator; + } + + /** + * Generates a fixed URL to the given Element that can be embedded in a 2D code (e.g. QR code). + * @param AbstractDBElement $target + * @return string + */ + public function getURLContent(AbstractDBElement $target): string + { + $type = $this->classToString(self::URL_MAP, $target); + + return $this->urlGenerator->generate('scan_qr', [ + 'type' => $type, + 'id' => $target->getID() ?? 0, + '_locale' => null, + ], UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * Returns a Code that can be used in a 1D barcode. + * The return value has a format of "L0123" + * @param AbstractDBElement $target + * @return string + */ + public function get1DBarcodeContent(AbstractDBElement $target): string + { + $prefix = $this->classToString(self::PREFIX_MAP, $target); + $id = sprintf('%04d', $target->getID() ?? 0); + return $prefix . $id; + } + + private function classToString(array $map, object $target): string + { + $class = get_class($target); + if (isset($map[$class])) { + return $map[$class]; + } + + foreach($map as $class => $string) { + if (is_a($target, $class)) { + return $string; + } + } + + throw new \InvalidArgumentException('Unknown object class ' . get_class($target)); + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeExampleElementsGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeExampleElementsGenerator.php new file mode 100644 index 00000000..96644960 --- /dev/null +++ b/src/Services/LabelSystem/Barcodes/BarcodeExampleElementsGenerator.php @@ -0,0 +1,118 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\Barcodes; + +use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Parts\Category; +use App\Entity\Parts\Footprint; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; + +final class BarcodeExampleElementsGenerator +{ + public function getElement(string $type): object + { + switch ($type) { + case 'part': + return $this->getExamplePart(); + case 'part_lot': + return $this->getExamplePartLot(); + case 'storelocation': + return $this->getStorelocation(); + default: + throw new \InvalidArgumentException('Unknown $type.'); + } + } + + private function getStorelocation(): Storelocation + { + $storelocation = new Storelocation(); + $storelocation->setName('Location 1'); + $storelocation->setComment('Example comment'); + $storelocation->updatedTimestamps(); + + $parent = new Storelocation(); + $parent->setName('Parent'); + + $storelocation->setParent($parent); + + return $storelocation; + } + + private function getStructuralData(string $class): AbstractStructuralDBElement + { + if (!is_a($class, AbstractStructuralDBElement::class, true)) { + throw new \InvalidArgumentException('$class must be an child of AbstractStructuralDBElement'); + } + + /** @var AbstractStructuralDBElement $parent */ + $parent = new $class(); + $parent->setName('Example'); + + /** @var AbstractStructuralDBElement $child */ + $child = new $class(); + $child->setName((new \ReflectionClass($class))->getShortName()); + $child->setParent($parent); + + return $child; + } + + public function getExamplePart(): Part + { + $part = new Part(); + $part->setName('Example Part'); + $part->setDescription('<b>Part</b> description'); + $part->setComment('<i>Part</i> comment'); + + $part->setCategory($this->getStructuralData(Category::class)); + $part->setFootprint($this->getStructuralData(Footprint::class)); + $part->setManufacturer($this->getStructuralData(Manufacturer::class)); + + $part->setMass(123.4); + $part->setManufacturerProductNumber('CUSTOM MPN'); + $part->setTags('Tag1, Tag2, Tag3'); + $part->setManufacturingStatus('active'); + $part->updatedTimestamps(); + + $part->setFavorite(true); + $part->setMinAmount(100); + $part->setNeedsReview(true); + + + return $part; + } + + public function getExamplePartLot(): PartLot + { + $lot = new PartLot(); + $lot->setPart($this->getExamplePart()); + + $lot->setDescription('Example Lot'); + $lot->setComment('Lot comment'); + $lot->setExpirationDate(new \DateTime('+1 days')); + $lot->setStorageLocation($this->getStructuralData(Storelocation::class)); + $lot->setAmount(123); + + return $lot; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php b/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php new file mode 100644 index 00000000..2170bcaf --- /dev/null +++ b/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php @@ -0,0 +1,86 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\Barcodes; + + +final class BarcodeNormalizer +{ + private const PREFIX_TYPE_MAP = [ + 'L' => 'lot', + 'P' => 'part', + 'S' => 'location', + ]; + + /** + * Parses barcode content and normalizes it. + * Returns an array in the format ['part', 1]: First entry contains element type, second the ID of the element + * @param string $input + * @return array + */ + public function normalizeBarcodeContent(string $input): array + { + $input = trim($input); + $matches = []; + + //Some scanner output '-' as ß, so replace it (ß is never used, so we can replace it safely) + $input = str_replace('ß', '-', $input); + + //Extract parts from QR code's URL + if (preg_match('#^https?://.*/scan/(\w+)/(\d+)/?$#', $input, $matches)) { + return [$matches[1], (int) $matches[2]]; + } + + //New Code39 barcode use L0001 format + if (preg_match('#^([A-Z])(\d{4,})$#', $input, $matches)) { + $prefix = $matches[1]; + $id = (int) $matches[2]; + + if (!isset(self::PREFIX_TYPE_MAP[$prefix])) { + throw new \InvalidArgumentException('Unknown prefix ' . $prefix); + } + return [self::PREFIX_TYPE_MAP[$prefix], $id]; + } + + //During development the L-000001 format was used + if (preg_match('#^(\w)-(\d{6,})$#', $input, $matches)) { + $prefix = $matches[1]; + $id = (int) $matches[2]; + + if (!isset(self::PREFIX_TYPE_MAP[$prefix])) { + throw new \InvalidArgumentException('Unknown prefix ' . $prefix); + } + return [self::PREFIX_TYPE_MAP[$prefix], $id]; + } + + //Legacy Part-DB location labels used $L00336 format + if (preg_match('#^\$L(\d{5,})$#', $input, $matches)) { + return ['location', (int) $matches[1]]; + } + + //Legacy Part-DB used EAN8 barcodes for part labels. Format 0000001(2) (note the optional 8th digit => checksum) + if (preg_match('#^(\d{7})\d?$#', $input, $matches)) { + return ['part', (int) $matches[1]]; + } + + + throw new \InvalidArgumentException('Unknown barcode format!'); + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php b/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php new file mode 100644 index 00000000..8a3455f3 --- /dev/null +++ b/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php @@ -0,0 +1,69 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\Barcodes; + + +use App\Entity\Parts\PartLot; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityNotFoundException; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +final class BarcodeRedirector +{ + private $urlGenerator; + private $em; + + + public function __construct(UrlGeneratorInterface $urlGenerator, EntityManagerInterface $entityManager) + { + $this->urlGenerator = $urlGenerator; + $this->em = $entityManager; + } + + /** + * Determines the URL to which the user should be redirected, when scanning a QR code + * @param string $type The type of the element that was scanned (e.g. 'part', 'lot', etc.) + * @param int $id The ID of the element that was scanned + * @return string The URL to which should be redirected. + * @throws EntityNotFoundException + */ + public function getRedirectURL(string $type, int $id): string + { + switch ($type) { + case 'part': + return $this->urlGenerator->generate('app_part_show', ['id' => $id]); + case 'lot': + //Try to determine the part to the given lot + $lot = $this->em->find(PartLot::class, $id); + if ($lot === null) { + throw new EntityNotFoundException(); + } + + return $this->urlGenerator->generate('app_part_show', ['id' => $lot->getPart()->getID()]); + + case 'location': + return $this->urlGenerator->generate('part_list_store_location', ['id' => $id]); + + default: + throw new \InvalidArgumentException('Unknown $type: ' . $type); + } + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/LabelGenerator.php b/src/Services/LabelSystem/LabelGenerator.php new file mode 100644 index 00000000..8c8dd70e --- /dev/null +++ b/src/Services/LabelSystem/LabelGenerator.php @@ -0,0 +1,105 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem; + + +use App\Entity\Contracts\NamedElementInterface; +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; +use App\Services\ElementTypeNameGenerator; +use Dompdf\Dompdf; +use Twig\Environment; + +final class LabelGenerator +{ + public const CLASS_SUPPORT_MAPPING = [ + 'part' => Part::class, + 'part_lot' => PartLot::class, + 'storelocation' => Storelocation::class, + ]; + + public const MM_TO_POINTS_FACTOR = 2.83465; + + private $labelHTMLGenerator; + + public function __construct(LabelHTMLGenerator $labelHTMLGenerator) + { + $this->labelHTMLGenerator = $labelHTMLGenerator; + } + + /** + * @param LabelOptions $options + * @param object|object[] $elements An element or an array of elements for which labels should be generated + * @return string + */ + public function generateLabel(LabelOptions $options, $elements): string + { + if (!is_array($elements) && !is_object($elements)) { + throw new \InvalidArgumentException('$element must be an object or an array of objects!'); + } + + if (!is_array($elements)) { + $elements = [$elements]; + } + + foreach ($elements as $element) { + if (!$this->supports($options, $element)) { + throw new \InvalidArgumentException('The given options are not compatible with the given element!'); + } + } + + + $dompdf = new Dompdf(); + $dompdf->setPaper($this->mmToPointsArray($options->getWidth(), $options->getHeight())); + $dompdf->loadHtml($this->labelHTMLGenerator->getLabelHTML($options, $elements)); + $dompdf->render(); + return $dompdf->output(); + } + + /** + * Check if the given LabelOptions can be used with $element. + * @param LabelOptions $options + * @param object $element + * @return bool + */ + public function supports(LabelOptions $options, object $element) + { + $supported_type = $options->getSupportedElement(); + if (!isset(static::CLASS_SUPPORT_MAPPING[$supported_type])) { + throw new \InvalidArgumentException('Supported type name of the Label options not known!'); + } + + return is_a($element, static::CLASS_SUPPORT_MAPPING[$supported_type]); + } + + /** + * Converts width and height given in mm to an size array, that can be used by DOMPDF for page size + * @param float $width The width of the paper + * @param float $height The height of the paper + * @return float[] + */ + public function mmToPointsArray(float $width, float $height): array + { + return [0.0, 0.0, $width * self::MM_TO_POINTS_FACTOR, $height * self::MM_TO_POINTS_FACTOR]; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/LabelHTMLGenerator.php b/src/Services/LabelSystem/LabelHTMLGenerator.php new file mode 100644 index 00000000..ef161940 --- /dev/null +++ b/src/Services/LabelSystem/LabelHTMLGenerator.php @@ -0,0 +1,115 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem; + +use App\Entity\Contracts\NamedElementInterface; +use App\Entity\LabelSystem\LabelOptions; +use App\Exceptions\TwigModeException; +use App\Services\ElementTypeNameGenerator; +use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; +use Symfony\Component\Security\Core\Security; +use Twig\Environment; +use Twig\Error\Error; +use Twig\Error\SyntaxError; + +final class LabelHTMLGenerator +{ + private $twig; + private $elementTypeNameGenerator; + private $replacer; + private $barcodeGenerator; + private $sandboxedTwigProvider; + private $partdb_title; + private $security; + + public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator, LabelTextReplacer $replacer, Environment $twig, + BarcodeGenerator $barcodeGenerator, SandboxedTwigProvider $sandboxedTwigProvider, Security $security, string $partdb_title) + { + $this->twig = $twig; + $this->elementTypeNameGenerator = $elementTypeNameGenerator; + $this->replacer = $replacer; + $this->barcodeGenerator = $barcodeGenerator; + $this->sandboxedTwigProvider = $sandboxedTwigProvider; + $this->security = $security; + $this->partdb_title = $partdb_title; + } + + public function getLabelHTML(LabelOptions $options, array $elements): string + { + if (empty($elements)) { + throw new \InvalidArgumentException('$elements must not be empty'); + } + + $twig_elements = []; + + if ($options->getLinesMode() === 'twig') { + $sandboxed_twig = $this->sandboxedTwigProvider->getTwig($options); + $current_user = $this->security->getUser(); + } + + $page = 1; + foreach ($elements as $element) { + if ($options->getLinesMode() === 'twig' && isset($sandboxed_twig) && isset($current_user)) { + try { + $lines = $sandboxed_twig->render( + 'lines', + [ + 'element' => $element, + 'page' => $page, + 'user' => $current_user, + 'install_title' => $this->partdb_title, + ] + ); + } catch (Error $exception) { + throw new TwigModeException($exception); + } + } else { + $lines = $this->replacer->replace($options->getLines(), $element); + } + + $twig_elements[] = [ + 'element' => $element, + 'lines' => $lines, + 'barcode' => $this->barcodeGenerator->generateSVG($options, $element), + 'barcode_content' => $this->barcodeGenerator->getContent($options, $element), + ]; + + $page++; + } + + + return $this->twig->render('LabelSystem/labels/base_label.html.twig', [ + 'meta_title' => $this->getPDFTitle($options, $elements[0]), + 'elements' => $twig_elements, + 'options' => $options, + ]); + } + + + protected function getPDFTitle(LabelOptions $options, object $element) + { + if ($element instanceof NamedElementInterface) { + return $this->elementTypeNameGenerator->getTypeNameCombination($element, false); + } + + return 'Part-DB label'; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/LabelProfileDropdownHelper.php b/src/Services/LabelSystem/LabelProfileDropdownHelper.php new file mode 100644 index 00000000..21c33592 --- /dev/null +++ b/src/Services/LabelSystem/LabelProfileDropdownHelper.php @@ -0,0 +1,59 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem; + + +use App\Entity\LabelSystem\LabelProfile; +use App\Repository\LabelProfileRepository; +use App\Services\UserCacheKeyGenerator; +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Contracts\Cache\ItemInterface; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +final class LabelProfileDropdownHelper +{ + private $cache; + private $entityManager; + private $keyGenerator; + + public function __construct(TagAwareCacheInterface $treeCache, EntityManagerInterface $entityManager, UserCacheKeyGenerator $keyGenerator) + { + $this->cache = $treeCache; + $this->entityManager = $entityManager; + $this->keyGenerator = $keyGenerator; + } + + public function getDropdownProfiles(string $type): array + { + $secure_class_name = str_replace('\\', '_', LabelProfile::class); + $key = 'profile_dropdown_'.$this->keyGenerator->generateKey().'_'.$secure_class_name . '_' . $type; + + /** @var LabelProfileRepository $repo */ + $repo = $this->entityManager->getRepository(LabelProfile::class); + + return $this->cache->get($key, function (ItemInterface $item) use ($repo, $type, $secure_class_name) { + // Invalidate when groups, a element with the class or the user changes + $item->tag(['groups', 'tree_treeview', $this->keyGenerator->generateKey(), $secure_class_name]); + + return $repo->getDropdownProfiles($type); + }); + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/LabelTextReplacer.php b/src/Services/LabelSystem/LabelTextReplacer.php new file mode 100644 index 00000000..abf4c401 --- /dev/null +++ b/src/Services/LabelSystem/LabelTextReplacer.php @@ -0,0 +1,75 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem; + +use App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface; + +/** + * This service replaces the Placeholders of the user provided lines with the proper informations. + * It uses the PlaceholderProviders provided by PlaceholderProviderInterface classes. + * @package App\Services\LabelSystem + */ +final class LabelTextReplacer +{ + private $providers; + + public function __construct(iterable $providers) + { + $this->providers = $providers; + } + + /** + * Determine the replacement for a single placeholder. It is iterated over all Replacement Providers. + * If the given string is not a placeholder or the placeholder is not known, it will be returned unchanged. + * @param string $placeholder The placeholder that should be replaced. (e.g. '%%PLACEHOLDER%%') + * @param object $target The object that should be used for the placeholder info source. + * @return string If the placeholder was valid, the replaced info. Otherwise the passed string. + */ + public function handlePlaceholder(string $placeholder, object $target): string + { + foreach ($this->providers as $provider) { + /** @var PlaceholderProviderInterface $provider */ + $ret = $provider->replace($placeholder, $target); + if ($ret !== null) { + return $ret; + } + } + + return $placeholder; + } + + /** + * Replaces all placeholders in the input lines. + * @param string $lines The input lines that should be replaced + * @param object $target The object that should be used as source for the informations. + * @return string The Lines with replaced informations. + */ + public function replace(string $lines, object $target): string + { + $patterns = [ + '/(\[\[[A-Z_]+\]\])/' => function ($match) use ($target) { + return $this->handlePlaceholder($match[0], $target); + }, + ]; + + return preg_replace_callback_array($patterns, $lines); + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php new file mode 100644 index 00000000..1b60dfd3 --- /dev/null +++ b/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php @@ -0,0 +1,55 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\PlaceholderProviders; + + +use App\Entity\Base\AbstractDBElement; +use App\Services\ElementTypeNameGenerator; + +final class AbstractDBElementProvider implements PlaceholderProviderInterface +{ + private $elementTypeNameGenerator; + + public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator) + { + $this->elementTypeNameGenerator = $elementTypeNameGenerator; + } + + /** + * @inheritDoc + */ + public function replace(string $placeholder, object $label_target, array $options = []): ?string + { + if ($label_target instanceof AbstractDBElement) { + + if ($placeholder === '[[TYPE]]') { + return $this->elementTypeNameGenerator->getLocalizedTypeLabel($label_target); + } + + if ($placeholder === '[[ID]]') { + return (string) ($label_target->getID() ?? 'unknown'); + } + + } + + return null; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php new file mode 100644 index 00000000..3b6093e0 --- /dev/null +++ b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php @@ -0,0 +1,107 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\PlaceholderProviders; + + +use App\Entity\UserSystem\User; +use IntlDateFormatter; +use Locale; +use Symfony\Component\Security\Core\Security; + +/** + * Provides Placeholders for infos about global infos like Installation name or datetimes. + * @package App\Services\LabelSystem\PlaceholderProviders + */ +final class GlobalProviders implements PlaceholderProviderInterface +{ + + private $partdb_title; + private $security; + + public function __construct(string $partdb_title, Security $security) + { + $this->partdb_title = $partdb_title; + $this->security = $security; + } + + /** + * @inheritDoc + */ + public function replace(string $placeholder, object $label_target, array $options = []): ?string + { + if ($placeholder === "[[INSTALL_NAME]]") { + return $this->partdb_title; + } + + + $user = $this->security->getUser(); + if ($placeholder === "[[USERNAME]]") { + if ($user instanceof User) { + return $user->getName(); + } + return 'anonymous'; + } + + if ($placeholder === "[[USERNAME_FULL]]") { + if ($user instanceof User) { + return $user->getFullName(true); + } + return 'anonymous'; + } + + $now = new \DateTime(); + + if ($placeholder === '[[DATETIME]]') { + $formatter = IntlDateFormatter::create( + Locale::getDefault(), + IntlDateFormatter::SHORT, + IntlDateFormatter::SHORT, + $now->getTimezone() + ); + + return $formatter->format($now); + } + + if ($placeholder === '[[DATE]]') { + $formatter = IntlDateFormatter::create( + Locale::getDefault(), + IntlDateFormatter::SHORT, + IntlDateFormatter::NONE, + $now->getTimezone() + ); + + return $formatter->format($now); + } + + if ($placeholder === '[[TIME]]') { + $formatter = IntlDateFormatter::create( + Locale::getDefault(), + IntlDateFormatter::NONE, + IntlDateFormatter::SHORT, + $now->getTimezone() + ); + + return $formatter->format($now); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php new file mode 100644 index 00000000..fdaf6031 --- /dev/null +++ b/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php @@ -0,0 +1,40 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\PlaceholderProviders; + + +use App\Entity\Contracts\NamedElementInterface; + +final class NamedElementProvider implements PlaceholderProviderInterface +{ + + /** + * @inheritDoc + */ + public function replace(string $placeholder, object $label_target, array $options = []): ?string + { + if ($label_target instanceof NamedElementInterface && $placeholder === '[[NAME]]') { + return $label_target->getName(); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php new file mode 100644 index 00000000..0635a91c --- /dev/null +++ b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php @@ -0,0 +1,91 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\PlaceholderProviders; + + +use App\Entity\Parts\PartLot; +use App\Services\AmountFormatter; +use App\Services\LabelSystem\LabelTextReplacer; +use IntlDateFormatter; +use Locale; + +final class PartLotProvider implements PlaceholderProviderInterface +{ + private $labelTextReplacer; + private $amountFormatter; + + public function __construct(LabelTextReplacer $labelTextReplacer, AmountFormatter $amountFormatter) + { + $this->labelTextReplacer = $labelTextReplacer; + $this->amountFormatter = $amountFormatter; + } + + public function replace(string $placeholder, object $label_target, array $options = []): ?string + { + if ($label_target instanceof PartLot) { + if ($placeholder === '[[LOT_ID]]') { + return $label_target->getID() ?? 'unknown'; + } + + if ($placeholder === '[[LOT_NAME]]') { + return $label_target->getName(); + } + + if ($placeholder === '[[LOT_COMMENT]]') { + return $label_target->getComment(); + } + + if ($placeholder === '[[EXPIRATION_DATE]]') { + if ($label_target->getExpirationDate() === null) { + return ''; + } + $formatter = IntlDateFormatter::create( + Locale::getDefault(), + IntlDateFormatter::SHORT, + IntlDateFormatter::NONE + //$label_target->getExpirationDate()->getTimezone() + ); + + return $formatter->format($label_target->getExpirationDate()); + } + + if ($placeholder === '[[AMOUNT]]') { + if ($label_target->isInstockUnknown()) { + return '?'; + } + return $this->amountFormatter->format($label_target->getAmount(), $label_target->getPart()->getPartUnit()); + } + + if ($placeholder === '[[LOCATION]]') { + return $label_target->getStorageLocation() ? $label_target->getStorageLocation()->getName() : ''; + } + + if ($placeholder === '[[LOCATION_FULL]]') { + return $label_target->getStorageLocation() ? $label_target->getStorageLocation()->getFullPath() : ''; + } + + + return $this->labelTextReplacer->handlePlaceholder($placeholder, $label_target->getPart()); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php new file mode 100644 index 00000000..b6fb5e02 --- /dev/null +++ b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php @@ -0,0 +1,112 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\PlaceholderProviders; + + +use App\Entity\Parts\Part; +use App\Services\SIFormatter; +use Symfony\Contracts\Translation\TranslatorInterface; + +final class PartProvider implements PlaceholderProviderInterface +{ + + private $siFormatter; + private $translator; + + public function __construct(SIFormatter $SIFormatter, TranslatorInterface $translator) + { + $this->siFormatter = $SIFormatter; + $this->translator = $translator; + } + + /** + * @inheritDoc + */ + public function replace(string $placeholder, object $part, array $options = []): ?string + { + if (!$part instanceof Part) { + return null; + } + + if ($placeholder === '[[CATEGORY]]') { + return $part->getCategory() ? $part->getCategory()->getName() : ''; + } + + if ($placeholder === '[[CATEGORY_FULL]]') { + return $part->getCategory() ? $part->getCategory()->getFullPath() : ''; + } + + if ($placeholder === '[[MANUFACTURER]]') { + return $part->getManufacturer() ? $part->getManufacturer()->getName() : ''; + } + + if ($placeholder === '[[MANUFACTURER_FULL]]') { + return $part->getManufacturer() ? $part->getManufacturer()->getFullPath() : ''; + } + + if ($placeholder === '[[FOOTPRINT]]') { + return $part->getFootprint() ? $part->getFootprint()->getName() : ''; + } + + if ($placeholder === '[[FOOTPRINT_FULL]]') { + return $part->getFootprint() ? $part->getFootprint()->getFullPath() : ''; + } + + if ($placeholder === '[[MASS]]') { + return $part->getMass() ? $this->siFormatter->format($part->getMass(), 'g', 1) : ''; + } + + if ($placeholder === '[[MPN]]') { + return $part->getManufacturerProductNumber(); + } + + if ($placeholder === '[[TAGS]]') { + return $part->getTags(); + } + + if ($placeholder === '[[M_STATUS]]') { + if ($part->getManufacturingStatus() === '') { + return ''; + } + return $this->translator->trans('m_status.' . $part->getManufacturingStatus()); + } + + $parsedown = new \Parsedown(); + + if ($placeholder === '[[DESCRIPTION]]') { + return $parsedown->line($part->getDescription()); + } + + if ($placeholder === '[[DESCRIPTION_T]]') { + return strip_tags($parsedown->line($part->getDescription())); + } + + if ($placeholder === '[[COMMENT]]') { + return $parsedown->line($part->getComment()); + } + + if ($placeholder === '[[COMMENT_T]]') { + return strip_tags($parsedown->line($part->getComment())); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/PlaceholderProviders/PlaceholderProviderInterface.php b/src/Services/LabelSystem/PlaceholderProviders/PlaceholderProviderInterface.php new file mode 100644 index 00000000..4bf62049 --- /dev/null +++ b/src/Services/LabelSystem/PlaceholderProviders/PlaceholderProviderInterface.php @@ -0,0 +1,36 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\PlaceholderProviders; + + +interface PlaceholderProviderInterface +{ + + /** + * Determines the real value of this placeholder. + * If the placeholder is not supported, null must be returned. + * @param string $placeholder The placeholder (e.g. "%%PLACEHOLDER%%") that should be replaced + * @param object $label_target The object that is targeted by the label + * @param array $options A list of options that can be used to specify the generated output further. + * @return string|null The real value of this placeholder, null if not supported. + */ + public function replace(string $placeholder, object $label_target, array $options = []): ?string; +} \ No newline at end of file diff --git a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php new file mode 100644 index 00000000..6a643c64 --- /dev/null +++ b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php @@ -0,0 +1,52 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\PlaceholderProviders; + + +use App\Entity\Base\AbstractDBElement; +use App\Entity\Base\AbstractStructuralDBElement; + +final class StructuralDBElementProvider implements PlaceholderProviderInterface +{ + + public function replace(string $placeholder, object $label_target, array $options = []): ?string + { + if ($label_target instanceof AbstractStructuralDBElement) { + if ($placeholder === '[[COMMENT]]') { + return $label_target->getComment(); + } + if ($placeholder === '[[COMMENT_T]]') { + return strip_tags($label_target->getComment()); + } + if ($placeholder === '[[FULL_PATH]]') { + return $label_target->getFullPath(); + } + if ($placeholder === '[[PARENT]]') { + return $label_target->getParent() ? $label_target->getParent()->getName() : ''; + } + if ($placeholder === '[[PARENT_FULL_PATH]]') { + return $label_target->getParent() ? $label_target->getParent()->getFullPath() : ''; + } + } + + return null; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php new file mode 100644 index 00000000..4cb295cb --- /dev/null +++ b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php @@ -0,0 +1,48 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem\PlaceholderProviders; + +use App\Entity\Contracts\TimeStampableInterface; +use IntlDateFormatter; +use Locale; + +final class TimestampableElementProvider implements PlaceholderProviderInterface +{ + + /** + * @inheritDoc + */ + public function replace(string $placeholder, object $label_target, array $options = []): ?string + { + if ($label_target instanceof TimeStampableInterface) { + if ($placeholder === '[[LAST_MODIFIED]]') { + return IntlDateFormatter::formatObject($label_target->getLastModified() ?? new \DateTime(), IntlDateFormatter::SHORT, Locale::getDefault()); + } + + if ($placeholder === '[[CREATION_DATE]]') { + return IntlDateFormatter::formatObject($label_target->getAddedDate() ?? new \DateTime(), IntlDateFormatter::SHORT, Locale::getDefault()); + } + + } + + return null; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/SandboxedTwigProvider.php b/src/Services/LabelSystem/SandboxedTwigProvider.php new file mode 100644 index 00000000..630820fe --- /dev/null +++ b/src/Services/LabelSystem/SandboxedTwigProvider.php @@ -0,0 +1,140 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\LabelSystem; + + +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentContainingDBElement; +use App\Entity\Base\AbstractCompany; +use App\Entity\Base\AbstractDBElement; +use App\Entity\Base\AbstractNamedDBElement; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\Contracts\NamedElementInterface; +use App\Entity\Contracts\TimeStampableInterface; +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; +use App\Entity\Parts\Supplier; +use App\Entity\PriceInformations\Currency; +use App\Entity\PriceInformations\Orderdetail; +use App\Entity\PriceInformations\Pricedetail; +use App\Entity\UserSystem\User; +use App\Twig\AppExtension; +use App\Twig\Sandbox\InheritanceSecurityPolicy; +use Twig\Environment; +use Twig\Extension\SandboxExtension; +use Twig\Extra\Intl\IntlExtension; +use Twig\Sandbox\SecurityPolicy; +use Twig\Sandbox\SecurityPolicyInterface; + +final class SandboxedTwigProvider +{ + private const ALLOWED_TAGS = ['apply', 'autoescape', 'do', 'for', 'if', 'set', 'verbatim', 'with']; + private const ALLOWED_FILTERS = ['abs', 'batch', 'capitalize', 'column', 'country_name', + 'currency_name', 'currency_symbol', 'date', 'date_modify', 'default', 'escape', 'filter', 'first', 'format', + 'format_currency', 'format_date', 'format_datetime', 'format_number', 'format_time', 'join', 'keys', + 'language_name', 'last', 'length', 'locale_name', 'lower', 'map', 'merge', 'nl2br', 'raw', 'number_format', + 'reduce', 'replace', 'reverse', 'slice', 'sort', 'spaceless', 'split', 'striptags', 'timezone_name', 'title', + 'trim', 'upper', 'url_encode', + //Part-DB specific filters: + 'moneyFormat', 'siFormat', 'amountFormat']; + + private const ALLOWED_FUNCTIONS = ['date', 'html_classes', 'max', 'min', 'random', 'range']; + + private const ALLOWED_METHODS = [ + NamedElementInterface::class => ['getName'], + AbstractDBElement::class => ['getID', '__toString'], + TimeStampableInterface::class => ['getLastModified', 'getAddedDate'], + AbstractStructuralDBElement::class => ['isChildOf', 'isRoot', 'getParent', 'getComment', 'getLevel', + 'getFullPath', 'getPathArray', 'getChildren', 'isNotSelectable'], + AbstractCompany::class => ['getAddress', 'getPhoneNumber', 'getFaxNumber', 'getEmailAddress', 'getWebsite'], + AttachmentContainingDBElement::class => ['getAttachments', 'getMasterPictureAttachment'], + Attachment::class => ['isPicture', 'is3DModel', 'isExternal', 'isSecure', 'isBuiltIn', 'getExtension', + 'getElement', 'getURL', 'getFilename', 'getAttachmentType', 'getShowInTable'], + AbstractParameter::class => ['getFormattedValue', 'getGroup', 'getSymbol', 'getValueMin', 'getValueMax', + 'getValueTypical', 'getUnit', 'getValueText'], + MeasurementUnit::class => ['getUnit', 'isInteger', 'useSIPrefix'], + PartLot::class => ['isExpired', 'getDescription', 'getComment', 'getExpirationDate', 'getStorageLocation', + 'getPart', 'isInstockUnknown', 'getAmount', 'getNeedsRefill'], + Storelocation::class => ['isFull', 'isOnlySinglePart', 'isLimitToExistingParts', 'getStorageType'], + Supplier::class => ['getShippingCosts', 'getDefaultCurrency'], + Part::class => ['isNeedsReview', 'getTags', 'getMass', 'getDescription', 'isFavorite', 'getCategory', + 'getFootprint', 'getPartLots', 'getPartUnit', 'useFloatAmount', 'getMinAmount', 'getAmountSum', + 'getManufacturerProductUrl', 'getCustomProductURL', 'getManufacturingStatus', 'getManufacturer', + 'getManufacturerProductNumber', 'getOrderdetails', 'isObsolete'], + Currency::class => ['getIsoCode', 'getInverseExchangeRate', 'getExchangeRate'], + Orderdetail::class => ['getPart', 'getSupplier', 'getSupplierPartNr', 'getObsolete', + 'getPricedetails', 'findPriceForQty', ], + Pricedetail::class => ['getOrderdetail', 'getPrice', 'getPricePerUnit', 'getPriceRelatedQuantity', + 'getMinDiscountQuantity', 'getCurrency'], + //Only allow very little information about users... + User::class => ['isAnonymousUser', 'getUsername', 'getFullName', 'getFirstName', 'getLastName', + 'getDepartment', 'getEmail'] + + ]; + private const ALLOWED_PROPERTIES = []; + + private $appExtension; + + public function __construct(AppExtension $appExtension) + { + $this->appExtension = $appExtension; + } + + public function getTwig(LabelOptions $options): Environment + { + if ($options->getLinesMode() !== 'twig') { + throw new \InvalidArgumentException('The LabelOptions must explicitly allow twig via lines_mode = "twig"!'); + } + + + $loader = new \Twig\Loader\ArrayLoader([ + 'lines' => $options->getLines(), + ]); + $twig = new Environment($loader); + + //Second argument activate sandbox globally. + $sandbox = new SandboxExtension($this->getSecurityPolicy(), true); + $twig->addExtension($sandbox); + + //Add IntlExtension + $twig->addExtension(new IntlExtension()); + + //Add Part-DB specific extension + $twig->addExtension($this->appExtension); + + return $twig; + } + + protected function getSecurityPolicy(): SecurityPolicyInterface + { + return new InheritanceSecurityPolicy( + self::ALLOWED_TAGS, + self::ALLOWED_FILTERS, + self::ALLOWED_METHODS, + self::ALLOWED_PROPERTIES, + self::ALLOWED_FUNCTIONS + ); + } +} \ No newline at end of file diff --git a/src/Services/Misc/RangeParser.php b/src/Services/Misc/RangeParser.php new file mode 100644 index 00000000..3714878f --- /dev/null +++ b/src/Services/Misc/RangeParser.php @@ -0,0 +1,94 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Services\Misc; + +/** + * This Parser allows to parse number ranges like 1-3, 4, 5 + */ +class RangeParser +{ + /** + * Converts the given range string to an array of all numbers in the given range. + * @param string $range_str A range string like '1-3, 5, 6' + * @return int[] An array with all numbers from the range (e.g. [1, 2, 3, 5, 6] + */ + public function parse(string $range_str): array + { + //Normalize number separator (we allow , and ;): + $range_str = str_replace(';', ',', $range_str); + + $numbers = explode(',', $range_str); + $ranges = []; + foreach ($numbers as $number) { + $number = trim($number); + //Extract min / max if token is a range + $matches = []; + if (preg_match('/^(-?\s*\d+)\s*-\s*(-?\s*\d+)$/', $number, $matches)) { + $ranges[] = $this->generateMinMaxRange($matches[1], $matches[2]); + } elseif (is_numeric($number)) { + $ranges[] = [(int) $number]; + } elseif (empty($number)) { //Allow empty tokens + continue; + } else { + throw new \InvalidArgumentException('Invalid range encoutered: ' . $number); + } + } + + //Flatten ranges array + return array_merge([], ...$ranges); + } + + /** + * Checks if the given string is a valid range. + * @param string $range_str The string that should be checked + * @return bool True if the string is valid, false if not. + */ + public function isValidRange(string $range_str): bool + { + try { + $this->parse($range_str); + return true; + } catch (\InvalidArgumentException $exception) { + return false; + } + } + + protected function generateMinMaxRange(string $min, string $max): array + { + $min = (int) $min; + $max = (int) $max; + + //Ensure that $max > $min + if ($min > $max) { + $a = $max; + $max = $min; + $min = $a; + } + + $tmp = []; + while ($min <= $max) { + $tmp[] = $min; + $min++; + }; + + return $tmp; + } +} \ No newline at end of file diff --git a/src/Services/SIFormatter.php b/src/Services/SIFormatter.php index 7dbbae58..0a736d84 100644 --- a/src/Services/SIFormatter.php +++ b/src/Services/SIFormatter.php @@ -105,6 +105,7 @@ class SIFormatter * @param float $value The value that should be converted * @param string $unit The unit that should be appended after the prefix * @param int $decimals the number of decimals (after decimal dot) that should be outputed + * @return string The formatted value */ public function format(float $value, string $unit = '', int $decimals = 2): string { diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index 65c8ae2f..184359f8 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -45,6 +45,7 @@ namespace App\Services\Trees; use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\PartAttachment; use App\Entity\Devices\Device; +use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; @@ -104,6 +105,7 @@ class ToolsTreeBuilder $item->tag(['tree_tools', 'groups', $this->keyGenerator->generateKey()]); $tree = []; + $tree[] = new TreeViewNode($this->translator->trans('tree.tools.tools'), null, $this->getToolsNode()); $tree[] = new TreeViewNode($this->translator->trans('tree.tools.edit'), null, $this->getEditNodes()); $tree[] = new TreeViewNode($this->translator->trans('tree.tools.show'), null, $this->getShowNodes()); $tree[] = new TreeViewNode($this->translator->trans('tree.tools.system'), null, $this->getSystemNodes()); @@ -112,6 +114,27 @@ class ToolsTreeBuilder }); } + protected function getToolsNode(): array + { + $nodes = []; + + if ($this->security->isGranted('@labels.create_labels')) { + $nodes[] = new TreeViewNode( + $this->translator->trans('tree.tools.tools.label_dialog'), + $this->urlGenerator->generate('label_dialog') + ); + } + + if ($this->security->isGranted('@tools.label_scanner')) { + $nodes[] = new TreeViewNode( + $this->translator->trans('tree.tools.tools.label_scanner'), + $this->urlGenerator->generate('scan_dialog') + ); + } + + return $nodes; + } + /** * This functions creates a tree entries for the "edit" node of the tool's tree. * @@ -175,6 +198,12 @@ class ToolsTreeBuilder $this->urlGenerator->generate('measurement_unit_new') ); } + if ($this->security->isGranted('read', new LabelProfile())) { + $nodes[] = new TreeViewNode( + $this->translator->trans('tree.tools.edit.label_profile'), + $this->urlGenerator->generate('label_profile_new') + ); + } if ($this->security->isGranted('create', new Part())) { $nodes[] = new TreeViewNode( $this->translator->trans('tree.tools.edit.part'), @@ -182,6 +211,7 @@ class ToolsTreeBuilder ); } + return $nodes; } diff --git a/src/Services/Trees/TreeViewGenerator.php b/src/Services/Trees/TreeViewGenerator.php index 44334cf9..1aff2ab4 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -118,10 +118,15 @@ class TreeViewGenerator $item->addTag((string) \count($item->getNodes())); } - if (! empty($href_type)) { + if (! empty($href_type) && $item->getId() !== null) { $entity = $this->em->getPartialReference($class, $item->getId()); $item->setHref($this->urlGenerator->getURL($entity, $href_type)); } + + //Translate text if text starts with $$ + if (substr($item->getText(), 0, 2) === '$$') { + $item->setText($this->translator->trans(substr($item->getText(), 2))); + } } return array_merge($head, $generic); diff --git a/src/Twig/Sandbox/InheritanceSecurityPolicy.php b/src/Twig/Sandbox/InheritanceSecurityPolicy.php new file mode 100644 index 00000000..3f0a32d8 --- /dev/null +++ b/src/Twig/Sandbox/InheritanceSecurityPolicy.php @@ -0,0 +1,140 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Twig\Sandbox; + +use Twig\Markup; +use Twig\Sandbox\SecurityNotAllowedFilterError; +use Twig\Sandbox\SecurityNotAllowedFunctionError; +use Twig\Sandbox\SecurityNotAllowedMethodError; +use Twig\Sandbox\SecurityNotAllowedPropertyError; +use Twig\Sandbox\SecurityNotAllowedTagError; +use Twig\Sandbox\SecurityPolicyInterface; +use Twig\Template; + +/** + * Represents a security policy which need to be enforced when sandbox mode is enabled. + * + * Modified by Jan Böhmer, to allow inheritance of methods and properties via class hierachy. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class InheritanceSecurityPolicy implements SecurityPolicyInterface +{ + private $allowedTags; + private $allowedFilters; + private $allowedMethods; + private $allowedProperties; + private $allowedFunctions; + + public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) + { + $this->allowedTags = $allowedTags; + $this->allowedFilters = $allowedFilters; + $this->setAllowedMethods($allowedMethods); + $this->allowedProperties = $allowedProperties; + $this->allowedFunctions = $allowedFunctions; + } + + public function setAllowedTags(array $tags): void + { + $this->allowedTags = $tags; + } + + public function setAllowedFilters(array $filters): void + { + $this->allowedFilters = $filters; + } + + public function setAllowedMethods(array $methods): void + { + $this->allowedMethods = []; + foreach ($methods as $class => $m) { + $this->allowedMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]); + } + } + + public function setAllowedProperties(array $properties): void + { + $this->allowedProperties = $properties; + } + + public function setAllowedFunctions(array $functions): void + { + $this->allowedFunctions = $functions; + } + + public function checkSecurity($tags, $filters, $functions): void + { + foreach ($tags as $tag) { + if (!\in_array($tag, $this->allowedTags)) { + throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag); + } + } + + foreach ($filters as $filter) { + if (!\in_array($filter, $this->allowedFilters)) { + throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter); + } + } + + foreach ($functions as $function) { + if (!\in_array($function, $this->allowedFunctions)) { + throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function); + } + } + } + + public function checkMethodAllowed($obj, $method): void + { + if ($obj instanceof Template || $obj instanceof Markup) { + return; + } + + $allowed = false; + $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + foreach ($this->allowedMethods as $class => $methods) { + if ($obj instanceof $class) { + $allowed = \in_array($method, $methods); + + //CHANGED: Only break if we the method is allowed, otherwise try it on the other methods + if ($allowed) { + break; + } + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); + } + } + + public function checkPropertyAllowed($obj, $property): void + { + $allowed = false; + foreach ($this->allowedProperties as $class => $properties) { + if ($obj instanceof $class) { + $allowed = \in_array($property, \is_array($properties) ? $properties : [$properties]); + + //CHANGED: Only break if we the method is allowed, otherwise try it on the other methods + if ($allowed) { + break; + } + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); + } + } +} diff --git a/src/Validator/Constraints/Misc/ValidRange.php b/src/Validator/Constraints/Misc/ValidRange.php new file mode 100644 index 00000000..9f7e33d5 --- /dev/null +++ b/src/Validator/Constraints/Misc/ValidRange.php @@ -0,0 +1,33 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Validator\Constraints\Misc; + + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + * @package App\Validator\Constraints\Misc + */ +class ValidRange extends Constraint +{ + public $message = 'validator.invalid_range'; +} \ No newline at end of file diff --git a/src/Validator/Constraints/Misc/ValidRangeValidator.php b/src/Validator/Constraints/Misc/ValidRangeValidator.php new file mode 100644 index 00000000..17d36855 --- /dev/null +++ b/src/Validator/Constraints/Misc/ValidRangeValidator.php @@ -0,0 +1,61 @@ +<?php +/** + * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). + * + * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace App\Validator\Constraints\Misc; + + +use App\Services\Misc\RangeParser; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; +use Symfony\Component\Validator\Exception\UnexpectedValueException; + +class ValidRangeValidator extends ConstraintValidator +{ + + protected $rangeParser; + + public function __construct(RangeParser $rangeParser) + { + $this->rangeParser = $rangeParser; + } + + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof ValidRange) { + throw new UnexpectedTypeException($constraint, ValidRange::class); + } + + // custom constraints should ignore null and empty values to allow + // other constraints (NotBlank, NotNull, etc.) take care of that + if (null === $value || '' === $value) { + return; + } + + if (!is_string($value)) { + throw new UnexpectedValueException($value, 'string'); + } + + if(!$this->rangeParser->isValidRange($value)) { + $this->context->buildViolation($constraint->message) + ->addViolation(); + } + } +} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index fbaf0878..872a9b77 100644 --- a/symfony.lock +++ b/symfony.lock @@ -120,12 +120,18 @@ "doctrine/reflection": { "version": "v1.0.0" }, + "dompdf/dompdf": { + "version": "v0.8.5" + }, "egulias/email-validator": { "version": "2.1.11" }, "ekino/phpstan-banned-code": { "version": "v0.3.1" }, + "erusev/parsedown": { + "version": "1.7.4" + }, "felixfbecker/advanced-json-rpc": { "version": "v3.0.4" }, @@ -205,24 +211,12 @@ "netresearch/jsonmapper": { "version": "v1.6.0" }, - "nette/application": { - "version": "v3.0.3" - }, - "nette/component-model": { - "version": "v3.0.0" - }, "nette/finder": { "version": "v2.5.2" }, - "nette/http": { - "version": "v3.0.3" - }, "nette/robot-loader": { "version": "v3.2.1" }, - "nette/routing": { - "version": "v3.0.0" - }, "nette/utils": { "version": "v3.1.0" }, @@ -271,6 +265,12 @@ "paragonie/random_compat": { "version": "v9.99.99" }, + "phenx/php-font-lib": { + "version": "0.5.1" + }, + "phenx/php-svg-lib": { + "version": "v0.3.3" + }, "php": { "version": "7.2.5" }, @@ -373,6 +373,9 @@ "s9e/text-formatter": { "version": "2.1.2" }, + "sabberworm/php-css-parser": { + "version": "8.3.0" + }, "scheb/two-factor-bundle": { "version": "3.16", "recipe": { @@ -656,6 +659,9 @@ "symfony/polyfill-mbstring": { "version": "v1.10.0" }, + "symfony/polyfill-php70": { + "version": "v1.15.0" + }, "symfony/polyfill-php72": { "version": "v1.10.0" }, @@ -830,18 +836,30 @@ "symplify/coding-standard": { "version": "v7.1.3" }, + "symplify/console-color-diff": { + "version": "v7.3.10" + }, "symplify/easy-coding-standard": { "version": "v7.1.3" }, "symplify/package-builder": { "version": "v7.1.3" }, + "symplify/phpstan-extensions": { + "version": "v7.3.10" + }, "symplify/set-config-resolver": { "version": "v7.1.3" }, "symplify/smart-file-system": { "version": "v7.1.3" }, + "tecnickcom/tc-lib-barcode": { + "version": "1.15.20" + }, + "tecnickcom/tc-lib-color": { + "version": "1.12.15" + }, "thecodingmachine/safe": { "version": "v0.1.16" }, @@ -854,6 +872,9 @@ "twig/extra-bundle": { "version": "v3.0.0" }, + "twig/html-extra": { + "version": "v3.0.3" + }, "twig/inky-extra": { "version": "v3.0.0" }, diff --git a/templates/AdminPages/LabelProfileAdmin.html.twig b/templates/AdminPages/LabelProfileAdmin.html.twig new file mode 100644 index 00000000..b4e8977e --- /dev/null +++ b/templates/AdminPages/LabelProfileAdmin.html.twig @@ -0,0 +1,52 @@ +{% extends "AdminPages/EntityAdminBase.html.twig" %} + +{% block card_title %} + <i class="fas fa-qrcode fa-fw"></i> {% trans %}label_profile.caption{% endtrans %} +{% endblock %} + +{% block additional_pills %} + <li class="nav-item"><a data-toggle="tab" class="nav-link link-anchor" href="#tab_advanced">{% trans %}label_profile.advanced{% endtrans %}</a></li> + <li class="nav-item"><a data-toggle="tab" class="nav-link link-anchor" href="#tab_comment">{% trans %}label_profile.comment{% endtrans %}</a></li> +{% endblock %} + +{% block additional_panes %} + <div class="tab-pane" id="tab_advanced"> + {{ form_row(form.options.additional_css) }} + {{ form_widget(form.options) }} + </div> + + <div class="tab-pane" id="tab_comment"> + {{ form_widget(form.comment) }} + </div> +{% endblock %} + +{% block comment %}{% endblock %} + +{% block additional_controls %} + {{ form_row(form.show_in_dropdown) }} + {{ form_row(form.options.supported_element) }} + <div class="form-group row"> + {{ form_label(form.options.width) }} + <div class="input-group col-9"> + {{ form_widget(form.options.width) }} + <div class="input-group-append input-group-prepend"> + <span class="input-group-text">x</span> + </div> + {{ form_widget(form.options.height) }} + <div class="input-group-append"> + <span class="input-group-text">mm</span> + </div> + </div> + </div> + {{ form_row(form.options.barcode_type) }} + {{ form_row(form.options.lines) }} +{% endblock %} + +{% block additional_content %} + {% if pdf_data is defined and pdf_data is not empty %} + <div class="card mt-2 mb-2 p-1 border-secondary" style="resize: vertical; overflow: scroll; height: 250px;"> + <object id="pdf_preview" data="{{ pdf_data | data_uri(mime='application/pdf') }}"style="height: inherit"> + </object> + </div> + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/AdminPages/StorelocationAdmin.html.twig b/templates/AdminPages/StorelocationAdmin.html.twig index 60bc95ba..57103a41 100644 --- a/templates/AdminPages/StorelocationAdmin.html.twig +++ b/templates/AdminPages/StorelocationAdmin.html.twig @@ -1,10 +1,18 @@ {% extends "AdminPages/EntityAdminBase.html.twig" %} +{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %} {% block card_title %} <i class="fas fa-cube fa-fw"></i> {% trans %}storelocation.labelp{% endtrans %} {% endblock %} {% block additional_controls %} + {% if entity.id %} + <div class="row form-group"> + <div class="offset-sm-3 col-sm-9"> + {{ dropdown.profile_dropdown('storelocation', entity.id) }} + </div> + </div> + {% endif %} {% endblock %} {% block additional_pills %} diff --git a/templates/LabelSystem/Scanner/dialog.html.twig b/templates/LabelSystem/Scanner/dialog.html.twig new file mode 100644 index 00000000..9e5e0503 --- /dev/null +++ b/templates/LabelSystem/Scanner/dialog.html.twig @@ -0,0 +1,33 @@ +{% extends 'main_card.html.twig' %} + +{% block card_title %}<i class="fas fa-atom fa-fw"></i> {% trans %}label_scanner.title{% endtrans %}{% endblock %} + +{% block card_content %} + <div class="alert alert-warning" id="scanner-warning"> + <strong>{% trans %}label_scanner.no_cam_found.title{% endtrans %}</strong>: {% trans %}label_scanner.no_cam_found.text{% endtrans %} + </div> + + {{ form_start(form, {'attr': {'id': 'scan_dialog_form'}}) }} + + {{ form_end(form) }} + + <div class=""> + <div class="form-group row"> + <div class="offset-sm-3 col-sm-9"> + <video id="video" width="100%" height="auto" class="scanner-video img-thumbnail d-none"> + </video> + </div> + </div> + + {# <div> + <button type="button" class="btn btn-secondary" id="changeSrcBtn">Change Source</button> + </div>#} + + <div id="sourceSelectPanel" class="form-group row d-none"> + <label for="sourceSelect" class="col-sm-3 col-form-label">{% trans %}label_scanner.source_select{% endtrans %}</label> + <div class="col-sm-9"> + <select id="sourceSelect" style="" class="form-control"></select> + </div> + </div> + </div> +{% endblock %} diff --git a/templates/LabelSystem/dialog.html.twig b/templates/LabelSystem/dialog.html.twig new file mode 100644 index 00000000..e02bc099 --- /dev/null +++ b/templates/LabelSystem/dialog.html.twig @@ -0,0 +1,116 @@ +{% extends 'main_card.html.twig' %} + +{% block title %}{% trans %}label_generator.title{% endtrans %}{% endblock %} + +{%- block card_title -%} + <i class="fas fa-qrcode fa-fw"></i> {% trans %}label_generator.title{% endtrans %} + {% if profile %}({{ profile.name }}){% endif %} +{%- endblock -%} + +{% block card_content %} + {{ form_start(form) }} + + <ul class="nav nav-tabs"> + <li class="nav-item"> + <a class="nav-link active" data-toggle="tab" id="common-tab" role="tab" aria-controls="common" aria-selected="true" href="#common" + >{% trans %}label_generator.common{% endtrans %}</a> + </li> + <li class="nav-item"> + <a class="nav-link" data-toggle="tab" id="advanced-tab" role="tab" aria-controls="advanced" aria-selected="false" href="#advanced" + >{% trans %}label_generator.advanced{% endtrans %}</a> + </li> + <li class="nav-item"> + <a class="nav-link" data-toggle="tab" id="profiles-tab" role="tab" aria-controls="profiles" aria-selected="false" href="#profiles" + >{% trans %}label_generator.profiles{% endtrans %}</a> + </li> + </ul> + + <div class="tab-content mt-2"> + + <div class="tab-pane active" id="common" role="tabpanel" aria-labelledby="common-tab"> + {{ form_row(form.target_id) }} + + {{ form_row(form.options.supported_element) }} + <div class="form-group row"> + {{ form_label(form.options.width) }} + <div class="input-group col-9"> + {{ form_widget(form.options.width) }} + <div class="input-group-append input-group-prepend"> + <span class="input-group-text">x</span> + </div> + {{ form_widget(form.options.height) }} + <div class="input-group-append"> + <span class="input-group-text">mm</span> + </div> + </div> + </div> + {{ form_row(form.options.barcode_type) }} + {{ form_row(form.options.lines) }} + </div> + + <div class="tab-pane" id="advanced" role="tabpanel" aria-labelledby="advanced-tab"> + {{ form_row(form.options.additional_css) }} + {{ form_widget(form.options) }} + </div> + + <div class="tab-pane" id="profiles" role="tabpanel" aria-labelledby="profiles-tab"> + <div class="form-group row"> + <label class="col-sm-3 col-form-label">{% trans %}label_generator.selected_profile{% endtrans %}</label> + <div class="col-sm-9"> + <span class="form-control-plaintext">{{ profile.name ?? '-' }} + {% if profile %} + <a href="{{ profile | entityURL('edit') }}" title="{% trans %}label_generator.edit_profile{% endtrans %}" + ><i class="fas fa-edit"></i></a> + {% endif %} + </span> + </div> + </div> + + <div class="form-group row"> + <div class="offset-sm-3 col-sm-9"> + <div class="dropdown"> + <button class="btn btn-info dropdown-toggle" type="button" id="loadProfilesButton" + {% if not is_granted("@labels.create_labels") %}disabled{% endif %} + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + {% trans %}label_generator.load_profile{% endtrans %} + </button> + <div class="dropdown-menu" aria-labelledby="loadProfilesButton"> + {% if is_granted("@labels.create_labels") %} + {% for type in constant("App\\Entity\\LabelSystem\\LabelOptions::SUPPORTED_ELEMENTS") %} + {% set profiles = label_profile_dropdown_helper.dropdownProfiles(type) %} + {% if profiles is not empty %} + <h6 class="dropdown-header">{{ (type~'.label') | trans }}</h6> + {% endif %} + {% for profile in profiles %} + <a class="dropdown-item" href="{{ path('label_dialog_profile', {'profile': profile.id }) }}">{{ profile.name }}</a> + {% endfor %} + {% endfor %} + {% endif %} + </div> + </div> + </div> + </div> + </div> + </div> + + + {{ form_end(form) }} + {% if pdf_data %} + <div class="row"> + <div class="col-sm-9 offset-sm-3"> + <a data-no-ajax class="btn btn-secondary" href="#" onclick="this.href = document.getElementById('pdf_preview').data" download="{{ filename ?? '' }}"> + {% trans %}label_generator.download{% endtrans %} + </a> + </div> + </div> + {% endif %} +{% endblock %} + +{% block additional_content %} + {% if pdf_data %} + <div class="card mt-2 p-1 border-secondary" style="resize: vertical; overflow: scroll; height: 250px"> + <object id="pdf_preview" data="{{ pdf_data | data_uri(mime='application/pdf') }}"style="height: inherit"> + </object> + </div> + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/LabelSystem/dropdown_macro.html.twig b/templates/LabelSystem/dropdown_macro.html.twig new file mode 100644 index 00000000..98d198f9 --- /dev/null +++ b/templates/LabelSystem/dropdown_macro.html.twig @@ -0,0 +1,24 @@ +{% macro profile_dropdown(type, id = null, include_text = true, btn_type = 'btn-secondary') %} + <div class="dropdown"> + <button type="button" class="btn {{ btn_type }} dropdown-toggle" title="{% trans %}label_generator.label_btn{% endtrans %}" + data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" {% if not is_granted("@labels.create_labels") %}disabled{% endif %}> + <i class="fas fa-fw fa-qrcode"></i> {% if include_text %}{% trans %}label_generator.label_btn{% endtrans %}{% endif %} + </button> + <div class="dropdown-menu"> + {% if is_granted('@labels.read_profiles') %} + {% set profiles = label_profile_dropdown_helper.dropdownProfiles(type) %} + {% else %} + {% set profiles = [] %} + {% endif %} + {% for profile in profiles %} + <a class="dropdown-item" href="{{ path('label_dialog_profile', {'profile': profile.id, 'target_type': type, 'target_id': id, 'generate': true}) }}">{{ profile.name }}</a> + {% endfor %} + {% if profiles is not empty and is_granted('@labels.edit_options') %} + <div class="dropdown-divider"></div> + {% endif %} + {% if is_granted('@labels.edit_options') %} {# An empty dialog does not make much sense, when you can not edit the options... #} + <a class="dropdown-item" href="{{ path('label_dialog', {'target_type': type, 'target_id': id}) }}">{% trans %}label_generator.label_empty{% endtrans %}</a> + {% endif %} + </div> + </div> +{% endmacro %} \ No newline at end of file diff --git a/templates/LabelSystem/labels/base_label.html.twig b/templates/LabelSystem/labels/base_label.html.twig new file mode 100644 index 00000000..4c41a048 --- /dev/null +++ b/templates/LabelSystem/labels/base_label.html.twig @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <title>{{ meta_title }} + + + + + + +{% for element in elements %} +
+ {% if options.barcodeType == 'none' %} + {% include "LabelSystem/labels/label_page_none.html.twig" %} + {% elseif options.barcodeType in ['qr', 'datamatrix'] %} + {% include "LabelSystem/labels/label_page_qr.html.twig" %} + {% elseif options.barcodeType in ['code39', 'code93', 'code128'] %} + {% include "LabelSystem/labels/label_page_1d.html.twig" %} + {% endif %} +
+{% endfor %} + + \ No newline at end of file diff --git a/templates/LabelSystem/labels/label_page_1d.html.twig b/templates/LabelSystem/labels/label_page_1d.html.twig new file mode 100644 index 00000000..ffdc8d5c --- /dev/null +++ b/templates/LabelSystem/labels/label_page_1d.html.twig @@ -0,0 +1,27 @@ +
+
+ {{ element.lines | raw }} +
+
+ + {{ element.barcode_content }} +
+
+ +{# +
+
+
+
+ +
+ +
+
+
+ {{ element.lines | raw }} +
+
+
+
+#} \ No newline at end of file diff --git a/templates/LabelSystem/labels/label_page_none.html.twig b/templates/LabelSystem/labels/label_page_none.html.twig new file mode 100644 index 00000000..acbd58b0 --- /dev/null +++ b/templates/LabelSystem/labels/label_page_none.html.twig @@ -0,0 +1 @@ +{{ element.lines | raw }} \ No newline at end of file diff --git a/templates/LabelSystem/labels/label_page_qr.html.twig b/templates/LabelSystem/labels/label_page_qr.html.twig new file mode 100644 index 00000000..bc144b0b --- /dev/null +++ b/templates/LabelSystem/labels/label_page_qr.html.twig @@ -0,0 +1,17 @@ +
+
+
+
+ + + +
+ {#{{ element.barcode | raw }} #} +
+
+
+ {{ element.lines | raw }} +
+
+
+
\ No newline at end of file diff --git a/templates/LabelSystem/labels/label_style.css.twig b/templates/LabelSystem/labels/label_style.css.twig new file mode 100644 index 00000000..41e25eb5 --- /dev/null +++ b/templates/LabelSystem/labels/label_style.css.twig @@ -0,0 +1,157 @@ +@page { + margin: 12px 6px; +} + +body { + font-family: "DejaVu Sans Mono"; + font-size: 9pt; + line-height: 1.0; +} + +p { + margin: 0; +} + +hr { + margin: 2px; +} + +.qr { + max-width: 80%; +} + +.qr-container a { + display: block; +} + +.C39 { + max-width: 150px; +} + +.C39-container { + display: inline-block; + align-content: center; + text-align: left; + position: fixed; + bottom: 32px; +} + +.C39-text { + display: block; + margin-top: -6px; + font-size: 6pt; +} + +/************************************** + Grid system token from simplegrid.io + + SIMPLE GRID + (C) ZACH COLE 2016 + ************************************/ +.font-light { + font-weight: 300; +} + +.font-regular { + font-weight: 400; +} + +.font-heavy { + font-weight: 700; +} + +/* POSITIONING */ + +.left { + text-align: left; +} + +.right { + text-align: right; +} + +.center { + text-align: center; + margin-left: auto; + margin-right: auto; +} + +.justify { + text-align: justify; +} + +/* ==== GRID SYSTEM ==== */ + +.container { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +.row { + position: relative; + width: 100%; + page-break-after: avoid; + page-break-before: avoid; + page-break-inside: avoid; +} + +.row [class^="col"] { + float: left; + margin: 0 0.5%; + min-height: 0.125rem ; +} + +.row::after { + content: ""; + display: table; + clear: both; +} + +.col-1 { + width: 4.33%; +} + +.col-2 { + width: 12.66%; +} + +.col-3 { + width: 21%; +} + +.col-4 { + width: 29.33%; +} + +.col-5 { + width: 37.66%; +} + +.col-6 { + width: 46%; +} + +.col-7 { + width: 54.33%; +} + +.col-8 { + width: 62.66%; +} + +.col-9 { + width: 71%; +} + +.col-10 { + width: 79.33%; +} + +.col-11 { + width: 87.66%; +} + +.col-12 { + width: 96%; +} diff --git a/templates/Parts/info/_part_lots.html.twig b/templates/Parts/info/_part_lots.html.twig index a53d49e6..c57c69f8 100644 --- a/templates/Parts/info/_part_lots.html.twig +++ b/templates/Parts/info/_part_lots.html.twig @@ -1,4 +1,5 @@ {% import "helper.twig" as helper %} +{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %} @@ -7,6 +8,7 @@ {# Tags row #} + {# Button row #} @@ -55,6 +57,9 @@ {% endif %} + {% endfor %} diff --git a/templates/Parts/info/_tools.html.twig b/templates/Parts/info/_tools.html.twig index eb2cbe72..f6f424ed 100644 --- a/templates/Parts/info/_tools.html.twig +++ b/templates/Parts/info/_tools.html.twig @@ -1,3 +1,5 @@ +{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %} + {% if is_granted('edit', part) %} @@ -46,4 +48,6 @@ - \ No newline at end of file + + +{{ dropdown.profile_dropdown('part', part.id) }} \ No newline at end of file diff --git a/templates/Parts/lists/_info_card.html.twig b/templates/Parts/lists/_info_card.html.twig index ed5bdb4c..a73196aa 100644 --- a/templates/Parts/lists/_info_card.html.twig +++ b/templates/Parts/lists/_info_card.html.twig @@ -1,4 +1,5 @@ {% import "helper.twig" as helper %} +{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %} {{ helper.breadcrumb_entity_link(entity) }} @@ -77,6 +78,11 @@ {{ entity.addedDate | format_datetime("short") }} + + {% if entity is instanceof("App\\Entity\\Parts\\Storelocation") %} + {{ dropdown.profile_dropdown('storelocation', entity.id, true, 'btn-secondary btn-block mt-2') }} + {% endif %} + diff --git a/templates/_navbar.html.twig b/templates/_navbar.html.twig index 83d78e9b..ac1eef49 100644 --- a/templates/_navbar.html.twig +++ b/templates/_navbar.html.twig @@ -16,17 +16,19 @@
{% trans %}part_lots.storage_location{% endtrans %} {% trans %}part_lots.amount{% endtrans %}
+ {{ dropdown.profile_dropdown('part_lot', lot.id, false) }} +