mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-07-05 16:04:33 +02:00
Merge branch 'darkmode-migration'
This commit is contained in:
commit
6fd79688b0
552 changed files with 11575 additions and 13425 deletions
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: [ '7.4', '8.0', '8.1', '8.2' ]
|
||||
php-versions: [ '8.1', '8.2' ]
|
||||
db-type: [ 'mysql', 'sqlite' ]
|
||||
|
||||
env:
|
||||
|
@ -109,7 +109,7 @@ jobs:
|
|||
run: php bin/console --env test doctrine:migrations:migrate -n
|
||||
|
||||
- name: Load fixtures
|
||||
run: php bin/console --env test doctrine:fixtures:load -n --purger reset_autoincrement_purger
|
||||
run: php bin/console --env test doctrine:fixtures:load -n
|
||||
|
||||
- name: Run PHPunit and generate coverage
|
||||
run: ./bin/phpunit --coverage-clover=coverage.xml
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.4.2
|
||||
1.5.0-dev
|
||||
|
|
3
assets/bootstrap.js
vendored
3
assets/bootstrap.js
vendored
|
@ -4,8 +4,7 @@ import { startStimulusApp } from '@symfony/stimulus-bridge';
|
|||
export const app = startStimulusApp(require.context(
|
||||
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
|
||||
true,
|
||||
/\.(j|t)sx?$/
|
||||
/\.[jt]sx?$/
|
||||
));
|
||||
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
|
|
|
@ -18,43 +18,118 @@
|
|||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import Darkmode from "darkmode-js/src";
|
||||
import "darkmode-js"
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
_darkmode;
|
||||
|
||||
connect() {
|
||||
if (typeof window.getComputedStyle(document.body).mixBlendMode == 'undefined') {
|
||||
console.warn("The browser does not support mix blend mode. Darkmode will not work.");
|
||||
this.setMode(this.getMode());
|
||||
document.querySelectorAll('input[name="darkmode"]').forEach((radio) => {
|
||||
radio.addEventListener('change', this._radioChanged.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for the change of radio buttons
|
||||
* @private
|
||||
*/
|
||||
_radioChanged(event) {
|
||||
const new_mode = this.getSelectedMode();
|
||||
this.setMode(new_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current mode from the local storage
|
||||
* @return {'dark', 'light', 'auto'}
|
||||
*/
|
||||
getMode() {
|
||||
return localStorage.getItem('darkmode') ?? 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode in the local storage and apply it and change the state of the radio buttons
|
||||
* @param mode
|
||||
*/
|
||||
setMode(mode) {
|
||||
if (mode !== 'dark' && mode !== 'light' && mode !== 'auto') {
|
||||
console.warn('Invalid darkmode mode: ' + mode);
|
||||
mode = 'auto';
|
||||
}
|
||||
|
||||
localStorage.setItem('darkmode', mode);
|
||||
|
||||
this.setButtonMode(mode);
|
||||
|
||||
if (mode === 'auto') {
|
||||
this._setDarkmodeAuto();
|
||||
} else if (mode === 'dark') {
|
||||
this._enableDarkmode();
|
||||
} else if (mode === 'light') {
|
||||
this._disableDarkmode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected mode via the radio buttons
|
||||
* @return {'dark', 'light', 'auto'}
|
||||
*/
|
||||
getSelectedMode() {
|
||||
return document.querySelector('input[name="darkmode"]:checked').value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of the radio buttons
|
||||
* @param mode
|
||||
*/
|
||||
setButtonMode(mode) {
|
||||
document.querySelector('input[name="darkmode"][value="' + mode + '"]').checked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable darkmode by adding the data-bs-theme="dark" to the html tag
|
||||
* @private
|
||||
*/
|
||||
_enableDarkmode() {
|
||||
//Add data-bs-theme="dark" to the html tag
|
||||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable darkmode by adding the data-bs-theme="light" to the html tag
|
||||
* @private
|
||||
*/
|
||||
_disableDarkmode() {
|
||||
//Set data-bs-theme to light
|
||||
document.documentElement.setAttribute('data-bs-theme', 'light');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the darkmode to auto and enable/disable it depending on the system settings, also add
|
||||
* an event listener to change the darkmode if the system settings change
|
||||
* @private
|
||||
*/
|
||||
_setDarkmodeAuto() {
|
||||
if (this.getMode() !== 'auto') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const darkmode = new Darkmode();
|
||||
this._darkmode = darkmode;
|
||||
|
||||
//Unhide darkmode button
|
||||
this._showWidget();
|
||||
|
||||
//Set the switch according to our current darkmode state
|
||||
const toggler = document.getElementById("toggleDarkmode");
|
||||
toggler.checked = darkmode.isActivated();
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error(e);
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
this._enableDarkmode();
|
||||
} else {
|
||||
this._disableDarkmode();
|
||||
}
|
||||
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
console.log('Prefered color scheme changed to ' + event.matches ? 'dark' : 'light');
|
||||
this._setDarkmodeAuto();
|
||||
});
|
||||
}
|
||||
|
||||
_showWidget() {
|
||||
this.element.classList.remove('hidden');
|
||||
}
|
||||
|
||||
toggleDarkmode() {
|
||||
this._darkmode.toggle();
|
||||
/**
|
||||
* Check if darkmode is activated
|
||||
* @return {boolean}
|
||||
*/
|
||||
isDarkmodeActivated() {
|
||||
return document.documentElement.getAttribute('data-bs-theme') === 'dark';
|
||||
}
|
||||
}
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import { marked } from "marked";
|
||||
import { mangle } from "marked-mangle";
|
||||
import { gfmHeadingId } from "marked-gfm-heading-id";
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import "../../css/app/markdown.css";
|
||||
|
@ -81,6 +83,10 @@ export default class extends Controller {
|
|||
*/
|
||||
configureMarked()
|
||||
{
|
||||
marked.use(mangle());
|
||||
marked.use(gfmHeadingId({
|
||||
}));
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
});
|
||||
|
|
|
@ -97,7 +97,7 @@ export default class extends Controller {
|
|||
},
|
||||
buttons: [{
|
||||
"extend": 'colvis',
|
||||
'className': 'mr-2 btn-light',
|
||||
'className': 'mr-2 btn-outline-secondary',
|
||||
'columns': ':not(.no-colvis)',
|
||||
"text": "<i class='fa fa-cog'></i>"
|
||||
}],
|
||||
|
@ -123,6 +123,22 @@ export default class extends Controller {
|
|||
console.error("Error initializing datatables: " + err);
|
||||
});
|
||||
|
||||
//Fix height of the length selector
|
||||
promise.then((dt) => {
|
||||
//Find all length selectors (select with name dt_length), which are inside a label
|
||||
const lengthSelectors = document.querySelectorAll('label select[name="dt_length"]');
|
||||
//And remove the surrounding label, while keeping the select with all event handlers
|
||||
lengthSelectors.forEach((selector) => {
|
||||
selector.parentElement.replaceWith(selector);
|
||||
});
|
||||
|
||||
//Find all column visibility buttons (button with buttons-colvis class) and remove the btn-secondary class
|
||||
const colVisButtons = document.querySelectorAll('button.buttons-colvis');
|
||||
colVisButtons.forEach((button) => {
|
||||
button.classList.remove('btn-secondary');
|
||||
});
|
||||
});
|
||||
|
||||
//Dispatch an event to let others know that the datatables has been loaded
|
||||
promise.then((dt) => {
|
||||
const event = new CustomEvent(EVENT_DT_LOADED, {bubbles: true});
|
||||
|
|
|
@ -81,6 +81,14 @@ export default class extends Controller {
|
|||
this._tree.remove();
|
||||
}
|
||||
|
||||
const BS53Theme = {
|
||||
getOptions() {
|
||||
return {
|
||||
onhoverColor: 'var(--bs-secondary-bg)',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this._tree = new BSTreeView(this.treeTarget, {
|
||||
levels: 1,
|
||||
showTags: this._showTags,
|
||||
|
@ -93,7 +101,7 @@ export default class extends Controller {
|
|||
}
|
||||
},
|
||||
//onNodeContextmenu: contextmenu_handler,
|
||||
}, [BS5Theme, FAIconTheme]);
|
||||
}, [BS5Theme, BS53Theme, FAIconTheme]);
|
||||
|
||||
this.treeTarget.addEventListener(EVENT_INITIALIZED, (event) => {
|
||||
/** @type {BSTreeView} */
|
||||
|
|
|
@ -99,7 +99,7 @@ label:not(.form-check-label, .custom-control-label) {
|
|||
|
||||
form .col-form-label.required:after, form label.required:after {
|
||||
bottom: 4px;
|
||||
color: var(--bs-dark);
|
||||
color: var(--bs-secondary-color);
|
||||
content: "\2022";
|
||||
filter: opacity(75%);
|
||||
position: relative;
|
||||
|
|
|
@ -79,7 +79,7 @@ ul.structural_link li {
|
|||
/* Add a slash symbol (/) before/behind each list item */
|
||||
ul.structural_link li+li:before {
|
||||
padding: 2px;
|
||||
color: grey;
|
||||
color: var(--bs-tertiary-color);
|
||||
/*content: "/\00a0";*/
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-weight: 900;
|
||||
|
@ -89,13 +89,13 @@ ul.structural_link li+li:before {
|
|||
|
||||
/* Add a color to all links inside the list */
|
||||
ul.structural_link li a {
|
||||
color: #0275d8;
|
||||
color: var(--bs-link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Add a color on mouse-over */
|
||||
ul.structural_link li a:hover {
|
||||
color: #01447e;
|
||||
color: var(--bs-link-hover-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,8 +78,6 @@ body {
|
|||
overflow: -moz-scrollbars-none;
|
||||
/* Use standard version for hiding the scrollbar */
|
||||
scrollbar-width: none;
|
||||
|
||||
background-color: var(--light);
|
||||
}
|
||||
|
||||
#sidebar-container {
|
||||
|
|
|
@ -91,7 +91,7 @@ th.select-checkbox {
|
|||
/** Fix datatables select-checkbox position */
|
||||
table.dataTable tr.selected td.select-checkbox:after
|
||||
{
|
||||
margin-top: -28px !important;
|
||||
margin-top: -25px !important;
|
||||
}
|
||||
|
||||
|
||||
|
@ -116,23 +116,33 @@ table.dataTable > thead > tr > th.select-checkbox:before,
|
|||
table.dataTable > thead > tr > th.select-checkbox:after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1.2em;
|
||||
top: 0.9em;
|
||||
left: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 1em !important;
|
||||
height: 1em !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
table.dataTable > thead > tr > th.select-checkbox:before {
|
||||
content: " ";
|
||||
margin-top: -5px;
|
||||
margin-left: -6px;
|
||||
border: 1px solid black;
|
||||
border: 2px solid var(--bs-tertiary-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before {
|
||||
border: 2px solid var(--bs-tertiary-color) !important;
|
||||
}
|
||||
|
||||
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after, table.dataTable > tbody > tr > th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:after {
|
||||
width: 1em !important;
|
||||
height: 1em !important;
|
||||
}
|
||||
|
||||
table.dataTable > thead > tr.selected > th.select-checkbox:after {
|
||||
content: "✓";
|
||||
font-size: 20px;
|
||||
margin-top: -23px;
|
||||
margin-top: -20px;
|
||||
margin-left: -6px;
|
||||
text-align: center;
|
||||
/*text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9; */
|
||||
|
|
|
@ -36,3 +36,42 @@
|
|||
.ck-html-label .ck-content hr {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/***********************************************
|
||||
* Hide CKEditor powered by message
|
||||
***********************************************/
|
||||
.ck-powered-by {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/***********************************************
|
||||
* Use Bootstrap color vars for CKEditor
|
||||
***********************************************/
|
||||
:root {
|
||||
--ck-color-base-foreground: var(--bs-secondary-bg);
|
||||
--ck-color-base-background: var(--bs-body-bg);
|
||||
--ck-color-base-border: var(--bs-border-color);
|
||||
--ck-color-base-action: var(--bs-success);
|
||||
--ck-color-base-focus: var(--bs-primary-border-subtle);
|
||||
--ck-color-base-text: var(--bs-body-color);
|
||||
--ck-color-base-active: var(--bs-primary-bg-subtle);
|
||||
--ck-color-base-active-focus: var(--bs-primary);
|
||||
--ck-color-base-error: var(--bs-danger);
|
||||
|
||||
/* Improve contrast between text and toolbar */
|
||||
--ck-color-toolbar-background: var(--bs-tertiary-bg);
|
||||
|
||||
/* Buttons */
|
||||
--ck-color-button-default-hover-background: var(--bs-secondary-bg);
|
||||
--ck-color-button-default-active-background: var(--bs-secondary-bg);
|
||||
|
||||
--ck-color-button-on-background: var(--bs-body-bg);
|
||||
--ck-color-button-on-hover-background: var(--bs-secondary-bg);
|
||||
--ck-color-button-on-active-background: var(--bs-secondary-bg);
|
||||
--ck-color-button-on-disabled-background: var(--bs-secondary-bg);
|
||||
--ck-color-button-on-color: var(--bs-primary)
|
||||
|
||||
}
|
|
@ -18,6 +18,24 @@
|
|||
*/
|
||||
|
||||
.tagsinput.ts-wrapper.multi .ts-control > div {
|
||||
background: var(--bs-secondary);
|
||||
color: var(--bs-white);
|
||||
background: var(--bs-secondary-bg);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
/*********
|
||||
* BS 5.3 compatible dark mode
|
||||
***************/
|
||||
|
||||
.ts-dropdown .active {
|
||||
background-color: var(--bs-secondary-bg) !important;
|
||||
color: var(--bs-body-color) !important;
|
||||
}
|
||||
|
||||
.ts-dropdown, .ts-control, .ts-control input {
|
||||
color: var(--bs-body-color) !important;
|
||||
}
|
||||
|
||||
.ts-dropdown, .ts-dropdown.form-control, .ts-dropdown.form-select {
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
import '../css/app/layout.css';
|
||||
import '../css/app/helpers.css';
|
||||
import '../css/app/darkmode.css';
|
||||
import '../css/app/tables.css';
|
||||
import '../css/app/bs-overrides.css';
|
||||
import '../css/app/treeview.css';
|
||||
|
|
|
@ -62,7 +62,7 @@ class RegisterEventHelper {
|
|||
this.registerLoadHandler(() => {
|
||||
$(".tooltip").remove();
|
||||
//Exclude dropdown buttons from tooltips, otherwise we run into endless errors from bootstrap (bootstrap.esm.js:614 Bootstrap doesn't allow more than one instance per element. Bound instance: bs.dropdown.)
|
||||
$('a[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i.fas[title]')
|
||||
$('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i.fas[title]')
|
||||
//@ts-ignore
|
||||
.tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'});
|
||||
});
|
||||
|
|
|
@ -21,8 +21,13 @@
|
|||
|
||||
class WebauthnTFA {
|
||||
|
||||
_b64UrlSafeEncode = (str) => {
|
||||
const b64 = btoa(str);
|
||||
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
||||
}
|
||||
|
||||
// Decodes a Base64Url string
|
||||
_base64UrlDecode = (input) => {
|
||||
_b64UrlSafeDecode = (input) => {
|
||||
input = input
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
@ -39,13 +44,16 @@ class WebauthnTFA {
|
|||
};
|
||||
|
||||
// Converts an array of bytes into a Base64Url string
|
||||
_arrayToBase64String = (a) => btoa(String.fromCharCode(...a));
|
||||
_arrayToBase64String = (a) => {
|
||||
const str = String.fromCharCode(...a);
|
||||
return this._b64UrlSafeEncode(str);
|
||||
}
|
||||
|
||||
// Prepares the public key options object returned by the Webauthn Framework
|
||||
_preparePublicKeyOptions = publicKey => {
|
||||
//Convert challenge from Base64Url string to Uint8Array
|
||||
publicKey.challenge = Uint8Array.from(
|
||||
this._base64UrlDecode(publicKey.challenge),
|
||||
this._b64UrlSafeDecode(publicKey.challenge),
|
||||
c => c.charCodeAt(0)
|
||||
);
|
||||
|
||||
|
@ -67,7 +75,7 @@ class WebauthnTFA {
|
|||
return {
|
||||
...data,
|
||||
id: Uint8Array.from(
|
||||
this._base64UrlDecode(data.id),
|
||||
this._b64UrlSafeDecode(data.id),
|
||||
c => c.charCodeAt(0)
|
||||
),
|
||||
};
|
||||
|
@ -81,7 +89,7 @@ class WebauthnTFA {
|
|||
return {
|
||||
...data,
|
||||
id: Uint8Array.from(
|
||||
this._base64UrlDecode(data.id),
|
||||
this._b64UrlSafeDecode(data.id),
|
||||
c => c.charCodeAt(0)
|
||||
),
|
||||
};
|
||||
|
|
103
composer.json
103
composer.json
|
@ -2,7 +2,7 @@
|
|||
"type": "project",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"php": "^8.1",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-gd": "*",
|
||||
|
@ -11,9 +11,9 @@
|
|||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"beberlei/doctrineextensions": "^1.2",
|
||||
"brick/math": "^0.8.15",
|
||||
"composer/package-versions-deprecated": "1.11.99.4",
|
||||
"doctrine/annotations": "^1.6",
|
||||
"brick/math": "^0.11.0",
|
||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||
"doctrine/annotations": "1.14.3",
|
||||
"doctrine/data-fixtures": "^1.6.6",
|
||||
"doctrine/dbal": "^3.4.6",
|
||||
"doctrine/doctrine-bundle": "^2.0",
|
||||
|
@ -24,54 +24,53 @@
|
|||
"florianv/swap": "^4.0",
|
||||
"florianv/swap-bundle": "dev-master",
|
||||
"gregwar/captcha-bundle": "^2.1.0",
|
||||
"hslavich/oneloginsaml-bundle": "^2.10",
|
||||
"jbtronics/2fa-webauthn": "^1.0.0",
|
||||
"jbtronics/2fa-webauthn": "^v2.0.0",
|
||||
"jfcherng/php-diff": "^6.14",
|
||||
"league/csv": "^9.8.0",
|
||||
"league/html-to-markdown": "^5.0.1",
|
||||
"liip/imagine-bundle": "^2.2",
|
||||
"nbgrp/onelogin-saml-bundle": "^1.3",
|
||||
"nelexa/zip": "^4.0",
|
||||
"nelmio/security-bundle": "^3.0",
|
||||
"nyholm/psr7": "^1.1",
|
||||
"ocramius/proxy-manager": "2.2.*",
|
||||
"omines/datatables-bundle": "^0.5.0",
|
||||
"omines/datatables-bundle": "^0.7.2",
|
||||
"php-translation/symfony-bundle": "^0.13.0",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
"s9e/text-formatter": "^2.1",
|
||||
"scheb/2fa-backup-code": "^5.13",
|
||||
"scheb/2fa-bundle": "^5.13",
|
||||
"scheb/2fa-google-authenticator": "^5.13",
|
||||
"scheb/2fa-trusted-device": "^5.13",
|
||||
"sensio/framework-extra-bundle": "^6.1.1",
|
||||
"scheb/2fa-backup-code": "^6.8.0",
|
||||
"scheb/2fa-bundle": "^6.8.0",
|
||||
"scheb/2fa-google-authenticator": "^6.8.0",
|
||||
"scheb/2fa-trusted-device": "^6.8.0",
|
||||
"shivas/versioning-bundle": "^4.0",
|
||||
"spatie/db-dumper": "^2.21",
|
||||
"spatie/db-dumper": "^3.3.1",
|
||||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "5.4.*",
|
||||
"symfony/console": "5.4.*",
|
||||
"symfony/dotenv": "5.4.*",
|
||||
"symfony/expression-language": "5.4.*",
|
||||
"symfony/flex": "^1.1",
|
||||
"symfony/form": "5.4.*",
|
||||
"symfony/framework-bundle": "5.4.*",
|
||||
"symfony/http-client": "5.4.*",
|
||||
"symfony/http-kernel": "5.4.*",
|
||||
"symfony/mailer": "5.4.*",
|
||||
"symfony/asset": "6.3.*",
|
||||
"symfony/console": "6.3.*",
|
||||
"symfony/dotenv": "6.3.*",
|
||||
"symfony/expression-language": "6.3.*",
|
||||
"symfony/flex": "^v2.3.1",
|
||||
"symfony/form": "6.3.*",
|
||||
"symfony/framework-bundle": "6.3.*",
|
||||
"symfony/http-client": "6.3.*",
|
||||
"symfony/http-kernel": "6.3.*",
|
||||
"symfony/mailer": "6.3.*",
|
||||
"symfony/monolog-bundle": "^3.1",
|
||||
"symfony/process": "5.4.*",
|
||||
"symfony/property-access": "5.4.*",
|
||||
"symfony/property-info": "5.4.*",
|
||||
"symfony/proxy-manager-bridge": "5.4.*",
|
||||
"symfony/rate-limiter": "5.4.*",
|
||||
"symfony/runtime": "5.4.*",
|
||||
"symfony/security-bundle": "5.4.*",
|
||||
"symfony/serializer": "5.4.*",
|
||||
"symfony/translation": "5.4.*",
|
||||
"symfony/twig-bundle": "5.4.*",
|
||||
"symfony/process": "6.3.*",
|
||||
"symfony/property-access": "6.3.*",
|
||||
"symfony/property-info": "6.3.*",
|
||||
"symfony/proxy-manager-bridge": "6.3.*",
|
||||
"symfony/rate-limiter": "6.3.*",
|
||||
"symfony/runtime": "6.3.*",
|
||||
"symfony/security-bundle": "6.3.*",
|
||||
"symfony/serializer": "6.3.*",
|
||||
"symfony/translation": "6.3.*",
|
||||
"symfony/twig-bundle": "6.3.*",
|
||||
"symfony/ux-turbo": "^2.0",
|
||||
"symfony/validator": "5.4.*",
|
||||
"symfony/web-link": "5.4.*",
|
||||
"symfony/webpack-encore-bundle": "^1.1",
|
||||
"symfony/yaml": "5.4.*",
|
||||
"symfony/validator": "6.3.*",
|
||||
"symfony/web-link": "6.3.*",
|
||||
"symfony/webpack-encore-bundle": "^v2.0.1",
|
||||
"symfony/yaml": "6.3.*",
|
||||
"tecnickcom/tc-lib-barcode": "^1.15",
|
||||
"twig/cssinliner-extra": "^3.0",
|
||||
"twig/extra-bundle": "^3.0",
|
||||
|
@ -79,28 +78,30 @@
|
|||
"twig/inky-extra": "^3.0",
|
||||
"twig/intl-extra": "^3.0",
|
||||
"twig/markdown-extra": "^3.0",
|
||||
"web-auth/webauthn-symfony-bundle": "^3.3",
|
||||
"web-auth/webauthn-symfony-bundle": "^4.0.0",
|
||||
"webmozart/assert": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"dama/doctrine-test-bundle": "^7.0",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.2",
|
||||
"ekino/phpstan-banned-code": "^v1.0.0",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^1.4.7",
|
||||
"phpstan/phpstan-doctrine": "^1.2.11",
|
||||
"phpstan/phpstan-strict-rules": "^1.5",
|
||||
"phpstan/phpstan-symfony": "^1.1.7",
|
||||
"psalm/plugin-symfony": "^v5.0.1",
|
||||
"rector/rector": "^0.17.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/browser-kit": "^5.2",
|
||||
"symfony/css-selector": "^5.2",
|
||||
"symfony/debug-bundle": "^5.2",
|
||||
"symfony/browser-kit": "6.3.*",
|
||||
"symfony/css-selector": "6.3.*",
|
||||
"symfony/debug-bundle": "6.3.*",
|
||||
"symfony/maker-bundle": "^1.13",
|
||||
"symfony/phpunit-bridge": "5.4.*",
|
||||
"symfony/stopwatch": "^5.2",
|
||||
"symfony/web-profiler-bundle": "^5.2",
|
||||
"symfony/phpunit-bridge": "6.3.*",
|
||||
"symfony/stopwatch": "6.3.*",
|
||||
"symfony/web-profiler-bundle": "6.3.*",
|
||||
"symplify/easy-coding-standard": "^11.0",
|
||||
"vimeo/psalm": "^5.6.0",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.2"
|
||||
"vimeo/psalm": "^5.6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Used to improve price calculation performance",
|
||||
|
@ -111,7 +112,7 @@
|
|||
"*": "dist"
|
||||
},
|
||||
"platform": {
|
||||
"php": "7.4.0"
|
||||
"php": "8.1.0"
|
||||
},
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
|
@ -143,7 +144,7 @@
|
|||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"phpstan": "vendor/bin/phpstan analyse src --level 2 --memory-limit 1G"
|
||||
"phpstan": "vendor/bin/phpstan analyse src --level 5 --memory-limit 1G"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
|
@ -151,9 +152,7 @@
|
|||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "5.4.*"
|
||||
"require": "6.3.*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
]
|
||||
}
|
||||
|
|
5213
composer.lock
generated
5213
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
if (!class_exists(Dotenv::class)) {
|
||||
throw new LogicException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
|
||||
}
|
||||
|
||||
// Load cached env vars if the .env.local.php file exists
|
||||
// Run "composer dump-env prod" to create it (requires symfony/flex >=1.2)
|
||||
if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) {
|
||||
(new Dotenv(false))->populate($env);
|
||||
} else {
|
||||
// load all the .env files
|
||||
(new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env');
|
||||
}
|
||||
|
||||
$_SERVER += $_ENV;
|
||||
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
|
||||
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
|
||||
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
|
@ -27,5 +26,6 @@ return [
|
|||
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
||||
SpomkyLabs\CborBundle\SpomkyLabsCborBundle::class => ['all' => true],
|
||||
Webauthn\Bundle\WebauthnBundle::class => ['all' => true],
|
||||
Hslavich\OneloginSamlBundle\HslavichOneloginSamlBundle::class => ['all' => true],
|
||||
Nbgrp\OneloginSamlBundle\NbgrpOneloginSamlBundle::class => ['all' => true],
|
||||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
@ -21,12 +21,15 @@ doctrine:
|
|||
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
enable_lazy_ghost_objects: true
|
||||
report_fields_where_declared: true
|
||||
validate_xml_mapping: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
is_bundle: false
|
||||
type: annotation
|
||||
type: attribute
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
csrf_protection: true
|
||||
handle_all_throwables: true
|
||||
|
||||
# Must be set to true, to enable the change of HTTP methhod via _method parameter, otherwise our delete routines does not work anymore
|
||||
# Must be set to true, to enable the change of HTTP method via _method parameter, otherwise our delete routines does not work anymore
|
||||
# TODO: Rework delete routines to work without _method parameter as it is not recommended anymore (see https://github.com/symfony/symfony/issues/45278)
|
||||
http_method_override: true
|
||||
|
||||
|
@ -29,9 +30,6 @@ framework:
|
|||
php_errors:
|
||||
log: true
|
||||
|
||||
form:
|
||||
legacy_error_messages: false # Enable to use the new Form component validation messages
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
|
||||
|
||||
hslavich_onelogin_saml:
|
||||
# Basic settings
|
||||
idp:
|
||||
entityId: '%env(string:SAML_IDP_ENTITY_ID)%'
|
||||
singleSignOnService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
singleLogoutService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_IDP_X509_CERT)%'
|
||||
sp:
|
||||
entityId: '%env(string:SAML_SP_ENTITY_ID)%'
|
||||
assertionConsumerService:
|
||||
url: '%partdb.default_uri%saml/acs'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
||||
singleLogoutService:
|
||||
url: '%partdb.default_uri%logout'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_SP_X509_CERT)%'
|
||||
privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
|
||||
# Optional settings
|
||||
#baseurl: 'http://myapp.com'
|
||||
strict: true
|
||||
debug: false
|
||||
security:
|
||||
allowRepeatAttributeName: true
|
||||
# nameIdEncrypted: false
|
||||
authnRequestsSigned: true
|
||||
logoutRequestSigned: true
|
||||
logoutResponseSigned: true
|
||||
# wantMessagesSigned: false
|
||||
# wantAssertionsSigned: true
|
||||
# wantNameIdEncrypted: false
|
||||
# requestedAuthnContext: true
|
||||
# signMetadata: false
|
||||
# wantXMLValidation: true
|
||||
# relaxDestinationValidation: false
|
||||
# destinationStrictlyMatches: true
|
||||
# rejectUnsolicitedResponsesWithInResponseTo: false
|
||||
# signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
||||
# digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
|
||||
#contactPerson:
|
||||
# technical:
|
||||
# givenName: 'Tech User'
|
||||
# emailAddress: 'techuser@example.com'
|
||||
# support:
|
||||
# givenName: 'Support User'
|
||||
# emailAddress: 'supportuser@example.com'
|
||||
# administrative:
|
||||
# givenName: 'Administrative User'
|
||||
# emailAddress: 'administrativeuser@example.com'
|
||||
#organization:
|
||||
# en:
|
||||
# name: 'Part-DB-name'
|
||||
# displayname: 'Displayname'
|
||||
# url: 'http://example.com'
|
10
config/packages/http_discovery.yaml
Normal file
10
config/packages/http_discovery.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
services:
|
||||
Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory'
|
||||
Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory'
|
||||
|
||||
http_discovery.psr17_factory:
|
||||
class: Http\Discovery\Psr17Factory
|
|
@ -1,2 +0,0 @@
|
|||
framework:
|
||||
lock: '%env(LOCK_DSN)%'
|
62
config/packages/nbgrp_onelogin_saml.yaml
Normal file
62
config/packages/nbgrp_onelogin_saml.yaml
Normal file
|
@ -0,0 +1,62 @@
|
|||
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
|
||||
|
||||
nbgrp_onelogin_saml:
|
||||
onelogin_settings:
|
||||
default:
|
||||
# Basic settings
|
||||
idp:
|
||||
entityId: '%env(string:SAML_IDP_ENTITY_ID)%'
|
||||
singleSignOnService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
singleLogoutService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_IDP_X509_CERT)%'
|
||||
sp:
|
||||
entityId: '%env(string:SAML_SP_ENTITY_ID)%'
|
||||
assertionConsumerService:
|
||||
url: '%partdb.default_uri%saml/acs'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
||||
singleLogoutService:
|
||||
url: '%partdb.default_uri%logout'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_SP_X509_CERT)%'
|
||||
privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
|
||||
# Optional settings
|
||||
#baseurl: 'http://myapp.com'
|
||||
strict: true
|
||||
debug: false
|
||||
security:
|
||||
allowRepeatAttributeName: true
|
||||
# nameIdEncrypted: false
|
||||
authnRequestsSigned: true
|
||||
logoutRequestSigned: true
|
||||
logoutResponseSigned: true
|
||||
# wantMessagesSigned: false
|
||||
# wantAssertionsSigned: true
|
||||
# wantNameIdEncrypted: false
|
||||
# requestedAuthnContext: true
|
||||
# signMetadata: false
|
||||
# wantXMLValidation: true
|
||||
# relaxDestinationValidation: false
|
||||
# destinationStrictlyMatches: true
|
||||
# rejectUnsolicitedResponsesWithInResponseTo: false
|
||||
# signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
||||
# digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
|
||||
#contactPerson:
|
||||
# technical:
|
||||
# givenName: 'Tech User'
|
||||
# emailAddress: 'techuser@example.com'
|
||||
# support:
|
||||
# givenName: 'Support User'
|
||||
# emailAddress: 'supportuser@example.com'
|
||||
# administrative:
|
||||
# givenName: 'Administrative User'
|
||||
# emailAddress: 'administrativeuser@example.com'
|
||||
#organization:
|
||||
# en:
|
||||
# name: 'Part-DB-name'
|
||||
# displayname: 'Displayname'
|
||||
# url: 'http://example.com'
|
|
@ -1,4 +1,4 @@
|
|||
# See the configuration reference at https://symfony.com/bundles/SchebTwoFactorBundle/5.x/configuration.html
|
||||
# See the configuration reference at https://symfony.com/bundles/SchebTwoFactorBundle/6.x/configuration.html
|
||||
scheb_two_factor:
|
||||
|
||||
google:
|
||||
|
@ -23,6 +23,6 @@ scheb_two_factor:
|
|||
security_tokens:
|
||||
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
|
||||
# If you're using guard-based authentication, you have to use this one:
|
||||
# - Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken
|
||||
# If you're using authenticator-based security (introduced in Symfony 5.1), you have to use this one:
|
||||
# - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
|
||||
# If you're using authenticator-based security (introduced in Symfony 5.1), you have to use this one:
|
||||
- Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
security:
|
||||
enable_authenticator_manager: true
|
||||
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
sensio_framework_extra:
|
||||
router:
|
||||
annotations: false
|
|
@ -1,4 +1,2 @@
|
|||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_id: session.storage.mock_file
|
||||
|
|
4
config/packages/uid.yaml
Normal file
4
config/packages/uid.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
framework:
|
||||
uid:
|
||||
default_uuid_version: 7
|
||||
time_based_uuid_version: 7
|
|
@ -4,7 +4,9 @@ when@dev:
|
|||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { only_exceptions: false }
|
||||
profiler:
|
||||
only_exceptions: false
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
web_profiler:
|
||||
|
|
|
@ -19,7 +19,7 @@ parameters:
|
|||
######################################################################################################################
|
||||
# Users and Privacy
|
||||
######################################################################################################################
|
||||
partdb.gpdr_compliance: true # If this option is activated, IP addresses are anonymized to be GPDR compliant
|
||||
partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant
|
||||
partdb.users.use_gravatar: '%env(bool:USE_GRAVATAR)%' # Set to false, if no Gravatar images should be used for user profiles.
|
||||
partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured.
|
||||
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
#index:
|
||||
# path: /
|
||||
# controller: App\Controller\DefaultController::index
|
||||
|
||||
# Redirect every url without an locale to the locale of the user/the global base locale
|
||||
|
||||
scan_qr:
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
controllers:
|
||||
resource: ../../src/Controller/
|
||||
type: annotation
|
||||
resource:
|
||||
path: ../../src/Controller/
|
||||
namespace: App\Controller
|
||||
type: attribute
|
||||
prefix: '{_locale}'
|
||||
|
||||
defaults:
|
||||
|
@ -11,4 +13,4 @@ controllers:
|
|||
|
||||
kernel:
|
||||
resource: ../../src/Kernel.php
|
||||
type: annotation
|
||||
type: attribute
|
|
@ -1,4 +1,4 @@
|
|||
hslavich_saml_sp:
|
||||
resource: "@HslavichOneloginSamlBundle/Resources/config/routing.yml"
|
||||
nbgrp_saml:
|
||||
resource: "@NbgrpOneloginSamlBundle/Resources/config/routes.php"
|
||||
# Only load the SAML routes if SAML is enabled
|
||||
condition: "env('SAML_ENABLED') == '1' or env('SAML_ENABLED') == 'true'"
|
|
@ -14,11 +14,11 @@ services:
|
|||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
bind:
|
||||
bool $demo_mode: '%partdb.demo_mode%'
|
||||
bool $gpdr_compliance : '%partdb.gpdr_compliance%'
|
||||
bool $kernel_debug: '%kernel.debug%'
|
||||
bool $gdpr_compliance: '%partdb.gdpr_compliance%'
|
||||
bool $kernel_debug_enabled: '%kernel.debug%'
|
||||
string $kernel_cache_dir: '%kernel.cache_dir%'
|
||||
string $partdb_title: '%partdb.title%'
|
||||
string $default_currency: '%partdb.default_currency%'
|
||||
string $base_currency: '%partdb.default_currency%'
|
||||
|
||||
_instanceof:
|
||||
App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface:
|
||||
|
@ -88,7 +88,7 @@ services:
|
|||
|
||||
App\Form\AttachmentFormType:
|
||||
arguments:
|
||||
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
|
||||
$allow_attachments_download: '%partdb.attachments.allow_downloads%'
|
||||
$max_file_size: '%partdb.attachments.max_file_size%'
|
||||
|
||||
App\Services\Attachments\AttachmentSubmitHandler:
|
||||
|
@ -97,12 +97,6 @@ services:
|
|||
$mimeTypes: '@mime_types'
|
||||
$max_upload_size: '%partdb.attachments.max_file_size%'
|
||||
|
||||
App\EventSubscriber\LogSystem\LogoutLoggerListener:
|
||||
tags:
|
||||
- name: 'kernel.event_listener'
|
||||
event: 'Symfony\Component\Security\Http\Event\LogoutEvent'
|
||||
dispatcher: security.event_dispatcher.main
|
||||
|
||||
App\Services\LogSystem\EventCommentNeededHelper:
|
||||
arguments:
|
||||
$enforce_change_comments_for: '%partdb.enforce_change_comments_for%'
|
||||
|
@ -183,7 +177,7 @@ services:
|
|||
|
||||
App\EventSubscriber\UserSystem\SetUserTimezoneSubscriber:
|
||||
arguments:
|
||||
$timezone: '%partdb.timezone%'
|
||||
$default_timezone: '%partdb.timezone%'
|
||||
|
||||
App\Controller\SecurityController:
|
||||
arguments:
|
||||
|
@ -265,7 +259,7 @@ services:
|
|||
tags:
|
||||
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
|
||||
|
||||
# We are needing this service inside of a migration, where only the container is injected. So we need to define it as public, to access it from the container.
|
||||
# We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container.
|
||||
App\Services\UserSystem\PermissionPresetsHelper:
|
||||
public: true
|
||||
|
||||
|
@ -289,3 +283,14 @@ services:
|
|||
autowire: true
|
||||
tags:
|
||||
- { name: monolog.processor }
|
||||
|
||||
when@test:
|
||||
services:
|
||||
# Decorate the doctrine fixtures load command to use our custom purger by default
|
||||
doctrine.fixtures_load_command.custom:
|
||||
decorates: doctrine.fixtures_load_command
|
||||
class: Doctrine\Bundle\FixturesBundle\Command\LoadDataFixturesDoctrineCommand
|
||||
arguments:
|
||||
- '@doctrine.fixtures.loader'
|
||||
- '@doctrine'
|
||||
- { default: '@App\Doctrine\Purger\ResetAutoIncrementPurgerFactory' }
|
|
@ -98,7 +98,7 @@ The following options are available:
|
|||
|
||||
* `partdb.global_theme`: The default theme to use, when no user specific theme is set. Should be one of the themes from the `partdb.available_themes` config option.
|
||||
* `partdb.locale_menu`: The codes of the languages, which should be shown in the language chooser menu (the one with the user icon in the navbar). The first language in the list will be the default language.
|
||||
* `partdb.gpdr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU.
|
||||
* `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU.
|
||||
* `partdb.sidebar.items`: The panel contents which should be shown in the sidebar by default. You can also change the number of sidebar panels by changing the number of items in this list.
|
||||
* `partdb.sidebar.root_node_enable`: Show a root node in the sidebar trees, of which all nodes are children of
|
||||
* `partdb.sidebar.root_expanded`: Expand the root node in the sidebar trees by default
|
||||
|
|
|
@ -8,9 +8,6 @@ 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
|
||||
|
|
65
migrations/Version20230528000149.php
Normal file
65
migrations/Version20230528000149.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20230528000149 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add other_ui column to webauthn_keys table, needed for future compatibility with more complex webauthn authenticators';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE webauthn_keys ADD other_ui LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\'');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE webauthn_keys DROP other_ui');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
|
||||
$this->addSql('DROP TABLE webauthn_keys');
|
||||
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
|
||||
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
|
||||
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
|
||||
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
|
||||
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
|
||||
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array)
|
||||
, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
|
||||
$this->addSql('DROP TABLE __temp__webauthn_keys');
|
||||
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
|
||||
$this->addSql('DROP TABLE webauthn_keys');
|
||||
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
|
||||
(DC2Type:base64)
|
||||
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
|
||||
(DC2Type:array)
|
||||
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
|
||||
(DC2Type:trust_path)
|
||||
, aaguid CLOB NOT NULL --
|
||||
(DC2Type:aaguid)
|
||||
, credential_public_key CLOB NOT NULL --
|
||||
(DC2Type:base64)
|
||||
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
|
||||
$this->addSql('DROP TABLE __temp__webauthn_keys');
|
||||
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
|
||||
}
|
||||
}
|
67
package.json
67
package.json
|
@ -28,45 +28,44 @@
|
|||
"build": "encore production --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ckeditor/ckeditor5-alignment": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-autoformat": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-basic-styles": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-block-quote": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-code-block": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^37.0.0",
|
||||
"@ckeditor/ckeditor5-alignment": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-autoformat": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-basic-styles": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-block-quote": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-code-block": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
|
||||
"@ckeditor/ckeditor5-editor-classic": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-essentials": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-find-and-replace": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-font": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-heading": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-highlight": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-horizontal-line": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-html-embed": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-html-support": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-image": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-indent": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-link": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-list": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-media-embed": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-paragraph": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-paste-from-office": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-remove-format": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-source-editing": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-special-characters": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-table": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-upload": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-watchdog": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-word-count": "^37.1.0",
|
||||
"@ckeditor/ckeditor5-editor-classic": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-essentials": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-find-and-replace": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-font": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-heading": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-highlight": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-horizontal-line": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-html-embed": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-html-support": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-image": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-indent": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-link": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-list": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-media-embed": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-paragraph": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-paste-from-office": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-remove-format": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-source-editing": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-special-characters": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-table": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-upload": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-watchdog": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-word-count": "^38.0.1",
|
||||
"@jbtronics/bs-treeview": "^1.0.1",
|
||||
"bootbox": "^6.0.0",
|
||||
"bootswatch": "^5.1.3",
|
||||
"bs-custom-file-input": "^1.3.4",
|
||||
"clipboard": "^2.0.4",
|
||||
"compression-webpack-plugin": "^10.0.0",
|
||||
"darkmode-js": "^1.5.0",
|
||||
"datatables.net-bs5": "^1.10.20",
|
||||
"datatables.net-buttons-bs5": "^2.2.2",
|
||||
"datatables.net-colreorder-bs5": "^1.5.1",
|
||||
|
@ -80,7 +79,9 @@
|
|||
"json-formatter-js": "^2.3.4",
|
||||
"jszip": "^3.2.0",
|
||||
"katex": "^0.16.0",
|
||||
"marked": "^4.3.0",
|
||||
"marked": "^5.1.0",
|
||||
"marked-gfm-heading-id": "^3.0.4",
|
||||
"marked-mangle": "^1.0.1",
|
||||
"pdfmake": "^0.2.2",
|
||||
"stimulus-use": "^0.52.0",
|
||||
"tom-select": "^2.1.0",
|
||||
|
|
52
phpstan.neon
52
phpstan.neon
|
@ -1,11 +1,55 @@
|
|||
parameters:
|
||||
|
||||
level: 5
|
||||
|
||||
paths:
|
||||
- src
|
||||
# - tests
|
||||
|
||||
excludePaths:
|
||||
- src/DataTables/Adapter/*
|
||||
- src/Configuration/*
|
||||
- src/Doctrine/Purger/*
|
||||
|
||||
|
||||
|
||||
inferPrivatePropertyTypeFromConstructor: true
|
||||
treatPhpDocTypesAsCertain: false
|
||||
|
||||
symfony:
|
||||
container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml'
|
||||
|
||||
excludes_analyse:
|
||||
- src/DataTables/Adapter/*
|
||||
- src/Configuration/*
|
||||
- src/Doctrine/Purger/*
|
||||
checkUninitializedProperties: true
|
||||
|
||||
checkFunctionNameCase: true
|
||||
|
||||
checkAlwaysTrueInstanceof: false
|
||||
checkAlwaysTrueCheckTypeFunctionCall: false
|
||||
checkAlwaysTrueStrictComparison: false
|
||||
reportAlwaysTrueInLastCondition: false
|
||||
|
||||
reportMaybesInPropertyPhpDocTypes: false
|
||||
reportMaybesInMethodSignatures: false
|
||||
|
||||
strictRules:
|
||||
disallowedLooseComparison: false
|
||||
booleansInConditions: false
|
||||
uselessCast: false
|
||||
requireParentConstructorCall: true
|
||||
disallowedConstructs: false
|
||||
overwriteVariablesWithLoop: false
|
||||
closureUsesThis: false
|
||||
matchingInheritedMethodNames: true
|
||||
numericOperandsInArithmeticOperators: true
|
||||
strictCalls: true
|
||||
switchConditionsMatchingType: false
|
||||
noVariableVariables: false
|
||||
|
||||
ignoreErrors:
|
||||
# Ignore errors caused by complex mapping with AbstractStructuralDBElement
|
||||
- '#AbstractStructuralDBElement does not have a field named \$parent#'
|
||||
- '#AbstractStructuralDBElement does not have a field named \$name#'
|
||||
|
||||
# Ignore errors related to the use of the ParametersTrait in Part entity
|
||||
- '#expects .*PartParameter, .*AbstractParameter given.#'
|
||||
- '#Part::getParameters\(\) should return .*AbstractParameter#'
|
|
@ -1,20 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="tests/bootstrap.php">
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
convertDeprecationsToExceptions="false"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1"/>
|
||||
<server name="APP_ENV" value="test" force="true"/>
|
||||
<server name="SHELL_VERBOSITY" value="-1"/>
|
||||
<server name="SYMFONY_PHPUNIT_REMOVE" value=""/>
|
||||
<server name="SYMFONY_PHPUNIT_VERSION" value="9"/>
|
||||
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5"/>
|
||||
<ini name="memory_limit" value="512M"/>
|
||||
<ini name="display_errors" value="1"/>
|
||||
</php>
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Project Test Suite">
|
||||
<directory>tests</directory>
|
||||
|
|
63
rector.php
Normal file
63
rector.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector;
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\Doctrine\Set\DoctrineSetList;
|
||||
use Rector\PHPUnit\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector;
|
||||
use Rector\PHPUnit\Set\PHPUnitLevelSetList;
|
||||
use Rector\PHPUnit\Set\PHPUnitSetList;
|
||||
use Rector\Set\ValueObject\LevelSetList;
|
||||
use Rector\Set\ValueObject\SetList;
|
||||
use Rector\Symfony\Set\SymfonyLevelSetList;
|
||||
use Rector\Symfony\Set\SymfonySetList;
|
||||
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
|
||||
|
||||
return static function (RectorConfig $rectorConfig): void {
|
||||
$rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml');
|
||||
$rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php');
|
||||
|
||||
//Import class names instead of using fully qualified class names
|
||||
$rectorConfig->importNames();
|
||||
//But keep the fully qualified class names for classes in the global namespace
|
||||
$rectorConfig->importShortClasses(false);
|
||||
|
||||
$rectorConfig->paths([
|
||||
__DIR__ . '/config',
|
||||
__DIR__ . '/public',
|
||||
__DIR__ . '/src',
|
||||
__DIR__ . '/tests',
|
||||
]);
|
||||
|
||||
// register a single rule
|
||||
//$rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
|
||||
|
||||
$rectorConfig->rules([
|
||||
DeclareStrictTypesRector::class,
|
||||
]);
|
||||
|
||||
// define sets of rules
|
||||
$rectorConfig->sets([
|
||||
//PHP rules
|
||||
SetList::CODE_QUALITY,
|
||||
LevelSetList::UP_TO_PHP_81,
|
||||
|
||||
//Symfony rules
|
||||
SymfonyLevelSetList::UP_TO_SYMFONY_62,
|
||||
SymfonySetList::SYMFONY_CODE_QUALITY,
|
||||
|
||||
//Doctrine rules
|
||||
DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
|
||||
DoctrineSetList::DOCTRINE_CODE_QUALITY,
|
||||
|
||||
//PHPUnit rules
|
||||
PHPUnitLevelSetList::UP_TO_PHPUNIT_90,
|
||||
PHPUnitSetList::PHPUNIT_CODE_QUALITY,
|
||||
]);
|
||||
|
||||
$rectorConfig->skip([
|
||||
AddDoesNotPerformAssertionToNonAssertingTestRector::class,
|
||||
CountArrayToEmptyArrayComparisonRector::class,
|
||||
]);
|
||||
};
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Command\Attachments;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Services\Attachments\AttachmentManager;
|
||||
use App\Services\Attachments\AttachmentPathResolver;
|
||||
use App\Services\Attachments\AttachmentReverseSearch;
|
||||
|
@ -40,29 +41,20 @@ use function count;
|
|||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
|
||||
#[AsCommand('partdb:attachments:clean-unused|app:clean-attachments', 'Lists (and deletes if wanted) attachments files that are not used anymore (abandoned files).')]
|
||||
class CleanAttachmentsCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:attachments:clean-unused|app:clean-attachments';
|
||||
|
||||
protected AttachmentManager $attachment_helper;
|
||||
protected AttachmentReverseSearch $reverseSearch;
|
||||
protected MimeTypes $mimeTypeGuesser;
|
||||
protected AttachmentPathResolver $pathResolver;
|
||||
|
||||
public function __construct(AttachmentManager $attachmentHelper, AttachmentReverseSearch $reverseSearch, AttachmentPathResolver $pathResolver)
|
||||
public function __construct(protected AttachmentManager $attachment_helper, protected AttachmentReverseSearch $reverseSearch, protected AttachmentPathResolver $pathResolver)
|
||||
{
|
||||
$this->attachment_helper = $attachmentHelper;
|
||||
$this->pathResolver = $pathResolver;
|
||||
$this->reverseSearch = $reverseSearch;
|
||||
$this->mimeTypeGuesser = new MimeTypes();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Lists (and deletes if wanted) attachments files that are not used anymore (abandoned files).')
|
||||
->setHelp('This command allows to find all files in the media folder which are not associated with an attachment anymore.'.
|
||||
$this->setHelp('This command allows to find all files in the media folder which are not associated with an attachment anymore.'.
|
||||
' These files are not needed and can eventually deleted.');
|
||||
}
|
||||
|
||||
|
@ -91,7 +83,7 @@ class CleanAttachmentsCommand extends Command
|
|||
|
||||
foreach ($finder as $file) {
|
||||
//If not attachment object uses this file, print it
|
||||
if (0 === count($this->reverseSearch->findAttachmentsByFile($file))) {
|
||||
if ([] === $this->reverseSearch->findAttachmentsByFile($file)) {
|
||||
$file_list[] = $file;
|
||||
$table->addRow([
|
||||
$fs->makePathRelative($file->getPathname(), $mediaPath),
|
||||
|
@ -101,14 +93,14 @@ class CleanAttachmentsCommand extends Command
|
|||
}
|
||||
}
|
||||
|
||||
if (count($file_list) > 0) {
|
||||
if ($file_list !== []) {
|
||||
$table->render();
|
||||
|
||||
$continue = $io->confirm(sprintf('Found %d abandoned files. Do you want to delete them? This can not be undone!', count($file_list)), false);
|
||||
|
||||
if (!$continue) {
|
||||
//We are finished here, when no files should be deleted
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
//Delete the files
|
||||
|
@ -121,7 +113,7 @@ class CleanAttachmentsCommand extends Command
|
|||
$io->success('No abandoned files found.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PhpZip\Constants\ZipCompressionMethod;
|
||||
|
@ -16,19 +20,11 @@ use Symfony\Component\Console\Input\InputOption;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('partdb:backup', 'Backup the files and the database of Part-DB')]
|
||||
class BackupCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:backup';
|
||||
protected static $defaultDescription = 'Backup the files and the database of Part-DB';
|
||||
|
||||
private string $project_dir;
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(string $project_dir, EntityManagerInterface $entityManager)
|
||||
public function __construct(private readonly string $project_dir, private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->project_dir = $project_dir;
|
||||
$this->entityManager = $entityManager;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
@ -71,14 +67,11 @@ class BackupCommand extends Command
|
|||
$io->info('Backup Part-DB to '.$output_filepath);
|
||||
|
||||
//Check if the file already exists
|
||||
if (file_exists($output_filepath)) {
|
||||
//Then ask the user, if he wants to overwrite the file
|
||||
if (!$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) {
|
||||
if (file_exists($output_filepath) && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) {
|
||||
$io->error('Backup aborted!');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$io->note('Starting backup...');
|
||||
|
||||
|
@ -115,8 +108,6 @@ class BackupCommand extends Command
|
|||
/**
|
||||
* Constructs the MySQL PDO DSN.
|
||||
* Taken from https://github.com/doctrine/dbal/blob/3.5.x/src/Driver/PDO/MySQL/Driver.php
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
private function configureDumper(array $params, DbDumper $dumper): void
|
||||
{
|
||||
|
@ -166,7 +157,7 @@ class BackupCommand extends Command
|
|||
$io->error('Could not dump database: '.$e->getMessage());
|
||||
$io->error('This can maybe be fixed by installing the mysqldump binary and adding it to the PATH variable!');
|
||||
}
|
||||
} elseif ($connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform) {
|
||||
} elseif ($connection->getDatabasePlatform() instanceof SqlitePlatform) {
|
||||
$io->note('SQLite database detected. Copy DB file to ZIP...');
|
||||
$params = $connection->getParams();
|
||||
$zip->addFile($params['path'], 'var/app.db');
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,9 +20,9 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -27,23 +30,17 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
|
||||
|
||||
#[AsCommand('partdb:check-requirements', 'Checks if the requirements Part-DB needs or recommends are fulfilled.')]
|
||||
class CheckRequirementsCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:check-requirements';
|
||||
|
||||
protected ContainerBagInterface $params;
|
||||
|
||||
public function __construct(ContainerBagInterface $params)
|
||||
public function __construct(protected ContainerBagInterface $params)
|
||||
{
|
||||
$this->params = $params;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Checks if the requirements Part-DB needs or recommends are fulfilled.')
|
||||
->addOption('only_issues', 'i', InputOption::VALUE_NONE, 'Only show issues, not success messages.')
|
||||
$this->addOption('only_issues', 'i', InputOption::VALUE_NONE, 'Only show issues, not success messages.')
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -66,105 +63,124 @@ class CheckRequirementsCommand extends Command
|
|||
|
||||
}
|
||||
|
||||
protected function checkPHP(SymfonyStyle $io, $only_issues = false): void
|
||||
protected function checkPHP(SymfonyStyle $io, bool $only_issues = false): void
|
||||
{
|
||||
//Check PHP versions
|
||||
$io->isVerbose() && $io->comment('Checking PHP version...');
|
||||
if (PHP_VERSION_ID < 80100) {
|
||||
if ($io->isVerbose()) {
|
||||
$io->comment('Checking PHP version...');
|
||||
}
|
||||
//We recommend PHP 8.2, but 8.1 is the minimum
|
||||
if (PHP_VERSION_ID < 80200) {
|
||||
$io->warning('You are using PHP '. PHP_VERSION .'. This will work, but a newer version is recommended.');
|
||||
} else {
|
||||
!$only_issues && $io->success('PHP version is sufficient.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('PHP version is sufficient.');
|
||||
}
|
||||
|
||||
//Check if opcache is enabled
|
||||
$io->isVerbose() && $io->comment('Checking Opcache...');
|
||||
if ($io->isVerbose()) {
|
||||
$io->comment('Checking Opcache...');
|
||||
}
|
||||
$opcache_enabled = ini_get('opcache.enable') === '1';
|
||||
if (!$opcache_enabled) {
|
||||
$io->warning('Opcache is not enabled. This will work, but performance will be better with opcache enabled. Set opcache.enable=1 in your php.ini to enable it');
|
||||
} else {
|
||||
!$only_issues && $io->success('Opcache is enabled.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('Opcache is enabled.');
|
||||
}
|
||||
|
||||
//Check if opcache is configured correctly
|
||||
$io->isVerbose() && $io->comment('Checking Opcache configuration...');
|
||||
if ($io->isVerbose()) {
|
||||
$io->comment('Checking Opcache configuration...');
|
||||
}
|
||||
if ($opcache_enabled && (ini_get('opcache.memory_consumption') < 256 || ini_get('opcache.max_accelerated_files') < 20000)) {
|
||||
$io->warning('Opcache configuration can be improved. See https://symfony.com/doc/current/performance.html for more info.');
|
||||
} else {
|
||||
!$only_issues && $io->success('Opcache configuration is already performance optimized.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('Opcache configuration is already performance optimized.');
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkPartDBConfig(SymfonyStyle $io, $only_issues = false): void
|
||||
protected function checkPartDBConfig(SymfonyStyle $io, bool $only_issues = false): void
|
||||
{
|
||||
//Check if APP_ENV is set to prod
|
||||
$io->isVerbose() && $io->comment('Checking debug mode...');
|
||||
if ($io->isVerbose()) {
|
||||
$io->comment('Checking debug mode...');
|
||||
}
|
||||
if ($this->params->get('kernel.debug')) {
|
||||
$io->warning('You have activated debug mode, this is will leak informations in a production environment.');
|
||||
} else {
|
||||
!$only_issues && $io->success('Debug mode disabled.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('Debug mode disabled.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function checkPHPExtensions(SymfonyStyle $io, $only_issues = false): void
|
||||
protected function checkPHPExtensions(SymfonyStyle $io, bool $only_issues = false): void
|
||||
{
|
||||
//Get all installed PHP extensions
|
||||
$extensions = get_loaded_extensions();
|
||||
$io->isVerbose() && $io->comment('Your PHP installation has '. count($extensions) .' extensions installed: '. implode(', ', $extensions));
|
||||
if ($io->isVerbose()) {
|
||||
$io->comment('Your PHP installation has '. count($extensions) .' extensions installed: '. implode(', ', $extensions));
|
||||
}
|
||||
|
||||
$db_drivers_count = 0;
|
||||
if(!in_array('pdo_mysql', $extensions)) {
|
||||
if(!in_array('pdo_mysql', $extensions, true)) {
|
||||
$io->error('pdo_mysql is not installed. You will not be able to use MySQL databases.');
|
||||
} else {
|
||||
!$only_issues && $io->success('PHP extension pdo_mysql is installed.');
|
||||
if (!$only_issues) {
|
||||
$io->success('PHP extension pdo_mysql is installed.');
|
||||
}
|
||||
$db_drivers_count++;
|
||||
}
|
||||
|
||||
if(!in_array('pdo_sqlite', $extensions)) {
|
||||
if(!in_array('pdo_sqlite', $extensions, true)) {
|
||||
$io->error('pdo_sqlite is not installed. You will not be able to use SQLite. databases');
|
||||
} else {
|
||||
!$only_issues && $io->success('PHP extension pdo_sqlite is installed.');
|
||||
if (!$only_issues) {
|
||||
$io->success('PHP extension pdo_sqlite is installed.');
|
||||
}
|
||||
$db_drivers_count++;
|
||||
}
|
||||
|
||||
$io->isVerbose() && $io->comment('You have '. $db_drivers_count .' database drivers installed.');
|
||||
if ($io->isVerbose()) {
|
||||
$io->comment('You have '. $db_drivers_count .' database drivers installed.');
|
||||
}
|
||||
if ($db_drivers_count === 0) {
|
||||
$io->error('You have no database drivers installed. You have to install at least one database driver!');
|
||||
}
|
||||
|
||||
if(!in_array('curl', $extensions)) {
|
||||
if (!in_array('curl', $extensions, true)) {
|
||||
$io->warning('curl extension is not installed. Install curl extension for better performance');
|
||||
} else {
|
||||
!$only_issues && $io->success('PHP extension curl is installed.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('PHP extension curl is installed.');
|
||||
}
|
||||
|
||||
$gd_installed = in_array('gd', $extensions);
|
||||
$gd_installed = in_array('gd', $extensions, true);
|
||||
if (!$gd_installed) {
|
||||
$io->error('GD is not installed. GD is required for image processing.');
|
||||
} else {
|
||||
!$only_issues && $io->success('PHP extension GD is installed.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('PHP extension GD is installed.');
|
||||
}
|
||||
|
||||
//Check if GD has jpeg support
|
||||
$io->isVerbose() && $io->comment('Checking if GD has jpeg support...');
|
||||
if ($io->isVerbose()) {
|
||||
$io->comment('Checking if GD has jpeg support...');
|
||||
}
|
||||
if ($gd_installed) {
|
||||
$gd_info = gd_info();
|
||||
if ($gd_info['JPEG Support'] === false) {
|
||||
$io->warning('Your GD does not have jpeg support. You will not be able to generate thumbnails of jpeg images.');
|
||||
} else {
|
||||
!$only_issues && $io->success('GD has jpeg support.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('GD has jpeg support.');
|
||||
}
|
||||
|
||||
if ($gd_info['PNG Support'] === false) {
|
||||
$io->warning('Your GD does not have png support. You will not be able to generate thumbnails of png images.');
|
||||
} else {
|
||||
!$only_issues && $io->success('GD has png support.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('GD has png support.');
|
||||
}
|
||||
|
||||
if ($gd_info['WebP Support'] === false) {
|
||||
$io->warning('Your GD does not have WebP support. You will not be able to generate thumbnails of WebP images.');
|
||||
} else {
|
||||
!$only_issues && $io->success('GD has WebP support.');
|
||||
} elseif (!$only_issues) {
|
||||
$io->success('GD has WebP support.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Command\Currencies;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Services\Tools\ExchangeRateUpdater;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
@ -35,30 +36,17 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
|||
use function count;
|
||||
use function strlen;
|
||||
|
||||
#[AsCommand('partdb:currencies:update-exchange-rates|partdb:update-exchange-rates|app:update-exchange-rates', 'Updates the currency exchange rates.')]
|
||||
class UpdateExchangeRatesCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:currencies:update-exchange-rates|partdb:update-exchange-rates|app:update-exchange-rates';
|
||||
|
||||
protected string $base_current;
|
||||
protected EntityManagerInterface $em;
|
||||
protected ExchangeRateUpdater $exchangeRateUpdater;
|
||||
|
||||
public function __construct(string $base_current, EntityManagerInterface $entityManager, ExchangeRateUpdater $exchangeRateUpdater)
|
||||
public function __construct(protected string $base_current, protected EntityManagerInterface $em, protected ExchangeRateUpdater $exchangeRateUpdater)
|
||||
{
|
||||
//$this->swap = $swap;
|
||||
$this->base_current = $base_current;
|
||||
|
||||
$this->em = $entityManager;
|
||||
$this->exchangeRateUpdater = $exchangeRateUpdater;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Updates the currency exchange rates.')
|
||||
->addArgument('iso_code', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The ISO Codes of the currencies that should be updated.');
|
||||
$this->addArgument('iso_code', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The ISO Codes of the currencies that should be updated.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
|
@ -69,7 +57,7 @@ class UpdateExchangeRatesCommand extends Command
|
|||
if (3 !== strlen($this->base_current)) {
|
||||
$io->error('Chosen Base current is not valid. Check your settings!');
|
||||
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->note('Update currency exchange rates with base currency: '.$this->base_current);
|
||||
|
@ -78,11 +66,7 @@ class UpdateExchangeRatesCommand extends Command
|
|||
$iso_code = $input->getArgument('iso_code');
|
||||
$repo = $this->em->getRepository(Currency::class);
|
||||
|
||||
if (!empty($iso_code)) {
|
||||
$candidates = $repo->findBy(['iso_code' => $iso_code]);
|
||||
} else {
|
||||
$candidates = $repo->findAll();
|
||||
}
|
||||
$candidates = empty($iso_code) ? $repo->findAll() : $repo->findBy(['iso_code' => $iso_code]);
|
||||
|
||||
$success_counter = 0;
|
||||
|
||||
|
@ -106,6 +90,6 @@ class UpdateExchangeRatesCommand extends Command
|
|||
|
||||
$io->success(sprintf('%d (of %d) currency exchange rates were updated.', $success_counter, count($candidates)));
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Command\Logs;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\LogSystem\AbstractLogEntry;
|
||||
use App\Repository\LogEntryRepository;
|
||||
|
@ -36,23 +38,14 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
#[AsCommand('partdb:logs:show|app:show-logs', 'List the last event log entries.')]
|
||||
class ShowEventLogCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:logs:show|app:show-logs';
|
||||
protected EntityManagerInterface $entityManager;
|
||||
protected TranslatorInterface $translator;
|
||||
protected ElementTypeNameGenerator $elementTypeNameGenerator;
|
||||
protected LogEntryRepository $repo;
|
||||
protected LogEntryExtraFormatter $formatter;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager,
|
||||
TranslatorInterface $translator, ElementTypeNameGenerator $elementTypeNameGenerator, LogEntryExtraFormatter $formatter)
|
||||
public function __construct(protected EntityManagerInterface $entityManager,
|
||||
protected TranslatorInterface $translator, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected LogEntryExtraFormatter $formatter)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->translator = $translator;
|
||||
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
|
||||
$this->formatter = $formatter;
|
||||
|
||||
$this->repo = $this->entityManager->getRepository(AbstractLogEntry::class);
|
||||
parent::__construct();
|
||||
}
|
||||
|
@ -74,7 +67,7 @@ class ShowEventLogCommand extends Command
|
|||
if ($page > $max_page && $max_page > 0) {
|
||||
$io->error("There is no page ${page}! The maximum page is ${max_page}.");
|
||||
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->note("There are a total of ${total_count} log entries in the DB.");
|
||||
|
@ -84,21 +77,19 @@ class ShowEventLogCommand extends Command
|
|||
$this->showPage($output, $desc, $limit, $page, $max_page, $showExtra);
|
||||
|
||||
if ($onePage) {
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$continue = $io->confirm('Do you want to show the next page?');
|
||||
++$page;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('List the last event log entries.')
|
||||
->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many log entries should be shown per page.', 50)
|
||||
$this->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many log entries should be shown per page.', 50)
|
||||
->addOption('oldest_first', null, InputOption::VALUE_NONE, 'Show older entries first.')
|
||||
->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'Which page should be shown?', 1)
|
||||
->addOption('onePage', null, InputOption::VALUE_NONE, 'Show only one page (dont ask to go to next).')
|
||||
|
@ -147,15 +138,13 @@ class ShowEventLogCommand extends Command
|
|||
$target_class = $this->elementTypeNameGenerator->getLocalizedTypeLabel($entry->getTargetClass());
|
||||
}
|
||||
|
||||
if ($entry->getUser()) {
|
||||
if ($entry->getUser() instanceof User) {
|
||||
$user = $entry->getUser()->getFullName(true);
|
||||
} else {
|
||||
if ($entry->isCLIEntry()) {
|
||||
} elseif ($entry->isCLIEntry()) {
|
||||
$user = $entry->getCLIUsername() . ' [CLI]';
|
||||
} else {
|
||||
$user = $entry->getUsername() . ' [deleted]';
|
||||
}
|
||||
}
|
||||
|
||||
$row = [
|
||||
$entry->getID(),
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Command\Migrations;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
|
@ -47,6 +48,7 @@ use function count;
|
|||
/**
|
||||
* This command converts the BBCode used by old Part-DB versions (<1.0), to the current used Markdown format.
|
||||
*/
|
||||
#[AsCommand('partdb:migrations:convert-bbcode|app:convert-bbcode', 'Converts BBCode used in old Part-DB versions to newly used Markdown')]
|
||||
class ConvertBBCodeCommand extends Command
|
||||
{
|
||||
/**
|
||||
|
@ -57,18 +59,10 @@ class ConvertBBCodeCommand extends Command
|
|||
* @var string The regex (performed in PHP) used to check if a property really contains BBCODE
|
||||
*/
|
||||
protected const BBCODE_REGEX = '/\\[.+\\].*\\[\\/.+\\]/';
|
||||
|
||||
protected static $defaultName = 'partdb:migrations:convert-bbcode|app:convert-bbcode';
|
||||
|
||||
protected EntityManagerInterface $em;
|
||||
protected PropertyAccessorInterface $propertyAccessor;
|
||||
protected BBCodeToMarkdownConverter $converter;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, PropertyAccessorInterface $propertyAccessor)
|
||||
public function __construct(protected EntityManagerInterface $em, protected PropertyAccessorInterface $propertyAccessor)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
$this->propertyAccessor = $propertyAccessor;
|
||||
|
||||
$this->converter = new BBCodeToMarkdownConverter();
|
||||
|
||||
parent::__construct();
|
||||
|
@ -76,9 +70,7 @@ class ConvertBBCodeCommand extends Command
|
|||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Converts BBCode used in old Part-DB versions to newly used Markdown')
|
||||
->setHelp('Older versions of Part-DB (<1.0) used BBCode for rich text formatting.
|
||||
$this->setHelp('Older versions of Part-DB (<1.0) used BBCode for rich text formatting.
|
||||
Part-DB now uses Markdown which offers more features but is incompatible with BBCode.
|
||||
When you upgrade from an pre 1.0 version you have to run this command to convert your comment fields');
|
||||
|
||||
|
@ -129,25 +121,25 @@ class ConvertBBCodeCommand extends Command
|
|||
|
||||
//Fetch resulting classes
|
||||
$results = $qb->getQuery()->getResult();
|
||||
$io->note(sprintf('Found %d entities, that need to be converted!', count($results)));
|
||||
$io->note(sprintf('Found %d entities, that need to be converted!', is_countable($results) ? count($results) : 0));
|
||||
|
||||
//In verbose mode print the names of the entities
|
||||
foreach ($results as $result) {
|
||||
/** @var AbstractNamedDBElement $result */
|
||||
$io->writeln(
|
||||
'Convert entity: '.$result->getName().' ('.get_class($result).': '.$result->getID().')',
|
||||
'Convert entity: '.$result->getName().' ('.$result::class.': '.$result->getID().')',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
foreach ($properties as $property) {
|
||||
//Retrieve bbcode from entity
|
||||
$bbcode = $this->propertyAccessor->getValue($result, $property);
|
||||
//Check if the current property really contains BBCode
|
||||
if (!preg_match(static::BBCODE_REGEX, $bbcode)) {
|
||||
if (!preg_match(static::BBCODE_REGEX, (string) $bbcode)) {
|
||||
continue;
|
||||
}
|
||||
$io->writeln(
|
||||
'BBCode (old): '
|
||||
.str_replace('\n', ' ', substr($bbcode, 0, 255)),
|
||||
.str_replace('\n', ' ', substr((string) $bbcode, 0, 255)),
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
$markdown = $this->converter->convert($bbcode);
|
||||
|
@ -168,6 +160,6 @@ class ConvertBBCodeCommand extends Command
|
|||
$io->success('Changes saved to DB successfully!');
|
||||
}
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,9 +20,9 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command\Migrations;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Services\ImportExportSystem\PartKeeprImporter\PKDatastructureImporter;
|
||||
use App\Services\ImportExportSystem\PartKeeprImporter\MySQLDumpXMLConverter;
|
||||
use App\Services\ImportExportSystem\PartKeeprImporter\PKImportHelper;
|
||||
|
@ -33,34 +36,19 @@ use Symfony\Component\Console\Input\InputOption;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('partdb:migrations:import-partkeepr', 'Import a PartKeepr database XML dump into Part-DB')]
|
||||
class ImportPartKeeprCommand extends Command
|
||||
{
|
||||
|
||||
protected static $defaultName = 'partdb:migrations:import-partkeepr';
|
||||
|
||||
protected EntityManagerInterface $em;
|
||||
protected MySQLDumpXMLConverter $xml_converter;
|
||||
protected PKDatastructureImporter $datastructureImporter;
|
||||
protected PKImportHelper $importHelper;
|
||||
protected PKPartImporter $partImporter;
|
||||
protected PKOptionalImporter $optionalImporter;
|
||||
|
||||
public function __construct(EntityManagerInterface $em, MySQLDumpXMLConverter $xml_converter,
|
||||
PKDatastructureImporter $datastructureImporter, PKPartImporter $partImporter, PKImportHelper $importHelper,
|
||||
PKOptionalImporter $optionalImporter)
|
||||
public function __construct(protected EntityManagerInterface $em, protected MySQLDumpXMLConverter $xml_converter,
|
||||
protected PKDatastructureImporter $datastructureImporter, protected PKPartImporter $partImporter, protected PKImportHelper $importHelper,
|
||||
protected PKOptionalImporter $optionalImporter)
|
||||
{
|
||||
parent::__construct(self::$defaultName);
|
||||
$this->em = $em;
|
||||
$this->datastructureImporter = $datastructureImporter;
|
||||
$this->importHelper = $importHelper;
|
||||
$this->partImporter = $partImporter;
|
||||
$this->xml_converter = $xml_converter;
|
||||
$this->optionalImporter = $optionalImporter;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Import a PartKeepr database XML dump into Part-DB');
|
||||
$this->setHelp('This command allows you to import a PartKeepr database exported by mysqldump as XML file into Part-DB');
|
||||
|
||||
$this->addArgument('file', InputArgument::REQUIRED, 'The file to which should be imported.');
|
||||
|
@ -100,7 +88,7 @@ class ImportPartKeeprCommand extends Command
|
|||
if (!$this->importHelper->checkVersion($data)) {
|
||||
$db_version = $this->importHelper->getDatabaseSchemaVersion($data);
|
||||
$io->error('The version of the imported database is not supported! (Version: '.$db_version.')');
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
//Import the mandatory data
|
||||
|
@ -118,7 +106,7 @@ class ImportPartKeeprCommand extends Command
|
|||
$io->success('Imported '.$count.' users.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function doImport(SymfonyStyle $io, array $data): void
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,9 +20,9 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command\User;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Security\SamlUserFactory;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
@ -30,25 +33,17 @@ use Symfony\Component\Console\Input\InputOption;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user', 'Converts a local user to a SAML user (and vice versa)')]
|
||||
class ConvertToSAMLUserCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user';
|
||||
|
||||
protected EntityManagerInterface $entityManager;
|
||||
protected bool $saml_enabled;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, bool $saml_enabled)
|
||||
public function __construct(protected EntityManagerInterface $entityManager, protected bool $saml_enabled)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->entityManager = $entityManager;
|
||||
$this->saml_enabled = $saml_enabled;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Converts a local user to a SAML user (and vice versa)')
|
||||
->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.')
|
||||
$this->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.')
|
||||
->addArgument('user', InputArgument::REQUIRED, 'The username (or email) of the user')
|
||||
->addOption('to-local', null, InputOption::VALUE_NONE, 'Converts a SAML user to a local user')
|
||||
;
|
||||
|
@ -70,7 +65,7 @@ class ConvertToSAMLUserCommand extends Command
|
|||
if (!$user) {
|
||||
$io->error('User not found!');
|
||||
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->info('User found: '.$user->getFullName(true) . ': '.$user->getEmail().' [ID: ' . $user->getID() . ']');
|
||||
|
@ -87,7 +82,7 @@ class ConvertToSAMLUserCommand extends Command
|
|||
$io->confirm('You are going to convert a SAML user to a local user. This means, that the user can only login via the login form. '
|
||||
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
|
||||
|
||||
$user->setSAMLUser(false);
|
||||
$user->setSamlUser(false);
|
||||
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
@ -102,7 +97,7 @@ class ConvertToSAMLUserCommand extends Command
|
|||
$io->confirm('You are going to convert a local user to a SAML user. This means, that the user can only login via SAML afterwards. The password in the DB will be removed. '
|
||||
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
|
||||
|
||||
$user->setSAMLUser(true);
|
||||
$user->setSamlUser(true);
|
||||
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Command\User;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Events\SecurityEvent;
|
||||
use App\Events\SecurityEvents;
|
||||
|
@ -34,28 +35,17 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
|||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
#[AsCommand('partdb:users:set-password|app:set-password|users:set-password|partdb:user:set-password', 'Sets the password of a user')]
|
||||
class SetPasswordCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:users:set-password|app:set-password|users:set-password|partdb:user:set-password';
|
||||
|
||||
protected EntityManagerInterface $entityManager;
|
||||
protected UserPasswordHasherInterface $encoder;
|
||||
protected EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordEncoder, EventDispatcherInterface $eventDispatcher)
|
||||
public function __construct(protected EntityManagerInterface $entityManager, protected UserPasswordHasherInterface $encoder, protected EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->encoder = $passwordEncoder;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Sets the password of a user')
|
||||
->setHelp('This password allows you to set the password of a user, without knowing the old password.')
|
||||
$this->setHelp('This password allows you to set the password of a user, without knowing the old password.')
|
||||
->addArgument('user', InputArgument::REQUIRED, 'The username or email of the user')
|
||||
;
|
||||
}
|
||||
|
@ -67,17 +57,17 @@ class SetPasswordCommand extends Command
|
|||
|
||||
$user = $this->entityManager->getRepository(User::class)->findByEmailOrName($user_name);
|
||||
|
||||
if (!$user) {
|
||||
if (!$user instanceof User) {
|
||||
$io->error(sprintf('No user with the given username %s found in the database!', $user_name));
|
||||
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->note('User found!');
|
||||
|
||||
if ($user->isSamlUser()) {
|
||||
$io->error('This user is a SAML user, so you can not change the password!');
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$proceed = $io->confirm(
|
||||
|
@ -85,7 +75,7 @@ class SetPasswordCommand extends Command
|
|||
$user->getFullName(true), $user->getID()));
|
||||
|
||||
if (!$proceed) {
|
||||
return 1;
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$success = false;
|
||||
|
@ -116,6 +106,6 @@ class SetPasswordCommand extends Command
|
|||
$security_event = new SecurityEvent($user);
|
||||
$this->eventDispatcher->dispatch($security_event, SecurityEvents::PASSWORD_CHANGED);
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,9 +20,9 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command\User;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\UserSystem\Group;
|
||||
use App\Entity\UserSystem\PermissionData;
|
||||
use App\Entity\UserSystem\User;
|
||||
|
@ -31,22 +34,12 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('partdb:users:upgrade-permissions-schema', '(Manually) upgrades the permissions schema of all users to the latest version.')]
|
||||
final class UpgradePermissionsSchemaCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:users:upgrade-permissions-schema';
|
||||
protected static $defaultDescription = '(Manually) upgrades the permissions schema of all users to the latest version.';
|
||||
|
||||
private PermissionSchemaUpdater $permissionSchemaUpdater;
|
||||
private EntityManagerInterface $em;
|
||||
private EventCommentHelper $eventCommentHelper;
|
||||
|
||||
public function __construct(PermissionSchemaUpdater $permissionSchemaUpdater, EntityManagerInterface $entityManager, EventCommentHelper $eventCommentHelper)
|
||||
public function __construct(private readonly PermissionSchemaUpdater $permissionSchemaUpdater, private readonly EntityManagerInterface $em, private readonly EventCommentHelper $eventCommentHelper)
|
||||
{
|
||||
parent::__construct(self::$defaultName);
|
||||
|
||||
$this->permissionSchemaUpdater = $permissionSchemaUpdater;
|
||||
$this->eventCommentHelper = $eventCommentHelper;
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
|
@ -81,26 +74,22 @@ final class UpgradePermissionsSchemaCommand extends Command
|
|||
}
|
||||
|
||||
$io->info('Found '. count($groups_to_upgrade) .' groups and '. count($users_to_upgrade) .' users that need an update.');
|
||||
if (empty($groups_to_upgrade) && empty($users_to_upgrade)) {
|
||||
if ($groups_to_upgrade === [] && $users_to_upgrade === []) {
|
||||
$io->success('All users and group permissions schemas are up-to-date. No update needed.');
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
//List all users and groups that need an update
|
||||
$io->section('Groups that need an update:');
|
||||
$io->listing(array_map(static function (Group $group) {
|
||||
return $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')';
|
||||
}, $groups_to_upgrade));
|
||||
$io->listing(array_map(static fn(Group $group): string => $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')', $groups_to_upgrade));
|
||||
|
||||
$io->section('Users that need an update:');
|
||||
$io->listing(array_map(static function (User $user) {
|
||||
return $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')';
|
||||
}, $users_to_upgrade));
|
||||
$io->listing(array_map(static fn(User $user): string => $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')', $users_to_upgrade));
|
||||
|
||||
if(!$io->confirm('Continue with the update?', false)) {
|
||||
$io->warning('Update aborted.');
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
//Update all users and groups
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,9 +20,9 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command\User;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\UserSystem\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
@ -29,24 +32,17 @@ use Symfony\Component\Console\Input\InputOption;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('partdb:users:enable|partdb:user:enable', 'Enables/Disable the login of one or more users')]
|
||||
class UserEnableCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:users:enable|partdb:user:enable';
|
||||
|
||||
protected EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, string $name = null)
|
||||
public function __construct(protected EntityManagerInterface $entityManager, string $name = null)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Enables/Disable the login of one or more users')
|
||||
->setHelp('This allows you to allow or prevent the login of certain user. Use the --disable option to disable the login for the given users')
|
||||
$this->setHelp('This allows you to allow or prevent the login of certain user. Use the --disable option to disable the login for the given users')
|
||||
->addArgument('users', InputArgument::IS_ARRAY, 'The usernames of the users to use')
|
||||
->addOption('all', 'a', InputOption::VALUE_NONE, 'Enable/Disable all users')
|
||||
->addOption('disable', 'd', InputOption::VALUE_NONE, 'Disable the login of the given users')
|
||||
|
@ -73,7 +69,7 @@ class UserEnableCommand extends Command
|
|||
} else { //Otherwise, fetch the users from DB
|
||||
foreach ($usernames as $username) {
|
||||
$user = $repo->findByEmailOrName($username);
|
||||
if ($user === null) {
|
||||
if (!$user instanceof User) {
|
||||
$io->error('No user found with username: '.$username);
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
@ -87,9 +83,7 @@ class UserEnableCommand extends Command
|
|||
$io->note('The following users will be enabled:');
|
||||
}
|
||||
$io->table(['Username', 'Enabled/Disabled'],
|
||||
array_map(static function(User $user) {
|
||||
return [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled'];
|
||||
}, $users));
|
||||
array_map(static fn(User $user) => [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled'], $users));
|
||||
|
||||
if(!$io->confirm('Do you want to continue?')) {
|
||||
$io->warning('Aborting!');
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,9 +20,10 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command\User;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\UserSystem\Group;
|
||||
use App\Entity\UserSystem\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
@ -28,24 +32,17 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('partdb:users:list|users:list', 'Lists all users')]
|
||||
class UserListCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:users:list|users:list';
|
||||
|
||||
protected EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
public function __construct(protected EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Lists all users')
|
||||
->setHelp('This command lists all users in the database.')
|
||||
$this->setHelp('This command lists all users in the database.')
|
||||
->addOption('local', 'l', null, 'Only list local users')
|
||||
->addOption('saml', 's', null, 'Only list SAML users')
|
||||
;
|
||||
|
@ -82,13 +79,13 @@ class UserListCommand extends Command
|
|||
|
||||
foreach ($users as $user) {
|
||||
$table->addRow([
|
||||
$user->getId(),
|
||||
$user->getID(),
|
||||
$user->getUsername(),
|
||||
$user->getFullName(),
|
||||
$user->getEmail(),
|
||||
$user->getGroup() !== null ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group',
|
||||
$user->getGroup() instanceof Group ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group',
|
||||
$user->isDisabled() ? 'Yes' : 'No',
|
||||
$user->isSAMLUser() ? 'SAML' : 'Local',
|
||||
$user->isSamlUser() ? 'SAML' : 'Local',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,9 +20,9 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command\User;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Services\UserSystem\PermissionManager;
|
||||
|
@ -34,22 +37,14 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
#[AsCommand('partdb:users:permissions|partdb:user:permissions', 'View and edit the permissions of a given user')]
|
||||
class UsersPermissionsCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:users:permissions|partdb:user:permissions';
|
||||
protected static $defaultDescription = 'View and edit the permissions of a given user';
|
||||
|
||||
protected EntityManagerInterface $entityManager;
|
||||
protected UserRepository $userRepository;
|
||||
protected PermissionManager $permissionResolver;
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, PermissionManager $permissionResolver, TranslatorInterface $translator)
|
||||
public function __construct(protected EntityManagerInterface $entityManager, protected PermissionManager $permissionResolver, protected TranslatorInterface $translator)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->userRepository = $entityManager->getRepository(User::class);
|
||||
$this->permissionResolver = $permissionResolver;
|
||||
$this->translator = $translator;
|
||||
|
||||
parent::__construct(self::$defaultName);
|
||||
}
|
||||
|
@ -73,12 +68,12 @@ class UsersPermissionsCommand extends Command
|
|||
//Find user
|
||||
$io->note('Finding user with username: ' . $username);
|
||||
$user = $this->userRepository->findByEmailOrName($username);
|
||||
if ($user === null) {
|
||||
if (!$user instanceof User) {
|
||||
$io->error('No user found with username: ' . $username);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->note(sprintf('Found user %s with ID %d', $user->getFullName(true), $user->getId()));
|
||||
$io->note(sprintf('Found user %s with ID %d', $user->getFullName(true), $user->getID()));
|
||||
|
||||
$edit_mapping = $this->renderPermissionTable($output, $user, $inherit);
|
||||
|
||||
|
@ -102,7 +97,7 @@ class UsersPermissionsCommand extends Command
|
|||
|
||||
|
||||
$new_value_str = $io->ask('Enter the new value for the permission (A = allow, D = disallow, I = inherit)');
|
||||
switch (strtolower($new_value_str)) {
|
||||
switch (strtolower((string) $new_value_str)) {
|
||||
case 'a':
|
||||
case 'allow':
|
||||
$new_value = true;
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,9 +20,9 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Services\Misc\GitVersionInfo;
|
||||
use Shivas\VersioningBundle\Service\VersionManagerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
@ -27,25 +30,16 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('partdb:version|app:version', 'Shows the currently installed version of Part-DB.')]
|
||||
class VersionCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:version|app:version';
|
||||
|
||||
protected VersionManagerInterface $versionManager;
|
||||
protected GitVersionInfo $gitVersionInfo;
|
||||
|
||||
public function __construct(VersionManagerInterface $versionManager, GitVersionInfo $gitVersionInfo)
|
||||
public function __construct(protected VersionManagerInterface $versionManager, protected GitVersionInfo $gitVersionInfo)
|
||||
{
|
||||
$this->versionManager = $versionManager;
|
||||
$this->gitVersionInfo = $gitVersionInfo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Shows the currently installed version of Part-DB.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
|
@ -66,6 +60,6 @@ class VersionCommand extends Command
|
|||
$io->info('OS: '. php_uname());
|
||||
$io->info('PHP extension: '. implode(', ', get_loaded_extensions()));
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
|
@ -37,8 +37,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/attachment_type")
|
||||
* @see \App\Tests\Controller\AdminPages\AttachmentTypeControllerTest
|
||||
*/
|
||||
#[Route(path: '/attachment_type')]
|
||||
class AttachmentTypeController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = AttachmentType::class;
|
||||
|
@ -48,44 +49,34 @@ class AttachmentTypeController extends BaseAdminController
|
|||
protected string $attachment_class = AttachmentTypeAttachment::class;
|
||||
protected ?string $parameter_class = AttachmentTypeParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="attachment_type_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'attachment_type_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, AttachmentType $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="attachment_type_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'attachment_type_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(AttachmentType $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="attachment_type_new")
|
||||
* @Route("/{id}/clone", name="attachment_type_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'attachment_type_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'attachment_type_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AttachmentType $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="attachment_type_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'attachment_type_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="attachment_type_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'attachment_type_export')]
|
||||
public function exportEntity(AttachmentType $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -29,6 +29,7 @@ use App\Entity\Base\AbstractNamedDBElement;
|
|||
use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Base\PartsContainingRepositoryInterface;
|
||||
use App\Entity\LabelSystem\LabelProcessMode;
|
||||
use App\Entity\LabelSystem\LabelProfile;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Entity\UserSystem\User;
|
||||
|
@ -72,29 +73,16 @@ abstract class BaseAdminController extends AbstractController
|
|||
protected string $route_base = '';
|
||||
protected string $attachment_class = '';
|
||||
protected ?string $parameter_class = '';
|
||||
|
||||
protected UserPasswordHasherInterface $passwordEncoder;
|
||||
protected TranslatorInterface $translator;
|
||||
protected AttachmentSubmitHandler $attachmentSubmitHandler;
|
||||
protected EventCommentHelper $commentHelper;
|
||||
|
||||
protected HistoryHelper $historyHelper;
|
||||
protected TimeTravel $timeTravel;
|
||||
protected DataTableFactory $dataTableFactory;
|
||||
/**
|
||||
* @var EventDispatcher|EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
protected LabelGenerator $labelGenerator;
|
||||
protected LabelExampleElementsGenerator $barcodeExampleGenerator;
|
||||
|
||||
protected EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(TranslatorInterface $translator, UserPasswordHasherInterface $passwordEncoder,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler,
|
||||
EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
|
||||
DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, LabelExampleElementsGenerator $barcodeExampleGenerator,
|
||||
LabelGenerator $labelGenerator, EntityManagerInterface $entityManager)
|
||||
public function __construct(protected TranslatorInterface $translator, protected UserPasswordHasherInterface $passwordEncoder,
|
||||
protected AttachmentSubmitHandler $attachmentSubmitHandler,
|
||||
protected EventCommentHelper $commentHelper, protected HistoryHelper $historyHelper, protected TimeTravel $timeTravel,
|
||||
protected DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator,
|
||||
protected LabelGenerator $labelGenerator, protected EntityManagerInterface $entityManager)
|
||||
{
|
||||
if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) {
|
||||
throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!');
|
||||
|
@ -107,18 +95,7 @@ abstract class BaseAdminController extends AbstractController
|
|||
if ('' === $this->parameter_class || ($this->parameter_class && !is_a($this->parameter_class, AbstractParameter::class, true))) {
|
||||
throw new InvalidArgumentException('You have to override the $parameter_class value with a valid Parameter class in your subclass!');
|
||||
}
|
||||
|
||||
$this->translator = $translator;
|
||||
$this->passwordEncoder = $passwordEncoder;
|
||||
$this->attachmentSubmitHandler = $attachmentSubmitHandler;
|
||||
$this->commentHelper = $commentHelper;
|
||||
$this->historyHelper = $historyHelper;
|
||||
$this->timeTravel = $timeTravel;
|
||||
$this->dataTableFactory = $dataTableFactory;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->barcodeExampleGenerator = $barcodeExampleGenerator;
|
||||
$this->labelGenerator = $labelGenerator;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?DateTime
|
||||
|
@ -177,13 +154,13 @@ abstract class BaseAdminController extends AbstractController
|
|||
$form_options = [
|
||||
'attachment_class' => $this->attachment_class,
|
||||
'parameter_class' => $this->parameter_class,
|
||||
'disabled' => null !== $timeTravel_timestamp,
|
||||
'disabled' => $timeTravel_timestamp instanceof \DateTime,
|
||||
];
|
||||
|
||||
//Disable editing of options, if user is not allowed to use twig...
|
||||
if (
|
||||
$entity instanceof LabelProfile
|
||||
&& 'twig' === $entity->getOptions()->getLinesMode()
|
||||
&& LabelProcessMode::TWIG === $entity->getOptions()->getProcessMode()
|
||||
&& !$this->isGranted('@labels.use_twig')
|
||||
) {
|
||||
$form_options['disable_options'] = true;
|
||||
|
@ -245,7 +222,7 @@ abstract class BaseAdminController extends AbstractController
|
|||
/** @var AbstractPartsContainingRepository $repo */
|
||||
$repo = $this->entityManager->getRepository($this->entity_class);
|
||||
|
||||
return $this->renderForm($this->twig_template, [
|
||||
return $this->render($this->twig_template, [
|
||||
'entity' => $entity,
|
||||
'form' => $form,
|
||||
'route_base' => $this->route_base,
|
||||
|
@ -267,15 +244,9 @@ abstract class BaseAdminController extends AbstractController
|
|||
return true;
|
||||
}
|
||||
|
||||
protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null)
|
||||
protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null): Response
|
||||
{
|
||||
if (null === $entity) {
|
||||
/** @var AbstractStructuralDBElement|User $new_entity */
|
||||
$new_entity = new $this->entity_class();
|
||||
} else {
|
||||
/** @var AbstractStructuralDBElement|User $new_entity */
|
||||
$new_entity = clone $entity;
|
||||
}
|
||||
$new_entity = $entity instanceof AbstractNamedDBElement ? clone $entity : new $this->entity_class();
|
||||
|
||||
$this->denyAccessUnlessGranted('read', $new_entity);
|
||||
|
||||
|
@ -287,9 +258,8 @@ abstract class BaseAdminController extends AbstractController
|
|||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
//Perform additional actions
|
||||
if ($this->additionalActionNew($form, $new_entity)) {
|
||||
if ($form->isSubmitted() && $form->isValid() && $this->additionalActionNew($form, $new_entity)) {
|
||||
//Upload passed files
|
||||
$attachments = $form['attachments'];
|
||||
foreach ($attachments as $attachment) {
|
||||
|
@ -314,16 +284,12 @@ abstract class BaseAdminController extends AbstractController
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->commentHelper->setMessage($form['log_comment']->getData());
|
||||
|
||||
$em->persist($new_entity);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'entity.created_flash');
|
||||
|
||||
return $this->redirectToRoute($this->route_base.'_edit', ['id' => $new_entity->getID()]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && !$form->isValid()) {
|
||||
$this->addFlash('error', 'entity.created_flash.invalid');
|
||||
|
@ -362,14 +328,13 @@ abstract class BaseAdminController extends AbstractController
|
|||
try {
|
||||
$errors = $importer->importFileAndPersistToDB($file, $options);
|
||||
|
||||
/** @var ConstraintViolationList $error */
|
||||
foreach ($errors as $name => $error) {
|
||||
foreach ($error['violations'] as $violation) {
|
||||
foreach ($error as $violation) {
|
||||
$this->addFlash('error', $name.': '.$violation->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UnexpectedValueException $e) {
|
||||
catch (UnexpectedValueException) {
|
||||
$this->addFlash('error', 'parts.import.flash.error.invalid_file');
|
||||
}
|
||||
}
|
||||
|
@ -402,7 +367,7 @@ abstract class BaseAdminController extends AbstractController
|
|||
}
|
||||
|
||||
ret:
|
||||
return $this->renderForm($this->twig_template, [
|
||||
return $this->render($this->twig_template, [
|
||||
'entity' => $new_entity,
|
||||
'form' => $form,
|
||||
'import_form' => $import_form,
|
||||
|
@ -437,7 +402,7 @@ abstract class BaseAdminController extends AbstractController
|
|||
{
|
||||
$this->denyAccessUnlessGranted('delete', $entity);
|
||||
|
||||
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
|
||||
if ($this->isCsrfTokenValid('delete'.$entity->getID(), $request->request->get('_token'))) {
|
||||
|
||||
$entityManager = $this->entityManager;
|
||||
|
||||
|
|
|
@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/category")
|
||||
* @see \App\Tests\Controller\AdminPages\CategoryControllerTest
|
||||
*/
|
||||
#[Route(path: '/category')]
|
||||
class CategoryController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Category::class;
|
||||
|
@ -47,44 +48,34 @@ class CategoryController extends BaseAdminController
|
|||
protected string $attachment_class = CategoryAttachment::class;
|
||||
protected ?string $parameter_class = CategoryParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="category_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'category_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Category $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="category_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'category_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(Category $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="category_new")
|
||||
* @Route("/{id}/clone", name="category_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'category_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'category_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Category $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="category_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'category_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="category_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'category_export')]
|
||||
public function exportEntity(Category $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -52,10 +52,9 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @Route("/currency")
|
||||
*
|
||||
* Class CurrencyController
|
||||
*/
|
||||
#[Route(path: '/currency')]
|
||||
class CurrencyController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Currency::class;
|
||||
|
@ -65,8 +64,6 @@ class CurrencyController extends BaseAdminController
|
|||
protected string $attachment_class = CurrencyAttachment::class;
|
||||
protected ?string $parameter_class = CurrencyParameter::class;
|
||||
|
||||
protected ExchangeRateUpdater $exchangeRateUpdater;
|
||||
|
||||
public function __construct(
|
||||
TranslatorInterface $translator,
|
||||
UserPasswordHasherInterface $passwordEncoder,
|
||||
|
@ -79,10 +76,8 @@ class CurrencyController extends BaseAdminController
|
|||
LabelExampleElementsGenerator $barcodeExampleGenerator,
|
||||
LabelGenerator $labelGenerator,
|
||||
EntityManagerInterface $entityManager,
|
||||
ExchangeRateUpdater $exchangeRateUpdater
|
||||
protected ExchangeRateUpdater $exchangeRateUpdater
|
||||
) {
|
||||
$this->exchangeRateUpdater = $exchangeRateUpdater;
|
||||
|
||||
parent::__construct(
|
||||
$translator,
|
||||
$passwordEncoder,
|
||||
|
@ -98,9 +93,7 @@ class CurrencyController extends BaseAdminController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="currency_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'currency_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Currency $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
|
@ -131,36 +124,28 @@ class CurrencyController extends BaseAdminController
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="currency_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'currency_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(Currency $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="currency_new")
|
||||
* @Route("/{id}/clone", name="currency_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'currency_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'currency_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Currency $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="currency_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'currency_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="currency_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'currency_export')]
|
||||
public function exportEntity(Currency $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -37,8 +37,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/footprint")
|
||||
* @see \App\Tests\Controller\AdminPages\FootprintControllerTest
|
||||
*/
|
||||
#[Route(path: '/footprint')]
|
||||
class FootprintController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Footprint::class;
|
||||
|
@ -48,44 +49,34 @@ class FootprintController extends BaseAdminController
|
|||
protected string $attachment_class = FootprintAttachment::class;
|
||||
protected ?string $parameter_class = FootprintParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="footprint_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'footprint_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Footprint $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="footprint_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'footprint_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(Footprint $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="footprint_new")
|
||||
* @Route("/{id}/clone", name="footprint_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'footprint_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'footprint_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Footprint $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="footprint_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'footprint_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="footprint_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'footprint_export')]
|
||||
public function exportEntity(AttachmentType $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
/**
|
||||
* @Route("/label_profile")
|
||||
* @see \App\Tests\Controller\AdminPages\LabelProfileControllerTest
|
||||
*/
|
||||
#[Route(path: '/label_profile')]
|
||||
class LabelProfileController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = LabelProfile::class;
|
||||
|
@ -48,44 +49,34 @@ class LabelProfileController extends BaseAdminController
|
|||
//Just a placeholder
|
||||
protected ?string $parameter_class = null;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="label_profile_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'label_profile_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, LabelProfile $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="label_profile_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'label_profile_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(LabelProfile $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="label_profile_new")
|
||||
* @Route("/{id}/clone", name="label_profile_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'label_profile_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'label_profile_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?LabelProfile $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="label_profile_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'label_profile_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="label_profile_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'label_profile_export')]
|
||||
public function exportEntity(LabelProfile $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/manufacturer")
|
||||
* @see \App\Tests\Controller\AdminPages\ManufacturerControllerTest
|
||||
*/
|
||||
#[Route(path: '/manufacturer')]
|
||||
class ManufacturerController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Manufacturer::class;
|
||||
|
@ -47,46 +48,34 @@ class ManufacturerController extends BaseAdminController
|
|||
protected string $attachment_class = ManufacturerAttachment::class;
|
||||
protected ?string $parameter_class = ManufacturerParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="manufacturer_delete", methods={"DELETE"})
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'manufacturer_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Manufacturer $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="manufacturer_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'manufacturer_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(Manufacturer $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="manufacturer_new")
|
||||
* @Route("/{id}/clone", name="manufacturer_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'manufacturer_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'manufacturer_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Manufacturer $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="manufacturer_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'manufacturer_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="manufacturer_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'manufacturer_export')]
|
||||
public function exportEntity(Manufacturer $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -37,8 +37,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/measurement_unit")
|
||||
* @see \App\Tests\Controller\AdminPages\MeasurementUnitControllerTest
|
||||
*/
|
||||
#[Route(path: '/measurement_unit')]
|
||||
class MeasurementUnitController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = MeasurementUnit::class;
|
||||
|
@ -48,44 +49,34 @@ class MeasurementUnitController extends BaseAdminController
|
|||
protected string $attachment_class = MeasurementUnitAttachment::class;
|
||||
protected ?string $parameter_class = MeasurementUnitParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="measurement_unit_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'measurement_unit_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, MeasurementUnit $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="measurement_unit_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'measurement_unit_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(MeasurementUnit $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="measurement_unit_new")
|
||||
* @Route("/{id}/clone", name="measurement_unit_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'measurement_unit_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'measurement_unit_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?MeasurementUnit $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="measurement_unit_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'measurement_unit_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="measurement_unit_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'measurement_unit_export')]
|
||||
public function exportEntity(AttachmentType $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -35,9 +35,7 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/project")
|
||||
*/
|
||||
#[Route(path: '/project')]
|
||||
class ProjectAdminController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Project::class;
|
||||
|
@ -47,44 +45,34 @@ class ProjectAdminController extends BaseAdminController
|
|||
protected string $attachment_class = ProjectAttachment::class;
|
||||
protected ?string $parameter_class = ProjectParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="project_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'project_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Project $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="project_edit")
|
||||
* @Route("/{id}/edit", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'project_edit')]
|
||||
#[Route(path: '/{id}/edit', requirements: ['id' => '\d+'])]
|
||||
public function edit(Project $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="project_new")
|
||||
* @Route("/{id}/clone", name="device_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'project_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'device_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Project $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="project_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'project_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="project_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'project_export')]
|
||||
public function exportEntity(Project $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/store_location")
|
||||
* @see \App\Tests\Controller\AdminPages\StorelocationControllerTest
|
||||
*/
|
||||
#[Route(path: '/store_location')]
|
||||
class StorelocationController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Storelocation::class;
|
||||
|
@ -47,44 +48,34 @@ class StorelocationController extends BaseAdminController
|
|||
protected string $attachment_class = StorelocationAttachment::class;
|
||||
protected ?string $parameter_class = StorelocationParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="store_location_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'store_location_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Storelocation $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="store_location_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'store_location_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(Storelocation $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="store_location_new")
|
||||
* @Route("/{id}/clone", name="store_location_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'store_location_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'store_location_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Storelocation $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="store_location_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'store_location_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="store_location_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'store_location_export')]
|
||||
public function exportEntity(Storelocation $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/supplier")
|
||||
* @see \App\Tests\Controller\AdminPages\SupplierControllerTest
|
||||
*/
|
||||
#[Route(path: '/supplier')]
|
||||
class SupplierController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Supplier::class;
|
||||
|
@ -47,44 +48,34 @@ class SupplierController extends BaseAdminController
|
|||
protected string $attachment_class = SupplierAttachment::class;
|
||||
protected ?string $parameter_class = SupplierParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="supplier_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'supplier_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Supplier $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="supplier_edit")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'supplier_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(Supplier $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="supplier_new")
|
||||
* @Route("/{id}/clone", name="supplier_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'supplier_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'supplier_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Supplier $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="supplier_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'supplier_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="supplier_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'supplier_export')]
|
||||
public function exportEntity(Supplier $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -42,9 +42,8 @@ class AttachmentFileController extends AbstractController
|
|||
{
|
||||
/**
|
||||
* Download the selected attachment.
|
||||
*
|
||||
* @Route("/attachment/{id}/download", name="attachment_download")
|
||||
*/
|
||||
#[Route(path: '/attachment/{id}/download', name: 'attachment_download')]
|
||||
public function download(Attachment $attachment, AttachmentManager $helper): BinaryFileResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('read', $attachment);
|
||||
|
@ -72,9 +71,8 @@ class AttachmentFileController extends AbstractController
|
|||
|
||||
/**
|
||||
* View the attachment.
|
||||
*
|
||||
* @Route("/attachment/{id}/view", name="attachment_view")
|
||||
*/
|
||||
#[Route(path: '/attachment/{id}/view', name: 'attachment_view')]
|
||||
public function view(Attachment $attachment, AttachmentManager $helper): BinaryFileResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('read', $attachment);
|
||||
|
@ -100,9 +98,7 @@ class AttachmentFileController extends AbstractController
|
|||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/attachment/list", name="attachment_list")
|
||||
*/
|
||||
#[Route(path: '/attachment/list', name: 'attachment_list')]
|
||||
public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@attachments.list_attachments');
|
||||
|
@ -124,7 +120,7 @@ class AttachmentFileController extends AbstractController
|
|||
|
||||
return $this->render('attachment_list.html.twig', [
|
||||
'datatable' => $table,
|
||||
'filterForm' => $filterForm->createView(),
|
||||
'filterForm' => $filterForm,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,7 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/group")
|
||||
*/
|
||||
#[Route(path: '/group')]
|
||||
class GroupController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Group::class;
|
||||
|
@ -51,10 +49,8 @@ class GroupController extends BaseAdminController
|
|||
protected string $attachment_class = GroupAttachment::class;
|
||||
protected ?string $parameter_class = GroupParameter::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="group_edit")
|
||||
* @Route("/{id}/", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'group_edit')]
|
||||
#[Route(path: '/{id}/', requirements: ['id' => '\d+'])]
|
||||
public function edit(Group $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response
|
||||
{
|
||||
//Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet)
|
||||
|
@ -63,7 +59,7 @@ class GroupController extends BaseAdminController
|
|||
//Handle permissions presets
|
||||
if ($request->request->has('permission_preset')) {
|
||||
$this->denyAccessUnlessGranted('edit_permissions', $entity);
|
||||
if ($this->isCsrfTokenValid('group'.$entity->getId(), $request->request->get('_token'))) {
|
||||
if ($this->isCsrfTokenValid('group'.$entity->getID(), $request->request->get('_token'))) {
|
||||
$preset = $request->request->get('permission_preset');
|
||||
|
||||
$permissionPresetsHelper->applyPreset($entity, $preset);
|
||||
|
@ -82,35 +78,27 @@ class GroupController extends BaseAdminController
|
|||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="group_new")
|
||||
* @Route("/{id}/clone", name="group_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'group_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'group_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Group $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="group_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'group_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Group $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="group_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'group_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="group_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'group_export')]
|
||||
public function exportEntity(Group $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
|
|
|
@ -37,33 +37,31 @@ use Symfony\Contracts\Cache\CacheInterface;
|
|||
|
||||
class HomepageController extends AbstractController
|
||||
{
|
||||
protected CacheInterface $cache;
|
||||
protected KernelInterface $kernel;
|
||||
protected DataTableFactory $dataTable;
|
||||
|
||||
public function __construct(CacheInterface $cache, KernelInterface $kernel, DataTableFactory $dataTable)
|
||||
public function __construct(protected CacheInterface $cache, protected KernelInterface $kernel, protected DataTableFactory $dataTable)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->kernel = $kernel;
|
||||
$this->dataTable = $dataTable;
|
||||
}
|
||||
|
||||
public function getBanner(): string
|
||||
{
|
||||
$banner = $this->getParameter('partdb.banner');
|
||||
if (!is_string($banner)) {
|
||||
throw new \RuntimeException('The parameter "partdb.banner" must be a string.');
|
||||
}
|
||||
if (empty($banner)) {
|
||||
$banner_path = $this->kernel->getProjectDir()
|
||||
.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md';
|
||||
|
||||
return file_get_contents($banner_path);
|
||||
$tmp = file_get_contents($banner_path);
|
||||
if (false === $tmp) {
|
||||
throw new \RuntimeException('The banner file could not be read.');
|
||||
}
|
||||
$banner = $tmp;
|
||||
}
|
||||
|
||||
return $banner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", name="homepage")
|
||||
*/
|
||||
#[Route(path: '/', name: 'homepage')]
|
||||
public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if ($this->isGranted('@tools.lastActivity')) {
|
||||
|
|
|
@ -43,7 +43,9 @@ namespace App\Controller;
|
|||
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\LabelSystem\LabelOptions;
|
||||
use App\Entity\LabelSystem\LabelProcessMode;
|
||||
use App\Entity\LabelSystem\LabelProfile;
|
||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||
use App\Exceptions\TwigModeException;
|
||||
use App\Form\LabelSystem\LabelDialogType;
|
||||
use App\Repository\DBElementRepository;
|
||||
|
@ -59,59 +61,39 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @Route("/label")
|
||||
*/
|
||||
#[Route(path: '/label')]
|
||||
class LabelController extends AbstractController
|
||||
{
|
||||
protected LabelGenerator $labelGenerator;
|
||||
protected EntityManagerInterface $em;
|
||||
protected ElementTypeNameGenerator $elementTypeNameGenerator;
|
||||
protected RangeParser $rangeParser;
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
public function __construct(LabelGenerator $labelGenerator, EntityManagerInterface $em, ElementTypeNameGenerator $elementTypeNameGenerator,
|
||||
RangeParser $rangeParser, TranslatorInterface $translator)
|
||||
public function __construct(protected LabelGenerator $labelGenerator, protected EntityManagerInterface $em, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected RangeParser $rangeParser, protected TranslatorInterface $translator)
|
||||
{
|
||||
$this->labelGenerator = $labelGenerator;
|
||||
$this->em = $em;
|
||||
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
|
||||
$this->rangeParser = $rangeParser;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/dialog", name="label_dialog")
|
||||
* @Route("/{profile}/dialog", name="label_dialog_profile")
|
||||
*/
|
||||
#[Route(path: '/dialog', name: 'label_dialog')]
|
||||
#[Route(path: '/{profile}/dialog', name: 'label_dialog_profile')]
|
||||
public function generator(Request $request, ?LabelProfile $profile = null): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@labels.create_labels');
|
||||
|
||||
//If we inherit a LabelProfile, the user need to have access to it...
|
||||
if (null !== $profile) {
|
||||
if ($profile instanceof LabelProfile) {
|
||||
$this->denyAccessUnlessGranted('read', $profile);
|
||||
}
|
||||
|
||||
if ($profile) {
|
||||
$label_options = $profile->getOptions();
|
||||
} else {
|
||||
$label_options = new LabelOptions();
|
||||
}
|
||||
$label_options = $profile instanceof LabelProfile ? $profile->getOptions() : new LabelOptions();
|
||||
|
||||
//We have to disable the options, if twig mode is selected and user is not allowed to use it.
|
||||
$disable_options = 'twig' === $label_options->getLinesMode() && !$this->isGranted('@labels.use_twig');
|
||||
$disable_options = (LabelProcessMode::TWIG === $label_options->getProcessMode()) && !$this->isGranted('@labels.use_twig');
|
||||
|
||||
$form = $this->createForm(LabelDialogType::class, null, [
|
||||
'disable_options' => $disable_options,
|
||||
]);
|
||||
|
||||
//Try to parse given target_type and target_id
|
||||
$target_type = $request->query->get('target_type', null);
|
||||
$target_type = $request->query->getEnum('target_type', LabelSupportedElement::class, null);
|
||||
$target_id = $request->query->get('target_id', null);
|
||||
$generate = $request->query->getBoolean('generate', false);
|
||||
|
||||
if (null === $profile && is_string($target_type)) {
|
||||
if (!$profile instanceof LabelProfile && $target_type instanceof LabelSupportedElement) {
|
||||
$label_options->setSupportedElement($target_type);
|
||||
}
|
||||
if (is_string($target_id)) {
|
||||
|
@ -128,10 +110,10 @@ class LabelController extends AbstractController
|
|||
$filename = 'invalid.pdf';
|
||||
|
||||
//Generate PDF either when the form is submitted and valid, or the form was not submit yet, and generate is set
|
||||
if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && null !== $profile)) {
|
||||
if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile instanceof LabelProfile)) {
|
||||
$target_id = (string) $form->get('target_id')->getData();
|
||||
$targets = $this->findObjects($form_options->getSupportedElement(), $target_id);
|
||||
if (!empty($targets)) {
|
||||
if ($targets !== []) {
|
||||
try {
|
||||
$pdf_data = $this->labelGenerator->generateLabel($form_options, $targets);
|
||||
$filename = $this->getLabelName($targets[0], $profile);
|
||||
|
@ -146,7 +128,7 @@ class LabelController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
return $this->renderForm('label_system/dialog.html.twig', [
|
||||
return $this->render('label_system/dialog.html.twig', [
|
||||
'form' => $form,
|
||||
'pdf_data' => $pdf_data,
|
||||
'filename' => $filename,
|
||||
|
@ -162,16 +144,12 @@ class LabelController extends AbstractController
|
|||
return $ret.'.pdf';
|
||||
}
|
||||
|
||||
protected function findObjects(string $type, string $ids): array
|
||||
protected function findObjects(LabelSupportedElement $type, string $ids): array
|
||||
{
|
||||
if (!isset(LabelGenerator::CLASS_SUPPORT_MAPPING[$type])) {
|
||||
throw new InvalidArgumentException('The given type is not known and can not be mapped to a class!');
|
||||
}
|
||||
|
||||
$id_array = $this->rangeParser->parse($ids);
|
||||
|
||||
/** @var DBElementRepository $repo */
|
||||
$repo = $this->em->getRepository(LabelGenerator::CLASS_SUPPORT_MAPPING[$type]);
|
||||
$repo = $this->em->getRepository($type->getEntityClass());
|
||||
|
||||
return $repo->getElementsFromIDArray($id_array);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ use App\Entity\LogSystem\ElementEditedLogEntry;
|
|||
use App\Form\Filters\LogFilterType;
|
||||
use App\Repository\DBElementRepository;
|
||||
use App\Services\LogSystem\EventUndoHelper;
|
||||
use App\Services\LogSystem\EventUndoMode;
|
||||
use App\Services\LogSystem\LogEntryExtraFormatter;
|
||||
use App\Services\LogSystem\LogLevelHelper;
|
||||
use App\Services\LogSystem\LogTargetHelper;
|
||||
|
@ -49,27 +50,17 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/log")
|
||||
*/
|
||||
#[Route(path: '/log')]
|
||||
class LogController extends AbstractController
|
||||
{
|
||||
protected EntityManagerInterface $entityManager;
|
||||
protected TimeTravel $timeTravel;
|
||||
protected DBElementRepository $dbRepository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, TimeTravel $timeTravel)
|
||||
public function __construct(protected EntityManagerInterface $entityManager, protected TimeTravel $timeTravel)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->timeTravel = $timeTravel;
|
||||
$this->dbRepository = $entityManager->getRepository(AbstractDBElement::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", name="log_view")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/', name: 'log_view')]
|
||||
public function showLogs(Request $request, DataTableFactory $dataTable): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@system.show_logs');
|
||||
|
@ -93,17 +84,12 @@ class LogController extends AbstractController
|
|||
|
||||
return $this->render('log_system/log_list.html.twig', [
|
||||
'datatable' => $table,
|
||||
'filterForm' => $filterForm->createView(),
|
||||
'filterForm' => $filterForm,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/details", name="log_details")
|
||||
* @param Request $request
|
||||
* @param AbstractLogEntry $logEntry
|
||||
* @return Response
|
||||
*/
|
||||
public function logDetails(Request $request, AbstractLogEntry $logEntry, LogEntryExtraFormatter $logEntryExtraFormatter,
|
||||
#[Route(path: '/{id}/details', name: 'log_details')]
|
||||
public function logDetails(AbstractLogEntry $logEntry, LogEntryExtraFormatter $logEntryExtraFormatter,
|
||||
LogLevelHelper $logLevelHelper, LogTargetHelper $logTargetHelper, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('show_details', $logEntry);
|
||||
|
@ -123,14 +109,12 @@ class LogController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/delete", name="log_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}/delete', name: 'log_delete', methods: ['DELETE'])]
|
||||
public function deleteLogEntry(Request $request, AbstractLogEntry $logEntry, EntityManagerInterface $entityManager): RedirectResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('delete', $logEntry);
|
||||
|
||||
if ($this->isCsrfTokenValid('delete'.$logEntry->getId(), $request->request->get('_token'))) {
|
||||
if ($this->isCsrfTokenValid('delete'.$logEntry->getID(), $request->request->get('_token'))) {
|
||||
//Remove part
|
||||
$entityManager->remove($logEntry);
|
||||
//Flush changes
|
||||
|
@ -142,22 +126,24 @@ class LogController extends AbstractController
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Route("/undo", name="log_undo", methods={"POST"})
|
||||
*/
|
||||
#[Route(path: '/undo', name: 'log_undo', methods: ['POST'])]
|
||||
public function undoRevertLog(Request $request, EventUndoHelper $eventUndoHelper): RedirectResponse
|
||||
{
|
||||
$mode = EventUndoHelper::MODE_UNDO;
|
||||
$id = $request->request->get('undo');
|
||||
$mode = EventUndoMode::UNDO;
|
||||
$id = $request->request->getInt('undo');
|
||||
|
||||
//If no undo value was set check if a revert was set
|
||||
if (null === $id) {
|
||||
$id = $request->get('revert');
|
||||
$mode = EventUndoHelper::MODE_REVERT;
|
||||
if (0 === $id) {
|
||||
$id = $request->request->getInt('revert');
|
||||
$mode = EventUndoMode::REVERT;
|
||||
}
|
||||
|
||||
if (0 === $id) {
|
||||
throw new InvalidArgumentException('No log entry ID was given!');
|
||||
}
|
||||
|
||||
$log_element = $this->entityManager->find(AbstractLogEntry::class, $id);
|
||||
if (null === $log_element) {
|
||||
if (!$log_element instanceof AbstractLogEntry) {
|
||||
throw new InvalidArgumentException('No log entry with the given ID is existing!');
|
||||
}
|
||||
|
||||
|
@ -166,9 +152,9 @@ class LogController extends AbstractController
|
|||
$eventUndoHelper->setMode($mode);
|
||||
$eventUndoHelper->setUndoneEvent($log_element);
|
||||
|
||||
if (EventUndoHelper::MODE_UNDO === $mode) {
|
||||
if (EventUndoMode::UNDO === $mode) {
|
||||
$this->undoLog($log_element);
|
||||
} elseif (EventUndoHelper::MODE_REVERT === $mode) {
|
||||
} elseif (EventUndoMode::REVERT === $mode) {
|
||||
$this->revertLog($log_element);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||
use Exception;
|
||||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
@ -59,29 +60,19 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
/**
|
||||
* @Route("/part")
|
||||
*/
|
||||
#[Route(path: '/part')]
|
||||
class PartController extends AbstractController
|
||||
{
|
||||
protected PricedetailHelper $pricedetailHelper;
|
||||
protected PartPreviewGenerator $partPreviewGenerator;
|
||||
protected EventCommentHelper $commentHelper;
|
||||
|
||||
public function __construct(PricedetailHelper $pricedetailHelper,
|
||||
PartPreviewGenerator $partPreviewGenerator, EventCommentHelper $commentHelper)
|
||||
public function __construct(protected PricedetailHelper $pricedetailHelper, protected PartPreviewGenerator $partPreviewGenerator, protected EventCommentHelper $commentHelper)
|
||||
{
|
||||
$this->pricedetailHelper = $pricedetailHelper;
|
||||
$this->partPreviewGenerator = $partPreviewGenerator;
|
||||
$this->commentHelper = $commentHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/info/{timestamp}", name="part_info")
|
||||
* @Route("/{id}", requirements={"id"="\d+"})
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
#[Route(path: '/{id}/info/{timestamp}', name: 'part_info')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function show(Part $part, Request $request, TimeTravel $timeTravel, HistoryHelper $historyHelper,
|
||||
DataTableFactory $dataTable, ParameterExtractor $parameterExtractor, PartLotWithdrawAddHelper $withdrawAddHelper, ?string $timestamp = null): Response
|
||||
{
|
||||
|
@ -129,9 +120,7 @@ class PartController extends AbstractController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit", name="part_edit")
|
||||
*/
|
||||
#[Route(path: '/{id}/edit', name: 'part_edit')]
|
||||
public function edit(Part $part, Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
||||
{
|
||||
|
@ -182,21 +171,19 @@ class PartController extends AbstractController
|
|||
$this->addFlash('error', 'part.edited_flash.invalid');
|
||||
}
|
||||
|
||||
return $this->renderForm('parts/edit/edit_part_info.html.twig',
|
||||
return $this->render('parts/edit/edit_part_info.html.twig',
|
||||
[
|
||||
'part' => $part,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/delete", name="part_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/{id}/delete', name: 'part_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Part $part, EntityManagerInterface $entityManager): RedirectResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('delete', $part);
|
||||
|
||||
if ($this->isCsrfTokenValid('delete'.$part->getId(), $request->request->get('_token'))) {
|
||||
if ($this->isCsrfTokenValid('delete'.$part->getID(), $request->request->get('_token'))) {
|
||||
|
||||
$this->commentHelper->setMessage($request->request->get('log_comment', null));
|
||||
|
||||
|
@ -212,23 +199,22 @@ class PartController extends AbstractController
|
|||
return $this->redirectToRoute('homepage');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="part_new")
|
||||
* @Route("/{id}/clone", name="part_clone")
|
||||
* @Route("/new_build_part/{project_id}", name="part_new_build_part")
|
||||
* @ParamConverter("part", options={"id" = "id"})
|
||||
* @ParamConverter("project", options={"id" = "project_id"})
|
||||
*/
|
||||
#[Route(path: '/new', name: 'part_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'part_clone')]
|
||||
#[Route(path: '/new_build_part/{project_id}', name: 'part_new_build_part')]
|
||||
public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler, ProjectBuildPartHelper $projectBuildPartHelper,
|
||||
?Part $part = null, ?Project $project = null): Response
|
||||
#[MapEntity(mapping: ['id' => 'id'])] ?Part $part = null,
|
||||
#[MapEntity(mapping: ['id' => 'project_id'])] ?Project $project = null): Response
|
||||
{
|
||||
|
||||
if ($part) { //Clone part
|
||||
if ($part instanceof Part) {
|
||||
//Clone part
|
||||
$new_part = clone $part;
|
||||
} else if ($project) { //Initialize a new part for a build part from the given project
|
||||
} elseif ($project instanceof Project) {
|
||||
//Initialize a new part for a build part from the given project
|
||||
//Ensure that the project has not already a build part
|
||||
if ($project->getBuildPart() !== null) {
|
||||
if ($project->getBuildPart() instanceof Part) {
|
||||
$this->addFlash('error', 'part.new_build_part.error.build_part_already_exists');
|
||||
return $this->redirectToRoute('part_edit', ['id' => $project->getBuildPart()->getID()]);
|
||||
}
|
||||
|
@ -241,7 +227,7 @@ class PartController extends AbstractController
|
|||
|
||||
$cid = $request->get('category', null);
|
||||
$category = $cid ? $em->find(Category::class, $cid) : null;
|
||||
if (null !== $category && null === $new_part->getCategory()) {
|
||||
if ($category instanceof Category && !$new_part->getCategory() instanceof Category) {
|
||||
$new_part->setCategory($category);
|
||||
$new_part->setDescription($category->getDefaultDescription());
|
||||
$new_part->setComment($category->getDefaultComment());
|
||||
|
@ -249,19 +235,19 @@ class PartController extends AbstractController
|
|||
|
||||
$fid = $request->get('footprint', null);
|
||||
$footprint = $fid ? $em->find(Footprint::class, $fid) : null;
|
||||
if (null !== $footprint && null === $new_part->getFootprint()) {
|
||||
if ($footprint instanceof Footprint && !$new_part->getFootprint() instanceof Footprint) {
|
||||
$new_part->setFootprint($footprint);
|
||||
}
|
||||
|
||||
$mid = $request->get('manufacturer', null);
|
||||
$manufacturer = $mid ? $em->find(Manufacturer::class, $mid) : null;
|
||||
if (null !== $manufacturer && null === $new_part->getManufacturer()) {
|
||||
if ($manufacturer instanceof Manufacturer && !$new_part->getManufacturer() instanceof Manufacturer) {
|
||||
$new_part->setManufacturer($manufacturer);
|
||||
}
|
||||
|
||||
$store_id = $request->get('storelocation', null);
|
||||
$storelocation = $store_id ? $em->find(Storelocation::class, $store_id) : null;
|
||||
if (null !== $storelocation && $new_part->getPartLots()->isEmpty()) {
|
||||
if ($storelocation instanceof Storelocation && $new_part->getPartLots()->isEmpty()) {
|
||||
$partLot = new PartLot();
|
||||
$partLot->setStorageLocation($storelocation);
|
||||
$partLot->setInstockUnknown(true);
|
||||
|
@ -270,7 +256,7 @@ class PartController extends AbstractController
|
|||
|
||||
$supplier_id = $request->get('supplier', null);
|
||||
$supplier = $supplier_id ? $em->find(Supplier::class, $supplier_id) : null;
|
||||
if (null !== $supplier && $new_part->getOrderdetails()->isEmpty()) {
|
||||
if ($supplier instanceof Supplier && $new_part->getOrderdetails()->isEmpty()) {
|
||||
$orderdetail = new Orderdetail();
|
||||
$orderdetail->setSupplier($supplier);
|
||||
$new_part->addOrderdetail($orderdetail);
|
||||
|
@ -328,22 +314,20 @@ class PartController extends AbstractController
|
|||
$this->addFlash('error', 'part.created_flash.invalid');
|
||||
}
|
||||
|
||||
return $this->renderForm('parts/edit/new_part.html.twig',
|
||||
return $this->render('parts/edit/new_part.html.twig',
|
||||
[
|
||||
'part' => $new_part,
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/add_withdraw", name="part_add_withdraw", methods={"POST"})
|
||||
*/
|
||||
#[Route(path: '/{id}/add_withdraw', name: 'part_add_withdraw', methods: ['POST'])]
|
||||
public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response
|
||||
{
|
||||
if ($this->isCsrfTokenValid('part_withraw' . $part->getID(), $request->request->get('_csfr'))) {
|
||||
//Retrieve partlot from the request
|
||||
$partLot = $em->find(PartLot::class, $request->request->get('lot_id'));
|
||||
if($partLot === null) {
|
||||
if(!$partLot instanceof PartLot) {
|
||||
throw new \RuntimeException('Part lot not found!');
|
||||
}
|
||||
//Ensure that the partlot belongs to the part
|
||||
|
@ -383,7 +367,7 @@ class PartController extends AbstractController
|
|||
default:
|
||||
throw new \RuntimeException("Unknown action!");
|
||||
}
|
||||
} catch (AccessDeniedException $exception) {
|
||||
} catch (AccessDeniedException) {
|
||||
$this->addFlash('error', t('part.withdraw.access_denied'));
|
||||
goto err;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,7 +20,6 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
|
@ -36,23 +38,11 @@ use UnexpectedValueException;
|
|||
|
||||
class PartImportExportController extends AbstractController
|
||||
{
|
||||
private PartsTableActionHandler $partsTableActionHandler;
|
||||
private EntityImporter $entityImporter;
|
||||
private EventCommentHelper $commentHelper;
|
||||
|
||||
public function __construct(PartsTableActionHandler $partsTableActionHandler,
|
||||
EntityImporter $entityImporter, EventCommentHelper $commentHelper)
|
||||
public function __construct(private readonly PartsTableActionHandler $partsTableActionHandler, private readonly EntityImporter $entityImporter, private readonly EventCommentHelper $commentHelper)
|
||||
{
|
||||
$this->partsTableActionHandler = $partsTableActionHandler;
|
||||
$this->entityImporter = $entityImporter;
|
||||
$this->commentHelper = $commentHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parts/import", name="parts_import")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/parts/import', name: 'parts_import')]
|
||||
public function importParts(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@parts.import');
|
||||
|
@ -109,23 +99,20 @@ class PartImportExportController extends AbstractController
|
|||
|
||||
|
||||
ret:
|
||||
return $this->renderForm('parts/import/parts_import.html.twig', [
|
||||
return $this->render('parts/import/parts_import.html.twig', [
|
||||
'import_form' => $import_form,
|
||||
'imported_entities' => $entities ?? [],
|
||||
'import_errors' => $errors ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parts/export", name="parts_export", methods={"GET"})
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/parts/export', name: 'parts_export', methods: ['GET'])]
|
||||
public function exportParts(Request $request, EntityExporter $entityExporter): Response
|
||||
{
|
||||
$ids = $request->query->get('ids', '');
|
||||
$parts = $this->partsTableActionHandler->idStringToArray($ids);
|
||||
|
||||
if (empty($parts)) {
|
||||
if ($parts === []) {
|
||||
throw new \RuntimeException('No parts found!');
|
||||
}
|
||||
|
||||
|
|
|
@ -48,23 +48,11 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
|
||||
class PartListsController extends AbstractController
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
private NodesListBuilder $nodesListBuilder;
|
||||
private DataTableFactory $dataTableFactory;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, NodesListBuilder $nodesListBuilder, DataTableFactory $dataTableFactory, TranslatorInterface $translator)
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->nodesListBuilder = $nodesListBuilder;
|
||||
$this->dataTableFactory = $dataTableFactory;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/table/action", name="table_action", methods={"POST"})
|
||||
*/
|
||||
#[Route(path: '/table/action', name: 'table_action', methods: ['POST'])]
|
||||
public function tableAction(Request $request, PartsTableActionHandler $actionHandler): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@parts.edit');
|
||||
|
@ -102,8 +90,6 @@ class PartListsController extends AbstractController
|
|||
|
||||
/**
|
||||
* Disable the given form interface after creation of the form by removing and reattaching the form.
|
||||
* @param FormInterface $form
|
||||
* @return void
|
||||
*/
|
||||
private function disableFormFieldAfterCreation(FormInterface $form, bool $disabled = true): void
|
||||
{
|
||||
|
@ -111,12 +97,12 @@ class PartListsController extends AbstractController
|
|||
$attrs['disabled'] = $disabled;
|
||||
|
||||
$parent = $form->getParent();
|
||||
if ($parent === null) {
|
||||
if (!$parent instanceof FormInterface) {
|
||||
throw new \RuntimeException('This function can only be used on form fields that are children of another form!');
|
||||
}
|
||||
|
||||
$parent->remove($form->getName());
|
||||
$parent->add($form->getName(), get_class($form->getConfig()->getType()->getInnerType()), $attrs);
|
||||
$parent->add($form->getName(), $form->getConfig()->getType()->getInnerType()::class, $attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,7 +113,6 @@ class PartListsController extends AbstractController
|
|||
* @param callable|null $form_changer A function that is called with the form object as parameter. This function can be used to customize the form
|
||||
* @param array $additonal_template_vars Any additional template variables that should be passed to the template
|
||||
* @param array $additional_table_vars Any additional variables that should be passed to the table creation
|
||||
* @return Response
|
||||
*/
|
||||
protected function showListWithFilter(Request $request, string $template, ?callable $filter_changer = null, ?callable $form_changer = null, array $additonal_template_vars = [], array $additional_table_vars = []): Response
|
||||
{
|
||||
|
@ -174,11 +159,7 @@ class PartListsController extends AbstractController
|
|||
], $additonal_template_vars));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/category/{id}/parts", name="part_list_category")
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
#[Route(path: '/category/{id}/parts', name: 'part_list_category')]
|
||||
public function showCategory(Category $category, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@categories.read');
|
||||
|
@ -186,7 +167,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/category_list.html.twig',
|
||||
function (PartFilter $filter) use ($category) {
|
||||
$filter->getCategory()->setOperator('INCLUDING_CHILDREN')->setValue($category);
|
||||
$filter->category->setOperator('INCLUDING_CHILDREN')->setValue($category);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('category')->get('value'));
|
||||
}, [
|
||||
|
@ -196,11 +177,7 @@ class PartListsController extends AbstractController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/footprint/{id}/parts", name="part_list_footprint")
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
#[Route(path: '/footprint/{id}/parts', name: 'part_list_footprint')]
|
||||
public function showFootprint(Footprint $footprint, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@footprints.read');
|
||||
|
@ -208,7 +185,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/footprint_list.html.twig',
|
||||
function (PartFilter $filter) use ($footprint) {
|
||||
$filter->getFootprint()->setOperator('INCLUDING_CHILDREN')->setValue($footprint);
|
||||
$filter->footprint->setOperator('INCLUDING_CHILDREN')->setValue($footprint);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value'));
|
||||
}, [
|
||||
|
@ -218,11 +195,7 @@ class PartListsController extends AbstractController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/manufacturer/{id}/parts", name="part_list_manufacturer")
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
#[Route(path: '/manufacturer/{id}/parts', name: 'part_list_manufacturer')]
|
||||
public function showManufacturer(Manufacturer $manufacturer, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@manufacturers.read');
|
||||
|
@ -230,7 +203,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/manufacturer_list.html.twig',
|
||||
function (PartFilter $filter) use ($manufacturer) {
|
||||
$filter->getManufacturer()->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer);
|
||||
$filter->manufacturer->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value'));
|
||||
}, [
|
||||
|
@ -240,11 +213,7 @@ class PartListsController extends AbstractController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/store_location/{id}/parts", name="part_list_store_location")
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
#[Route(path: '/store_location/{id}/parts', name: 'part_list_store_location')]
|
||||
public function showStorelocation(Storelocation $storelocation, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@storelocations.read');
|
||||
|
@ -252,7 +221,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/store_location_list.html.twig',
|
||||
function (PartFilter $filter) use ($storelocation) {
|
||||
$filter->getStorelocation()->setOperator('INCLUDING_CHILDREN')->setValue($storelocation);
|
||||
$filter->storelocation->setOperator('INCLUDING_CHILDREN')->setValue($storelocation);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value'));
|
||||
}, [
|
||||
|
@ -262,11 +231,7 @@ class PartListsController extends AbstractController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/supplier/{id}/parts", name="part_list_supplier")
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
#[Route(path: '/supplier/{id}/parts', name: 'part_list_supplier')]
|
||||
public function showSupplier(Supplier $supplier, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@suppliers.read');
|
||||
|
@ -274,7 +239,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/supplier_list.html.twig',
|
||||
function (PartFilter $filter) use ($supplier) {
|
||||
$filter->getSupplier()->setOperator('INCLUDING_CHILDREN')->setValue($supplier);
|
||||
$filter->supplier->setOperator('INCLUDING_CHILDREN')->setValue($supplier);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value'));
|
||||
}, [
|
||||
|
@ -284,11 +249,7 @@ class PartListsController extends AbstractController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parts/by_tag/{tag}", name="part_list_tags", requirements={"tag": ".*"})
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
#[Route(path: '/parts/by_tag/{tag}', name: 'part_list_tags', requirements: ['tag' => '.*'])]
|
||||
public function showTag(string $tag, Request $request): Response
|
||||
{
|
||||
$tag = trim($tag);
|
||||
|
@ -296,7 +257,7 @@ class PartListsController extends AbstractController
|
|||
return $this->showListWithFilter($request,
|
||||
'parts/lists/tags_list.html.twig',
|
||||
function (PartFilter $filter) use ($tag) {
|
||||
$filter->getTags()->setOperator('ANY')->setValue($tag);
|
||||
$filter->tags->setOperator('ANY')->setValue($tag);
|
||||
}, function (FormInterface $filterForm) {
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('tags')->get('value'));
|
||||
}, [
|
||||
|
@ -329,11 +290,7 @@ class PartListsController extends AbstractController
|
|||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parts/search", name="parts_search")
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
#[Route(path: '/parts/search', name: 'parts_search')]
|
||||
public function showSearch(Request $request, DataTableFactory $dataTable): Response
|
||||
{
|
||||
$searchFilter = $this->searchRequestToFilter($request);
|
||||
|
@ -352,11 +309,7 @@ class PartListsController extends AbstractController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parts", name="parts_show_all")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/parts', name: 'parts_show_all')]
|
||||
public function showAll(Request $request): Response
|
||||
{
|
||||
return $this->showListWithFilter($request,'parts/lists/all_list.html.twig');
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,7 +20,6 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\DataTables\ProjectBomEntriesDataTable;
|
||||
|
@ -47,21 +49,14 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
/**
|
||||
* @Route("/project")
|
||||
*/
|
||||
#[Route(path: '/project')]
|
||||
class ProjectController extends AbstractController
|
||||
{
|
||||
private DataTableFactory $dataTableFactory;
|
||||
|
||||
public function __construct(DataTableFactory $dataTableFactory)
|
||||
public function __construct(private readonly DataTableFactory $dataTableFactory)
|
||||
{
|
||||
$this->dataTableFactory = $dataTableFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/info", name="project_info", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/info', name: 'project_info', requirements: ['id' => '\d+'])]
|
||||
public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('read', $project);
|
||||
|
@ -80,9 +75,7 @@ class ProjectController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/build", name="project_build", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/build', name: 'project_build', requirements: ['id' => '\d+'])]
|
||||
public function build(Project $project, Request $request, ProjectBuildHelper $buildHelper, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('read', $project);
|
||||
|
@ -117,7 +110,7 @@ class ProjectController extends AbstractController
|
|||
$this->addFlash('error', 'project.build.flash.invalid_input');
|
||||
}
|
||||
|
||||
return $this->renderForm('projects/build/build.html.twig', [
|
||||
return $this->render('projects/build/build.html.twig', [
|
||||
'buildHelper' => $buildHelper,
|
||||
'project' => $project,
|
||||
'build_request' => $projectBuildRequest,
|
||||
|
@ -126,9 +119,7 @@ class ProjectController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/import_bom", name="project_import_bom", requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}/import_bom', name: 'project_import_bom', requirements: ['id' => '\d+'])]
|
||||
public function importBOM(Request $request, EntityManagerInterface $entityManager, Project $project,
|
||||
BOMImporter $BOMImporter, ValidatorInterface $validator): Response
|
||||
{
|
||||
|
@ -185,32 +176,26 @@ class ProjectController extends AbstractController
|
|||
return $this->redirectToRoute('project_edit', ['id' => $project->getID()]);
|
||||
}
|
||||
|
||||
if (count ($errors) > 0) {
|
||||
//When we get here, there were validation errors
|
||||
$this->addFlash('error', t('project.bom_import.flash.invalid_entries'));
|
||||
}
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
$this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()]));
|
||||
} catch (SyntaxError $e) {
|
||||
|
||||
} catch (\UnexpectedValueException|SyntaxError $e) {
|
||||
$this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()]));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renderForm('projects/import_bom.html.twig', [
|
||||
return $this->render('projects/import_bom.html.twig', [
|
||||
'project' => $project,
|
||||
'form' => $form,
|
||||
'errors' => $errors ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/add_parts", name="project_add_parts_no_id")
|
||||
* @Route("/{id}/add_parts", name="project_add_parts", requirements={"id"="\d+"})
|
||||
* @param Request $request
|
||||
* @param Project|null $project
|
||||
*/
|
||||
#[Route(path: '/add_parts', name: 'project_add_parts_no_id')]
|
||||
#[Route(path: '/{id}/add_parts', name: 'project_add_parts', requirements: ['id' => '\d+'])]
|
||||
public function addPart(Request $request, EntityManagerInterface $entityManager, ?Project $project): Response
|
||||
{
|
||||
if($project) {
|
||||
if($project instanceof Project) {
|
||||
$this->denyAccessUnlessGranted('edit', $project);
|
||||
} else {
|
||||
$this->denyAccessUnlessGranted('@projects.edit');
|
||||
|
@ -220,7 +205,7 @@ class ProjectController extends AbstractController
|
|||
$builder->add('project', StructuralEntityType::class, [
|
||||
'class' => Project::class,
|
||||
'required' => true,
|
||||
'disabled' => $project !== null, //If a project is given, disable the field
|
||||
'disabled' => $project instanceof Project, //If a project is given, disable the field
|
||||
'data' => $project,
|
||||
'constraints' => [
|
||||
new NotNull()
|
||||
|
@ -232,7 +217,7 @@ class ProjectController extends AbstractController
|
|||
|
||||
//Preset the BOM entries with the selected parts, when the form was not submitted yet
|
||||
$preset_data = new ArrayCollection();
|
||||
foreach (explode(',', $request->get('parts', '')) as $part_id) {
|
||||
foreach (explode(',', (string) $request->get('parts', '')) as $part_id) {
|
||||
$part = $entityManager->getRepository(Part::class)->find($part_id);
|
||||
if (null !== $part) {
|
||||
//If there is already a BOM entry for this part, we use this one (we edit it then)
|
||||
|
@ -274,7 +259,7 @@ class ProjectController extends AbstractController
|
|||
return $this->redirectToRoute('project_info', ['id' => $target_project->getID()]);
|
||||
}
|
||||
|
||||
return $this->renderForm('projects/add_parts.html.twig', [
|
||||
return $this->render('projects/add_parts.html.twig', [
|
||||
'project' => $project,
|
||||
'form' => $form,
|
||||
]);
|
||||
|
|
|
@ -30,17 +30,13 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
|
|||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @see \App\Tests\Controller\RedirectControllerTest
|
||||
*/
|
||||
class RedirectController extends AbstractController
|
||||
{
|
||||
protected string $default_locale;
|
||||
protected TranslatorInterface $translator;
|
||||
protected bool $enforce_index_php;
|
||||
|
||||
public function __construct(string $default_locale, TranslatorInterface $translator, bool $enforce_index_php)
|
||||
public function __construct(protected string $default_locale, protected TranslatorInterface $translator, protected bool $enforce_index_php)
|
||||
{
|
||||
$this->default_locale = $default_locale;
|
||||
$this->translator = $translator;
|
||||
$this->enforce_index_php = $enforce_index_php;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,7 +50,7 @@ class RedirectController extends AbstractController
|
|||
|
||||
//Check if a user has set a preferred language setting:
|
||||
$user = $this->getUser();
|
||||
if (($user instanceof User) && !empty($user->getLanguage())) {
|
||||
if (($user instanceof User) && ($user->getLanguage() !== null && $user->getLanguage() !== '')) {
|
||||
$locale = $user->getLanguage();
|
||||
}
|
||||
|
||||
|
@ -62,7 +58,7 @@ class RedirectController extends AbstractController
|
|||
|
||||
//If either mod_rewrite is not enabled or the index.php version is enforced, add index.php to the string
|
||||
if (($this->enforce_index_php || !$this->checkIfModRewriteAvailable())
|
||||
&& false === strpos($new_url, 'index.php')) {
|
||||
&& !str_contains((string) $new_url, 'index.php')) {
|
||||
//Like Request::getUriForPath only with index.php
|
||||
$new_url = $request->getSchemeAndHttpHost().$request->getBaseUrl().'/index.php/'.$locale.$request->getPathInfo();
|
||||
}
|
||||
|
@ -87,6 +83,6 @@ class RedirectController extends AbstractController
|
|||
}
|
||||
|
||||
//Check if the mod_rewrite module is loaded
|
||||
return in_array('mod_rewrite', apache_get_modules(), false);
|
||||
return in_array('mod_rewrite', apache_get_modules(), true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,23 +51,14 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/scan")
|
||||
*/
|
||||
#[Route(path: '/scan')]
|
||||
class ScanController extends AbstractController
|
||||
{
|
||||
protected BarcodeRedirector $barcodeParser;
|
||||
protected BarcodeNormalizer $barcodeNormalizer;
|
||||
|
||||
public function __construct(BarcodeRedirector $barcodeParser, BarcodeNormalizer $barcodeNormalizer)
|
||||
public function __construct(protected BarcodeRedirector $barcodeParser, protected BarcodeNormalizer $barcodeNormalizer)
|
||||
{
|
||||
$this->barcodeParser = $barcodeParser;
|
||||
$this->barcodeNormalizer = $barcodeNormalizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("", name="scan_dialog")
|
||||
*/
|
||||
#[Route(path: '', name: 'scan_dialog')]
|
||||
public function dialog(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@tools.label_scanner');
|
||||
|
@ -83,15 +74,15 @@ class ScanController extends AbstractController
|
|||
|
||||
try {
|
||||
return $this->redirect($this->barcodeParser->getRedirectURL($type, $id));
|
||||
} catch (EntityNotFoundException $exception) {
|
||||
} catch (EntityNotFoundException) {
|
||||
$this->addFlash('success', 'scan.qr_not_found');
|
||||
}
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
} catch (InvalidArgumentException) {
|
||||
$this->addFlash('error', 'scan.format_unknown');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renderForm('label_system/scanner/scanner.html.twig', [
|
||||
return $this->render('label_system/scanner/scanner.html.twig', [
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
@ -105,7 +96,7 @@ class ScanController extends AbstractController
|
|||
$this->addFlash('success', 'scan.qr_success');
|
||||
|
||||
return $this->redirect($this->barcodeParser->getRedirectURL($type, $id));
|
||||
} catch (EntityNotFoundException $exception) {
|
||||
} catch (EntityNotFoundException) {
|
||||
$this->addFlash('success', 'scan.qr_not_found');
|
||||
|
||||
return $this->redirectToRoute('homepage');
|
||||
|
|
|
@ -48,18 +48,11 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
|
||||
class SecurityController extends AbstractController
|
||||
{
|
||||
protected TranslatorInterface $translator;
|
||||
protected bool $allow_email_pw_reset;
|
||||
|
||||
public function __construct(TranslatorInterface $translator, bool $allow_email_pw_reset)
|
||||
public function __construct(protected TranslatorInterface $translator, protected bool $allow_email_pw_reset)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->allow_email_pw_reset = $allow_email_pw_reset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/login", name="login", methods={"GET", "POST"})
|
||||
*/
|
||||
#[Route(path: '/login', name: 'login', methods: ['GET', 'POST'])]
|
||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
// get the login error if there is one
|
||||
|
@ -75,11 +68,10 @@ class SecurityController extends AbstractController
|
|||
}
|
||||
|
||||
/**
|
||||
* @Route("/pw_reset/request", name="pw_reset_request")
|
||||
*
|
||||
* @return RedirectResponse|Response
|
||||
*/
|
||||
public function requestPwReset(PasswordResetManager $passwordReset, Request $request)
|
||||
#[Route(path: '/pw_reset/request', name: 'pw_reset_request')]
|
||||
public function requestPwReset(PasswordResetManager $passwordReset, Request $request): RedirectResponse|Response
|
||||
{
|
||||
if (!$this->allow_email_pw_reset) {
|
||||
throw new AccessDeniedHttpException('The password reset via email is disabled!');
|
||||
|
@ -113,17 +105,16 @@ class SecurityController extends AbstractController
|
|||
return $this->redirectToRoute('login');
|
||||
}
|
||||
|
||||
return $this->renderForm('security/pw_reset_request.html.twig', [
|
||||
return $this->render('security/pw_reset_request.html.twig', [
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/pw_reset/new_pw/{user}/{token}", name="pw_reset_new_pw")
|
||||
*
|
||||
* @return RedirectResponse|Response
|
||||
*/
|
||||
public function pwResetNewPw(PasswordResetManager $passwordReset, Request $request, EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher, ?string $user = null, ?string $token = null)
|
||||
#[Route(path: '/pw_reset/new_pw/{user}/{token}', name: 'pw_reset_new_pw')]
|
||||
public function pwResetNewPw(PasswordResetManager $passwordReset, Request $request, EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher, ?string $user = null, ?string $token = null): RedirectResponse|Response
|
||||
{
|
||||
if (!$this->allow_email_pw_reset) {
|
||||
throw new AccessDeniedHttpException('The password reset via email is disabled!');
|
||||
|
@ -187,15 +178,13 @@ class SecurityController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
return $this->renderForm('security/pw_reset_new_pw.html.twig', [
|
||||
return $this->render('security/pw_reset_new_pw.html.twig', [
|
||||
'form' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/logout", name="logout")
|
||||
*/
|
||||
public function logout(): void
|
||||
#[Route(path: '/logout', name: 'logout')]
|
||||
public function logout(): never
|
||||
{
|
||||
throw new RuntimeException('Will be intercepted before getting here');
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,7 +20,6 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
|
@ -37,66 +39,46 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @Route("/select_api")
|
||||
*
|
||||
* This endpoint is used by the select2 library to dynamically load data (used in the multiselect action helper in parts lists)
|
||||
*/
|
||||
#[Route(path: '/select_api')]
|
||||
class SelectAPIController extends AbstractController
|
||||
{
|
||||
private NodesListBuilder $nodesListBuilder;
|
||||
private TranslatorInterface $translator;
|
||||
private StructuralEntityChoiceHelper $choiceHelper;
|
||||
|
||||
public function __construct(NodesListBuilder $nodesListBuilder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper)
|
||||
public function __construct(private readonly NodesListBuilder $nodesListBuilder, private readonly TranslatorInterface $translator, private readonly StructuralEntityChoiceHelper $choiceHelper)
|
||||
{
|
||||
$this->nodesListBuilder = $nodesListBuilder;
|
||||
$this->translator = $translator;
|
||||
$this->choiceHelper = $choiceHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/category", name="select_category")
|
||||
*/
|
||||
#[Route(path: '/category', name: 'select_category')]
|
||||
public function category(): Response
|
||||
{
|
||||
return $this->getResponseForClass(Category::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/footprint", name="select_footprint")
|
||||
*/
|
||||
#[Route(path: '/footprint', name: 'select_footprint')]
|
||||
public function footprint(): Response
|
||||
{
|
||||
return $this->getResponseForClass(Footprint::class, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/manufacturer", name="select_manufacturer")
|
||||
*/
|
||||
#[Route(path: '/manufacturer', name: 'select_manufacturer')]
|
||||
public function manufacturer(): Response
|
||||
{
|
||||
return $this->getResponseForClass(Manufacturer::class, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/measurement_unit", name="select_measurement_unit")
|
||||
*/
|
||||
#[Route(path: '/measurement_unit', name: 'select_measurement_unit')]
|
||||
public function measurement_unit(): Response
|
||||
{
|
||||
return $this->getResponseForClass(MeasurementUnit::class, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/project", name="select_project")
|
||||
*/
|
||||
#[Route(path: '/project', name: 'select_project')]
|
||||
public function projects(): Response
|
||||
{
|
||||
return $this->getResponseForClass(Project::class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export_level", name="select_export_level")
|
||||
*/
|
||||
#[Route(path: '/export_level', name: 'select_export_level')]
|
||||
public function exportLevel(): Response
|
||||
{
|
||||
$entries = [
|
||||
|
@ -105,18 +87,13 @@ class SelectAPIController extends AbstractController
|
|||
3 => $this->translator->trans('export.level.full'),
|
||||
];
|
||||
|
||||
return $this->json(array_map(static function ($key, $value) {
|
||||
return [
|
||||
return $this->json(array_map(static fn($key, $value) => [
|
||||
'text' => $value,
|
||||
'value' => $key,
|
||||
];
|
||||
}, array_keys($entries), $entries));
|
||||
], array_keys($entries), $entries));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/label_profiles", name="select_label_profiles")
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/label_profiles', name: 'select_label_profiles')]
|
||||
public function labelProfiles(EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@labels.create_labels');
|
||||
|
@ -134,10 +111,7 @@ class SelectAPIController extends AbstractController
|
|||
return $this->json($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/label_profiles_lot", name="select_label_profiles_lot")
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/label_profiles_lot', name: 'select_label_profiles_lot')]
|
||||
public function labelProfilesLot(EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@labels.create_labels');
|
||||
|
@ -198,7 +172,7 @@ class SelectAPIController extends AbstractController
|
|||
//Remove the data-* prefix for each key
|
||||
$data = array_combine(
|
||||
array_map(static function ($key) {
|
||||
if (strpos($key, 'data-') === 0) {
|
||||
if (str_starts_with($key, 'data-')) {
|
||||
return substr($key, 5);
|
||||
}
|
||||
return $key;
|
||||
|
|
|
@ -48,9 +48,7 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||
|
||||
class StatisticsController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route("/statistics", name="statistics_view")
|
||||
*/
|
||||
#[Route(path: '/statistics', name: 'statistics_view')]
|
||||
public function showStatistics(StatisticsHelper $helper): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@tools.statistics');
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,7 +20,6 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Services\Attachments\AttachmentPathResolver;
|
||||
|
@ -32,14 +34,10 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGenerator;
|
||||
|
||||
/**
|
||||
* @Route("/tools")
|
||||
*/
|
||||
#[Route(path: '/tools')]
|
||||
class ToolsController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route("/reel_calc", name="tools_reel_calculator")
|
||||
*/
|
||||
#[Route(path: '/reel_calc', name: 'tools_reel_calculator')]
|
||||
public function reelCalculator(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@tools.reel_calculator');
|
||||
|
@ -47,9 +45,7 @@ class ToolsController extends AbstractController
|
|||
return $this->render('tools/reel_calculator/reel_calculator.html.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/server_infos", name="tools_server_infos")
|
||||
*/
|
||||
#[Route(path: '/server_infos', name: 'tools_server_infos')]
|
||||
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
||||
{
|
||||
|
@ -65,7 +61,7 @@ class ToolsController extends AbstractController
|
|||
'default_theme' => $this->getParameter('partdb.global_theme'),
|
||||
'enabled_locales' => $this->getParameter('partdb.locale_menu'),
|
||||
'demo_mode' => $this->getParameter('partdb.demo_mode'),
|
||||
'gpdr_compliance' => $this->getParameter('partdb.gpdr_compliance'),
|
||||
'gpdr_compliance' => $this->getParameter('partdb.gdpr_compliance'),
|
||||
'use_gravatar' => $this->getParameter('partdb.users.use_gravatar'),
|
||||
'email_password_reset' => $this->getParameter('partdb.users.email_pw_reset'),
|
||||
'enviroment' => $this->getParameter('kernel.environment'),
|
||||
|
@ -83,7 +79,7 @@ class ToolsController extends AbstractController
|
|||
'php_version' => PHP_VERSION,
|
||||
'php_uname' => php_uname('a'),
|
||||
'php_sapi' => PHP_SAPI,
|
||||
'php_extensions' => array_merge(get_loaded_extensions()),
|
||||
'php_extensions' => [...get_loaded_extensions()],
|
||||
'php_opcache_enabled' => ini_get('opcache.enable'),
|
||||
'php_upload_max_filesize' => ini_get('upload_max_filesize'),
|
||||
'php_post_max_size' => ini_get('post_max_size'),
|
||||
|
@ -97,33 +93,23 @@ class ToolsController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/builtin_footprints", name="tools_builtin_footprints_viewer")
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/builtin_footprints', name: 'tools_builtin_footprints_viewer')]
|
||||
public function builtInFootprintsViewer(BuiltinAttachmentsFinder $builtinAttachmentsFinder, AttachmentURLGenerator $urlGenerator): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@tools.builtin_footprints_viewer');
|
||||
|
||||
$grouped_footprints = $builtinAttachmentsFinder->getListOfFootprintsGroupedByFolder();
|
||||
$grouped_footprints = array_map(function($group) use ($urlGenerator) {
|
||||
return array_map(function($placeholder_filepath) use ($urlGenerator) {
|
||||
return [
|
||||
'filename' => basename($placeholder_filepath),
|
||||
$grouped_footprints = array_map(fn($group) => array_map(fn($placeholder_filepath) => [
|
||||
'filename' => basename((string) $placeholder_filepath),
|
||||
'assets_path' => $urlGenerator->placeholderPathToAssetPath($placeholder_filepath),
|
||||
];
|
||||
}, $group);
|
||||
}, $grouped_footprints);
|
||||
], $group), $grouped_footprints);
|
||||
|
||||
return $this->render('tools/builtin_footprints_viewer/builtin_footprints_viewer.html.twig', [
|
||||
'grouped_footprints' => $grouped_footprints,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/ic_logos", name="tools_ic_logos")
|
||||
* @return Response
|
||||
*/
|
||||
#[Route(path: '/ic_logos', name: 'tools_ic_logos')]
|
||||
public function icLogos(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@tools.ic_logos');
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
|
@ -36,21 +37,15 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||
|
||||
/**
|
||||
* This controller has the purpose to provide the data for all treeviews.
|
||||
*
|
||||
* @Route("/tree")
|
||||
*/
|
||||
#[Route(path: '/tree')]
|
||||
class TreeController extends AbstractController
|
||||
{
|
||||
protected TreeViewGenerator $treeGenerator;
|
||||
|
||||
public function __construct(TreeViewGenerator $treeGenerator)
|
||||
public function __construct(protected TreeViewGenerator $treeGenerator)
|
||||
{
|
||||
$this->treeGenerator = $treeGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/tools", name="tree_tools")
|
||||
*/
|
||||
#[Route(path: '/tools', name: 'tree_tools')]
|
||||
public function tools(ToolsTreeBuilder $builder): JsonResponse
|
||||
{
|
||||
$tree = $builder->getTree();
|
||||
|
@ -58,90 +53,78 @@ class TreeController extends AbstractController
|
|||
return new JsonResponse($tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/category/{id}", name="tree_category")
|
||||
* @Route("/categories", name="tree_category_root")
|
||||
*/
|
||||
#[Route(path: '/category/{id}', name: 'tree_category')]
|
||||
#[Route(path: '/categories', name: 'tree_category_root')]
|
||||
public function categoryTree(?Category $category = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@categories.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Category::class, $category, 'list_parts_root');
|
||||
} else {
|
||||
return new JsonResponse("Access denied", 403);
|
||||
return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return new JsonResponse($tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/footprint/{id}", name="tree_footprint")
|
||||
* @Route("/footprints", name="tree_footprint_root")
|
||||
*/
|
||||
#[Route(path: '/footprint/{id}', name: 'tree_footprint')]
|
||||
#[Route(path: '/footprints', name: 'tree_footprint_root')]
|
||||
public function footprintTree(?Footprint $footprint = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@footprints.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Footprint::class, $footprint, 'list_parts_root');
|
||||
} else {
|
||||
return new JsonResponse("Access denied", 403);
|
||||
return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
return new JsonResponse($tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/location/{id}", name="tree_location")
|
||||
* @Route("/locations", name="tree_location_root")
|
||||
*/
|
||||
#[Route(path: '/location/{id}', name: 'tree_location')]
|
||||
#[Route(path: '/locations', name: 'tree_location_root')]
|
||||
public function locationTree(?Storelocation $location = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Storelocation::class, $location, 'list_parts_root');
|
||||
} else {
|
||||
return new JsonResponse("Access denied", 403);
|
||||
return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return new JsonResponse($tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/manufacturer/{id}", name="tree_manufacturer")
|
||||
* @Route("/manufacturers", name="tree_manufacturer_root")
|
||||
*/
|
||||
#[Route(path: '/manufacturer/{id}', name: 'tree_manufacturer')]
|
||||
#[Route(path: '/manufacturers', name: 'tree_manufacturer_root')]
|
||||
public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@manufacturers.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Manufacturer::class, $manufacturer, 'list_parts_root');
|
||||
} else {
|
||||
return new JsonResponse("Access denied", 403);
|
||||
return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return new JsonResponse($tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/supplier/{id}", name="tree_supplier")
|
||||
* @Route("/suppliers", name="tree_supplier_root")
|
||||
*/
|
||||
#[Route(path: '/supplier/{id}', name: 'tree_supplier')]
|
||||
#[Route(path: '/suppliers', name: 'tree_supplier_root')]
|
||||
public function supplierTree(?Supplier $supplier = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@suppliers.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Supplier::class, $supplier, 'list_parts_root');
|
||||
} else {
|
||||
return new JsonResponse("Access denied", 403);
|
||||
return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return new JsonResponse($tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/device/{id}", name="tree_device")
|
||||
* @Route("/devices", name="tree_device_root")
|
||||
*/
|
||||
#[Route(path: '/device/{id}', name: 'tree_device')]
|
||||
#[Route(path: '/devices', name: 'tree_device_root')]
|
||||
public function deviceTree(?Project $device = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@projects.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Project::class, $device, 'devices');
|
||||
} else {
|
||||
return new JsonResponse("Access denied", 403);
|
||||
return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return new JsonResponse($tree);
|
||||
|
|
|
@ -22,6 +22,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parameters\AttachmentTypeParameter;
|
||||
use App\Entity\Parameters\CategoryParameter;
|
||||
use App\Entity\Parameters\ProjectParameter;
|
||||
|
@ -51,23 +55,15 @@ use Symfony\Component\Serializer\Serializer;
|
|||
|
||||
/**
|
||||
* In this controller the endpoints for the typeaheads are collected.
|
||||
*
|
||||
* @Route("/typeahead")
|
||||
*/
|
||||
#[Route(path: '/typeahead')]
|
||||
class TypeaheadController extends AbstractController
|
||||
{
|
||||
protected AttachmentURLGenerator $urlGenerator;
|
||||
protected Packages $assets;
|
||||
|
||||
public function __construct(AttachmentURLGenerator $URLGenerator, Packages $assets)
|
||||
public function __construct(protected AttachmentURLGenerator $urlGenerator, protected Packages $assets)
|
||||
{
|
||||
$this->urlGenerator = $URLGenerator;
|
||||
$this->assets = $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/builtInResources/search", name="typeahead_builtInRessources")
|
||||
*/
|
||||
#[Route(path: '/builtInResources/search', name: 'typeahead_builtInRessources')]
|
||||
public function builtInResources(Request $request, BuiltinAttachmentsFinder $finder): JsonResponse
|
||||
{
|
||||
$query = $request->get('query');
|
||||
|
@ -91,51 +87,32 @@ class TypeaheadController extends AbstractController
|
|||
$serializer = new Serializer($normalizers, $encoders);
|
||||
$data = $serializer->serialize($result, 'json');
|
||||
|
||||
return new JsonResponse($data, 200, [], true);
|
||||
return new JsonResponse($data, Response::HTTP_OK, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function map the parameter type to the class, so we can access its repository
|
||||
* @param string $type
|
||||
* @return class-string
|
||||
*/
|
||||
private function typeToParameterClass(string $type): string
|
||||
{
|
||||
switch ($type) {
|
||||
case 'category':
|
||||
return CategoryParameter::class;
|
||||
case 'part':
|
||||
return PartParameter::class;
|
||||
case 'device':
|
||||
return ProjectParameter::class;
|
||||
case 'footprint':
|
||||
return FootprintParameter::class;
|
||||
case 'manufacturer':
|
||||
return ManufacturerParameter::class;
|
||||
case 'storelocation':
|
||||
return StorelocationParameter::class;
|
||||
case 'supplier':
|
||||
return SupplierParameter::class;
|
||||
case 'attachment_type':
|
||||
return AttachmentTypeParameter::class;
|
||||
case 'group':
|
||||
return GroupParameter::class;
|
||||
case 'measurement_unit':
|
||||
return MeasurementUnitParameter::class;
|
||||
case 'currency':
|
||||
return Currency::class;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid parameter type: '.$type);
|
||||
}
|
||||
return match ($type) {
|
||||
'category' => CategoryParameter::class,
|
||||
'part' => PartParameter::class,
|
||||
'device' => ProjectParameter::class,
|
||||
'footprint' => FootprintParameter::class,
|
||||
'manufacturer' => ManufacturerParameter::class,
|
||||
'storelocation' => StorelocationParameter::class,
|
||||
'supplier' => SupplierParameter::class,
|
||||
'attachment_type' => AttachmentTypeParameter::class,
|
||||
'group' => GroupParameter::class,
|
||||
'measurement_unit' => MeasurementUnitParameter::class,
|
||||
'currency' => Currency::class,
|
||||
default => throw new \InvalidArgumentException('Invalid parameter type: '.$type),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parts/search/{query}", name="typeahead_parts")
|
||||
* @param string $query
|
||||
* @param EntityManagerInterface $entityManager
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/parts/search/{query}', name: 'typeahead_parts')]
|
||||
public function parts(EntityManagerInterface $entityManager, PartPreviewGenerator $previewGenerator,
|
||||
AttachmentURLGenerator $attachmentURLGenerator, string $query = ""): JsonResponse
|
||||
{
|
||||
|
@ -149,7 +126,7 @@ class TypeaheadController extends AbstractController
|
|||
foreach ($parts as $part) {
|
||||
//Determine the picture to show:
|
||||
$preview_attachment = $previewGenerator->getTablePreviewAttachment($part);
|
||||
if($preview_attachment !== null) {
|
||||
if($preview_attachment instanceof Attachment) {
|
||||
$preview_url = $attachmentURLGenerator->getThumbnailURL($preview_attachment, 'thumbnail_sm');
|
||||
} else {
|
||||
$preview_url = '';
|
||||
|
@ -159,8 +136,8 @@ class TypeaheadController extends AbstractController
|
|||
$data[] = [
|
||||
'id' => $part->getID(),
|
||||
'name' => $part->getName(),
|
||||
'category' => $part->getCategory() ? $part->getCategory()->getName() : 'Unknown',
|
||||
'footprint' => $part->getFootprint() ? $part->getFootprint()->getName() : '',
|
||||
'category' => $part->getCategory() instanceof Category ? $part->getCategory()->getName() : 'Unknown',
|
||||
'footprint' => $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getName() : '',
|
||||
'description' => mb_strimwidth($part->getDescription(), 0, 127, '...'),
|
||||
'image' => $preview_url,
|
||||
];
|
||||
|
@ -169,11 +146,7 @@ class TypeaheadController extends AbstractController
|
|||
return new JsonResponse($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parameters/{type}/search/{query}", name="typeahead_parameters", requirements={"type" = ".+"})
|
||||
* @param string $query
|
||||
* @return JsonResponse
|
||||
*/
|
||||
#[Route(path: '/parameters/{type}/search/{query}', name: 'typeahead_parameters', requirements: ['type' => '.+'])]
|
||||
public function parameters(string $type, EntityManagerInterface $entityManager, string $query = ""): JsonResponse
|
||||
{
|
||||
$class = $this->typeToParameterClass($type);
|
||||
|
@ -190,9 +163,7 @@ class TypeaheadController extends AbstractController
|
|||
return new JsonResponse($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/tags/search/{query}", name="typeahead_tags", requirements={"query"= ".+"})
|
||||
*/
|
||||
#[Route(path: '/tags/search/{query}', name: 'typeahead_tags', requirements: ['query' => '.+'])]
|
||||
public function tags(string $query, TagFinder $finder): JsonResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@parts.read');
|
||||
|
@ -209,6 +180,6 @@ class TypeaheadController extends AbstractController
|
|||
$serializer = new Serializer($normalizers, $encoders);
|
||||
$data = $serializer->serialize($array, 'json');
|
||||
|
||||
return new JsonResponse($data, 200, [], true);
|
||||
return new JsonResponse($data, Response::HTTP_OK, [], true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AdminPages\BaseAdminController;
|
||||
use App\DataTables\LogDataTable;
|
||||
use App\Entity\Attachments\UserAttachment;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
|
@ -46,11 +47,8 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/user")
|
||||
* Class UserController
|
||||
*/
|
||||
class UserController extends AdminPages\BaseAdminController
|
||||
#[Route(path: '/user')]
|
||||
class UserController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = User::class;
|
||||
protected string $twig_template = 'admin/user_admin.html.twig';
|
||||
|
@ -76,11 +74,11 @@ class UserController extends AdminPages\BaseAdminController
|
|||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="user_edit")
|
||||
* @Route("/{id}/", requirements={"id"="\d+"})
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'user_edit')]
|
||||
#[Route(path: '/{id}/', requirements: ['id' => '\d+'])]
|
||||
public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response
|
||||
{
|
||||
//Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet)
|
||||
|
@ -90,7 +88,7 @@ class UserController extends AdminPages\BaseAdminController
|
|||
if ($request->request->has('reset_2fa')) {
|
||||
//Check if the admin has the needed permissions
|
||||
$this->denyAccessUnlessGranted('set_password', $entity);
|
||||
if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) {
|
||||
if ($this->isCsrfTokenValid('reset_2fa'.$entity->getID(), $request->request->get('_token'))) {
|
||||
//Disable Google authenticator
|
||||
$entity->setGoogleAuthenticatorSecret(null);
|
||||
$entity->setBackupCodes([]);
|
||||
|
@ -98,7 +96,7 @@ class UserController extends AdminPages\BaseAdminController
|
|||
foreach ($entity->getLegacyU2FKeys() as $key) {
|
||||
$em->remove($key);
|
||||
}
|
||||
foreach ($entity->getWebAuthnKeys() as $key) {
|
||||
foreach ($entity->getWebauthnKeys() as $key) {
|
||||
$em->remove($key);
|
||||
}
|
||||
//Invalidate trusted devices
|
||||
|
@ -117,7 +115,7 @@ class UserController extends AdminPages\BaseAdminController
|
|||
//Handle permissions presets
|
||||
if ($request->request->has('permission_preset')) {
|
||||
$this->denyAccessUnlessGranted('edit_permissions', $entity);
|
||||
if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) {
|
||||
if ($this->isCsrfTokenValid('reset_2fa'.$entity->getID(), $request->request->get('_token'))) {
|
||||
$preset = $request->request->get('permission_preset');
|
||||
|
||||
$permissionPresetsHelper->applyPreset($entity, $preset);
|
||||
|
@ -148,19 +146,15 @@ class UserController extends AdminPages\BaseAdminController
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="user_new")
|
||||
* @Route("/{id}/clone", name="user_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
#[Route(path: '/new', name: 'user_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'user_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?User $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}", name="user_delete", methods={"DELETE"}, requirements={"id"="\d+"})
|
||||
*/
|
||||
#[Route(path: '/{id}', name: 'user_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
|
||||
public function delete(Request $request, User $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
if (User::ID_ANONYMOUS === $entity->getID()) {
|
||||
|
@ -170,30 +164,24 @@ class UserController extends AdminPages\BaseAdminController
|
|||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export", name="user_export_all")
|
||||
*/
|
||||
#[Route(path: '/export', name: 'user_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/export", name="user_export")
|
||||
*/
|
||||
#[Route(path: '/{id}/export', name: 'user_export')]
|
||||
public function exportEntity(User $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/info", name="user_info_self")
|
||||
* @Route("/{id}/info", name="user_info")
|
||||
*/
|
||||
#[Route(path: '/info', name: 'user_info_self')]
|
||||
#[Route(path: '/{id}/info', name: 'user_info')]
|
||||
public function userInfo(?User $user, Packages $packages, Request $request, DataTableFactory $dataTableFactory): Response
|
||||
{
|
||||
//If no user id was passed, then we show info about the current user
|
||||
if (null === $user) {
|
||||
if (!$user instanceof User) {
|
||||
$tmp = $this->getUser();
|
||||
if (!$tmp instanceof User) {
|
||||
throw new InvalidArgumentException('Userinfo only works for database users!');
|
||||
|
@ -229,7 +217,7 @@ class UserController extends AdminPages\BaseAdminController
|
|||
'data' => $user,
|
||||
]);
|
||||
|
||||
return $this->renderForm('users/user_info.html.twig', [
|
||||
return $this->render('users/user_info.html.twig', [
|
||||
'user' => $user,
|
||||
'form' => $builder->getForm(),
|
||||
'datatable' => $table ?? null,
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\UserSystem\U2FKey;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Entity\UserSystem\WebauthnKey;
|
||||
|
@ -51,28 +52,21 @@ use Symfony\Component\Routing\Annotation\Route;
|
|||
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
|
||||
/**
|
||||
* @Route("/user")
|
||||
*/
|
||||
#[Route(path: '/user')]
|
||||
class UserSettingsController extends AbstractController
|
||||
{
|
||||
protected bool $demo_mode;
|
||||
|
||||
/**
|
||||
* @var EventDispatcher|EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
public function __construct(bool $demo_mode, EventDispatcherInterface $eventDispatcher)
|
||||
public function __construct(protected bool $demo_mode, EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->demo_mode = $demo_mode;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/2fa_backup_codes", name="show_backup_codes")
|
||||
*/
|
||||
public function showBackupCodes()
|
||||
#[Route(path: '/2fa_backup_codes', name: 'show_backup_codes')]
|
||||
public function showBackupCodes(): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
|
@ -80,14 +74,14 @@ class UserSettingsController extends AbstractController
|
|||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
throw new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
if ($user->isSamlUser()) {
|
||||
throw new RuntimeException('You can not remove U2F keys from SAML users!');
|
||||
}
|
||||
|
||||
if (empty($user->getBackupCodes())) {
|
||||
if ($user->getBackupCodes() === []) {
|
||||
$this->addFlash('error', 'tfa_backup.no_codes_enabled');
|
||||
|
||||
throw new RuntimeException('You do not have any backup codes enabled, therefore you can not view them!');
|
||||
|
@ -98,9 +92,7 @@ class UserSettingsController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/u2f_delete", name="u2f_delete", methods={"DELETE"})
|
||||
*/
|
||||
#[Route(path: '/u2f_delete', name: 'u2f_delete', methods: ['DELETE'])]
|
||||
public function removeU2FToken(Request $request, EntityManagerInterface $entityManager, BackupCodeManager $backupCodeManager): RedirectResponse
|
||||
{
|
||||
if ($this->demo_mode) {
|
||||
|
@ -120,31 +112,28 @@ class UserSettingsController extends AbstractController
|
|||
throw new RuntimeException('You can not remove U2F keys from SAML users!');
|
||||
}
|
||||
|
||||
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
|
||||
if ($this->isCsrfTokenValid('delete'.$user->getID(), $request->request->get('_token'))) {
|
||||
//Handle U2F key removal
|
||||
if ($request->request->has('key_id')) {
|
||||
$key_id = $request->request->get('key_id');
|
||||
$key_repo = $entityManager->getRepository(U2FKey::class);
|
||||
/** @var U2FKey|null $u2f */
|
||||
$u2f = $key_repo->find($key_id);
|
||||
if (null === $u2f) {
|
||||
if (!$u2f instanceof U2FKey) {
|
||||
$this->addFlash('danger', 'tfa_u2f.u2f_delete.not_existing');
|
||||
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
//User can only delete its own U2F keys
|
||||
if ($u2f->getUser() !== $user) {
|
||||
$this->addFlash('danger', 'tfa_u2f.u2f_delete.access_denied');
|
||||
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
$backupCodeManager->disableBackupCodesIfUnused($user);
|
||||
$entityManager->remove($u2f);
|
||||
$entityManager->flush();
|
||||
$this->addFlash('success', 'tfa.u2f.u2f_delete.success');
|
||||
|
||||
$security_event = new SecurityEvent($user);
|
||||
$this->eventDispatcher->dispatch($security_event, SecurityEvents::U2F_REMOVED);
|
||||
} elseif ($request->request->has('webauthn_key_id')) {
|
||||
|
@ -152,24 +141,21 @@ class UserSettingsController extends AbstractController
|
|||
$key_repo = $entityManager->getRepository(WebauthnKey::class);
|
||||
/** @var WebauthnKey|null $key */
|
||||
$key = $key_repo->find($key_id);
|
||||
if (null === $key) {
|
||||
if (!$key instanceof WebauthnKey) {
|
||||
$this->addFlash('error', 'tfa_u2f.u2f_delete.not_existing');
|
||||
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
//User can only delete its own U2F keys
|
||||
if ($key->getUser() !== $user) {
|
||||
$this->addFlash('error', 'tfa_u2f.u2f_delete.access_denied');
|
||||
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
$backupCodeManager->disableBackupCodesIfUnused($user);
|
||||
$entityManager->remove($key);
|
||||
$entityManager->flush();
|
||||
$this->addFlash('success', 'tfa.u2f.u2f_delete.success');
|
||||
|
||||
$security_event = new SecurityEvent($user);
|
||||
$this->eventDispatcher->dispatch($security_event, SecurityEvents::U2F_REMOVED);
|
||||
}
|
||||
|
@ -180,12 +166,8 @@ class UserSettingsController extends AbstractController
|
|||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/invalidate_trustedDevices", name="tfa_trustedDevices_invalidate", methods={"DELETE"})
|
||||
*
|
||||
* @return RuntimeException|RedirectResponse
|
||||
*/
|
||||
public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager)
|
||||
#[Route(path: '/invalidate_trustedDevices', name: 'tfa_trustedDevices_invalidate', methods: ['DELETE'])]
|
||||
public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager): \RuntimeException|RedirectResponse
|
||||
{
|
||||
if ($this->demo_mode) {
|
||||
throw new RuntimeException('You can not do 2FA things in demo mode');
|
||||
|
@ -204,7 +186,7 @@ class UserSettingsController extends AbstractController
|
|||
throw new RuntimeException('You can not remove U2F keys from SAML users!');
|
||||
}
|
||||
|
||||
if ($this->isCsrfTokenValid('devices_reset'.$user->getId(), $request->request->get('_token'))) {
|
||||
if ($this->isCsrfTokenValid('devices_reset'.$user->getID(), $request->request->get('_token'))) {
|
||||
$user->invalidateTrustedDeviceTokens();
|
||||
$entityManager->flush();
|
||||
$this->addFlash('success', 'tfa_trustedDevice.invalidate.success');
|
||||
|
@ -219,11 +201,10 @@ class UserSettingsController extends AbstractController
|
|||
}
|
||||
|
||||
/**
|
||||
* @Route("/settings", name="user_settings")
|
||||
*
|
||||
* @return RedirectResponse|Response
|
||||
*/
|
||||
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordHasherInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager, FormFactoryInterface $formFactory, UserAvatarHelper $avatarHelper)
|
||||
#[Route(path: '/settings', name: 'user_settings')]
|
||||
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordHasherInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager, FormFactoryInterface $formFactory, UserAvatarHelper $avatarHelper): RedirectResponse|Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $this->getUser();
|
||||
|
@ -262,14 +243,12 @@ class UserSettingsController extends AbstractController
|
|||
}
|
||||
|
||||
/** @var Form $form We need a form implementation for the next calls */
|
||||
if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName()) {
|
||||
//Remove the avatar attachment from the user if requested
|
||||
if ($user->getMasterPictureAttachment() !== null) {
|
||||
if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName() && $user->getMasterPictureAttachment() instanceof Attachment) {
|
||||
$em->remove($user->getMasterPictureAttachment());
|
||||
$user->setMasterPictureAttachment(null);
|
||||
$page_need_reload = true;
|
||||
}
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.saved_flash');
|
||||
|
@ -346,7 +325,7 @@ class UserSettingsController extends AbstractController
|
|||
'disabled' => $this->demo_mode || $user->isSamlUser(),
|
||||
]);
|
||||
$google_enabled = $user->isGoogleAuthenticatorEnabled();
|
||||
if (!$google_enabled && !$form->isSubmitted()) {
|
||||
if (!$google_enabled && !$google_form->isSubmitted()) {
|
||||
$user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret());
|
||||
$google_form->get('googleAuthenticatorSecret')->setData($user->getGoogleAuthenticatorSecret());
|
||||
}
|
||||
|
@ -382,7 +361,7 @@ class UserSettingsController extends AbstractController
|
|||
'attr' => [
|
||||
'class' => 'btn-danger',
|
||||
],
|
||||
'disabled' => empty($user->getBackupCodes()),
|
||||
'disabled' => $user->getBackupCodes() === [],
|
||||
])->getForm();
|
||||
|
||||
$backup_form->handleRequest($request);
|
||||
|
@ -397,7 +376,7 @@ class UserSettingsController extends AbstractController
|
|||
* Output both forms
|
||||
*****************************/
|
||||
|
||||
return $this->renderForm('users/user_settings.html.twig', [
|
||||
return $this->render('users/user_settings.html.twig', [
|
||||
'user' => $user,
|
||||
'settings_form' => $form,
|
||||
'pw_form' => $pw_form,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,7 +20,6 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
|
@ -27,23 +29,19 @@ use Jbtronics\TFAWebauthn\Services\TFAWebauthnRegistrationHelper;
|
|||
use RuntimeException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
class WebauthnKeyRegistrationController extends AbstractController
|
||||
{
|
||||
private bool $demo_mode;
|
||||
|
||||
public function __construct(bool $demo_mode)
|
||||
public function __construct(private readonly bool $demo_mode)
|
||||
{
|
||||
$this->demo_mode = $demo_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/webauthn/register", name="webauthn_register")
|
||||
*/
|
||||
public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em)
|
||||
#[Route(path: '/webauthn/register', name: 'webauthn_register')]
|
||||
public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em): Response
|
||||
{
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
@ -75,14 +73,19 @@ class WebauthnKeyRegistrationController extends AbstractController
|
|||
//Check the response
|
||||
try {
|
||||
$new_key = $registrationHelper->checkRegistrationResponse($webauthnResponse);
|
||||
} catch (\Exception $exception) {
|
||||
} catch (\Exception) {
|
||||
$this->addFlash('error', t('tfa_u2f.add_key.registration_error'));
|
||||
return $this->redirectToRoute('webauthn_register');
|
||||
}
|
||||
|
||||
$user = $this->getUser();
|
||||
if (!$user instanceof User) {
|
||||
throw new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
$keyEntity = WebauthnKey::fromRegistration($new_key);
|
||||
$keyEntity->setName($keyName);
|
||||
$keyEntity->setUser($this->getUser());
|
||||
$keyEntity->setUser($user);
|
||||
|
||||
$em->persist($keyEntity);
|
||||
$em->flush();
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class AppFixtures extends Fixture
|
||||
{
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
// $product = new Product();
|
||||
// $manager->persist($product);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
|
@ -31,18 +31,17 @@ use App\Entity\Parts\Manufacturer;
|
|||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\UserSystem\User;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class DataStructureFixtures extends Fixture
|
||||
class DataStructureFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
protected EntityManagerInterface $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
public function __construct(protected EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,4 +108,11 @@ class DataStructureFixtures extends Fixture
|
|||
$manager->persist($node2_1);
|
||||
$manager->persist($node1_1_1);
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
UserFixtures::class
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,18 +30,12 @@ use Doctrine\Persistence\ObjectManager;
|
|||
|
||||
class GroupFixtures extends Fixture
|
||||
{
|
||||
public const ADMINS = 'group-admin';
|
||||
public const USERS = 'group-users';
|
||||
public const READONLY = 'group-readonly';
|
||||
final public const ADMINS = 'group-admin';
|
||||
final public const USERS = 'group-users';
|
||||
final public const READONLY = 'group-readonly';
|
||||
|
||||
|
||||
private PermissionPresetsHelper $permission_presets;
|
||||
private PermissionManager $permissionManager;
|
||||
|
||||
public function __construct(PermissionPresetsHelper $permissionPresetsHelper, PermissionManager $permissionManager)
|
||||
public function __construct(private readonly PermissionPresetsHelper $permission_presets, private readonly PermissionManager $permissionManager)
|
||||
{
|
||||
$this->permission_presets = $permissionPresetsHelper;
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
|
|
|
@ -41,19 +41,19 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use App\Entity\LabelSystem\BarcodeType;
|
||||
use App\Entity\LabelSystem\LabelOptions;
|
||||
use App\Entity\LabelSystem\LabelProcessMode;
|
||||
use App\Entity\LabelSystem\LabelProfile;
|
||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class LabelProfileFixtures extends Fixture
|
||||
{
|
||||
protected EntityManagerInterface $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
public function __construct(protected EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
|
@ -65,8 +65,8 @@ class LabelProfileFixtures extends Fixture
|
|||
|
||||
$option1 = new LabelOptions();
|
||||
$option1->setLines("[[NAME]]\n[[DESCRIPION]]");
|
||||
$option1->setBarcodeType('none');
|
||||
$option1->setSupportedElement('part');
|
||||
$option1->setBarcodeType(BarcodeType::NONE);
|
||||
$option1->setSupportedElement(LabelSupportedElement::PART);
|
||||
$profile1->setOptions($option1);
|
||||
|
||||
$manager->persist($profile1);
|
||||
|
@ -77,8 +77,8 @@ class LabelProfileFixtures extends Fixture
|
|||
|
||||
$option2 = new LabelOptions();
|
||||
$option2->setLines("[[NAME]]\n[[DESCRIPION]]");
|
||||
$option2->setBarcodeType('qr');
|
||||
$option2->setSupportedElement('part');
|
||||
$option2->setBarcodeType(BarcodeType::QR);
|
||||
$option2->setSupportedElement(LabelSupportedElement::PART);
|
||||
$profile2->setOptions($option2);
|
||||
|
||||
$manager->persist($profile2);
|
||||
|
@ -89,8 +89,8 @@ class LabelProfileFixtures extends Fixture
|
|||
|
||||
$option3 = new LabelOptions();
|
||||
$option3->setLines("[[NAME]]\n[[DESCRIPION]]");
|
||||
$option3->setBarcodeType('code128');
|
||||
$option3->setSupportedElement('part_lot');
|
||||
$option3->setBarcodeType(BarcodeType::CODE128);
|
||||
$option3->setSupportedElement(LabelSupportedElement::PART_LOT);
|
||||
$profile3->setOptions($option3);
|
||||
|
||||
$manager->persist($profile3);
|
||||
|
@ -101,13 +101,20 @@ class LabelProfileFixtures extends Fixture
|
|||
|
||||
$option4 = new LabelOptions();
|
||||
$option4->setLines('{{ element.name }}');
|
||||
$option4->setBarcodeType('code39');
|
||||
$option4->setSupportedElement('part');
|
||||
$option4->setLinesMode('twig');
|
||||
$option4->setBarcodeType(BarcodeType::CODE39);
|
||||
$option4->setSupportedElement(LabelSupportedElement::PART);
|
||||
$option4->setProcessMode(LabelProcessMode::TWIG);
|
||||
$profile4->setOptions($option4);
|
||||
|
||||
$manager->persist($profile4);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
PartFixtures::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,16 +55,14 @@ use App\Entity\PriceInformations\Pricedetail;
|
|||
use Brick\Math\BigDecimal;
|
||||
use DateTime;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class PartFixtures extends Fixture
|
||||
class PartFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
protected EntityManagerInterface $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
public function __construct(protected EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
|
@ -135,4 +133,11 @@ class PartFixtures extends Fixture
|
|||
$manager->persist($part);
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
DataStructureFixtures::class
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,19 +24,15 @@ namespace App\DataFixtures;
|
|||
|
||||
use App\Entity\UserSystem\User;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
class UserFixtures extends Fixture
|
||||
class UserFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
protected UserPasswordHasherInterface $encoder;
|
||||
protected EntityManagerInterface $em;
|
||||
|
||||
public function __construct(UserPasswordHasherInterface $encoder, EntityManagerInterface $entityManager)
|
||||
public function __construct(protected UserPasswordHasherInterface $encoder, protected EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
$this->encoder = $encoder;
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
|
@ -71,4 +67,11 @@ class UserFixtures extends Fixture
|
|||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [
|
||||
GroupFixtures::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
|
@ -17,7 +20,6 @@
|
|||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Adapters;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
@ -37,7 +39,7 @@ use Omines\DataTablesBundle\Adapter\Doctrine\FetchJoinORMAdapter;
|
|||
*/
|
||||
class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter
|
||||
{
|
||||
public function getCount(QueryBuilder $queryBuilder, $identifier): ?int
|
||||
public function getCount(QueryBuilder $queryBuilder, $identifier): int
|
||||
{
|
||||
$qb_without_group_by = clone $queryBuilder;
|
||||
|
||||
|
@ -48,6 +50,6 @@ class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter
|
|||
|
||||
$paginator = new Paginator($qb_without_group_by);
|
||||
|
||||
return $paginator->count();
|
||||
return $paginator->count() ?? 0;
|
||||
}
|
||||
}
|
|
@ -42,27 +42,14 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
|
||||
final class AttachmentDataTable implements DataTableTypeInterface
|
||||
{
|
||||
private TranslatorInterface $translator;
|
||||
private EntityURLGenerator $entityURLGenerator;
|
||||
private AttachmentManager $attachmentHelper;
|
||||
private ElementTypeNameGenerator $elementTypeNameGenerator;
|
||||
private AttachmentURLGenerator $attachmentURLGenerator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator, EntityURLGenerator $entityURLGenerator,
|
||||
AttachmentManager $attachmentHelper, AttachmentURLGenerator $attachmentURLGenerator,
|
||||
ElementTypeNameGenerator $elementTypeNameGenerator)
|
||||
public function __construct(private readonly TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator, private readonly AttachmentManager $attachmentHelper, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly ElementTypeNameGenerator $elementTypeNameGenerator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->entityURLGenerator = $entityURLGenerator;
|
||||
$this->attachmentHelper = $attachmentHelper;
|
||||
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
|
||||
$this->attachmentURLGenerator = $attachmentURLGenerator;
|
||||
}
|
||||
|
||||
public function configure(DataTable $dataTable, array $options): void
|
||||
{
|
||||
$dataTable->add('dont_matter', RowClassColumn::class, [
|
||||
'render' => function ($value, Attachment $context) {
|
||||
'render' => function ($value, Attachment $context): string {
|
||||
//Mark attachments with missing files yellow
|
||||
if(!$this->attachmentHelper->isFileExisting($context)){
|
||||
return 'table-warning';
|
||||
|
@ -75,7 +62,7 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
$dataTable->add('picture', TextColumn::class, [
|
||||
'label' => '',
|
||||
'className' => 'no-colvis',
|
||||
'render' => function ($value, Attachment $context) {
|
||||
'render' => function ($value, Attachment $context): string {
|
||||
if ($context->isPicture()
|
||||
&& !$context->isExternal()
|
||||
&& $this->attachmentHelper->isFileExisting($context)) {
|
||||
|
@ -125,25 +112,21 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
$dataTable->add('attachment_type', TextColumn::class, [
|
||||
'label' => 'attachment.table.type',
|
||||
'field' => 'attachment_type.name',
|
||||
'render' => function ($value, Attachment $context) {
|
||||
return sprintf(
|
||||
'render' => fn($value, Attachment $context): string => sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
$this->entityURLGenerator->editURL($context->getAttachmentType()),
|
||||
htmlspecialchars($value)
|
||||
);
|
||||
},
|
||||
htmlspecialchars((string) $value)
|
||||
),
|
||||
]);
|
||||
|
||||
$dataTable->add('element', TextColumn::class, [
|
||||
'label' => 'attachment.table.element',
|
||||
//'propertyPath' => 'element.name',
|
||||
'render' => function ($value, Attachment $context) {
|
||||
return sprintf(
|
||||
'render' => fn($value, Attachment $context): string => sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
$this->entityURLGenerator->infoURL($context->getElement()),
|
||||
$this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true)
|
||||
);
|
||||
},
|
||||
),
|
||||
]);
|
||||
|
||||
$dataTable->add('filename', TextColumn::class, [
|
||||
|
|
|
@ -31,13 +31,8 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
|||
|
||||
class EntityColumn extends AbstractColumn
|
||||
{
|
||||
protected EntityURLGenerator $urlGenerator;
|
||||
protected PropertyAccessorInterface $accessor;
|
||||
|
||||
public function __construct(EntityURLGenerator $URLGenerator, PropertyAccessorInterface $accessor)
|
||||
public function __construct(protected EntityURLGenerator $urlGenerator, protected PropertyAccessorInterface $accessor)
|
||||
{
|
||||
$this->urlGenerator = $URLGenerator;
|
||||
$this->accessor = $accessor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,24 +41,24 @@ class EntityColumn extends AbstractColumn
|
|||
* @param mixed $value The single value of the column
|
||||
* @return mixed
|
||||
*/
|
||||
public function normalize($value)
|
||||
public function normalize($value): mixed
|
||||
{
|
||||
/** @var AbstractNamedDBElement $value */
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setRequired('property');
|
||||
|
||||
$resolver->setDefault('field', static function (Options $option) {
|
||||
return $option['property'].'.name';
|
||||
});
|
||||
$resolver->setDefault('field', static fn(Options $option): string => $option['property'].'.name');
|
||||
|
||||
$resolver->setDefault('render', function (Options $options) {
|
||||
return function ($value, $context) use ($options) {
|
||||
$resolver->setDefault('render', fn(Options $options) => function ($value, $context) use ($options): string {
|
||||
if ($this->accessor->isReadable($context, $options['property'])) {
|
||||
$entity = $this->accessor->getValue($context, $options['property']);
|
||||
} else {
|
||||
|
@ -72,7 +67,7 @@ class EntityColumn extends AbstractColumn
|
|||
|
||||
/** @var AbstractNamedDBElement|null $entity */
|
||||
|
||||
if (null !== $entity) {
|
||||
if ($entity instanceof AbstractNamedDBElement) {
|
||||
if (null !== $entity->getID()) {
|
||||
return sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
|
@ -85,7 +80,6 @@ class EntityColumn extends AbstractColumn
|
|||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
});
|
||||
|
||||
return $this;
|
||||
|
|
64
src/DataTables/Column/EnumColumn.php
Normal file
64
src/DataTables/Column/EnumColumn.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Column;
|
||||
|
||||
use Omines\DataTablesBundle\Column\AbstractColumn;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use UnitEnum;
|
||||
|
||||
/**
|
||||
* @template T of UnitEnum
|
||||
*/
|
||||
class EnumColumn extends AbstractColumn
|
||||
{
|
||||
|
||||
/**
|
||||
* @phpstan-return T
|
||||
*/
|
||||
public function normalize($value): UnitEnum
|
||||
{
|
||||
if (is_a($value, $this->getEnumClass())) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
return ($this->getEnumClass())::from($value);
|
||||
}
|
||||
|
||||
protected function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setRequired('class');
|
||||
$resolver->setAllowedTypes('class', 'string');
|
||||
$resolver->addAllowedValues('class', enum_exists(...));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<T>
|
||||
*/
|
||||
public function getEnumClass(): string
|
||||
{
|
||||
return $this->options['class'];
|
||||
}
|
||||
}
|
|
@ -50,12 +50,15 @@ class IconLinkColumn extends AbstractColumn
|
|||
* @param $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function normalize($value)
|
||||
public function normalize($value): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
$resolver->setDefaults([
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue