forked from mirror/Part-DB.Part-DB-server
Merge branch 'label_generator'
This commit is contained in:
commit
c0f5952994
118 changed files with 10459 additions and 3205 deletions
|
@ -832,4 +832,9 @@ div.dataTables_wrapper div.dataTables_info {
|
||||||
|
|
||||||
.darkmode--activated img {
|
.darkmode--activated img {
|
||||||
mix-blend-mode: difference;
|
mix-blend-mode: difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scanner-video {
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 250px;
|
||||||
}
|
}
|
|
@ -23,6 +23,7 @@ import "marked";
|
||||||
import * as marked from "marked";
|
import * as marked from "marked";
|
||||||
import "qrcode";
|
import "qrcode";
|
||||||
import {parse} from "marked";
|
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
|
//Need for proper body padding, with every navbar height
|
||||||
$(window).resize(function () {
|
$(window).resize(function () {
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
"beberlei/doctrineextensions": "^1.2",
|
"beberlei/doctrineextensions": "^1.2",
|
||||||
"doctrine/annotations": "^1.6",
|
"doctrine/annotations": "^1.6",
|
||||||
"doctrine/doctrine-bundle": "^2.0",
|
"doctrine/doctrine-bundle": "^2.0",
|
||||||
|
"dompdf/dompdf": "^0.8.5",
|
||||||
|
"erusev/parsedown": "^1.7",
|
||||||
"florianv/swap": "^4.0",
|
"florianv/swap": "^4.0",
|
||||||
"friendsofsymfony/ckeditor-bundle": "^2.0",
|
"friendsofsymfony/ckeditor-bundle": "^2.0",
|
||||||
"gregwar/captcha-bundle": "^2.1.0",
|
"gregwar/captcha-bundle": "^2.1.0",
|
||||||
|
@ -50,8 +52,10 @@
|
||||||
"symfony/web-link": "4.4.*",
|
"symfony/web-link": "4.4.*",
|
||||||
"symfony/webpack-encore-bundle": "^1.1",
|
"symfony/webpack-encore-bundle": "^1.1",
|
||||||
"symfony/yaml": "4.4.*",
|
"symfony/yaml": "4.4.*",
|
||||||
|
"tecnickcom/tc-lib-barcode": "^1.15",
|
||||||
"twig/cssinliner-extra": "^3.0",
|
"twig/cssinliner-extra": "^3.0",
|
||||||
"twig/extra-bundle": "^3.0",
|
"twig/extra-bundle": "^3.0",
|
||||||
|
"twig/html-extra": "^3.0",
|
||||||
"twig/inky-extra": "^3.0",
|
"twig/inky-extra": "^3.0",
|
||||||
"twig/intl-extra": "^3.0",
|
"twig/intl-extra": "^3.0",
|
||||||
"twig/markdown-extra": "^3.0",
|
"twig/markdown-extra": "^3.0",
|
||||||
|
|
1846
composer.lock
generated
1846
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,19 @@ fos_ck_editor:
|
||||||
extraPlugins: "markdown"
|
extraPlugins: "markdown"
|
||||||
height: 60
|
height: 60
|
||||||
toolbar: description_toolbar
|
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:
|
plugins:
|
||||||
bbcode:
|
bbcode:
|
||||||
|
@ -29,10 +41,25 @@ fos_ck_editor:
|
||||||
specialchar:
|
specialchar:
|
||||||
path: "ckeditor/plugins/specialchar"
|
path: "ckeditor/plugins/specialchar"
|
||||||
filename: "plugin.js"
|
filename: "plugin.js"
|
||||||
|
partdb_label:
|
||||||
|
path: "ckeditor/plugins/partdb_label/"
|
||||||
|
filename: "plugin.js"
|
||||||
|
showprotected:
|
||||||
|
path: "ckeditor/plugins/showprotected/"
|
||||||
|
filename: "plugin.js"
|
||||||
|
|
||||||
|
|
||||||
toolbars:
|
toolbars:
|
||||||
configs:
|
configs:
|
||||||
|
label_toolbar:
|
||||||
|
- [ 'Bold', 'Italic', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ]
|
||||||
|
- ['JustifyLeft', 'JustifyCenter', 'JustifyRight']
|
||||||
|
- ["SpecialChar"]
|
||||||
|
- ["Source"]
|
||||||
|
- "/"
|
||||||
|
- ['Format', 'FontSize', 'Font']
|
||||||
|
- ['Table', 'HorizontalRule']
|
||||||
|
- ['Placeholders']
|
||||||
description_toolbar:
|
description_toolbar:
|
||||||
- [ 'Bold', 'Italic', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ]
|
- [ 'Bold', 'Italic', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ]
|
||||||
- ["SpecialChar"]
|
- ["SpecialChar"]
|
||||||
|
|
|
@ -15,3 +15,4 @@ twig:
|
||||||
allow_email_pw_reset: '%allow_email_pw_reset%'
|
allow_email_pw_reset: '%allow_email_pw_reset%'
|
||||||
locale_menu: '%locale_menu%'
|
locale_menu: '%locale_menu%'
|
||||||
attachment_manager: '@App\Services\Attachments\AttachmentManager'
|
attachment_manager: '@App\Services\Attachments\AttachmentManager'
|
||||||
|
label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper'
|
||||||
|
|
|
@ -291,6 +291,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||||
timetravel:
|
timetravel:
|
||||||
label: "perm.tools.timeTravel"
|
label: "perm.tools.timeTravel"
|
||||||
bit: 14
|
bit: 14
|
||||||
|
label_scanner:
|
||||||
|
label: "perm.tools.label_scanner"
|
||||||
|
bit: 16
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
label: "perm.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:
|
edit_options:
|
||||||
label: "perm.self.edit_options"
|
label: "perm.self.edit_options"
|
||||||
bit: 2
|
bit: 2
|
||||||
delete_profiles:
|
alsoSet: ['create_labels']
|
||||||
label: "perm.self.delete_profiles"
|
read_profiles:
|
||||||
bit: 4
|
label: "perm.self.read_profiles"
|
||||||
|
bit: 10
|
||||||
edit_profiles:
|
edit_profiles:
|
||||||
label: "perm.self.edit_profiles"
|
label: "perm.self.edit_profiles"
|
||||||
bit: 6
|
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']
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
# controller: App\Controller\DefaultController::index
|
# controller: App\Controller\DefaultController::index
|
||||||
|
|
||||||
# Redirect every url without an locale to the locale of the user/the global base locale
|
# 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:
|
redirector:
|
||||||
path: /{url}
|
path: /{url}
|
||||||
requirements:
|
requirements:
|
||||||
|
|
|
@ -35,6 +35,11 @@ services:
|
||||||
bool $gpdr_compliance : '%gpdr_compliance%'
|
bool $gpdr_compliance : '%gpdr_compliance%'
|
||||||
bool $kernel_debug: '%kernel.debug%'
|
bool $kernel_debug: '%kernel.debug%'
|
||||||
string $kernel_cache_dir: '%kernel.cache_dir%'
|
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
|
# makes classes in src/ available to be used as services
|
||||||
# this creates a service per class whose id is the fully-qualified class name
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
|
@ -163,6 +168,15 @@ services:
|
||||||
$code_length: 8
|
$code_length: 8
|
||||||
$code_count: 15
|
$code_count: 15
|
||||||
|
|
||||||
|
App\Services\LabelSystem\LabelTextReplacer:
|
||||||
|
arguments:
|
||||||
|
$providers: !tagged_iterator 'app.label_placeholder_provider'
|
||||||
|
|
||||||
App\Services\TranslationExtractor\PermissionExtractor:
|
App\Services\TranslationExtractor\PermissionExtractor:
|
||||||
tags:
|
tags:
|
||||||
- { name: 'translation.extractor', alias: 'permissionExtractor'}
|
- { 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}
|
|
@ -25,6 +25,7 @@
|
||||||
"@types/jquery.form": "^3.26.30",
|
"@types/jquery.form": "^3.26.30",
|
||||||
"@types/marked": "^0.7.2",
|
"@types/marked": "^0.7.2",
|
||||||
"@types/typeahead": "^0.11.32",
|
"@types/typeahead": "^0.11.32",
|
||||||
|
"@zxing/library": "^0.16.3",
|
||||||
"bootbox": "^5.4.0",
|
"bootbox": "^5.4.0",
|
||||||
"bootstrap-fileinput": "^5.0.1",
|
"bootstrap-fileinput": "^5.0.1",
|
||||||
"bootstrap-select": "^1.13.8",
|
"bootstrap-select": "^1.13.8",
|
||||||
|
|
BIN
public/ckeditor/plugins/partdb_label/icons/hidpi/placeholder.png
Normal file
BIN
public/ckeditor/plugins/partdb_label/icons/hidpi/placeholder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
BIN
public/ckeditor/plugins/partdb_label/icons/placeholder.png
Normal file
BIN
public/ckeditor/plugins/partdb_label/icons/placeholder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
69
public/ckeditor/plugins/partdb_label/lang/de.js
Normal file
69
public/ckeditor/plugins/partdb_label/lang/de.js
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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',
|
||||||
|
} );
|
69
public/ckeditor/plugins/partdb_label/lang/en.js
Normal file
69
public/ckeditor/plugins/partdb_label/lang/en.js
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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',
|
||||||
|
} );
|
209
public/ckeditor/plugins/partdb_label/plugin.js
Normal file
209
public/ckeditor/plugins/partdb_label/plugin.js
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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: '<span class="cke_placeholder">[[]]</span>',
|
||||||
|
|
||||||
|
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 <title> 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
BIN
public/ckeditor/plugins/partdb_label/styles/DejaVuSans-Bold.woff
Normal file
BIN
public/ckeditor/plugins/partdb_label/styles/DejaVuSans-Bold.woff
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/ckeditor/plugins/partdb_label/styles/DejaVuSans.woff
Normal file
BIN
public/ckeditor/plugins/partdb_label/styles/DejaVuSans.woff
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono.woff
Normal file
BIN
public/ckeditor/plugins/partdb_label/styles/DejaVuSansMono.woff
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/ckeditor/plugins/partdb_label/styles/DejaVuSerif.woff
Normal file
BIN
public/ckeditor/plugins/partdb_label/styles/DejaVuSerif.woff
Normal file
Binary file not shown.
117
public/ckeditor/plugins/partdb_label/styles/style.css
Normal file
117
public/ckeditor/plugins/partdb_label/styles/style.css
Normal file
|
@ -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;
|
||||||
|
}
|
52
public/ckeditor/plugins/showprotected/dialogs/protected.js
Normal file
52
public/ckeditor/plugins/showprotected/dialogs/protected.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
} );
|
BIN
public/ckeditor/plugins/showprotected/images/code.gif
Normal file
BIN
public/ckeditor/plugins/showprotected/images/code.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 278 B |
105
public/ckeditor/plugins/showprotected/plugin.js
Normal file
105
public/ckeditor/plugins/showprotected/plugin.js
Normal file
|
@ -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' ) +
|
||||||
|
'-->';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -45,6 +45,8 @@ namespace App\Controller\AdminPages;
|
||||||
use App\DataTables\LogDataTable;
|
use App\DataTables\LogDataTable;
|
||||||
use App\Entity\Base\AbstractNamedDBElement;
|
use App\Entity\Base\AbstractNamedDBElement;
|
||||||
use App\Entity\Base\AbstractStructuralDBElement;
|
use App\Entity\Base\AbstractStructuralDBElement;
|
||||||
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
|
use App\Entity\Parts\PartLot;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use App\Events\SecurityEvent;
|
use App\Events\SecurityEvent;
|
||||||
use App\Events\SecurityEvents;
|
use App\Events\SecurityEvents;
|
||||||
|
@ -54,6 +56,8 @@ use App\Form\AdminPages\MassCreationForm;
|
||||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||||
use App\Services\EntityExporter;
|
use App\Services\EntityExporter;
|
||||||
use App\Services\EntityImporter;
|
use App\Services\EntityImporter;
|
||||||
|
use App\Services\LabelSystem\Barcodes\BarcodeExampleElementsGenerator;
|
||||||
|
use App\Services\LabelSystem\LabelGenerator;
|
||||||
use App\Services\LogSystem\EventCommentHelper;
|
use App\Services\LogSystem\EventCommentHelper;
|
||||||
use App\Services\LogSystem\HistoryHelper;
|
use App\Services\LogSystem\HistoryHelper;
|
||||||
use App\Services\LogSystem\TimeTravel;
|
use App\Services\LogSystem\TimeTravel;
|
||||||
|
@ -92,11 +96,14 @@ abstract class BaseAdminController extends AbstractController
|
||||||
protected $dataTableFactory;
|
protected $dataTableFactory;
|
||||||
/** @var EventDispatcher */
|
/** @var EventDispatcher */
|
||||||
protected $eventDispatcher;
|
protected $eventDispatcher;
|
||||||
|
protected $labelGenerator;
|
||||||
|
protected $barcodeExampleGenerator;
|
||||||
|
|
||||||
public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder,
|
public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder,
|
||||||
AttachmentSubmitHandler $attachmentSubmitHandler,
|
AttachmentSubmitHandler $attachmentSubmitHandler,
|
||||||
EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
|
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) {
|
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!');
|
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->timeTravel = $timeTravel;
|
||||||
$this->dataTableFactory = $dataTableFactory;
|
$this->dataTableFactory = $dataTableFactory;
|
||||||
$this->eventDispatcher = $eventDispatcher;
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
|
$this->barcodeExampleGenerator = $barcodeExampleGenerator;
|
||||||
|
$this->labelGenerator = $labelGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _edit(AbstractNamedDBElement $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
protected function _edit(AbstractNamedDBElement $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||||
|
@ -156,11 +165,22 @@ abstract class BaseAdminController extends AbstractController
|
||||||
$table = null;
|
$table = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = $this->createForm($this->form_class, $entity, [
|
$form_options = [
|
||||||
'attachment_class' => $this->attachment_class,
|
'attachment_class' => $this->attachment_class,
|
||||||
'parameter_class' => $this->parameter_class,
|
'parameter_class' => $this->parameter_class,
|
||||||
'disabled' => null !== $timeTravel_timestamp ? true : null,
|
'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);
|
$form->handleRequest($request);
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
@ -210,11 +230,18 @@ abstract class BaseAdminController extends AbstractController
|
||||||
$this->addFlash('error', 'entity.edit_flash.invalid');
|
$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, [
|
return $this->render($this->twig_template, [
|
||||||
'entity' => $entity,
|
'entity' => $entity,
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
'route_base' => $this->route_base,
|
'route_base' => $this->route_base,
|
||||||
'datatable' => $table,
|
'datatable' => $table,
|
||||||
|
'pdf_data' => $pdf_data ?? null,
|
||||||
'timeTravel' => $timeTravel_timestamp,
|
'timeTravel' => $timeTravel_timestamp,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
126
src/Controller/AdminPages/LabelProfileController.php
Normal file
126
src/Controller/AdminPages/LabelProfileController.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
167
src/Controller/LabelController.php
Normal file
167
src/Controller/LabelController.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
93
src/Controller/ScanController.php
Normal file
93
src/Controller/ScanController.php
Normal file
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -82,8 +82,8 @@ class GroupFixtures extends Fixture
|
||||||
'suppliers' => 5461,
|
'suppliers' => 5461,
|
||||||
'manufacturers' => 5461,
|
'manufacturers' => 5461,
|
||||||
'attachment_types' => 1365,
|
'attachment_types' => 1365,
|
||||||
'tools' => 1365,
|
'tools' => 87381,
|
||||||
'labels' => 21,
|
'labels' => 87381,
|
||||||
'parts_category' => 5,
|
'parts_category' => 5,
|
||||||
'parts_minamount' => 5,
|
'parts_minamount' => 5,
|
||||||
'parts_lots' => 85,
|
'parts_lots' => 85,
|
||||||
|
@ -125,8 +125,8 @@ class GroupFixtures extends Fixture
|
||||||
'suppliers' => 1705,
|
'suppliers' => 1705,
|
||||||
'manufacturers' => 1705,
|
'manufacturers' => 1705,
|
||||||
'attachment_types' => 681,
|
'attachment_types' => 681,
|
||||||
'tools' => 1366,
|
'tools' => 87382,
|
||||||
'labels' => 165,
|
'labels' => 173737,
|
||||||
'parts_category' => 9,
|
'parts_category' => 9,
|
||||||
'parts_minamount' => 9,
|
'parts_minamount' => 9,
|
||||||
'parts_lots' => 169,
|
'parts_lots' => 169,
|
||||||
|
@ -168,8 +168,8 @@ class GroupFixtures extends Fixture
|
||||||
'suppliers' => 5461,
|
'suppliers' => 5461,
|
||||||
'manufacturers' => 5461,
|
'manufacturers' => 5461,
|
||||||
'attachment_types' => 1365,
|
'attachment_types' => 1365,
|
||||||
'tools' => 1365,
|
'tools' => 87381,
|
||||||
'labels' => 85,
|
'labels' => 91477,
|
||||||
'parts_category' => 5,
|
'parts_category' => 5,
|
||||||
'parts_minamount' => 5,
|
'parts_minamount' => 5,
|
||||||
'parts_lots' => 85,
|
'parts_lots' => 85,
|
||||||
|
|
96
src/DataFixtures/LabelProfileFixtures.php
Normal file
96
src/DataFixtures/LabelProfileFixtures.php
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ use LogicException;
|
||||||
* "Footprint" = "FootprintAttachment", "Manufacturer" = "ManufacturerAttachment",
|
* "Footprint" = "FootprintAttachment", "Manufacturer" = "ManufacturerAttachment",
|
||||||
* "Currency" = "CurrencyAttachment", "Group" = "GroupAttachment",
|
* "Currency" = "CurrencyAttachment", "Group" = "GroupAttachment",
|
||||||
* "MeasurementUnit" = "MeasurementUnitAttachment", "Storelocation" = "StorelocationAttachment",
|
* "MeasurementUnit" = "MeasurementUnitAttachment", "Storelocation" = "StorelocationAttachment",
|
||||||
* "Supplier" = "SupplierAttachment", "User" = "UserAttachment"
|
* "Supplier" = "SupplierAttachment", "User" = "UserAttachment", "LabelProfile" = "LabelAttachment",
|
||||||
* })
|
* })
|
||||||
* @ORM\EntityListeners({"App\EntityListeners\AttachmentDeleteListener"})
|
* @ORM\EntityListeners({"App\EntityListeners\AttachmentDeleteListener"})
|
||||||
*/
|
*/
|
||||||
|
|
44
src/Entity/Attachments/LabelAttachment.php
Normal file
44
src/Entity/Attachments/LabelAttachment.php
Normal file
|
@ -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;
|
||||||
|
}
|
240
src/Entity/LabelSystem/LabelOptions.php
Normal file
240
src/Entity/LabelSystem/LabelOptions.php
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
126
src/Entity/LabelSystem/LabelProfile.php
Normal file
126
src/Entity/LabelSystem/LabelProfile.php
Normal file
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ use App\Entity\Attachments\AttachmentType;
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Devices\Device;
|
use App\Entity\Devices\Device;
|
||||||
use App\Entity\Devices\DevicePart;
|
use App\Entity\Devices\DevicePart;
|
||||||
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
use App\Entity\Parameters\AbstractParameter;
|
use App\Entity\Parameters\AbstractParameter;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
use App\Entity\Parts\Footprint;
|
use App\Entity\Parts\Footprint;
|
||||||
|
@ -117,6 +118,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
|
||||||
protected const TARGET_TYPE_PRICEDETAIL = 16;
|
protected const TARGET_TYPE_PRICEDETAIL = 16;
|
||||||
protected const TARGET_TYPE_MEASUREMENTUNIT = 17;
|
protected const TARGET_TYPE_MEASUREMENTUNIT = 17;
|
||||||
protected const TARGET_TYPE_PARAMETER = 18;
|
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 */
|
/** @var array This const is used to convert the numeric level to a PSR-3 compatible log level */
|
||||||
protected const LEVEL_ID_TO_STRING = [
|
protected const LEVEL_ID_TO_STRING = [
|
||||||
|
@ -149,6 +151,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
|
||||||
self::TARGET_TYPE_PRICEDETAIL => Pricedetail::class,
|
self::TARGET_TYPE_PRICEDETAIL => Pricedetail::class,
|
||||||
self::TARGET_TYPE_MEASUREMENTUNIT => MeasurementUnit::class,
|
self::TARGET_TYPE_MEASUREMENTUNIT => MeasurementUnit::class,
|
||||||
self::TARGET_TYPE_PARAMETER => AbstractParameter::class,
|
self::TARGET_TYPE_PARAMETER => AbstractParameter::class,
|
||||||
|
self::TARGET_TYPE_LABEL_PROFILE => LabelProfile::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var User The user which has caused this log entry
|
/** @var User The user which has caused this log entry
|
||||||
|
@ -163,7 +166,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
|
||||||
protected $timestamp;
|
protected $timestamp;
|
||||||
|
|
||||||
/** @var int The priority level of the associated level. 0 is highest, 7 lowest
|
/** @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;
|
protected $level;
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ namespace App\EntityListeners;
|
||||||
|
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Base\AbstractStructuralDBElement;
|
use App\Entity\Base\AbstractStructuralDBElement;
|
||||||
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
use App\Entity\UserSystem\Group;
|
use App\Entity\UserSystem\Group;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use App\Services\UserCacheKeyGenerator;
|
use App\Services\UserCacheKeyGenerator;
|
||||||
|
@ -71,7 +72,7 @@ class TreeCacheInvalidationListener
|
||||||
public function invalidate(AbstractDBElement $element, LifecycleEventArgs $event): void
|
public function invalidate(AbstractDBElement $element, LifecycleEventArgs $event): void
|
||||||
{
|
{
|
||||||
//If an element was changed, then invalidate all cached trees with this element class
|
//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));
|
$secure_class_name = str_replace('\\', '_', get_class($element));
|
||||||
$this->cache->invalidateTags([$secure_class_name]);
|
$this->cache->invalidateTags([$secure_class_name]);
|
||||||
}
|
}
|
||||||
|
|
33
src/Exceptions/TwigModeException.php
Normal file
33
src/Exceptions/TwigModeException.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,8 @@ namespace App\Form\AdminPages;
|
||||||
use App\Entity\Attachments\Attachment;
|
use App\Entity\Attachments\Attachment;
|
||||||
use App\Entity\Base\AbstractNamedDBElement;
|
use App\Entity\Base\AbstractNamedDBElement;
|
||||||
use App\Entity\Base\AbstractStructuralDBElement;
|
use App\Entity\Base\AbstractStructuralDBElement;
|
||||||
|
use App\Entity\LabelSystem\LabelOptions;
|
||||||
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
use App\Form\AttachmentFormType;
|
use App\Form\AttachmentFormType;
|
||||||
use App\Form\ParameterType;
|
use App\Form\ParameterType;
|
||||||
use App\Form\Type\MasterPictureAttachmentType;
|
use App\Form\Type\MasterPictureAttachmentType;
|
||||||
|
@ -94,36 +96,50 @@ class BaseEntityAdminForm extends AbstractType
|
||||||
'placeholder' => 'part.name.placeholder',
|
'placeholder' => 'part.name.placeholder',
|
||||||
],
|
],
|
||||||
'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
'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);
|
$this->additionalFormElements($builder, $options, $entity);
|
||||||
|
|
||||||
//Attachment section
|
//Attachment section
|
||||||
|
@ -154,19 +170,25 @@ class BaseEntityAdminForm extends AbstractType
|
||||||
'empty_data' => null,
|
'empty_data' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$builder->add('parameters', CollectionType::class, [
|
if ($entity instanceof AbstractStructuralDBElement) {
|
||||||
'entry_type' => ParameterType::class,
|
$builder->add(
|
||||||
'allow_add' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
'parameters',
|
||||||
'allow_delete' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
CollectionType::class,
|
||||||
'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
[
|
||||||
'reindex_enable' => true,
|
'entry_type' => ParameterType::class,
|
||||||
'label' => false,
|
'allow_add' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||||
'by_reference' => false,
|
'allow_delete' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||||
'prototype_data' => new $options['parameter_class'](),
|
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||||
'entry_options' => [
|
'reindex_enable' => true,
|
||||||
'data_class' => $options['parameter_class'],
|
'label' => false,
|
||||||
],
|
'by_reference' => false,
|
||||||
]);
|
'prototype_data' => new $options['parameter_class'](),
|
||||||
|
'entry_options' => [
|
||||||
|
'data_class' => $options['parameter_class'],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//Buttons
|
//Buttons
|
||||||
$builder->add('save', SubmitType::class, [
|
$builder->add('save', SubmitType::class, [
|
||||||
|
|
55
src/Form/AdminPages/LabelProfileAdminForm.php
Normal file
55
src/Form/AdminPages/LabelProfileAdminForm.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
142
src/Form/LabelOptionsType.php
Normal file
142
src/Form/LabelOptionsType.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
70
src/Form/LabelSystem/LabelDialogType.php
Normal file
70
src/Form/LabelSystem/LabelDialogType.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
51
src/Form/LabelSystem/ScanDialogType.php
Normal file
51
src/Form/LabelSystem/ScanDialogType.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -178,6 +178,7 @@ class UserAdminForm extends AbstractType
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'class' => 'selectpicker',
|
'class' => 'selectpicker',
|
||||||
],
|
],
|
||||||
|
'choice_translation_domain' => false,
|
||||||
'placeholder' => 'user_settings.theme.placeholder',
|
'placeholder' => 'user_settings.theme.placeholder',
|
||||||
'label' => 'user.theme.label',
|
'label' => 'user.theme.label',
|
||||||
'disabled' => ! $this->security->isGranted('change_user_settings', $entity),
|
'disabled' => ! $this->security->isGranted('change_user_settings', $entity),
|
||||||
|
|
112
src/Helpers/LabelResponse.php
Normal file
112
src/Helpers/LabelResponse.php
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
src/Migrations/Version20200502161750.php
Normal file
38
src/Migrations/Version20200502161750.php
Normal file
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,6 +50,22 @@ class DBElementRepository extends EntityRepository
|
||||||
$this->setField($element, 'id', $new_id);
|
$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
|
protected function setField(AbstractDBElement $element, string $field, int $new_value): void
|
||||||
{
|
{
|
||||||
$reflection = new \ReflectionClass(get_class($element));
|
$reflection = new \ReflectionClass(get_class($element));
|
||||||
|
|
89
src/Repository/LabelProfileRepository.php
Normal file
89
src/Repository/LabelProfileRepository.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
62
src/Security/Voter/LabelProfileVoter.php
Normal file
62
src/Security/Voter/LabelProfileVoter.php
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ use App\Entity\Attachments\Attachment;
|
||||||
use App\Entity\Attachments\AttachmentType;
|
use App\Entity\Attachments\AttachmentType;
|
||||||
use App\Entity\Contracts\NamedElementInterface;
|
use App\Entity\Contracts\NamedElementInterface;
|
||||||
use App\Entity\Devices\Device;
|
use App\Entity\Devices\Device;
|
||||||
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
use App\Entity\Parameters\AbstractParameter;
|
use App\Entity\Parameters\AbstractParameter;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
use App\Entity\Parts\Footprint;
|
use App\Entity\Parts\Footprint;
|
||||||
|
@ -92,6 +93,7 @@ class ElementTypeNameGenerator
|
||||||
Group::class => $this->translator->trans('group.label'),
|
Group::class => $this->translator->trans('group.label'),
|
||||||
User::class => $this->translator->trans('user.label'),
|
User::class => $this->translator->trans('user.label'),
|
||||||
AbstractParameter::class => $this->translator->trans('parameter.label'),
|
AbstractParameter::class => $this->translator->trans('parameter.label'),
|
||||||
|
LabelProfile::class => $this->translator->trans('label_profile.label'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ use App\Entity\Attachments\AttachmentType;
|
||||||
use App\Entity\Attachments\PartAttachment;
|
use App\Entity\Attachments\PartAttachment;
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Devices\Device;
|
use App\Entity\Devices\Device;
|
||||||
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
use App\Entity\Parts\Footprint;
|
use App\Entity\Parts\Footprint;
|
||||||
use App\Entity\Parts\Manufacturer;
|
use App\Entity\Parts\Manufacturer;
|
||||||
|
@ -133,7 +134,6 @@ class EntityURLGenerator
|
||||||
{
|
{
|
||||||
$map = [
|
$map = [
|
||||||
Part::class => 'part_info',
|
Part::class => 'part_info',
|
||||||
|
|
||||||
//As long we does not have own things for it use edit page
|
//As long we does not have own things for it use edit page
|
||||||
AttachmentType::class => 'attachment_type_edit',
|
AttachmentType::class => 'attachment_type_edit',
|
||||||
Category::class => 'category_edit',
|
Category::class => 'category_edit',
|
||||||
|
@ -146,6 +146,7 @@ class EntityURLGenerator
|
||||||
Currency::class => 'currency_edit',
|
Currency::class => 'currency_edit',
|
||||||
MeasurementUnit::class => 'measurement_unit_edit',
|
MeasurementUnit::class => 'measurement_unit_edit',
|
||||||
Group::class => 'group_edit',
|
Group::class => 'group_edit',
|
||||||
|
LabelProfile::class => 'label_profile_edit',
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -241,6 +242,7 @@ class EntityURLGenerator
|
||||||
Currency::class => 'currency_edit',
|
Currency::class => 'currency_edit',
|
||||||
MeasurementUnit::class => 'measurement_unit_edit',
|
MeasurementUnit::class => 'measurement_unit_edit',
|
||||||
Group::class => 'group_edit',
|
Group::class => 'group_edit',
|
||||||
|
LabelProfile::class => 'label_profile_edit'
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
||||||
|
@ -270,6 +272,7 @@ class EntityURLGenerator
|
||||||
Currency::class => 'currency_edit',
|
Currency::class => 'currency_edit',
|
||||||
MeasurementUnit::class => 'measurement_unit_edit',
|
MeasurementUnit::class => 'measurement_unit_edit',
|
||||||
Group::class => 'group_edit',
|
Group::class => 'group_edit',
|
||||||
|
LabelProfile::class => 'label_profile_edit'
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
||||||
|
@ -299,6 +302,7 @@ class EntityURLGenerator
|
||||||
Currency::class => 'currency_new',
|
Currency::class => 'currency_new',
|
||||||
MeasurementUnit::class => 'measurement_unit_new',
|
MeasurementUnit::class => 'measurement_unit_new',
|
||||||
Group::class => 'group_new',
|
Group::class => 'group_new',
|
||||||
|
LabelProfile::class => 'label_profile_new'
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->urlGenerator->generate($this->mapToController($map, $entity));
|
return $this->urlGenerator->generate($this->mapToController($map, $entity));
|
||||||
|
@ -329,6 +333,7 @@ class EntityURLGenerator
|
||||||
Currency::class => 'currency_clone',
|
Currency::class => 'currency_clone',
|
||||||
MeasurementUnit::class => 'measurement_unit_clone',
|
MeasurementUnit::class => 'measurement_unit_clone',
|
||||||
Group::class => 'group_clone',
|
Group::class => 'group_clone',
|
||||||
|
LabelProfile::class => 'label_profile_clone'
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
||||||
|
@ -371,6 +376,7 @@ class EntityURLGenerator
|
||||||
Currency::class => 'currency_delete',
|
Currency::class => 'currency_delete',
|
||||||
MeasurementUnit::class => 'measurement_unit_delete',
|
MeasurementUnit::class => 'measurement_unit_delete',
|
||||||
Group::class => 'group_delete',
|
Group::class => 'group_delete',
|
||||||
|
LabelProfile::class => 'label_profile_delete'
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
return $this->urlGenerator->generate($this->mapToController($map, $entity), ['id' => $entity->getID()]);
|
||||||
|
|
84
src/Services/LabelSystem/BarcodeGenerator.php
Normal file
84
src/Services/LabelSystem/BarcodeGenerator.php
Normal file
|
@ -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!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
86
src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php
Normal file
86
src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php
Normal file
|
@ -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!');
|
||||||
|
}
|
||||||
|
}
|
69
src/Services/LabelSystem/Barcodes/BarcodeRedirector.php
Normal file
69
src/Services/LabelSystem/Barcodes/BarcodeRedirector.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
src/Services/LabelSystem/LabelGenerator.php
Normal file
105
src/Services/LabelSystem/LabelGenerator.php
Normal file
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
115
src/Services/LabelSystem/LabelHTMLGenerator.php
Normal file
115
src/Services/LabelSystem/LabelHTMLGenerator.php
Normal file
|
@ -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';
|
||||||
|
}
|
||||||
|
}
|
59
src/Services/LabelSystem/LabelProfileDropdownHelper.php
Normal file
59
src/Services/LabelSystem/LabelProfileDropdownHelper.php
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
75
src/Services/LabelSystem/LabelTextReplacer.php
Normal file
75
src/Services/LabelSystem/LabelTextReplacer.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
112
src/Services/LabelSystem/PlaceholderProviders/PartProvider.php
Normal file
112
src/Services/LabelSystem/PlaceholderProviders/PartProvider.php
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
140
src/Services/LabelSystem/SandboxedTwigProvider.php
Normal file
140
src/Services/LabelSystem/SandboxedTwigProvider.php
Normal file
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
94
src/Services/Misc/RangeParser.php
Normal file
94
src/Services/Misc/RangeParser.php
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -105,6 +105,7 @@ class SIFormatter
|
||||||
* @param float $value The value that should be converted
|
* @param float $value The value that should be converted
|
||||||
* @param string $unit The unit that should be appended after the prefix
|
* @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
|
* @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
|
public function format(float $value, string $unit = '', int $decimals = 2): string
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,6 +45,7 @@ namespace App\Services\Trees;
|
||||||
use App\Entity\Attachments\AttachmentType;
|
use App\Entity\Attachments\AttachmentType;
|
||||||
use App\Entity\Attachments\PartAttachment;
|
use App\Entity\Attachments\PartAttachment;
|
||||||
use App\Entity\Devices\Device;
|
use App\Entity\Devices\Device;
|
||||||
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
use App\Entity\Parts\Footprint;
|
use App\Entity\Parts\Footprint;
|
||||||
use App\Entity\Parts\Manufacturer;
|
use App\Entity\Parts\Manufacturer;
|
||||||
|
@ -104,6 +105,7 @@ class ToolsTreeBuilder
|
||||||
$item->tag(['tree_tools', 'groups', $this->keyGenerator->generateKey()]);
|
$item->tag(['tree_tools', 'groups', $this->keyGenerator->generateKey()]);
|
||||||
|
|
||||||
$tree = [];
|
$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.edit'), null, $this->getEditNodes());
|
||||||
$tree[] = new TreeViewNode($this->translator->trans('tree.tools.show'), null, $this->getShowNodes());
|
$tree[] = new TreeViewNode($this->translator->trans('tree.tools.show'), null, $this->getShowNodes());
|
||||||
$tree[] = new TreeViewNode($this->translator->trans('tree.tools.system'), null, $this->getSystemNodes());
|
$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.
|
* 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')
|
$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())) {
|
if ($this->security->isGranted('create', new Part())) {
|
||||||
$nodes[] = new TreeViewNode(
|
$nodes[] = new TreeViewNode(
|
||||||
$this->translator->trans('tree.tools.edit.part'),
|
$this->translator->trans('tree.tools.edit.part'),
|
||||||
|
@ -182,6 +211,7 @@ class ToolsTreeBuilder
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return $nodes;
|
return $nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,10 +118,15 @@ class TreeViewGenerator
|
||||||
$item->addTag((string) \count($item->getNodes()));
|
$item->addTag((string) \count($item->getNodes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($href_type)) {
|
if (! empty($href_type) && $item->getId() !== null) {
|
||||||
$entity = $this->em->getPartialReference($class, $item->getId());
|
$entity = $this->em->getPartialReference($class, $item->getId());
|
||||||
$item->setHref($this->urlGenerator->getURL($entity, $href_type));
|
$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);
|
return array_merge($head, $generic);
|
||||||
|
|
140
src/Twig/Sandbox/InheritanceSecurityPolicy.php
Normal file
140
src/Twig/Sandbox/InheritanceSecurityPolicy.php
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/Validator/Constraints/Misc/ValidRange.php
Normal file
33
src/Validator/Constraints/Misc/ValidRange.php
Normal file
|
@ -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';
|
||||||
|
}
|
61
src/Validator/Constraints/Misc/ValidRangeValidator.php
Normal file
61
src/Validator/Constraints/Misc/ValidRangeValidator.php
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
symfony.lock
45
symfony.lock
|
@ -120,12 +120,18 @@
|
||||||
"doctrine/reflection": {
|
"doctrine/reflection": {
|
||||||
"version": "v1.0.0"
|
"version": "v1.0.0"
|
||||||
},
|
},
|
||||||
|
"dompdf/dompdf": {
|
||||||
|
"version": "v0.8.5"
|
||||||
|
},
|
||||||
"egulias/email-validator": {
|
"egulias/email-validator": {
|
||||||
"version": "2.1.11"
|
"version": "2.1.11"
|
||||||
},
|
},
|
||||||
"ekino/phpstan-banned-code": {
|
"ekino/phpstan-banned-code": {
|
||||||
"version": "v0.3.1"
|
"version": "v0.3.1"
|
||||||
},
|
},
|
||||||
|
"erusev/parsedown": {
|
||||||
|
"version": "1.7.4"
|
||||||
|
},
|
||||||
"felixfbecker/advanced-json-rpc": {
|
"felixfbecker/advanced-json-rpc": {
|
||||||
"version": "v3.0.4"
|
"version": "v3.0.4"
|
||||||
},
|
},
|
||||||
|
@ -205,24 +211,12 @@
|
||||||
"netresearch/jsonmapper": {
|
"netresearch/jsonmapper": {
|
||||||
"version": "v1.6.0"
|
"version": "v1.6.0"
|
||||||
},
|
},
|
||||||
"nette/application": {
|
|
||||||
"version": "v3.0.3"
|
|
||||||
},
|
|
||||||
"nette/component-model": {
|
|
||||||
"version": "v3.0.0"
|
|
||||||
},
|
|
||||||
"nette/finder": {
|
"nette/finder": {
|
||||||
"version": "v2.5.2"
|
"version": "v2.5.2"
|
||||||
},
|
},
|
||||||
"nette/http": {
|
|
||||||
"version": "v3.0.3"
|
|
||||||
},
|
|
||||||
"nette/robot-loader": {
|
"nette/robot-loader": {
|
||||||
"version": "v3.2.1"
|
"version": "v3.2.1"
|
||||||
},
|
},
|
||||||
"nette/routing": {
|
|
||||||
"version": "v3.0.0"
|
|
||||||
},
|
|
||||||
"nette/utils": {
|
"nette/utils": {
|
||||||
"version": "v3.1.0"
|
"version": "v3.1.0"
|
||||||
},
|
},
|
||||||
|
@ -271,6 +265,12 @@
|
||||||
"paragonie/random_compat": {
|
"paragonie/random_compat": {
|
||||||
"version": "v9.99.99"
|
"version": "v9.99.99"
|
||||||
},
|
},
|
||||||
|
"phenx/php-font-lib": {
|
||||||
|
"version": "0.5.1"
|
||||||
|
},
|
||||||
|
"phenx/php-svg-lib": {
|
||||||
|
"version": "v0.3.3"
|
||||||
|
},
|
||||||
"php": {
|
"php": {
|
||||||
"version": "7.2.5"
|
"version": "7.2.5"
|
||||||
},
|
},
|
||||||
|
@ -373,6 +373,9 @@
|
||||||
"s9e/text-formatter": {
|
"s9e/text-formatter": {
|
||||||
"version": "2.1.2"
|
"version": "2.1.2"
|
||||||
},
|
},
|
||||||
|
"sabberworm/php-css-parser": {
|
||||||
|
"version": "8.3.0"
|
||||||
|
},
|
||||||
"scheb/two-factor-bundle": {
|
"scheb/two-factor-bundle": {
|
||||||
"version": "3.16",
|
"version": "3.16",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
@ -656,6 +659,9 @@
|
||||||
"symfony/polyfill-mbstring": {
|
"symfony/polyfill-mbstring": {
|
||||||
"version": "v1.10.0"
|
"version": "v1.10.0"
|
||||||
},
|
},
|
||||||
|
"symfony/polyfill-php70": {
|
||||||
|
"version": "v1.15.0"
|
||||||
|
},
|
||||||
"symfony/polyfill-php72": {
|
"symfony/polyfill-php72": {
|
||||||
"version": "v1.10.0"
|
"version": "v1.10.0"
|
||||||
},
|
},
|
||||||
|
@ -830,18 +836,30 @@
|
||||||
"symplify/coding-standard": {
|
"symplify/coding-standard": {
|
||||||
"version": "v7.1.3"
|
"version": "v7.1.3"
|
||||||
},
|
},
|
||||||
|
"symplify/console-color-diff": {
|
||||||
|
"version": "v7.3.10"
|
||||||
|
},
|
||||||
"symplify/easy-coding-standard": {
|
"symplify/easy-coding-standard": {
|
||||||
"version": "v7.1.3"
|
"version": "v7.1.3"
|
||||||
},
|
},
|
||||||
"symplify/package-builder": {
|
"symplify/package-builder": {
|
||||||
"version": "v7.1.3"
|
"version": "v7.1.3"
|
||||||
},
|
},
|
||||||
|
"symplify/phpstan-extensions": {
|
||||||
|
"version": "v7.3.10"
|
||||||
|
},
|
||||||
"symplify/set-config-resolver": {
|
"symplify/set-config-resolver": {
|
||||||
"version": "v7.1.3"
|
"version": "v7.1.3"
|
||||||
},
|
},
|
||||||
"symplify/smart-file-system": {
|
"symplify/smart-file-system": {
|
||||||
"version": "v7.1.3"
|
"version": "v7.1.3"
|
||||||
},
|
},
|
||||||
|
"tecnickcom/tc-lib-barcode": {
|
||||||
|
"version": "1.15.20"
|
||||||
|
},
|
||||||
|
"tecnickcom/tc-lib-color": {
|
||||||
|
"version": "1.12.15"
|
||||||
|
},
|
||||||
"thecodingmachine/safe": {
|
"thecodingmachine/safe": {
|
||||||
"version": "v0.1.16"
|
"version": "v0.1.16"
|
||||||
},
|
},
|
||||||
|
@ -854,6 +872,9 @@
|
||||||
"twig/extra-bundle": {
|
"twig/extra-bundle": {
|
||||||
"version": "v3.0.0"
|
"version": "v3.0.0"
|
||||||
},
|
},
|
||||||
|
"twig/html-extra": {
|
||||||
|
"version": "v3.0.3"
|
||||||
|
},
|
||||||
"twig/inky-extra": {
|
"twig/inky-extra": {
|
||||||
"version": "v3.0.0"
|
"version": "v3.0.0"
|
||||||
},
|
},
|
||||||
|
|
52
templates/AdminPages/LabelProfileAdmin.html.twig
Normal file
52
templates/AdminPages/LabelProfileAdmin.html.twig
Normal file
|
@ -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 %}
|
|
@ -1,10 +1,18 @@
|
||||||
{% extends "AdminPages/EntityAdminBase.html.twig" %}
|
{% extends "AdminPages/EntityAdminBase.html.twig" %}
|
||||||
|
{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %}
|
||||||
|
|
||||||
{% block card_title %}
|
{% block card_title %}
|
||||||
<i class="fas fa-cube fa-fw"></i> {% trans %}storelocation.labelp{% endtrans %}
|
<i class="fas fa-cube fa-fw"></i> {% trans %}storelocation.labelp{% endtrans %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block additional_controls %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block additional_pills %}
|
{% block additional_pills %}
|
||||||
|
|
33
templates/LabelSystem/Scanner/dialog.html.twig
Normal file
33
templates/LabelSystem/Scanner/dialog.html.twig
Normal file
|
@ -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 %}
|
116
templates/LabelSystem/dialog.html.twig
Normal file
116
templates/LabelSystem/dialog.html.twig
Normal file
|
@ -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 %}
|
24
templates/LabelSystem/dropdown_macro.html.twig
Normal file
24
templates/LabelSystem/dropdown_macro.html.twig
Normal file
|
@ -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 %}
|
27
templates/LabelSystem/labels/base_label.html.twig
Normal file
27
templates/LabelSystem/labels/base_label.html.twig
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ meta_title }}</title>
|
||||||
|
<meta name="author" content="{{ partdb_title }}">
|
||||||
|
<meta name="description" content="Label for {{ meta_title }}">
|
||||||
|
<meta name="keywords" content="Part-DB, Label, Barcode">
|
||||||
|
<style>
|
||||||
|
{% include("LabelSystem/labels/label_style.css.twig") %}
|
||||||
|
{{ options.additionalCss | escape("html") }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% for element in elements %}
|
||||||
|
<div style="page-break-inside: avoid; page-break-before: avoid; {% if not loop.last %}page-break-after: always;{% endif %}">
|
||||||
|
{% 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 %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
27
templates/LabelSystem/labels/label_page_1d.html.twig
Normal file
27
templates/LabelSystem/labels/label_page_1d.html.twig
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<div>
|
||||||
|
<div class="lines-container">
|
||||||
|
{{ element.lines | raw }}
|
||||||
|
</div>
|
||||||
|
<div class="C39-container" style="">
|
||||||
|
<img class="C39" src="{{ element.barcode | data_uri(mime='image/svg+xml') }}" height="30px">
|
||||||
|
<span class="C39-text">{{ element.barcode_content }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="C39-container">
|
||||||
|
<img class="C39" src="{{ inlineData(element.barcode, 'image/svg+xml') }}" width="100%" height="50px" style="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-7">
|
||||||
|
<div class="lines-container">
|
||||||
|
{{ element.lines | raw }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
#}
|
1
templates/LabelSystem/labels/label_page_none.html.twig
Normal file
1
templates/LabelSystem/labels/label_page_none.html.twig
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{{ element.lines | raw }}
|
17
templates/LabelSystem/labels/label_page_qr.html.twig
Normal file
17
templates/LabelSystem/labels/label_page_qr.html.twig
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="qr-container">
|
||||||
|
<a href="{{ element.barcode_content }}">
|
||||||
|
<img class="qr" src="{{ element.barcode | data_uri(mime='image/svg+xml') }}" width="100%" style="">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{#{{ element.barcode | raw }} #}
|
||||||
|
</div>
|
||||||
|
<div class="col-7">
|
||||||
|
<div class="lines-container">
|
||||||
|
{{ element.lines | raw }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
157
templates/LabelSystem/labels/label_style.css.twig
Normal file
157
templates/LabelSystem/labels/label_style.css.twig
Normal file
|
@ -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%;
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
{% import "helper.twig" as helper %}
|
{% import "helper.twig" as helper %}
|
||||||
|
{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %}
|
||||||
|
|
||||||
<table class="table table-striped table-hover table-responsive-sm">
|
<table class="table table-striped table-hover table-responsive-sm">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -7,6 +8,7 @@
|
||||||
<th>{% trans %}part_lots.storage_location{% endtrans %}</th>
|
<th>{% trans %}part_lots.storage_location{% endtrans %}</th>
|
||||||
<th>{% trans %}part_lots.amount{% endtrans %}</th>
|
<th>{% trans %}part_lots.amount{% endtrans %}</th>
|
||||||
<th></th> {# Tags row #}
|
<th></th> {# Tags row #}
|
||||||
|
<th></th> {# Button row #}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
|
@ -55,6 +57,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h6>
|
</h6>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ dropdown.profile_dropdown('part_lot', lot.id, false) }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %}
|
||||||
|
|
||||||
{% if is_granted('edit', part) %}
|
{% if is_granted('edit', part) %}
|
||||||
<a href="{{ part|entityURL('edit') }}" class="btn btn-primary mt-3">
|
<a href="{{ part|entityURL('edit') }}" class="btn btn-primary mt-3">
|
||||||
<i class="fas fa-fw fa-edit"></i>
|
<i class="fas fa-fw fa-edit"></i>
|
||||||
|
@ -46,4 +48,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{{ dropdown.profile_dropdown('part', part.id) }}
|
|
@ -1,4 +1,5 @@
|
||||||
{% import "helper.twig" as helper %}
|
{% import "helper.twig" as helper %}
|
||||||
|
{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %}
|
||||||
|
|
||||||
{{ helper.breadcrumb_entity_link(entity) }}
|
{{ helper.breadcrumb_entity_link(entity) }}
|
||||||
|
|
||||||
|
@ -77,6 +78,11 @@
|
||||||
<i class="fas fa-calendar-plus fa-fw"></i> {{ entity.addedDate | format_datetime("short") }}
|
<i class="fas fa-calendar-plus fa-fw"></i> {{ entity.addedDate | format_datetime("short") }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if entity is instanceof("App\\Entity\\Parts\\Storelocation") %}
|
||||||
|
{{ dropdown.profile_dropdown('storelocation', entity.id, true, 'btn-secondary btn-block mt-2') }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,17 +16,19 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarContent">
|
<div class="collapse navbar-collapse" id="navbarContent">
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
{% if is_granted("@tools.label_scanner") %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url("scan_dialog") }}">{% trans %}navbar.scanner.link{% endtrans %}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="form-inline my-2 my-lg-0 ml-auto" id="searchbar">
|
<div class="form-inline my-2 my-lg-0 ml-auto" id="searchbar">
|
||||||
<!-- Searchbar -->
|
<!-- Searchbar -->
|
||||||
{% include "_navbar_search.html.twig" %}
|
{% include "_navbar_search.html.twig" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="d-block d-md-none nav-link link-datasheet" style="color: black;"
|
|
||||||
href="zxing://scan/?ret={if isset($smarty.server.HTTPS)}https{else}http{/if}%3A%2F%2F{$smarty.server.HTTP_HOST|escape:'url'}{$relative_path|escape:'url'}show_search_parts.php%3Fkeyword%3D%7BCODE%7D&SCAN_FORMATS=EAN_8,CODE_39">
|
|
||||||
<i class="fa fa-barcode fa-fw fa-lg" aria-hidden="true"></i>
|
|
||||||
<span class="ml-2 text-muted">{% trans %}barcode.scan{% endtrans %}</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<ul class="navbar-nav ml-3" id="login-content">
|
<ul class="navbar-nav ml-3" id="login-content">
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a href="#" class="dropdown-toggle link-anchor nav-link" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
<a href="#" class="dropdown-toggle link-anchor nav-link" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
||||||
|
|
|
@ -119,5 +119,12 @@ class ApplicationAvailabilityFunctionalTest extends WebTestCase
|
||||||
//Typeahead
|
//Typeahead
|
||||||
yield ['/typeahead/builtInResources/search/DIP8'];
|
yield ['/typeahead/builtInResources/search/DIP8'];
|
||||||
yield ['/typeahead/tags/search/test'];
|
yield ['/typeahead/tags/search/test'];
|
||||||
|
|
||||||
|
//Label test
|
||||||
|
yield ['/scan'];
|
||||||
|
yield ['/label/dialog'];
|
||||||
|
yield ['/label/dialog?target_id=1&target_type=part'];
|
||||||
|
yield ['/label/1/dialog'];
|
||||||
|
yield ['/label/1/dialog?target_id=1&target_type=part&generate=1'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
58
tests/Controller/AdminPages/LabelProfileControllerTest.php
Normal file
58
tests/Controller/AdminPages/LabelProfileControllerTest.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?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\Tests\Controller\AdminPages;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\LabelSystem\LabelProfile;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||||
|
|
||||||
|
class LabelProfileControllerTest extends AbstractAdminControllerTest
|
||||||
|
{
|
||||||
|
protected static $base_path = '/en'.'/label_profile';
|
||||||
|
protected static $entity_class = LabelProfile::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if deleting an entity is working.
|
||||||
|
*
|
||||||
|
* @group slow
|
||||||
|
* @dataProvider deleteDataProvider
|
||||||
|
*/
|
||||||
|
public function testDeleteEntity(string $user, bool $delete): void
|
||||||
|
{
|
||||||
|
//Test read access
|
||||||
|
$client = static::createClient([], [
|
||||||
|
'PHP_AUTH_USER' => $user,
|
||||||
|
'PHP_AUTH_PW' => 'test',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$client->catchExceptions(false);
|
||||||
|
if (false === $delete) {
|
||||||
|
$this->expectException(AccessDeniedException::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Test read/list access by access /new overview page
|
||||||
|
$client->request('DELETE', static::$base_path.'/3');
|
||||||
|
|
||||||
|
//Page is redirected to '/new', when delete was successful
|
||||||
|
$this->assertSame($delete, $client->getResponse()->isRedirect(static::$base_path.'/new'));
|
||||||
|
$this->assertSame($delete, ! $client->getResponse()->isForbidden(), 'Permission Checking not working!');
|
||||||
|
}
|
||||||
|
}
|
80
tests/Services/LabelSystem/BarcodeGeneratorTest.php
Normal file
80
tests/Services/LabelSystem/BarcodeGeneratorTest.php
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?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\Tests\Services\LabelSystem;
|
||||||
|
|
||||||
|
use App\Entity\LabelSystem\LabelOptions;
|
||||||
|
use App\Entity\Parts\Part;
|
||||||
|
use App\Services\LabelSystem\BarcodeGenerator;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
|
final class BarcodeGeneratorTest extends WebTestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var BarcodeGenerator */
|
||||||
|
protected $services;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->services = self::$container->get(BarcodeGenerator::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetContent(): void
|
||||||
|
{
|
||||||
|
$part = new Part();
|
||||||
|
$part->setName('Test');
|
||||||
|
|
||||||
|
//Test that all barcodes types are supported
|
||||||
|
foreach (LabelOptions::BARCODE_TYPES as $type) {
|
||||||
|
$options = new LabelOptions();
|
||||||
|
$options->setBarcodeType($type);
|
||||||
|
$content = $this->services->generateSVG($options, $part);
|
||||||
|
|
||||||
|
//When type is none, service must return null.
|
||||||
|
if ($type === 'none') {
|
||||||
|
$this->assertNull($content);
|
||||||
|
} else {
|
||||||
|
$this->assertIsString($content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGenerateSVG(): void
|
||||||
|
{
|
||||||
|
$part = new Part();
|
||||||
|
$part->setName('Test');
|
||||||
|
|
||||||
|
//Test that all barcodes types are supported
|
||||||
|
foreach (LabelOptions::BARCODE_TYPES as $type) {
|
||||||
|
$options = new LabelOptions();
|
||||||
|
$options->setBarcodeType($type);
|
||||||
|
$svg = $this->services->generateSVG($options, $part);
|
||||||
|
|
||||||
|
//When type is none, service must return null.
|
||||||
|
if ($type === "none") {
|
||||||
|
$this->assertNull($svg);
|
||||||
|
} else {
|
||||||
|
$this->assertStringContainsStringIgnoringCase("SVG", $svg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue