Merge branch 'master' into log_detail_page

This commit is contained in:
Jan Böhmer 2023-04-29 22:46:38 +02:00
commit 4c6ceab8e8
291 changed files with 1994 additions and 1621 deletions

View file

@ -1 +1 @@
1.3.0-dev 1.3.2

View file

@ -30,9 +30,73 @@ export default class SpecialCharactersEmoji extends Plugin {
const editor = this.editor; const editor = this.editor;
const specialCharsPlugin = editor.plugins.get('SpecialCharacters'); const specialCharsPlugin = editor.plugins.get('SpecialCharacters');
//Add greek characters to special characters
specialCharsPlugin.addItems('Greek', this.getGreek());
//Add Emojis to special characters
specialCharsPlugin.addItems('Emoji', this.getEmojis()); specialCharsPlugin.addItems('Emoji', this.getEmojis());
} }
getGreek() {
return [
{ title: 'Alpha', character: 'Α' },
{ title: 'Beta', character: 'Β' },
{ title: 'Gamma', character: 'Γ' },
{ title: 'Delta', character: 'Δ' },
{ title: 'Epsilon', character: 'Ε' },
{ title: 'Zeta', character: 'Ζ' },
{ title: 'Eta', character: 'Η' },
{ title: 'Theta', character: 'Θ' },
{ title: 'Iota', character: 'Ι' },
{ title: 'Kappa', character: 'Κ' },
{ title: 'Lambda', character: 'Λ' },
{ title: 'Mu', character: 'Μ' },
{ title: 'Nu', character: 'Ν' },
{ title: 'Xi', character: 'Ξ' },
{ title: 'Omicron', character: 'Ο' },
{ title: 'Pi', character: 'Π' },
{ title: 'Rho', character: 'Ρ' },
{ title: 'Sigma', character: 'Σ' },
{ title: 'Tau', character: 'Τ' },
{ title: 'Upsilon', character: 'Υ' },
{ title: 'Phi', character: 'Φ' },
{ title: 'Chi', character: 'Χ' },
{ title: 'Psi', character: 'Ψ' },
{ title: 'Omega', character: 'Ω' },
{ title: 'alpha', character: 'α' },
{ title: 'beta', character: 'β' },
{ title: 'gamma', character: 'γ' },
{ title: 'delta', character: 'δ' },
{ title: 'epsilon', character: 'ε' },
{ title: 'zeta', character: 'ζ' },
{ title: 'eta', character: 'η' },
{ title: 'theta', character: 'θ' },
{ title: 'alternate theta', character: 'ϑ' },
{ title: 'iota', character: 'ι' },
{ title: 'kappa', character: 'κ' },
{ title: 'lambda', character: 'λ' },
{ title: 'mu', character: 'μ' },
{ title: 'nu', character: 'ν' },
{ title: 'xi', character: 'ξ' },
{ title: 'omicron', character: 'ο' },
{ title: 'pi', character: 'π' },
{ title: 'rho', character: 'ρ' },
{ title: 'sigma', character: 'σ' },
{ title: 'tau', character: 'τ' },
{ title: 'upsilon', character: 'υ' },
{ title: 'phi', character: 'φ' },
{ title: 'chi', character: 'χ' },
{ title: 'psi', character: 'ψ' },
{ title: 'omega', character: 'ω' },
{ title: 'digamma', character: 'Ϝ' },
{ title: 'stigma', character: 'Ϛ' },
{ title: 'heta', character: 'Ͱ' },
{ title: 'sampi', character: 'Ϡ' },
{ title: 'koppa', character: 'Ϟ' },
{ title: 'san', character: 'Ϻ' },
];
}
getEmojis() { getEmojis() {
//Map our emoji data to the format the plugin expects //Map our emoji data to the format the plugin expects
return emoji.map(emoji => { return emoji.map(emoji => {

View file

@ -29,42 +29,16 @@ export default class extends Controller
this._confirmed = false; this._confirmed = false;
} }
click(event) {
//If a user has not already confirmed the deletion, just let turbo do its work
if(this._confirmed) {
this._confirmed = false;
return;
}
event.preventDefault();
const message = this.element.dataset.deleteMessage;
const title = this.element.dataset.deleteTitle;
const that = this;
const confirm = bootbox.confirm({
message: message, title: title, callback: function (result) {
//If the dialog was confirmed, then submit the form.
if (result) {
that._confirmed = true;
event.target.click();
} else {
that._confirmed = false;
}
}
});
}
submit(event) { submit(event) {
//If a user has not already confirmed the deletion, just let turbo do its work //If a user has not already confirmed the deletion, just let turbo do its work
if(this._confirmed) { if (this._confirmed) {
this._confirmed = false; this._confirmed = false;
return; return;
} }
//Prevent turbo from doing its work //Prevent turbo from doing its work
event.preventDefault(); event.preventDefault();
event.stopPropagation();
const message = this.element.dataset.deleteMessage; const message = this.element.dataset.deleteMessage;
const title = this.element.dataset.deleteTitle; const title = this.element.dataset.deleteTitle;
@ -72,19 +46,20 @@ export default class extends Controller
const form = this.element; const form = this.element;
const that = this; const that = this;
//Create a clone of the event with the same submitter, so we can redispatch it if needed
//We need to do this that way, as we need the submitter info, just calling form.submit() would not work
this._our_event = new SubmitEvent('submit', {
submitter: event.submitter,
bubbles: true, //This line is important, otherwise Turbo will not receive the event
});
const confirm = bootbox.confirm({ const confirm = bootbox.confirm({
message: message, title: title, callback: function (result) { message: message, title: title, callback: function (result) {
//If the dialog was confirmed, then submit the form. //If the dialog was confirmed, then submit the form.
if (result) { if (result) {
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
that._confirmed = true; that._confirmed = true;
form.dispatchEvent(that._our_event);
//Create a submit button in the form and click it to submit the form
//Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273
const submit_btn = document.createElement('button');
submit_btn.type = 'submit';
submit_btn.style.display = 'none';
form.appendChild(submit_btn);
submit_btn.click();
} else { } else {
that._confirmed = false; that._confirmed = false;
} }

View file

@ -72,63 +72,223 @@ class RegisterEventHelper {
this.registerLoadHandler(() => { this.registerLoadHandler(() => {
//@ts-ignore //@ts-ignore
$("input[type=text], input[type=search]").unbind("keydown").keydown(function (event) { $("input[type=text], input[type=search]").unbind("keydown").keydown(function (event) {
let greek = event.altKey; let use_special_char = event.altKey;
let greek_char = ""; let greek_char = "";
if (greek){ if (use_special_char){
//Use the key property to determine the greek letter (as it is independent of the keyboard layout)
switch(event.key) { switch(event.key) {
case "w": //Omega //Greek letters
greek_char = '\u2126'; case "a": //Alpha (lowercase)
break;
case "u":
case "m": //Micro
greek_char = "\u00B5";
break;
case "p": //Phi
greek_char = "\u03C6";
break;
case "a": //Alpha
greek_char = "\u03B1"; greek_char = "\u03B1";
break; break;
case "b": //Beta case "A": //Alpha (uppercase)
greek_char = "\u0391";
break;
case "b": //Beta (lowercase)
greek_char = "\u03B2"; greek_char = "\u03B2";
break; break;
case "c": //Gamma case "B": //Beta (uppercase)
greek_char = "\u0392";
break;
case "g": //Gamma (lowercase)
greek_char = "\u03B3"; greek_char = "\u03B3";
break; break;
case "d": //Delta case "G": //Gamma (uppercase)
greek_char = "\u0393";
break;
case "d": //Delta (lowercase)
greek_char = "\u03B4"; greek_char = "\u03B4";
break; break;
case "l": //Pound case "D": //Delta (uppercase)
greek_char = "\u00A3"; greek_char = "\u0394";
break; break;
case "y": //Yen case "e": //Epsilon (lowercase)
greek_char = "\u00A5"; greek_char = "\u03B5";
break; break;
case "o": //Yen case "E": //Epsilon (uppercase)
greek_char = "\u00A4"; greek_char = "\u0395";
break; break;
case "1": //Sum symbol case "z": //Zeta (lowercase)
greek_char = "\u2211"; greek_char = "\u03B6";
break; break;
case "2": //Integral case "Z": //Zeta (uppercase)
greek_char = "\u222B"; greek_char = "\u0396";
break; break;
case "3": //Less-than or equal case "h": //Eta (lowercase)
greek_char = "\u2264"; greek_char = "\u03B7";
break; break;
case "4": //Greater than or equal case "H": //Eta (uppercase)
greek_char = "\u2265"; greek_char = "\u0397";
break; break;
case "5": //PI case "q": //Theta (lowercase)
greek_char = "\u03c0"; greek_char = "\u03B8";
break; break;
case "q": //Copyright case "Q": //Theta (uppercase)
greek_char = "\u00A9"; greek_char = "\u0398";
break; break;
case "e": //Euro case "i": //Iota (lowercase)
greek_char = "\u20AC"; greek_char = "\u03B9";
break; break;
case "I": //Iota (uppercase)
greek_char = "\u0399";
break;
case "k": //Kappa (lowercase)
greek_char = "\u03BA";
break;
case "K": //Kappa (uppercase)
greek_char = "\u039A";
break;
case "l": //Lambda (lowercase)
greek_char = "\u03BB";
break;
case "L": //Lambda (uppercase)
greek_char = "\u039B";
break;
case "m": //Mu (lowercase)
greek_char = "\u03BC";
break;
case "M": //Mu (uppercase)
greek_char = "\u039C";
break;
case "n": //Nu (lowercase)
greek_char = "\u03BD";
break;
case "N": //Nu (uppercase)
greek_char = "\u039D";
break;
case "x": //Xi (lowercase)
greek_char = "\u03BE";
break;
case "X": //Xi (uppercase)
greek_char = "\u039E";
break;
case "o": //Omicron (lowercase)
greek_char = "\u03BF";
break;
case "O": //Omicron (uppercase)
greek_char = "\u039F";
break;
case "p": //Pi (lowercase)
greek_char = "\u03C0";
break;
case "P": //Pi (uppercase)
greek_char = "\u03A0";
break;
case "r": //Rho (lowercase)
greek_char = "\u03C1";
break;
case "R": //Rho (uppercase)
greek_char = "\u03A1";
break;
case "s": //Sigma (lowercase)
greek_char = "\u03C3";
break;
case "S": //Sigma (uppercase)
greek_char = "\u03A3";
break;
case "t": //Tau (lowercase)
greek_char = "\u03C4";
break;
case "T": //Tau (uppercase)
greek_char = "\u03A4";
break;
case "u": //Upsilon (lowercase)
greek_char = "\u03C5";
break;
case "U": //Upsilon (uppercase)
greek_char = "\u03A5";
break;
case "f": //Phi (lowercase)
greek_char = "\u03C6";
break;
case "F": //Phi (uppercase)
greek_char = "\u03A6";
break;
case "c": //Chi (lowercase)
greek_char = "\u03C7";
break;
case "C": //Chi (uppercase)
greek_char = "\u03A7";
break;
case "y": //Psi (lowercase)
greek_char = "\u03C8";
break;
case "Y": //Psi (uppercase)
greek_char = "\u03A8";
break;
case "w": //Omega (lowercase)
greek_char = "\u03C9";
break;
case "W": //Omega (uppercase)
greek_char = "\u03A9";
break;
}
//Use keycodes for special characters as the shift char on the number keys are layout dependent
switch (event.keyCode) {
case 49: //1 key
//Product symbol on shift, sum on no shift
greek_char = event.shiftKey ? "\u220F" : "\u2211";
break;
case 50: //2 key
//Integral on no shift, partial derivative on shift
greek_char = event.shiftKey ? "\u2202" : "\u222B";
break;
case 51: //3 key
//Less than or equal on no shift, greater than or equal on shift
greek_char = event.shiftKey ? "\u2265" : "\u2264";
break;
case 52: //4 key
//Empty set on shift, infinity on no shift
greek_char = event.shiftKey ? "\u2205" : "\u221E";
break;
case 53: //5 key
//Not equal on shift, approx equal on no shift
greek_char = event.shiftKey ? "\u2260" : "\u2248";
break;
case 54: //6 key
//Element of on no shift, not element of on shift
greek_char = event.shiftKey ? "\u2209" : "\u2208";
break;
case 55: //7 key
//And on shift, or on no shift
greek_char = event.shiftKey ? "\u2227" : "\u2228";
break;
case 56: //8 key
//Proportional to on shift, angle on no shift
greek_char = event.shiftKey ? "\u221D" : "\u2220";
break;
case 57: //9 key
//Cube root on shift, square root on no shift
greek_char = event.shiftKey ? "\u221B" : "\u221A";
break;
case 48: //0 key
//Minus-Plus on shift, plus-minus on no shift
greek_char = event.shiftKey ? "\u2213" : "\u00B1";
break;
//Special characters
case 219: //hyphen (or ß on german layout)
//Copyright on no shift, TM on shift
greek_char = event.shiftKey ? "\u2122" : "\u00A9";
break;
case 191: //forward slash (or # on german layout)
//Generic currency on no shift, paragraph on shift
greek_char = event.shiftKey ? "\u00B6" : "\u00A4";
break;
//Currency symbols
case 192: //: or (ö on german layout)
//Euro on no shift, pound on shift
greek_char = event.shiftKey ? "\u00A3" : "\u20AC";
break;
case 221: //; or (ä on german layout)
//Yen on no shift, dollar on shift
greek_char = event.shiftKey ? "\u0024" : "\u00A5";
break;
} }
if(greek_char=="") return; if(greek_char=="") return;

627
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,9 @@ liip_imagine:
# valid drivers options include "gd" or "gmagick" or "imagick" # valid drivers options include "gd" or "gmagick" or "imagick"
driver: "gd" driver: "gd"
twig:
mode: lazy
default_filter_set_settings: default_filter_set_settings:
format: webp format: webp

View file

@ -244,6 +244,13 @@ services:
tags: tags:
- {name: serializer.normalizer, priority: -9000} - {name: serializer.normalizer, priority: -9000}
# Disable igbinary serialization for cache even when igbinary is available, as it causes issues with the doctrine
# proxy objects (see https://github.com/igbinary/igbinary/issues/377 and https://github.com/igbinary/igbinary/issues/273)
cache.default_marshaller:
class: Symfony\Component\Cache\Marshaller\DefaultMarshaller
arguments:
$useIgbinarySerialize: false
#################################################################################################################### ####################################################################################################################
# Miscellaneous # Miscellaneous

View file

@ -22,6 +22,11 @@ php bin/console doctrine:migrations:migrate
If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony). If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony).
## Search for user and reset password:
You can list all users with the following command: `php bin/console partdb:users:list`
To reset the password of a user you can use the following command: `php bin/console partdb:users:set-password [username]`
## Error logs ## Error logs
Detailed error logs can be found in the `var/log` directory. Detailed error logs can be found in the `var/log` directory.
When Part-DB is installed directly, the errors are written to the `var/log/prod.log` file. When Part-DB is installed directly, the errors are written to the `var/log/prod.log` file.

119
docs/usage/keybindings.md Normal file
View file

@ -0,0 +1,119 @@
---
title: Keybindings
layout: default
parent: Usage
---
# Keybindings
This page lists all the keybindings of Part-DB. Currently, there are only the special character keybindings.
## Special characters
Using the keybindings below (Alt + key) you can insert special characters into the text fields of Part-DB. This works on all text and search fields in Part-DB.
### Greek letters
| Key | Character |
|---------------------|---------------------|
| **Alt + a** | α (Alpha) |
| **Alt + Shift + A** | Α (Alpha uppercase) |
| **Alt + b** | β (Beta) |
| **Alt + Shift + B** | Β (Beta uppercase) |
| **Alt + g** | γ (Gamma) |
| **Alt + Shift + G** | Γ (Gamma uppercase) |
| **Alt + d** | δ (Delta) |
| **Alt + Shift + D** | Δ (Delta uppercase) |
| **Alt + e** | ε (Epsilon) |
| **Alt + Shift + E** | Ε (Epsilon uppercase) |
| **Alt + z** | ζ (Zeta) |
| **Alt + Shift + Z** | Ζ (Zeta uppercase) |
| **Alt + h** | η (Eta) |
| **Alt + Shift + H** | Η (Eta uppercase) |
| **Alt + q** | θ (Theta) |
| **Alt + Shift + Q** | Θ (Theta uppercase) |
| **Alt + i** | ι (Iota) |
| **Alt + Shift + I** | Ι (Iota uppercase) |
| **Alt + k** | κ (Kappa) |
| **Alt + Shift + K** | Κ (Kappa uppercase) |
| **Alt + l** | λ (Lambda) |
| **Alt + Shift + L** | Λ (Lambda uppercase) |
| **Alt + m** | μ (Mu) |
| **Alt + Shift + M** | Μ (Mu uppercase) |
| **Alt + n** | ν (Nu) |
| **Alt + Shift + N** | Ν (Nu uppercase) |
| **Alt + x** | ξ (Xi) |
| **Alt + Shift + x** | Ξ (Xi uppercase) |
| **Alt + o** | ο (Omicron) |
| **Alt + Shift + O** | Ο (Omicron uppercase) |
| **Alt + p** | π (Pi) |
| **Alt + Shift + P** | Π (Pi uppercase) |
| **Alt + r** | ρ (Rho) |
| **Alt + Shift + R** | Ρ (Rho uppercase) |
| **Alt + s** | σ (Sigma) |
| **Alt + Shift + S** | Σ (Sigma uppercase) |
| **Alt + t** | τ (Tau) |
| **Alt + Shift + T** | Τ (Tau uppercase) |
| **Alt + u** | υ (Upsilon) |
| **Alt + Shift + U** | Υ (Upsilon uppercase) |
| **Alt + f** | φ (Phi) |
| **Alt + Shift + F** | Φ (Phi uppercase) |
| **Alt + y** | ψ (Psi) |
| **Alt + Shift + Y** | Ψ (Psi uppercase) |
| **Alt + c** | χ (Chi) |
| **Alt + Shift + C** | Χ (Chi uppercase) |
| **Alt + w** | ω (Omega) |
| **Alt + Shift + W** | Ω (Omega uppercase) |
### Mathematical symbols
| Key | Character |
|----------------------|-------------------------------------------|
| **Alt + 1** | ∑ (Sum symbol) |
| **Alt + Shift + 1** | ∏ (Product symbol) |
| **Alt + 2** | ∫ (Integral symbol) |
| **Alt + Shift + 2** | ∂ (Partial derivation) |
| **Alt + 3** | ≤ (Less or equal symbol) |
| **Alt + Shift + 3** | ≥ (Greater or equal symbol) |
| **Alt + 4** | ∞ (Infinity symbol) |
| **Alt + Shift + 4** | ∅ (Empty set symbol) |
| **Alt + 5** | ≈ (Approximatley) |
| **Alt + Shift + 5** | ≠ (Not equal symbol) |
| **Alt + 6** | ∈ (Element of) |
| **Alt + Shift + 6** | ∉ (Not element of) |
| **Alt + 7** | (Logical or) |
| **Alt + Shift + 7** | ∧ (Logical and) |
| **Alt + 8** | ∠ (Angle symbol) |
| **Alt + Shift + 8** | ∝ (Proportional to) |
| **Alt + 9** | √ (Square root) |
| **Alt + Shift + 9** | ∛ (Cube root) |
| **Alt + 0** | ± (Plus minus) |
| **Alt + Shift + 0** | ∓ (Minus plus) |
### Currency symbols
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all keyboards.
It is given here for a US keyboard layout.
For a German keyboard layout, replace ; with ö, and ' with ä.
| Key | Character |
|---------------------------------|---------------------------|
| **Alt + ;** (code 192) | € (Euro currency symbol) |
| **Alt + Shift + ;** (code 192) | £ (Pound currency symbol) |
| **Alt + '** (code 222) | ¥ (Yen currency symbol) |
| **Alt + Shift + '** (code 222) | $ (Dollar currency symbol) |
### Others
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all keyboards.
It is given here for a US keyboard layout.
For a German keyboard layout, replace `[` with `0`, and `]` with `´`.
| Key | Character |
|--------------------------------|--------------------|
| **Alt + [** (code 219) | © (Copyright char) |
| **Alt + Shift + [** (code 219) | (Registered char) |
| **Alt + ]** (code 221) | ™ (Trademark char) |
| **Alt + Shift + ]** (code 221) | (Degree char) |

View file

@ -216,10 +216,10 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)'); $this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)'); $this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)');
$this->addSql('DROP INDEX pricedetails_orderdetails_id_k ON pricedetails'); $this->addSql('DROP INDEX pricedetails_orderdetails_id_k ON pricedetails');
$this->addSql('DROP INDEX name ON groups'); $this->addSql('DROP INDEX name ON `groups`');
$this->addSql('ALTER TABLE groups ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE perms_labels perms_labels INT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL'); $this->addSql('ALTER TABLE `groups` ADD not_selectable TINYINT(1) NOT NULL, CHANGE name name VARCHAR(255) NOT NULL, CHANGE comment comment LONGTEXT NOT NULL, CHANGE perms_labels perms_labels INT NOT NULL, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL');
$this->addSql('ALTER TABLE groups ADD CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES `groups` (id)'); $this->addSql('ALTER TABLE `groups` ADD CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES `groups` (id)');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)'); $this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON `groups` (parent_id)');
//Fill empty timestamps with current date //Fill empty timestamps with current date
$tables = ['attachments', 'attachment_types', 'categories', 'devices', 'footprints', 'manufacturers', $tables = ['attachments', 'attachment_types', 'categories', 'devices', 'footprints', 'manufacturers',
@ -232,6 +232,10 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
//Set the dbVersion to a high value, to prevent the old Part-DB versions to upgrade DB! //Set the dbVersion to a high value, to prevent the old Part-DB versions to upgrade DB!
$this->addSql("UPDATE `internal` SET `keyValue` = '99' WHERE `internal`.`keyName` = 'dbVersion'"); $this->addSql("UPDATE `internal` SET `keyValue` = '99' WHERE `internal`.`keyName` = 'dbVersion'");
//Migrate theme config to new format
$this->addSql('UPDATE users SET users.config_theme = REPLACE(users.config_theme ,".min.css", "") WHERE users.config_theme LIKE "%.min.css"');
$this->addSql('UPDATE users SET users.config_theme = REPLACE(users.config_theme ,".css", "") WHERE users.config_theme LIKE "%.css"');
} }
public function mySQLDown(Schema $schema): void public function mySQLDown(Schema $schema): void
@ -357,10 +361,6 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
$this->addSql('ALTER TABLE `users` CHANGE name name VARCHAR(32) NOT NULL COLLATE utf8_general_ci, CHANGE need_pw_change need_pw_change TINYINT(1) DEFAULT \'0\' NOT NULL, CHANGE first_name first_name TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE last_name last_name TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE department department TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE email email TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_language config_language TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_timezone config_timezone TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_theme config_theme TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_currency config_currency TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CHANGE perms_labels perms_labels SMALLINT NOT NULL'); $this->addSql('ALTER TABLE `users` CHANGE name name VARCHAR(32) NOT NULL COLLATE utf8_general_ci, CHANGE need_pw_change need_pw_change TINYINT(1) DEFAULT \'0\' NOT NULL, CHANGE first_name first_name TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE last_name last_name TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE department department TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE email email TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_language config_language TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_timezone config_timezone TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_theme config_theme TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE config_currency config_currency TINYTEXT DEFAULT NULL COLLATE utf8_general_ci, CHANGE last_modified last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CHANGE perms_labels perms_labels SMALLINT NOT NULL');
$this->addSql('DROP INDEX uniq_1483a5e95e237e06 ON `users`'); $this->addSql('DROP INDEX uniq_1483a5e95e237e06 ON `users`');
$this->addSql('CREATE UNIQUE INDEX name ON `users` (name)'); $this->addSql('CREATE UNIQUE INDEX name ON `users` (name)');
//Migrate theme config to new format
$this->addSql('UPDATE users SET users.config_theme = REPLACE(users.config_theme ,".min.css", "") WHERE users.config_theme LIKE "%.min.css"');
$this->addSql('UPDATE users SET users.config_theme = REPLACE(users.config_theme ,".css", "") WHERE users.config_theme LIKE "%.css"');
} }
public function sqLiteUp(Schema $schema): void public function sqLiteUp(Schema $schema): void

View file

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230417211732 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Fix class names in attachments table for databases migrated from legacy Part-DB';
}
public function mySQLUp(Schema $schema): void
{
//Delete all attachments where the corresponding part or device was deleted in legacy Part-DB (and therefore does not exist in the new Part-DB
$this->addSql('DELETE FROM attachments WHERE class_name = "PartDB\\\\Part" AND NOT EXISTS (SELECT id FROM parts WHERE id = attachments.element_id)');
$this->addSql('DELETE FROM attachments WHERE class_name = "PartDB\\\\Device" AND NOT EXISTS (SELECT id FROM projects WHERE id = attachments.element_id)');
// Replace all attachments where class_name is the legacy "PartDB\Part" with the new version "Part"
//We have to use 4 backslashes here, as PHP reduces them to 2 backslashes, which MySQL interprets as an escaped backslash.
$this->addSql('UPDATE attachments SET class_name = "Part" WHERE class_name = "PartDB\\\\Part"');
//Do the same with PartDB\Device and Device
$this->addSql('UPDATE attachments SET class_name = "Device" WHERE class_name = "PartDB\\\\Device"');
}
public function mySQLDown(Schema $schema): void
{
// We can not revert this migration, because we don't know the old class name.
}
public function sqLiteUp(Schema $schema): void
{
//As legacy database can only be migrated to MySQL, we don't need to implement this method.
$this->skipIf(true, 'Not needed for SQLite');
}
public function sqLiteDown(Schema $schema): void
{
//As we done nothing, we don't need to implement this method.
}
}

View file

@ -10,7 +10,6 @@ use PhpZip\ZipFile;
use Spatie\DbDumper\Databases\MySql; use Spatie\DbDumper\Databases\MySql;
use Spatie\DbDumper\DbDumper; use Spatie\DbDumper\DbDumper;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\Input;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;

View file

@ -45,12 +45,12 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use function count; use function count;
/** /**
* This command converts the BBCode used by old Part-DB versions (<1.0), to the current used markdown format. * This command converts the BBCode used by old Part-DB versions (<1.0), to the current used Markdown format.
*/ */
class ConvertBBCodeCommand extends Command class ConvertBBCodeCommand extends Command
{ {
/** /**
* @var string The LIKE criteria used to detect on SQL server if a entry contains BBCode * @var string The LIKE criteria used to detect on SQL server if an entry contains BBCode
*/ */
protected const BBCODE_CRITERIA = '%[%]%[/%]%'; protected const BBCODE_CRITERIA = '%[%]%[/%]%';
/** /**

View file

@ -69,7 +69,7 @@ class ImportPartKeeprCommand extends Command
$this->addOption('--import-users', null, InputOption::VALUE_NONE, 'Import users (passwords will not be imported).'); $this->addOption('--import-users', null, InputOption::VALUE_NONE, 'Import users (passwords will not be imported).');
} }
public function execute(InputInterface $input, OutputInterface $output) public function execute(InputInterface $input, OutputInterface $output): int
{ {
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);

View file

@ -27,9 +27,7 @@ use App\Services\LogSystem\EventCommentHelper;
use App\Services\UserSystem\PermissionSchemaUpdater; use App\Services\UserSystem\PermissionSchemaUpdater;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
@ -91,12 +89,12 @@ final class UpgradePermissionsSchemaCommand extends Command
//List all users and groups that need an update //List all users and groups that need an update
$io->section('Groups that need an update:'); $io->section('Groups that need an update:');
$io->listing(array_map(function (Group $group) { $io->listing(array_map(static function (Group $group) {
return $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')'; return $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')';
}, $groups_to_upgrade)); }, $groups_to_upgrade));
$io->section('Users that need an update:'); $io->section('Users that need an update:');
$io->listing(array_map(function (User $user) { $io->listing(array_map(static function (User $user) {
return $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')'; return $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')';
}, $users_to_upgrade)); }, $users_to_upgrade));

View file

@ -87,7 +87,7 @@ class UserEnableCommand extends Command
$io->note('The following users will be enabled:'); $io->note('The following users will be enabled:');
} }
$io->table(['Username', 'Enabled/Disabled'], $io->table(['Username', 'Enabled/Disabled'],
array_map(function(User $user) { array_map(static function(User $user) {
return [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled']; return [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled'];
}, $users)); }, $users));

View file

@ -269,7 +269,6 @@ abstract class BaseAdminController extends AbstractController
protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null) protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null)
{ {
$master_picture_backup = null;
if (null === $entity) { if (null === $entity) {
/** @var AbstractStructuralDBElement|User $new_entity */ /** @var AbstractStructuralDBElement|User $new_entity */
$new_entity = new $this->entity_class(); $new_entity = new $this->entity_class();
@ -390,7 +389,7 @@ abstract class BaseAdminController extends AbstractController
foreach ($errors as $error) { foreach ($errors as $error) {
if ($error['entity'] instanceof AbstractStructuralDBElement) { if ($error['entity'] instanceof AbstractStructuralDBElement) {
$this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']); $this->addFlash('error', $error['entity']->getFullPath().':'.$error['violations']);
} else { //When we dont have a structural element, we can only show the name } else { //When we don't have a structural element, we can only show the name
$this->addFlash('error', $error['entity']->getName().':'.$error['violations']); $this->addFlash('error', $error['entity']->getName().':'.$error['violations']);
} }
} }
@ -413,11 +412,11 @@ abstract class BaseAdminController extends AbstractController
} }
/** /**
* Performs checks if the element can be deleted safely. Otherwise an flash message is added. * Performs checks if the element can be deleted safely. Otherwise, a flash message is added.
* *
* @param AbstractNamedDBElement $entity the element that should be checked * @param AbstractNamedDBElement $entity the element that should be checked
* *
* @return bool True if the the element can be deleted, false if not * @return bool True if the element can be deleted, false if not
*/ */
protected function deleteCheck(AbstractNamedDBElement $entity): bool protected function deleteCheck(AbstractNamedDBElement $entity): bool
{ {

View file

@ -25,7 +25,6 @@ namespace App\Controller\AdminPages;
use App\Entity\Attachments\ProjectAttachment; use App\Entity\Attachments\ProjectAttachment;
use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\Project;
use App\Entity\Parameters\ProjectParameter; use App\Entity\Parameters\ProjectParameter;
use App\Form\AdminPages\BaseEntityAdminForm;
use App\Form\AdminPages\ProjectAdminForm; use App\Form\AdminPages\ProjectAdminForm;
use App\Services\ImportExportSystem\EntityExporter; use App\Services\ImportExportSystem\EntityExporter;
use App\Services\ImportExportSystem\EntityImporter; use App\Services\ImportExportSystem\EntityImporter;

View file

@ -24,12 +24,8 @@ namespace App\Controller;
use App\DataTables\AttachmentDataTable; use App\DataTables\AttachmentDataTable;
use App\DataTables\Filters\AttachmentFilter; use App\DataTables\Filters\AttachmentFilter;
use App\DataTables\Filters\PartFilter;
use App\DataTables\PartsDataTable;
use App\Entity\Attachments\Attachment; use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\PartAttachment;
use App\Form\Filters\AttachmentFilterType; use App\Form\Filters\AttachmentFilterType;
use App\Form\Filters\PartFilterType;
use App\Services\Attachments\AttachmentManager; use App\Services\Attachments\AttachmentManager;
use App\Services\Trees\NodesListBuilder; use App\Services\Trees\NodesListBuilder;
use Omines\DataTablesBundle\DataTableFactory; use Omines\DataTablesBundle\DataTableFactory;
@ -106,10 +102,8 @@ class AttachmentFileController extends AbstractController
/** /**
* @Route("/attachment/list", name="attachment_list") * @Route("/attachment/list", name="attachment_list")
*
* @return JsonResponse|Response
*/ */
public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder) public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder): Response
{ {
$this->denyAccessUnlessGranted('@attachments.list_attachments'); $this->denyAccessUnlessGranted('@attachments.list_attachments');

View file

@ -74,9 +74,9 @@ class GroupController extends BaseAdminController
//We need to stop the execution here, or our permissions changes will be overwritten by the form values //We need to stop the execution here, or our permissions changes will be overwritten by the form values
return $this->redirectToRoute('group_edit', ['id' => $entity->getID()]); return $this->redirectToRoute('group_edit', ['id' => $entity->getID()]);
} else {
$this->addFlash('danger', 'csfr_invalid');
} }
$this->addFlash('danger', 'csfr_invalid');
} }
return $this->_edit($entity, $request, $em, $timestamp); return $this->_edit($entity, $request, $em, $timestamp);

View file

@ -68,9 +68,9 @@ class LogController extends AbstractController
/** /**
* @Route("/", name="log_view") * @Route("/", name="log_view")
* *
* @return JsonResponse|Response * @return Response
*/ */
public function showLogs(Request $request, DataTableFactory $dataTable) public function showLogs(Request $request, DataTableFactory $dataTable): Response
{ {
$this->denyAccessUnlessGranted('@system.show_logs'); $this->denyAccessUnlessGranted('@system.show_logs');

View file

@ -53,7 +53,6 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
@ -351,8 +350,10 @@ class PartController extends AbstractController
if($partLot->getPart() !== $part) { if($partLot->getPart() !== $part) {
throw new \RuntimeException("The origin partlot does not belong to the part!"); throw new \RuntimeException("The origin partlot does not belong to the part!");
} }
//Try to determine the target lot (used for move actions)
$targetLot = $em->find(PartLot::class, $request->request->get('target_id')); //Try to determine the target lot (used for move actions), if the parameter is existing
$targetId = $request->request->get('target_id', null);
$targetLot = $targetId ? $em->find(PartLot::class, $targetId) : null;
if ($targetLot && $targetLot->getPart() !== $part) { if ($targetLot && $targetLot->getPart() !== $part) {
throw new \RuntimeException("The target partlot does not belong to the part!"); throw new \RuntimeException("The target partlot does not belong to the part!");
} }
@ -396,7 +397,7 @@ class PartController extends AbstractController
} }
err: err:
//If an redirect was passed, then redirect there //If a redirect was passed, then redirect there
if($request->request->get('_redirect')) { if($request->request->get('_redirect')) {
return $this->redirect($request->request->get('_redirect')); return $this->redirect($request->request->get('_redirect'));
} }

View file

@ -26,8 +26,6 @@ use App\Services\ImportExportSystem\EntityExporter;
use App\Services\ImportExportSystem\EntityImporter; use App\Services\ImportExportSystem\EntityImporter;
use App\Services\LogSystem\EventCommentHelper; use App\Services\LogSystem\EventCommentHelper;
use App\Services\Parts\PartsTableActionHandler; use App\Services\Parts\PartsTableActionHandler;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;

View file

@ -158,7 +158,7 @@ class PartListsController extends AbstractController
* *
* @return JsonResponse|Response * @return JsonResponse|Response
*/ */
public function showCategory(Category $category, Request $request) public function showCategory(Category $category, Request $request): Response
{ {
$this->denyAccessUnlessGranted('@categories.read'); $this->denyAccessUnlessGranted('@categories.read');
@ -180,7 +180,7 @@ class PartListsController extends AbstractController
* *
* @return JsonResponse|Response * @return JsonResponse|Response
*/ */
public function showFootprint(Footprint $footprint, Request $request) public function showFootprint(Footprint $footprint, Request $request): Response
{ {
$this->denyAccessUnlessGranted('@footprints.read'); $this->denyAccessUnlessGranted('@footprints.read');
@ -202,7 +202,7 @@ class PartListsController extends AbstractController
* *
* @return JsonResponse|Response * @return JsonResponse|Response
*/ */
public function showManufacturer(Manufacturer $manufacturer, Request $request) public function showManufacturer(Manufacturer $manufacturer, Request $request): Response
{ {
$this->denyAccessUnlessGranted('@manufacturers.read'); $this->denyAccessUnlessGranted('@manufacturers.read');
@ -224,7 +224,7 @@ class PartListsController extends AbstractController
* *
* @return JsonResponse|Response * @return JsonResponse|Response
*/ */
public function showStorelocation(Storelocation $storelocation, Request $request) public function showStorelocation(Storelocation $storelocation, Request $request): Response
{ {
$this->denyAccessUnlessGranted('@storelocations.read'); $this->denyAccessUnlessGranted('@storelocations.read');
@ -246,7 +246,7 @@ class PartListsController extends AbstractController
* *
* @return JsonResponse|Response * @return JsonResponse|Response
*/ */
public function showSupplier(Supplier $supplier, Request $request) public function showSupplier(Supplier $supplier, Request $request): Response
{ {
$this->denyAccessUnlessGranted('@suppliers.read'); $this->denyAccessUnlessGranted('@suppliers.read');
@ -268,7 +268,7 @@ class PartListsController extends AbstractController
* *
* @return JsonResponse|Response * @return JsonResponse|Response
*/ */
public function showTag(string $tag, Request $request, DataTableFactory $dataTable) public function showTag(string $tag, Request $request): Response
{ {
$tag = trim($tag); $tag = trim($tag);
@ -291,6 +291,7 @@ class PartListsController extends AbstractController
$filter->setName($request->query->getBoolean('name', true)); $filter->setName($request->query->getBoolean('name', true));
$filter->setCategory($request->query->getBoolean('category', true)); $filter->setCategory($request->query->getBoolean('category', true));
$filter->setDescription($request->query->getBoolean('description', true)); $filter->setDescription($request->query->getBoolean('description', true));
$filter->setMpn($request->query->getBoolean('mpn', true));
$filter->setTags($request->query->getBoolean('tags', true)); $filter->setTags($request->query->getBoolean('tags', true));
$filter->setStorelocation($request->query->getBoolean('storelocation', true)); $filter->setStorelocation($request->query->getBoolean('storelocation', true));
$filter->setComment($request->query->getBoolean('comment', true)); $filter->setComment($request->query->getBoolean('comment', true));
@ -300,6 +301,7 @@ class PartListsController extends AbstractController
$filter->setManufacturer($request->query->getBoolean('manufacturer', false)); $filter->setManufacturer($request->query->getBoolean('manufacturer', false));
$filter->setFootprint($request->query->getBoolean('footprint', false)); $filter->setFootprint($request->query->getBoolean('footprint', false));
$filter->setRegex($request->query->getBoolean('regex', false)); $filter->setRegex($request->query->getBoolean('regex', false));
return $filter; return $filter;
@ -310,7 +312,7 @@ class PartListsController extends AbstractController
* *
* @return JsonResponse|Response * @return JsonResponse|Response
*/ */
public function showSearch(Request $request, DataTableFactory $dataTable) public function showSearch(Request $request, DataTableFactory $dataTable): Response
{ {
$searchFilter = $this->searchRequestToFilter($request); $searchFilter = $this->searchRequestToFilter($request);
@ -331,9 +333,9 @@ class PartListsController extends AbstractController
/** /**
* @Route("/parts", name="parts_show_all") * @Route("/parts", name="parts_show_all")
* *
* @return JsonResponse|Response * @return Response
*/ */
public function showAll(Request $request, DataTableFactory $dataTable) public function showAll(Request $request): Response
{ {
return $this->showListWithFilter($request,'parts/lists/all_list.html.twig'); return $this->showListWithFilter($request,'parts/lists/all_list.html.twig');
} }

View file

@ -32,10 +32,8 @@ use App\Services\ImportExportSystem\BOMImporter;
use App\Services\ProjectSystem\ProjectBuildHelper; use App\Services\ProjectSystem\ProjectBuildHelper;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use League\Csv\Exception;
use League\Csv\SyntaxError; use League\Csv\SyntaxError;
use Omines\DataTablesBundle\DataTableFactory; use Omines\DataTablesBundle\DataTableFactory;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
@ -64,7 +62,7 @@ class ProjectController extends AbstractController
/** /**
* @Route("/{id}/info", name="project_info", requirements={"id"="\d+"}) * @Route("/{id}/info", name="project_info", requirements={"id"="\d+"})
*/ */
public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper) public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper): Response
{ {
$this->denyAccessUnlessGranted('read', $project); $this->denyAccessUnlessGranted('read', $project);
@ -114,9 +112,9 @@ class ProjectController extends AbstractController
$request->get('_redirect', $request->get('_redirect',
$this->generateUrl('project_info', ['id' => $project->getID()] $this->generateUrl('project_info', ['id' => $project->getID()]
))); )));
} else {
$this->addFlash('error', 'project.build.flash.invalid_input');
} }
$this->addFlash('error', 'project.build.flash.invalid_input');
} }
return $this->renderForm('projects/build/build.html.twig', [ return $this->renderForm('projects/build/build.html.twig', [

View file

@ -28,20 +28,17 @@ use function in_array;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class RedirectController extends AbstractController class RedirectController extends AbstractController
{ {
protected string $default_locale; protected string $default_locale;
protected TranslatorInterface $translator; protected TranslatorInterface $translator;
protected SessionInterface $session;
protected bool $enforce_index_php; protected bool $enforce_index_php;
public function __construct(string $default_locale, TranslatorInterface $translator, SessionInterface $session, bool $enforce_index_php) public function __construct(string $default_locale, TranslatorInterface $translator, bool $enforce_index_php)
{ {
$this->default_locale = $default_locale; $this->default_locale = $default_locale;
$this->session = $session;
$this->translator = $translator; $this->translator = $translator;
$this->enforce_index_php = $enforce_index_php; $this->enforce_index_php = $enforce_index_php;
} }
@ -52,7 +49,7 @@ class RedirectController extends AbstractController
*/ */
public function addLocalePart(Request $request): RedirectResponse public function addLocalePart(Request $request): RedirectResponse
{ {
//By default we use the global default locale //By default, we use the global default locale
$locale = $this->default_locale; $locale = $this->default_locale;
//Check if a user has set a preferred language setting: //Check if a user has set a preferred language setting:
@ -61,7 +58,6 @@ class RedirectController extends AbstractController
$locale = $user->getLanguage(); $locale = $user->getLanguage();
} }
//$new_url = str_replace($request->getPathInfo(), '/' . $locale . $request->getPathInfo(), $request->getUri());
$new_url = $request->getUriForPath('/'.$locale.$request->getPathInfo()); $new_url = $request->getUriForPath('/'.$locale.$request->getPathInfo());
//If either mod_rewrite is not enabled or the index.php version is enforced, add index.php to the string //If either mod_rewrite is not enabled or the index.php version is enforced, add index.php to the string
@ -71,6 +67,9 @@ class RedirectController extends AbstractController
$new_url = $request->getSchemeAndHttpHost().$request->getBaseUrl().'/index.php/'.$locale.$request->getPathInfo(); $new_url = $request->getSchemeAndHttpHost().$request->getBaseUrl().'/index.php/'.$locale.$request->getPathInfo();
} }
//Add the query string
$new_url .= $request->getQueryString() ? '?'.$request->getQueryString() : '';
return $this->redirect($new_url); return $this->redirect($new_url);
} }

View file

@ -22,7 +22,6 @@ namespace App\Controller;
use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\Contracts\NamedElementInterface;
use App\Entity\LabelSystem\LabelProfile; 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;
@ -106,7 +105,7 @@ class SelectAPIController extends AbstractController
3 => $this->translator->trans('export.level.full'), 3 => $this->translator->trans('export.level.full'),
]; ];
return $this->json(array_map(function ($key, $value) { return $this->json(array_map(static function ($key, $value) {
return [ return [
'text' => $value, 'text' => $value,
'value' => $key, 'value' => $key,
@ -198,7 +197,7 @@ class SelectAPIController extends AbstractController
]); ]);
//Remove the data-* prefix for each key //Remove the data-* prefix for each key
$data = array_combine( $data = array_combine(
array_map(function ($key) { array_map(static function ($key) {
if (strpos($key, 'data-') === 0) { if (strpos($key, 'data-') === 0) {
return substr($key, 5); return substr($key, 5);
} }

View file

@ -95,7 +95,7 @@ class TypeaheadController extends AbstractController
} }
/** /**
* This functions map the parameter type to the class, so we can access its repository * This function map the parameter type to the class, so we can access its repository
* @param string $type * @param string $type
* @return class-string * @return class-string
*/ */

View file

@ -25,7 +25,6 @@ namespace App\Controller;
use App\DataTables\LogDataTable; use App\DataTables\LogDataTable;
use App\Entity\Attachments\UserAttachment; use App\Entity\Attachments\UserAttachment;
use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Parameters\AbstractParameter;
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;
@ -62,11 +61,11 @@ class UserController extends AdminPages\BaseAdminController
protected function additionalActionEdit(FormInterface $form, AbstractNamedDBElement $entity): bool protected function additionalActionEdit(FormInterface $form, AbstractNamedDBElement $entity): bool
{ {
//Check if we editing a user and if we need to change the password of it //Check if we're editing a user and if we need to change the password of it
if ($entity instanceof User && !empty($form['new_password']->getData())) { if ($entity instanceof User && !empty($form['new_password']->getData())) {
$password = $this->passwordEncoder->hashPassword($entity, $form['new_password']->getData()); $password = $this->passwordEncoder->hashPassword($entity, $form['new_password']->getData());
$entity->setPassword($password); $entity->setPassword($password);
//By default the user must change the password afterwards //By default, the user must change the password afterward
$entity->setNeedPwChange(true); $entity->setNeedPwChange(true);
$event = new SecurityEvent($entity); $event = new SecurityEvent($entity);
@ -129,9 +128,9 @@ class UserController extends AdminPages\BaseAdminController
//We need to stop the execution here, or our permissions changes will be overwritten by the form values //We need to stop the execution here, or our permissions changes will be overwritten by the form values
return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]); return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]);
} else {
$this->addFlash('danger', 'csfr_invalid');
} }
$this->addFlash('danger', 'csfr_invalid');
} }
return $this->_edit($entity, $request, $em, $timestamp); return $this->_edit($entity, $request, $em, $timestamp);
@ -142,7 +141,7 @@ class UserController extends AdminPages\BaseAdminController
if ($entity instanceof User && !empty($form['new_password']->getData())) { if ($entity instanceof User && !empty($form['new_password']->getData())) {
$password = $this->passwordEncoder->hashPassword($entity, $form['new_password']->getData()); $password = $this->passwordEncoder->hashPassword($entity, $form['new_password']->getData());
$entity->setPassword($password); $entity->setPassword($password);
//By default the user must change the password afterwards //By default, the user must change the password afterward
$entity->setNeedPwChange(true); $entity->setNeedPwChange(true);
} }

View file

@ -225,7 +225,7 @@ class UserSettingsController extends AbstractController
*/ */
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordHasherInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager, FormFactoryInterface $formFactory, UserAvatarHelper $avatarHelper) public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordHasherInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager, FormFactoryInterface $formFactory, UserAvatarHelper $avatarHelper)
{ {
/** @var User */ /** @var User $user */
$user = $this->getUser(); $user = $this->getUser();
$page_need_reload = false; $page_need_reload = false;
@ -261,7 +261,7 @@ class UserSettingsController extends AbstractController
$page_need_reload = true; $page_need_reload = true;
} }
/** @var Form $form We need an form implementation for the next calls */ /** @var Form $form We need a form implementation for the next calls */
if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName()) { if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName()) {
//Remove the avatar attachment from the user if requested //Remove the avatar attachment from the user if requested
if ($user->getMasterPictureAttachment() !== null) { if ($user->getMasterPictureAttachment() !== null) {
@ -327,7 +327,7 @@ class UserSettingsController extends AbstractController
$pw_form->handleRequest($request); $pw_form->handleRequest($request);
//Check if password if everything was correct, then save it to User and DB //Check if everything was correct, then save it to User and DB
if (!$this->demo_mode && $pw_form->isSubmitted() && $pw_form->isValid()) { if (!$this->demo_mode && $pw_form->isSubmitted() && $pw_form->isValid()) {
$password = $passwordEncoder->hashPassword($user, $pw_form['new_password']->getData()); $password = $passwordEncoder->hashPassword($user, $pw_form['new_password']->getData());
$user->setPassword($password); $user->setPassword($password);

View file

@ -20,7 +20,6 @@
namespace App\DataTables\Adapters; namespace App\DataTables\Adapters;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator; use Doctrine\ORM\Tools\Pagination\Paginator;
use Omines\DataTablesBundle\Adapter\Doctrine\FetchJoinORMAdapter; use Omines\DataTablesBundle\Adapter\Doctrine\FetchJoinORMAdapter;
@ -38,7 +37,7 @@ use Omines\DataTablesBundle\Adapter\Doctrine\FetchJoinORMAdapter;
*/ */
class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter
{ {
public function getCount(QueryBuilder $queryBuilder, $identifier) public function getCount(QueryBuilder $queryBuilder, $identifier): ?int
{ {
$qb_without_group_by = clone $queryBuilder; $qb_without_group_by = clone $queryBuilder;

View file

@ -22,9 +22,7 @@ declare(strict_types=1);
namespace App\DataTables\Column; namespace App\DataTables\Column;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Parts\Part;
use App\Services\EntityURLGenerator; use App\Services\EntityURLGenerator;
use Omines\DataTablesBundle\Column\AbstractColumn; use Omines\DataTablesBundle\Column\AbstractColumn;
use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\Options;

View file

@ -31,7 +31,7 @@ use Omines\DataTablesBundle\Column\AbstractColumn;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
/** /**
* Similar to the built in DateTimeColumn, but the datetime is formatted using a IntlDateFormatter, * Similar to the built-in DateTimeColumn, but the datetime is formatted using a IntlDateFormatter,
* to get prettier locale based formatting. * to get prettier locale based formatting.
*/ */
class LocaleDateTimeColumn extends AbstractColumn class LocaleDateTimeColumn extends AbstractColumn
@ -45,7 +45,9 @@ class LocaleDateTimeColumn extends AbstractColumn
{ {
if (null === $value) { if (null === $value) {
return $this->options['nullValue']; return $this->options['nullValue'];
} elseif (!$value instanceof DateTimeInterface) { }
if (!$value instanceof DateTimeInterface) {
$value = new DateTime((string) $value); $value = new DateTime((string) $value);
} }

View file

@ -41,7 +41,7 @@ class PrettyBoolColumn extends AbstractColumn
return (bool) $value; return (bool) $value;
} }
public function render($value, $context) public function render($value, $context): string
{ {
if ($value === true) { if ($value === true) {
return '<span class="badge bg-success"><i class="fa-solid fa-circle-check fa-fw"></i> ' return '<span class="badge bg-success"><i class="fa-solid fa-circle-check fa-fw"></i> '

View file

@ -27,7 +27,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class RowClassColumn extends AbstractColumn class RowClassColumn extends AbstractColumn
{ {
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver): self
{ {
parent::configureOptions($resolver); parent::configureOptions($resolver);
@ -48,6 +48,9 @@ class RowClassColumn extends AbstractColumn
parent::initialize('$$rowClass', $index, $options, $dataTable); // TODO: Change the autogenerated stub parent::initialize('$$rowClass', $index, $options, $dataTable); // TODO: Change the autogenerated stub
} }
/**
* @return mixed
*/
public function normalize($value) public function normalize($value)
{ {
return $value; return $value;

View file

@ -33,7 +33,7 @@ class SIUnitNumberColumn extends AbstractColumn
$this->formatter = $formatter; $this->formatter = $formatter;
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver): self
{ {
parent::configureOptions($resolver); parent::configureOptions($resolver);
@ -43,7 +43,7 @@ class SIUnitNumberColumn extends AbstractColumn
return $this; return $this;
} }
public function normalize($value) public function normalize($value): string
{ {
//Ignore null values //Ignore null values
if ($value === null) { if ($value === null) {

View file

@ -28,7 +28,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/ */
class SelectColumn extends AbstractColumn class SelectColumn extends AbstractColumn
{ {
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver): self
{ {
parent::configureOptions($resolver); parent::configureOptions($resolver);
@ -43,12 +43,15 @@ class SelectColumn extends AbstractColumn
return $this; return $this;
} }
/**
* @return mixed
*/
public function normalize($value) public function normalize($value)
{ {
return $value; return $value;
} }
public function render($value, $context) public function render($value, $context): string
{ {
//Return empty string, as it this column is filled by datatables on client side //Return empty string, as it this column is filled by datatables on client side
return ''; return '';

View file

@ -21,7 +21,6 @@
namespace App\DataTables\Filters\Constraints; namespace App\DataTables\Filters\Constraints;
use App\DataTables\Filters\FilterInterface; use App\DataTables\Filters\FilterInterface;
use Doctrine\ORM\QueryBuilder;
abstract class AbstractConstraint implements FilterInterface abstract class AbstractConstraint implements FilterInterface
{ {

View file

@ -20,7 +20,6 @@
namespace App\DataTables\Filters\Constraints; namespace App\DataTables\Filters\Constraints;
use App\DataTables\Filters\FilterInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
class BooleanConstraint extends AbstractConstraint class BooleanConstraint extends AbstractConstraint

View file

@ -20,7 +20,6 @@
namespace App\DataTables\Filters\Constraints; namespace App\DataTables\Filters\Constraints;
use Doctrine\DBAL\ParameterType;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
trait FilterTrait trait FilterTrait
@ -51,7 +50,7 @@ trait FilterTrait
protected function generateParameterIdentifier(string $property): string protected function generateParameterIdentifier(string $property): string
{ {
//Replace all special characters with underscores //Replace all special characters with underscores
$property = preg_replace('/[^a-zA-Z0-9_]/', '_', $property); $property = preg_replace('/\W/', '_', $property);
//Add a random number to the end of the property name for uniqueness //Add a random number to the end of the property name for uniqueness
return $property . '_' . uniqid("", false); return $property . '_' . uniqid("", false);
} }

View file

@ -98,7 +98,7 @@ class InstanceOfConstraint extends AbstractConstraint
if ($this->operator === 'ANY' || $this->operator === 'NONE') { if ($this->operator === 'ANY' || $this->operator === 'NONE') {
foreach($this->value as $value) { foreach($this->value as $value) {
//We cannnot use an paramater here, as this is the only way to pass the FCQN to the query (via binded params, we would need to use ClassMetaData). See: https://github.com/doctrine/orm/issues/4462 //We can not use a parameter here, as this is the only way to pass the FCQN to the query (via binded params, we would need to use ClassMetaData). See: https://github.com/doctrine/orm/issues/4462
$expressions[] = ($queryBuilder->expr()->isInstanceOf($this->property, $value)); $expressions[] = ($queryBuilder->expr()->isInstanceOf($this->property, $value));
} }

View file

@ -20,7 +20,6 @@
namespace App\DataTables\Filters\Constraints; namespace App\DataTables\Filters\Constraints;
use Doctrine\DBAL\ParameterType;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use RuntimeException; use RuntimeException;
@ -42,7 +41,7 @@ class NumberConstraint extends AbstractConstraint
protected $value2; protected $value2;
/** /**
* @var string The operator to use * @var string|null The operator to use
*/ */
protected ?string $operator; protected ?string $operator;

View file

@ -24,7 +24,6 @@ use App\DataTables\Filters\Constraints\AbstractConstraint;
use App\DataTables\Filters\Constraints\TextConstraint; use App\DataTables\Filters\Constraints\TextConstraint;
use App\Entity\Parameters\PartParameter; use App\Entity\Parameters\PartParameter;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Svg\Tag\Text;
class ParameterConstraint extends AbstractConstraint class ParameterConstraint extends AbstractConstraint
{ {

View file

@ -101,7 +101,7 @@ class TextConstraint extends AbstractConstraint
return; return;
} }
//The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator but we have to build the value string differently //The CONTAINS, LIKE, STARTS and ENDS operators use the LIKE operator, but we have to build the value string differently
$like_value = null; $like_value = null;
if ($this->operator === 'LIKE') { if ($this->operator === 'LIKE') {
$like_value = $this->value; $like_value = $this->value;

View file

@ -38,11 +38,9 @@ use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Storelocation; use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier; use App\Entity\Parts\Supplier;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Form\Filters\Constraints\UserEntityConstraintType;
use App\Services\Trees\NodesListBuilder; use App\Services\Trees\NodesListBuilder;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Svg\Tag\Text;
class PartFilter implements FilterInterface class PartFilter implements FilterInterface
{ {
@ -148,9 +146,9 @@ class PartFilter implements FilterInterface
/** /**
* @return BooleanConstraint|false * @return BooleanConstraint
*/ */
public function getFavorite() public function getFavorite(): BooleanConstraint
{ {
return $this->favorite; return $this->favorite;
} }

View file

@ -96,7 +96,7 @@ class PartSearchFilter implements FilterInterface
$fields_to_search[] = 'orderdetails.supplierpartnr'; $fields_to_search[] = 'orderdetails.supplierpartnr';
} }
if($this->mpn) { if($this->mpn) {
$fields_to_search[] = 'part.manufacturer_product_url'; $fields_to_search[] = 'part.manufacturer_product_number';
} }
if($this->supplier) { if($this->supplier) {
$fields_to_search[] = 'suppliers.name'; $fields_to_search[] = 'suppliers.name';

View file

@ -28,7 +28,6 @@ use App\DataTables\Column\LogEntryExtraColumn;
use App\DataTables\Column\LogEntryTargetColumn; use App\DataTables\Column\LogEntryTargetColumn;
use App\DataTables\Column\RevertLogColumn; use App\DataTables\Column\RevertLogColumn;
use App\DataTables\Column\RowClassColumn; use App\DataTables\Column\RowClassColumn;
use App\DataTables\Filters\AttachmentFilter;
use App\DataTables\Filters\LogFilter; use App\DataTables\Filters\LogFilter;
use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractDBElement;
use App\Entity\Contracts\TimeTravelInterface; use App\Entity\Contracts\TimeTravelInterface;
@ -60,8 +59,6 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use function Symfony\Component\Translation\t;
class LogDataTable implements DataTableTypeInterface class LogDataTable implements DataTableTypeInterface
{ {
protected ElementTypeNameGenerator $elementTypeNameGenerator; protected ElementTypeNameGenerator $elementTypeNameGenerator;

View file

@ -36,22 +36,14 @@ use App\DataTables\Column\TagsColumn;
use App\DataTables\Filters\PartFilter; use App\DataTables\Filters\PartFilter;
use App\DataTables\Filters\PartSearchFilter; use App\DataTables\Filters\PartSearchFilter;
use App\DataTables\Helpers\PartDataTableHelper; use App\DataTables\Helpers\PartDataTableHelper;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot; use App\Entity\Parts\PartLot;
use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier;
use App\Services\Formatters\AmountFormatter; use App\Services\Formatters\AmountFormatter;
use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Attachments\AttachmentURLGenerator;
use App\Services\Attachments\PartPreviewGenerator;
use App\Services\EntityURLGenerator; use App\Services\EntityURLGenerator;
use App\Services\Trees\NodesListBuilder; use App\Services\Trees\NodesListBuilder;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Omines\DataTablesBundle\Adapter\Doctrine\FetchJoinORMAdapter;
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider; use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
use Omines\DataTablesBundle\Column\BoolColumn;
use Omines\DataTablesBundle\Column\MapColumn; use Omines\DataTablesBundle\Column\MapColumn;
use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\Column\TextColumn;
use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTable;
@ -63,27 +55,19 @@ use Symfony\Contracts\Translation\TranslatorInterface;
final class PartsDataTable implements DataTableTypeInterface final class PartsDataTable implements DataTableTypeInterface
{ {
private TranslatorInterface $translator; private TranslatorInterface $translator;
private NodesListBuilder $treeBuilder;
private AmountFormatter $amountFormatter; private AmountFormatter $amountFormatter;
private AttachmentURLGenerator $attachmentURLGenerator;
private Security $security; private Security $security;
private PartDataTableHelper $partDataTableHelper; private PartDataTableHelper $partDataTableHelper;
/** private EntityURLGenerator $urlGenerator;
* @var EntityURLGenerator
*/
private $urlGenerator;
public function __construct(EntityURLGenerator $urlGenerator, TranslatorInterface $translator, public function __construct(EntityURLGenerator $urlGenerator, TranslatorInterface $translator,
NodesListBuilder $treeBuilder, AmountFormatter $amountFormatter,PartDataTableHelper $partDataTableHelper, AmountFormatter $amountFormatter,PartDataTableHelper $partDataTableHelper, Security $security)
AttachmentURLGenerator $attachmentURLGenerator, Security $security)
{ {
$this->urlGenerator = $urlGenerator; $this->urlGenerator = $urlGenerator;
$this->translator = $translator; $this->translator = $translator;
$this->treeBuilder = $treeBuilder;
$this->amountFormatter = $amountFormatter; $this->amountFormatter = $amountFormatter;
$this->attachmentURLGenerator = $attachmentURLGenerator;
$this->security = $security; $this->security = $security;
$this->partDataTableHelper = $partDataTableHelper; $this->partDataTableHelper = $partDataTableHelper;
} }
@ -168,6 +152,7 @@ final class PartsDataTable implements DataTableTypeInterface
if ($this->security->isGranted('@storelocations.read')) { if ($this->security->isGranted('@storelocations.read')) {
$dataTable->add('storelocation', TextColumn::class, [ $dataTable->add('storelocation', TextColumn::class, [
'label' => $this->translator->trans('part.table.storeLocations'), 'label' => $this->translator->trans('part.table.storeLocations'),
'orderField' => 'storelocations.name',
'render' => function ($value, Part $context) { 'render' => function ($value, Part $context) {
$tmp = []; $tmp = [];
foreach ($context->getPartLots() as $lot) { foreach ($context->getPartLots() as $lot) {
@ -193,7 +178,22 @@ final class PartsDataTable implements DataTableTypeInterface
$amount = $context->getAmountSum(); $amount = $context->getAmountSum();
$expiredAmount = $context->getExpiredAmountSum(); $expiredAmount = $context->getExpiredAmountSum();
$ret = htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit())); $ret = '';
if ($context->isAmountUnknown()) {
//When all amounts are unknown, we show a question mark
if ($amount === 0.0) {
$ret .= sprintf('<b class="text-primary" title="%s">?</b>',
$this->translator->trans('part_lots.instock_unknown'));
} else { //Otherwise mark it with greater equal and the (known) amount
$ret .= sprintf('<b class="text-primary" title="%s">≥</b>',
$this->translator->trans('part_lots.instock_unknown')
);
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
}
} else {
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
}
//If we have expired lots, we show them in parentheses behind //If we have expired lots, we show them in parentheses behind
if ($expiredAmount > 0) { if ($expiredAmount > 0) {

View file

@ -27,7 +27,6 @@ use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
use Doctrine\Common\DataFixtures\Sorter\TopologicalSorter; use Doctrine\Common\DataFixtures\Sorter\TopologicalSorter;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Identifier; use Doctrine\DBAL\Schema\Identifier;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadata;
@ -35,7 +34,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\ClassMetadataInfo;
use function array_reverse; use function array_reverse;
use function array_search;
use function assert; use function assert;
use function count; use function count;
use function is_callable; use function is_callable;
@ -53,21 +51,21 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface
public const PURGE_MODE_TRUNCATE = 2; public const PURGE_MODE_TRUNCATE = 2;
/** @var EntityManagerInterface|null */ /** @var EntityManagerInterface|null */
private $em; private ?EntityManagerInterface $em;
/** /**
* If the purge should be done through DELETE or TRUNCATE statements * If the purge should be done through DELETE or TRUNCATE statements
* *
* @var int * @var int
*/ */
private $purgeMode = self::PURGE_MODE_DELETE; private int $purgeMode = self::PURGE_MODE_DELETE;
/** /**
* Table/view names to be excluded from purge * Table/view names to be excluded from purge
* *
* @var string[] * @var string[]
*/ */
private $excluded; private array $excluded;
/** /**
* Construct new purger instance. * Construct new purger instance.

View file

@ -49,7 +49,7 @@ class SQLiteRegexExtension implements EventSubscriberInterface
} }
} }
public function getSubscribedEvents() public function getSubscribedEvents(): array
{ {
return[ return[
Events::postConnect Events::postConnect

View file

@ -24,7 +24,7 @@ use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\Driver\Middleware;
/** /**
* This class wraps the Doctrine DBAL driver and wraps it into an Midleware driver so we can change the SQL mode * This class wraps the Doctrine DBAL driver and wraps it into a Midleware driver, so we can change the SQL mode
*/ */
class SetSQLModeMiddlewareWrapper implements Middleware class SetSQLModeMiddlewareWrapper implements Middleware
{ {

View file

@ -29,17 +29,17 @@ use Doctrine\DBAL\Types\Type;
class TinyIntType extends Type class TinyIntType extends Type
{ {
public function getSQLDeclaration(array $column, AbstractPlatform $platform) public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{ {
return 'TINYINT'; return 'TINYINT';
} }
public function getName() public function getName(): string
{ {
return 'tinyint'; return 'tinyint';
} }
public function requiresSQLCommentHint(AbstractPlatform $platform) public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{ {
//We use the comment, so that doctrine migrations can properly detect, that nothing has changed and no migration is needed. //We use the comment, so that doctrine migrations can properly detect, that nothing has changed and no migration is needed.
return true; return true;

View file

@ -59,7 +59,7 @@ abstract class Attachment extends AbstractNamedDBElement
/** /**
* A list of file extensions, that browsers can show directly as image. * A list of file extensions, that browsers can show directly as image.
* Based on: https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types * Based on: https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
* It will be used to determine if a attachment is a picture and therefore will be shown to user as preview. * It will be used to determine if an attachment is a picture and therefore will be shown to user as preview.
*/ */
public const PICTURE_EXTS = ['apng', 'bmp', 'gif', 'ico', 'cur', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png', public const PICTURE_EXTS = ['apng', 'bmp', 'gif', 'ico', 'cur', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png',
'svg', 'webp', ]; 'svg', 'webp', ];
@ -70,7 +70,7 @@ abstract class Attachment extends AbstractNamedDBElement
public const MODEL_EXTS = ['x3d']; public const MODEL_EXTS = ['x3d'];
/** /**
* When the path begins with one of this placeholders. * When the path begins with one of the placeholders.
*/ */
public const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%', '%SECURE%']; public const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%', '%SECURE%'];
@ -105,7 +105,7 @@ abstract class Attachment extends AbstractNamedDBElement
protected string $name = ''; protected string $name = '';
/** /**
* ORM mapping is done in sub classes (like PartAttachment). * ORM mapping is done in subclasses (like PartAttachment).
*/ */
protected ?AttachmentContainingDBElement $element = null; protected ?AttachmentContainingDBElement $element = null;
@ -116,7 +116,7 @@ abstract class Attachment extends AbstractNamedDBElement
protected bool $show_in_table = false; protected bool $show_in_table = false;
/** /**
* @var AttachmentType * @var AttachmentType|null
* @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="attachments_with_type") * @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="attachments_with_type")
* @ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=false) * @ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=false)
* @Selectable() * @Selectable()
@ -153,7 +153,7 @@ abstract class Attachment extends AbstractNamedDBElement
*/ */
public function isPicture(): bool public function isPicture(): bool
{ {
//We can not check if a external link is a picture, so just assume this is false //We can not check if an external link is a picture, so just assume this is false
if ($this->isExternal()) { if ($this->isExternal()) {
return true; return true;
} }
@ -215,7 +215,7 @@ abstract class Attachment extends AbstractNamedDBElement
* Checks if the attachment file is using a builtin file. (see BUILTIN_PLACEHOLDERS const for possible placeholders) * Checks if the attachment file is using a builtin file. (see BUILTIN_PLACEHOLDERS const for possible placeholders)
* If a file is built in, the path is shown to user in url field (no sensitive infos are provided). * If a file is built in, the path is shown to user in url field (no sensitive infos are provided).
* *
* @return bool true if the attachment is using an builtin file * @return bool true if the attachment is using a builtin file
*/ */
public function isBuiltIn(): bool public function isBuiltIn(): bool
{ {
@ -259,7 +259,7 @@ abstract class Attachment extends AbstractNamedDBElement
} }
/** /**
* The URL to the external file, or the path to the built in file. * The URL to the external file, or the path to the built-in file.
* Returns null, if the file is not external (and not builtin). * Returns null, if the file is not external (and not builtin).
*/ */
public function getURL(): ?string public function getURL(): ?string
@ -455,9 +455,9 @@ abstract class Attachment extends AbstractNamedDBElement
* @param string $string The string which should be checked * @param string $string The string which should be checked
* @param bool $path_required If true, the string must contain a path to be valid. (e.g. foo.bar would be invalid, foo.bar/test.php would be valid). * @param bool $path_required If true, the string must contain a path to be valid. (e.g. foo.bar would be invalid, foo.bar/test.php would be valid).
* @param bool $only_http Set this to true, if only HTTPS or HTTP schemata should be allowed. * @param bool $only_http Set this to true, if only HTTPS or HTTP schemata should be allowed.
* *Caution: When this is set to false, a attacker could use the file:// schema, to get internal server files, like /etc/passwd.* * *Caution: When this is set to false, an attacker could use the file:// schema, to get internal server files, like /etc/passwd.*
* *
* @return bool True if the string is a valid URL. False, if the string is not an URL or invalid. * @return bool True if the string is a valid URL. False, if the string is not a URL or invalid.
*/ */
public static function isValidURL(string $string, bool $path_required = true, bool $only_http = true): bool public static function isValidURL(string $string, bool $path_required = true, bool $only_http = true): bool
{ {

View file

@ -46,7 +46,7 @@ abstract class AttachmentContainingDBElement extends AbstractNamedDBElement impl
* Mapping is done in sub classes like part * Mapping is done in sub classes like part
* @Groups({"full"}) * @Groups({"full"})
*/ */
protected $attachments; protected Collection $attachments;
public function __construct() public function __construct()
{ {

View file

@ -45,13 +45,13 @@ class AttachmentType extends AbstractStructuralDBElement
* @ORM\OneToMany(targetEntity="AttachmentType", mappedBy="parent", cascade={"persist"}) * @ORM\OneToMany(targetEntity="AttachmentType", mappedBy="parent", cascade={"persist"})
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected Collection $children;
/** /**
* @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="children") * @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/ */
protected $parent; protected ?AbstractStructuralDBElement $parent;
/** /**
* @var string * @var string
@ -65,14 +65,14 @@ class AttachmentType extends AbstractStructuralDBElement
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected Collection $attachments;
/** @var Collection<int, AttachmentTypeParameter> /** @var Collection<int, AttachmentTypeParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\AttachmentTypeParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\AttachmentTypeParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $parameters; protected Collection $parameters;
/** /**
* @var Collection<int, Attachment> * @var Collection<int, Attachment>
@ -99,7 +99,7 @@ class AttachmentType extends AbstractStructuralDBElement
} }
/** /**
* Gets an filter, which file types are allowed for attachment files. * Gets a filter, which file types are allowed for attachment files.
* Must be in the format of <input type=file> accept attribute * Must be in the format of <input type=file> accept attribute
* (See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers). * (See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers).
*/ */

View file

@ -35,7 +35,7 @@ class AttachmentTypeAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = AttachmentType::class; public const ALLOWED_ELEMENT_CLASS = AttachmentType::class;
/** /**
* @var AttachmentType the element this attachment is associated with * @var AttachmentContainingDBElement|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\Attachments\AttachmentType", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\AttachmentType", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -27,7 +27,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a category element. * An attachment attached to a category element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"}) * @UniqueEntity({"name", "attachment_type", "element"})
@ -36,7 +36,7 @@ class CategoryAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = Category::class; public const ALLOWED_ELEMENT_CLASS = Category::class;
/** /**
* @var Category the element this attachment is associated with * @var AttachmentContainingDBElement|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Category", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Category", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -36,7 +36,7 @@ class CurrencyAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = Currency::class; public const ALLOWED_ELEMENT_CLASS = Currency::class;
/** /**
* @var Currency the element this attachment is associated with * @var Currency|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -27,7 +27,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a footprint element. * An attachment attached to a footprint element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"}) * @UniqueEntity({"name", "attachment_type", "element"})
@ -36,7 +36,7 @@ class FootprintAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = Footprint::class; public const ALLOWED_ELEMENT_CLASS = Footprint::class;
/** /**
* @var Footprint the element this attachment is associated with * @var Footprint|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Footprint", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Footprint", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -27,7 +27,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a Group element. * An attachment attached to a Group element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"}) * @UniqueEntity({"name", "attachment_type", "element"})
@ -35,8 +35,9 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
class GroupAttachment extends Attachment class GroupAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = Group::class; public const ALLOWED_ELEMENT_CLASS = Group::class;
/** /**
* @var Group the element this attachment is associated with * @var Group|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\Group", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\Group", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -27,7 +27,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a manufacturer element. * An attachment attached to a manufacturer element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"}) * @UniqueEntity({"name", "attachment_type", "element"})
@ -35,8 +35,9 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
class ManufacturerAttachment extends Attachment class ManufacturerAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = Manufacturer::class; public const ALLOWED_ELEMENT_CLASS = Manufacturer::class;
/** /**
* @var Manufacturer the element this attachment is associated with * @var Manufacturer|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Manufacturer", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Manufacturer", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a measurement unit element. * An attachment attached to a measurement unit element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"}) * @UniqueEntity({"name", "attachment_type", "element"})
@ -37,7 +37,7 @@ class MeasurementUnitAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = MeasurementUnit::class; public const ALLOWED_ELEMENT_CLASS = MeasurementUnit::class;
/** /**
* @var Manufacturer the element this attachment is associated with * @var Manufacturer|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\MeasurementUnit", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\MeasurementUnit", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -36,7 +36,7 @@ class ProjectAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = Project::class; public const ALLOWED_ELEMENT_CLASS = Project::class;
/** /**
* @var Project the element this attachment is associated with * @var Project|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\ProjectSystem\Project", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\ProjectSystem\Project", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -27,7 +27,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a measurement unit element. * An attachment attached to a measurement unit element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"}) * @UniqueEntity({"name", "attachment_type", "element"})
@ -35,8 +35,9 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
class StorelocationAttachment extends Attachment class StorelocationAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = Storelocation::class; public const ALLOWED_ELEMENT_CLASS = Storelocation::class;
/** /**
* @var Storelocation the element this attachment is associated with * @var Storelocation|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Storelocation", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Storelocation", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -35,8 +35,9 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
class SupplierAttachment extends Attachment class SupplierAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = Supplier::class; public const ALLOWED_ELEMENT_CLASS = Supplier::class;
/** /**
* @var Supplier the element this attachment is associated with * @var Supplier|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Supplier", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Supplier", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -27,7 +27,7 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a user element. * An attachment attached to a user element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"}) * @UniqueEntity({"name", "attachment_type", "element"})
@ -35,8 +35,9 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
class UserAttachment extends Attachment class UserAttachment extends Attachment
{ {
public const ALLOWED_ELEMENT_CLASS = User::class; public const ALLOWED_ELEMENT_CLASS = User::class;
/** /**
* @var User the element this attachment is associated with * @var User|null the element this attachment is associated with
* @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", inversedBy="attachments") * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", inversedBy="attachments")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */

View file

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Entity\Base; namespace App\Entity\Base;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\Groups;
@ -33,5 +34,5 @@ use Symfony\Component\Serializer\Annotation\Groups;
abstract class AbstractPartsContainingDBElement extends AbstractStructuralDBElement abstract class AbstractPartsContainingDBElement extends AbstractStructuralDBElement
{ {
/** @Groups({"full"}) */ /** @Groups({"full"}) */
protected $parameters; protected Collection $parameters;
} }

View file

@ -81,22 +81,22 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
protected int $level = 0; protected int $level = 0;
/** /**
* We can not define the mapping here or we will get an exception. Unfortunately we have to do the mapping in the * We can not define the mapping here, or we will get an exception. Unfortunately we have to do the mapping in the
* subclasses. * subclasses.
* *
* @var AbstractStructuralDBElement[]|Collection * @var AbstractStructuralDBElement[]|Collection
* @Groups({"include_children"}) * @Groups({"include_children"})
*/ */
protected $children; protected Collection $children;
/** /**
* @var AbstractStructuralDBElement * @var AbstractStructuralDBElement
* @NoneOfItsChildren() * @NoneOfItsChildren()
* @Groups({"include_parents", "import"}) * @Groups({"include_parents", "import"})
*/ */
protected $parent = null; protected ?AbstractStructuralDBElement $parent = null;
/** @var string[] all names of all parent elements as a array of strings, /** @var string[] all names of all parent elements as an array of strings,
* the last array element is the name of the element itself * the last array element is the name of the element itself
*/ */
private array $full_path_strings = []; private array $full_path_strings = [];
@ -106,6 +106,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
parent::__construct(); parent::__construct();
$this->children = new ArrayCollection(); $this->children = new ArrayCollection();
$this->parameters = new ArrayCollection(); $this->parameters = new ArrayCollection();
$this->parent = null;
} }
public function __clone() public function __clone()
@ -155,11 +156,9 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
if ($this->getParent() === $another_element) { if ($this->getParent() === $another_element) {
return true; return true;
} }
} else { //If the IDs are defined, we can compare the IDs } elseif ($this->getParent()->getID() === $another_element->getID()) {
if ($this->getParent()->getID() === $another_element->getID()) {
return true; return true;
} }
}
//Otherwise, check recursively //Otherwise, check recursively
return $this->parent->isChildOf($another_element); return $this->parent->isChildOf($another_element);
@ -168,7 +167,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
/** /**
* Checks if this element is an root element (has no parent). * Checks if this element is an root element (has no parent).
* *
* @return bool true if the this element is an root element * @return bool true if this element is a root element
*/ */
public function isRoot(): bool public function isRoot(): bool
{ {

View file

@ -27,12 +27,12 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
/** /**
* A entity with this class has a master attachment, which is used as a preview image for this object. * An entity with this class has a master attachment, which is used as a preview image for this object.
*/ */
trait MasterAttachmentTrait trait MasterAttachmentTrait
{ {
/** /**
* @var Attachment * @var Attachment|null
* @ORM\ManyToOne(targetEntity="App\Entity\Attachments\Attachment") * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\Attachment")
* @ORM\JoinColumn(name="id_preview_attachment", referencedColumnName="id", onDelete="SET NULL", nullable=true) * @ORM\JoinColumn(name="id_preview_attachment", referencedColumnName="id", onDelete="SET NULL", nullable=true)
* @Assert\Expression("value == null or value.isPicture()", message="part.master_attachment.must_be_picture") * @Assert\Expression("value == null or value.isPicture()", message="part.master_attachment.must_be_picture")

View file

@ -27,9 +27,9 @@ use DateTime;
interface TimeTravelInterface interface TimeTravelInterface
{ {
/** /**
* Checks if this entry has informations which data has changed. * Checks if this entry has information which data has changed.
* *
* @return bool true if this entry has informations about the changed data * @return bool true if this entry has information about the changed data
*/ */
public function hasOldDataInformations(): bool; public function hasOldDataInformations(): bool;
@ -39,7 +39,7 @@ interface TimeTravelInterface
public function getOldData(): array; public function getOldData(): array;
/** /**
* Returns the the timestamp associated with this change. * Returns the timestamp associated with this change.
*/ */
public function getTimestamp(): DateTime; public function getTimestamp(): DateTime;
} }

View file

@ -61,7 +61,7 @@ class LabelProfile extends AttachmentContainingDBElement
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\LabelAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\LabelAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $attachments; protected Collection $attachments;
/** /**
* @var LabelOptions * @var LabelOptions

View file

@ -48,7 +48,7 @@ use InvalidArgumentException;
use Psr\Log\LogLevel; use Psr\Log\LogLevel;
/** /**
* This entity describes a entry in the event log. * This entity describes an entry in the event log.
* *
* @ORM\Entity(repositoryClass="App\Repository\LogEntryRepository") * @ORM\Entity(repositoryClass="App\Repository\LogEntryRepository")
* @ORM\Table("log", indexes={ * @ORM\Table("log", indexes={
@ -142,7 +142,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
self::TARGET_TYPE_LABEL_PROFILE => LabelProfile::class, self::TARGET_TYPE_LABEL_PROFILE => LabelProfile::class,
]; ];
/** @var User The user which has caused this log entry /** @var User|null The user which has caused this log entry
* @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", fetch="EAGER") * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", fetch="EAGER")
* @ORM\JoinColumn(name="id_user", nullable=true, onDelete="SET NULL") * @ORM\JoinColumn(name="id_user", nullable=true, onDelete="SET NULL")
*/ */
@ -183,7 +183,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
/** @var array The extra data in raw (short form) saved in the DB /** @var array The extra data in raw (short form) saved in the DB
* @ORM\Column(name="extra", type="json") * @ORM\Column(name="extra", type="json")
*/ */
protected $extra = []; protected array $extra = [];
public function __construct() public function __construct()
{ {
@ -340,7 +340,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
/** /**
* Returns the class name of the target element associated with this log entry. * Returns the class name of the target element associated with this log entry.
* Returns null, if this log entry is not associated with an log entry. * Returns null, if this log entry is not associated with a log entry.
* *
* @return string|null the class name of the target class * @return string|null the class name of the target class
*/ */
@ -355,7 +355,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
/** /**
* Returns the ID of the target element associated with this log entry. * Returns the ID of the target element associated with this log entry.
* Returns null, if this log entry is not associated with an log entry. * Returns null, if this log entry is not associated with a log entry.
* *
* @return int|null the ID of the associated element * @return int|null the ID of the associated element
*/ */
@ -451,7 +451,7 @@ abstract class AbstractLogEntry extends AbstractDBElement
} }
/** /**
* Converts an target type id to an full qualified class name. * Converts a target type id to a full qualified class name.
* *
* @param int $type_id The target type ID * @param int $type_id The target type ID
*/ */

View file

@ -81,13 +81,12 @@ use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Currency;
use App\Entity\UserSystem\Group; use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Repository\Parts\ManufacturerRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException; use InvalidArgumentException;
/** /**
* @ORM\Entity() * @ORM\Entity()
* This log entry is created when an element is deleted, that is used in a collection of an other entity. * This log entry is created when an element is deleted, that is used in a collection of another entity.
* This is needed to signal time travel, that it has to undelete the deleted entity. * This is needed to signal time travel, that it has to undelete the deleted entity.
*/ */
class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventUndoInterface class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventUndoInterface

View file

@ -43,7 +43,7 @@ class DatabaseUpdatedLogEntry extends AbstractLogEntry
*/ */
public function isSuccessful(): bool public function isSuccessful(): bool
{ {
//We dont save unsuccessful updates now, so just assume it to save space. //We don't save unsuccessful updates now, so just assume it to save space.
return $this->extra['s'] ?? true; return $this->extra['s'] ?? true;
} }

View file

@ -56,7 +56,7 @@ class LegacyInstockChangedLogEntry extends AbstractLogEntry
} }
/** /**
* Returns the price that has to be payed for the change (in the base currency). * Returns the price that has to be paid for the change (in the base currency).
* *
* @param bool $absolute Set this to true, if you want only get the absolute value of the price (without minus) * @param bool $absolute Set this to true, if you want only get the absolute value of the price (without minus)
*/ */
@ -92,9 +92,9 @@ class LegacyInstockChangedLogEntry extends AbstractLogEntry
} }
/** /**
* Checks if the Change was an withdrawal of parts. * Checks if the Change was a withdrawal of parts.
* *
* @return bool true if the change was an withdrawal, false if not * @return bool true if the change was a withdrawal, false if not
*/ */
public function isWithdrawal(): bool public function isWithdrawal(): bool
{ {

View file

@ -117,7 +117,7 @@ class SecurityEventLogEntry extends AbstractLogEntry
} }
/** /**
* Return the (anonymized) IP address used to login the user. * Return the (anonymized) IP address used to log in the user.
*/ */
public function getIPAddress(): string public function getIPAddress(): string
{ {
@ -125,9 +125,9 @@ class SecurityEventLogEntry extends AbstractLogEntry
} }
/** /**
* Sets the IP address used to login the user. * Sets the IP address used to log in the user.
* *
* @param string $ip the IP address used to login the user * @param string $ip the IP address used to log in the user
* @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant
* *
* @return $this * @return $this

View file

@ -42,7 +42,7 @@ class UserLoginLogEntry extends AbstractLogEntry
} }
/** /**
* Return the (anonymized) IP address used to login the user. * Return the (anonymized) IP address used to log in the user.
*/ */
public function getIPAddress(): string public function getIPAddress(): string
{ {
@ -50,9 +50,9 @@ class UserLoginLogEntry extends AbstractLogEntry
} }
/** /**
* Sets the IP address used to login the user. * Sets the IP address used to log in the user.
* *
* @param string $ip the IP address used to login the user * @param string $ip the IP address used to log in the user
* @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant
* *
* @return $this * @return $this

View file

@ -40,7 +40,7 @@ class UserLogoutLogEntry extends AbstractLogEntry
} }
/** /**
* Return the (anonymized) IP address used to login the user. * Return the (anonymized) IP address used to log in the user.
*/ */
public function getIPAddress(): string public function getIPAddress(): string
{ {
@ -48,9 +48,9 @@ class UserLogoutLogEntry extends AbstractLogEntry
} }
/** /**
* Sets the IP address used to login the user. * Sets the IP address used to log in the user.
* *
* @param string $ip the IP address used to login the user * @param string $ip the IP address used to log in the user
* @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant * @param bool $anonymize Anonymize the IP address (remove last block) to be GPDR compliant
* *
* @return $this * @return $this

View file

@ -139,11 +139,11 @@ abstract class AbstractParameter extends AbstractNamedDBElement
protected string $group = ''; protected string $group = '';
/** /**
* Mapping is done in sub classes. * Mapping is done in subclasses.
* *
* @var AbstractDBElement|null the element to which this parameter belongs to * @var AbstractDBElement|null the element to which this parameter belongs to
*/ */
protected $element; protected ?AbstractDBElement $element = null;
public function __construct() public function __construct()
{ {

View file

@ -42,6 +42,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\AttachmentType;
use App\Entity\Base\AbstractDBElement;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -57,5 +58,5 @@ class AttachmentTypeParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\Attachments\AttachmentType", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\AttachmentType", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Parts\Category; use App\Entity\Parts\Category;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -57,5 +58,5 @@ class CategoryParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Category", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Category", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,12 +41,13 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Currency;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a category element. * An attachment attached to a category element.
* *
* @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository")
* @UniqueEntity(fields={"name", "group", "element"}) * @UniqueEntity(fields={"name", "group", "element"})
@ -60,5 +61,5 @@ class CurrencyParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Parts\Footprint; use App\Entity\Parts\Footprint;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -58,5 +59,5 @@ class FootprintParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Footprint", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Footprint", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\UserSystem\Group; use App\Entity\UserSystem\Group;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -58,5 +59,5 @@ class GroupParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\Group", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\Group", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Manufacturer;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -58,5 +59,5 @@ class ManufacturerParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Manufacturer", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Manufacturer", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\MeasurementUnit;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -58,5 +59,5 @@ class MeasurementUnitParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\MeasurementUnit", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\MeasurementUnit", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -52,7 +52,7 @@ trait ParametersTrait
* @var Collection<int, AbstractParameter> * @var Collection<int, AbstractParameter>
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $parameters; protected Collection $parameters;
/** /**
* Return all associated specifications. * Return all associated specifications.

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -58,5 +59,5 @@ class PartParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\Project;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -58,5 +59,5 @@ class ProjectParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\ProjectSystem\Project", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\ProjectSystem\Project", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Parts\Storelocation; use App\Entity\Parts\Storelocation;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -58,5 +59,5 @@ class StorelocationParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Storelocation", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Storelocation", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -41,6 +41,7 @@ declare(strict_types=1);
namespace App\Entity\Parameters; namespace App\Entity\Parameters;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Parts\Supplier; use App\Entity\Parts\Supplier;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -58,5 +59,5 @@ class SupplierParameter extends AbstractParameter
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Supplier", inversedBy="parameters") * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Supplier", inversedBy="parameters")
* @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE").
*/ */
protected $element; protected ?AbstractDBElement $element = null;
} }

View file

@ -24,6 +24,7 @@ namespace App\Entity\Parts;
use App\Entity\Attachments\CategoryAttachment; use App\Entity\Attachments\CategoryAttachment;
use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Base\AbstractPartsContainingDBElement;
use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\Parameters\CategoryParameter; use App\Entity\Parameters\CategoryParameter;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@ -46,13 +47,13 @@ class Category extends AbstractPartsContainingDBElement
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @var Collection * @var Collection
*/ */
protected $children; protected Collection $children;
/** /**
* @ORM\ManyToOne(targetEntity="Category", inversedBy="children") * @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/ */
protected $parent; protected ?AbstractStructuralDBElement $parent = null;
/** /**
* @var string * @var string
@ -109,6 +110,7 @@ class Category extends AbstractPartsContainingDBElement
* @Groups({"full", "import"}) * @Groups({"full", "import"})
*/ */
protected string $default_comment = ''; protected string $default_comment = '';
/** /**
* @var Collection<int, CategoryAttachment> * @var Collection<int, CategoryAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\CategoryAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\CategoryAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
@ -116,7 +118,7 @@ class Category extends AbstractPartsContainingDBElement
* @Assert\Valid() * @Assert\Valid()
* @Groups({"full"}) * @Groups({"full"})
*/ */
protected $attachments; protected Collection $attachments;
/** @var Collection<int, CategoryParameter> /** @var Collection<int, CategoryParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\CategoryParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\CategoryParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
@ -124,7 +126,7 @@ class Category extends AbstractPartsContainingDBElement
* @Assert\Valid() * @Assert\Valid()
* @Groups({"full"}) * @Groups({"full"})
*/ */
protected $parameters; protected Collection $parameters;
public function getPartnameHint(): string public function getPartnameHint(): string
{ {

View file

@ -44,14 +44,14 @@ class Footprint extends AbstractPartsContainingDBElement
* @ORM\ManyToOne(targetEntity="Footprint", inversedBy="children") * @ORM\ManyToOne(targetEntity="Footprint", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/ */
protected $parent; protected ?\App\Entity\Base\AbstractStructuralDBElement $parent;
/** /**
* @ORM\OneToMany(targetEntity="Footprint", mappedBy="parent") * @ORM\OneToMany(targetEntity="Footprint", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @var Collection * @var Collection
*/ */
protected $children; protected Collection $children;
/** /**
* @var Collection<int, FootprintAttachment> * @var Collection<int, FootprintAttachment>
@ -59,7 +59,7 @@ class Footprint extends AbstractPartsContainingDBElement
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected Collection $attachments;
/** /**
* @var FootprintAttachment|null * @var FootprintAttachment|null
@ -73,7 +73,7 @@ class Footprint extends AbstractPartsContainingDBElement
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $parameters; protected Collection $parameters;
/**************************************** /****************************************
* Getters * Getters

View file

@ -44,14 +44,14 @@ class Manufacturer extends AbstractCompany
* @ORM\ManyToOne(targetEntity="Manufacturer", inversedBy="children") * @ORM\ManyToOne(targetEntity="Manufacturer", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/ */
protected $parent; protected ?\App\Entity\Base\AbstractStructuralDBElement $parent;
/** /**
* @ORM\OneToMany(targetEntity="Manufacturer", mappedBy="parent") * @ORM\OneToMany(targetEntity="Manufacturer", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @var Collection * @var Collection
*/ */
protected $children; protected Collection $children;
/** /**
* @var Collection<int, ManufacturerAttachment> * @var Collection<int, ManufacturerAttachment>
@ -59,12 +59,12 @@ class Manufacturer extends AbstractCompany
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected Collection $attachments;
/** @var Collection<int, ManufacturerParameter> /** @var Collection<int, ManufacturerParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\ManufacturerParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\ManufacturerParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $parameters; protected Collection $parameters;
} }

View file

@ -75,13 +75,13 @@ class MeasurementUnit extends AbstractPartsContainingDBElement
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @var Collection * @var Collection
*/ */
protected $children; protected Collection $children;
/** /**
* @ORM\ManyToOne(targetEntity="MeasurementUnit", inversedBy="children") * @ORM\ManyToOne(targetEntity="MeasurementUnit", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/ */
protected $parent; protected ?\App\Entity\Base\AbstractStructuralDBElement $parent;
/** /**
* @var Collection<int, MeasurementUnitAttachment> * @var Collection<int, MeasurementUnitAttachment>
@ -89,14 +89,14 @@ class MeasurementUnit extends AbstractPartsContainingDBElement
* @ORM\OrderBy({"name" = "ASC"}) * @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected Collection $attachments;
/** @var Collection<int, MeasurementUnitParameter> /** @var Collection<int, MeasurementUnitParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\MeasurementUnitParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\MeasurementUnitParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $parameters; protected Collection $parameters;
/** /**
* @return string * @return string

View file

@ -26,7 +26,6 @@ use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\AttachmentContainingDBElement;
use App\Entity\Attachments\PartAttachment; use App\Entity\Attachments\PartAttachment;
use App\Entity\Parts\PartTraits\ProjectTrait; use App\Entity\Parts\PartTraits\ProjectTrait;
use App\Entity\ProjectSystem\Project;
use App\Entity\Parameters\ParametersTrait; use App\Entity\Parameters\ParametersTrait;
use App\Entity\Parameters\PartParameter; use App\Entity\Parameters\PartParameter;
use App\Entity\Parts\PartTraits\AdvancedPropertyTrait; use App\Entity\Parts\PartTraits\AdvancedPropertyTrait;
@ -34,7 +33,6 @@ use App\Entity\Parts\PartTraits\BasicPropertyTrait;
use App\Entity\Parts\PartTraits\InstockTrait; use App\Entity\Parts\PartTraits\InstockTrait;
use App\Entity\Parts\PartTraits\ManufacturerTrait; use App\Entity\Parts\PartTraits\ManufacturerTrait;
use App\Entity\Parts\PartTraits\OrderTrait; use App\Entity\Parts\PartTraits\OrderTrait;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use DateTime; use DateTime;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
@ -48,7 +46,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
* Part class. * Part class.
* *
* The class properties are split over various traits in directory PartTraits. * The class properties are split over various traits in directory PartTraits.
* Otherwise this class would be too big, to be maintained. * Otherwise, this class would be too big, to be maintained.
* *
* @ORM\Entity(repositoryClass="App\Repository\PartRepository") * @ORM\Entity(repositoryClass="App\Repository\PartRepository")
* @ORM\Table("`parts`", indexes={ * @ORM\Table("`parts`", indexes={
@ -75,7 +73,7 @@ class Part extends AttachmentContainingDBElement
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Groups({"full"}) * @Groups({"full"})
*/ */
protected $parameters; protected Collection $parameters;
/** /**
* @ORM\Column(type="datetime", name="datetime_added", options={"default":"CURRENT_TIMESTAMP"}) * @ORM\Column(type="datetime", name="datetime_added", options={"default":"CURRENT_TIMESTAMP"})
@ -100,16 +98,16 @@ class Part extends AttachmentContainingDBElement
* @Assert\Valid() * @Assert\Valid()
* @Groups({"full"}) * @Groups({"full"})
*/ */
protected $attachments; protected Collection $attachments;
/** /**
* @var DateTime the date when this element was modified the last time * @var DateTime|null the date when this element was modified the last time
* @ORM\Column(type="datetime", name="last_modified", options={"default":"CURRENT_TIMESTAMP"}) * @ORM\Column(type="datetime", name="last_modified", options={"default":"CURRENT_TIMESTAMP"})
*/ */
protected ?DateTime $lastModified = null; protected ?DateTime $lastModified = null;
/** /**
* @var Attachment * @var Attachment|null
* @ORM\ManyToOne(targetEntity="App\Entity\Attachments\Attachment") * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\Attachment")
* @ORM\JoinColumn(name="id_preview_attachment", referencedColumnName="id", onDelete="SET NULL", nullable=true) * @ORM\JoinColumn(name="id_preview_attachment", referencedColumnName="id", onDelete="SET NULL", nullable=true)
* @Assert\Expression("value == null or value.isPicture()", message="part.master_attachment.must_be_picture") * @Assert\Expression("value == null or value.isPicture()", message="part.master_attachment.must_be_picture")

View file

@ -130,7 +130,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
/** /**
* Check if the current part lot is expired. * Check if the current part lot is expired.
* This is the case, if the expiration date is greater the the current date. * This is the case, if the expiration date is greater the current date.
* *
* @return bool|null True, if the part lot is expired. Returns null, if no expiration date was set. * @return bool|null True, if the part lot is expired. Returns null, if no expiration date was set.
* *
@ -195,7 +195,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
} }
/** /**
* Sets the expiration date for the part lot. Set to null, if the part lot does not expires. * Sets the expiration date for the part lot. Set to null, if the part lot does not expire.
* *
* @param DateTime|null $expiration_date * @param DateTime|null $expiration_date
* *

View file

@ -55,7 +55,7 @@ trait AdvancedPropertyTrait
protected ?float $mass = null; protected ?float $mass = null;
/** /**
* @var string The internal part number of the part * @var string|null The internal part number of the part
* @ORM\Column(type="string", length=100, nullable=true, unique=true) * @ORM\Column(type="string", length=100, nullable=true, unique=true)
* @Assert\Length(max="100") * @Assert\Length(max="100")
* @Groups({"extended", "full", "import"}) * @Groups({"extended", "full", "import"})

View file

@ -46,7 +46,7 @@ trait BasicPropertyTrait
protected string $comment = ''; protected string $comment = '';
/** /**
* @var bool Kept for compatibility (it is not used now, and I dont think it was used in old versions) * @var bool Kept for compatibility (it is not used now, and I don't think it was used in old versions)
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
protected bool $visible = true; protected bool $visible = true;
@ -59,7 +59,7 @@ trait BasicPropertyTrait
protected bool $favorite = false; protected bool $favorite = false;
/** /**
* @var Category The category this part belongs too (e.g. Resistors). Use tags, for more complex grouping. * @var Category|null The category this part belongs too (e.g. Resistors). Use tags, for more complex grouping.
* Every part must have a category. * Every part must have a category.
* @ORM\ManyToOne(targetEntity="Category") * @ORM\ManyToOne(targetEntity="Category")
* @ORM\JoinColumn(name="id_category", referencedColumnName="id", nullable=false) * @ORM\JoinColumn(name="id_category", referencedColumnName="id", nullable=false)
@ -136,7 +136,7 @@ trait BasicPropertyTrait
/** /**
* Gets the Footprint of this part (e.g. DIP8). * Gets the Footprint of this part (e.g. DIP8).
* *
* @return Footprint|null The footprint of this part. Null if this part should no have a footprint. * @return Footprint|null The footprint of this part. Null if this part should not have a footprint.
*/ */
public function getFootprint(): ?Footprint public function getFootprint(): ?Footprint
{ {

View file

@ -122,7 +122,7 @@ trait InstockTrait
/** /**
* Get the count of parts which must be in stock at least. * Get the count of parts which must be in stock at least.
* If a integer-based part unit is selected, the value will be rounded to integers. * If an integer-based part unit is selected, the value will be rounded to integers.
* *
* @return float count of parts which must be in stock at least * @return float count of parts which must be in stock at least
*/ */
@ -160,9 +160,25 @@ trait InstockTrait
return $this->getAmountSum() < $this->getMinAmount(); return $this->getAmountSum() < $this->getMinAmount();
} }
/**
* Returns true, if at least one of the part lots has an unknown amount.
* It is possible that other part lots have a known amount, then getAmountSum() will return sum of all known amounts.
* @return bool True if at least one part lot has an unknown amount.
*/
public function isAmountUnknown(): bool
{
foreach ($this->getPartLots() as $lot) {
if ($lot->isInstockUnknown()) {
return true;
}
}
return false;
}
/** /**
* Returns the summed amount of this part (over all part lots) * Returns the summed amount of this part (over all part lots)
* Part Lots that have unknown value or are expired, are not used for this value. * Part Lots that have unknown value or are expired, are not used for this value (counted as 0).
* *
* @return float The amount of parts given in partUnit * @return float The amount of parts given in partUnit
*/ */
@ -171,7 +187,7 @@ trait InstockTrait
//TODO: Find a method to do this natively in SQL, the current method could be a bit slow //TODO: Find a method to do this natively in SQL, the current method could be a bit slow
$sum = 0; $sum = 0;
foreach ($this->getPartLots() as $lot) { foreach ($this->getPartLots() as $lot) {
//Dont use the instock value, if it is unkown //Don't use the in stock value, if it is unknown
if ($lot->isInstockUnknown() || $lot->isExpired() ?? false) { if ($lot->isInstockUnknown() || $lot->isExpired() ?? false) {
continue; continue;
} }

Some files were not shown because too many files have changed in this diff Show more