forked from mirror/Part-DB.Part-DB-server
Compare commits
38 commits
master
...
settings-b
Author | SHA1 | Date | |
---|---|---|---|
|
3d4e91fc69 | ||
|
97aed847b6 | ||
|
8750573724 | ||
|
1ac9641a04 | ||
|
ad47c8d8ed | ||
|
0dbf417866 | ||
|
a45bf22ac5 | ||
|
b5a6ba921c | ||
|
f6a2467eae | ||
|
79da0518c2 | ||
|
5e512f8935 | ||
|
99c10ffe85 | ||
|
47830dcd08 | ||
|
947cce78d7 | ||
|
74e555d25d | ||
|
e9973af8f4 | ||
|
2ab2b7f77d | ||
|
463812fb3d | ||
|
d2406726c6 | ||
|
1f04d1b993 | ||
|
2ef46cdd34 | ||
|
2bc50b2888 | ||
|
9e0f86788d | ||
|
2681c7ded3 | ||
|
5ab6a63492 | ||
|
6df7bc5f2a | ||
|
a0a7ca3c9c | ||
|
f88584e1ca | ||
|
3e657a7cac | ||
|
7cc67f8bb1 | ||
|
0772d85918 | ||
|
26d83af298 | ||
|
4876068cce | ||
|
08ae313dfe | ||
|
3967c53468 | ||
|
7ad077862c | ||
|
5a563e4f8f | ||
|
5a4b7c525b |
193 changed files with 6816 additions and 9268 deletions
|
@ -44,8 +44,6 @@
|
|||
PassEnv PROVIDER_MOUSER_KEY PROVIDER_MOUSER_SEARCH_OPTION PROVIDER_MOUSER_SEARCH_LIMIT PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE
|
||||
PassEnv PROVIDER_LCSC_ENABLED PROVIDER_LCSC_CURRENCY
|
||||
PassEnv PROVIDER_OEMSECRETS_KEY PROVIDER_OEMSECRETS_COUNTRY_CODE PROVIDER_OEMSECRETS_CURRENCY PROVIDER_OEMSECRETS_ZERO_PRICE PROVIDER_OEMSECRETS_SET_PARAM PROVIDER_OEMSECRETS_SORT_CRITERIA
|
||||
PassEnv PROVIDER_REICHELT_ENABLED PROVIDER_REICHELT_CURRENCY PROVIDER_REICHELT_COUNTRY PROVIDER_REICHELT_LANGUAGE PROVIDER_REICHELT_INCLUDE_VAT
|
||||
PassEnv PROVIDER_POLLIN_ENABLED
|
||||
PassEnv EDA_KICAD_CATEGORY_DEPTH
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
|
|
73
.env
73
.env
|
@ -32,36 +32,16 @@ DATABASE_EMULATE_NATURAL_SORT=0
|
|||
###################################################################################
|
||||
|
||||
# The language to use serverwide as default (en, de, ru, etc.)
|
||||
DEFAULT_LANG="en"
|
||||
#DEFAULT_LANG="en"
|
||||
# The default timezone to use serverwide (e.g. Europe/Berlin)
|
||||
DEFAULT_TIMEZONE="Europe/Berlin"
|
||||
#DEFAULT_TIMEZONE="Europe/Berlin"
|
||||
# The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
|
||||
BASE_CURRENCY="EUR"
|
||||
# The name of this installation. This will be shown as title in the browser and in the header of the website
|
||||
INSTANCE_NAME="Part-DB"
|
||||
# Allow users to download attachments to the server by providing an URL
|
||||
# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet)
|
||||
ALLOW_ATTACHMENT_DOWNLOADS=0
|
||||
# Set this to 1, if the "download external files" checkbox should be checked by default for new attachments
|
||||
ATTACHMENT_DOWNLOAD_BY_DEFAULT=0
|
||||
# Use gravatars for user avatars, when user has no own avatar defined
|
||||
USE_GRAVATAR=0
|
||||
# The maximum allowed size for attachment files in bytes (you can use M for megabytes and G for gigabytes)
|
||||
# Please note that the php.ini setting upload_max_filesize also limits the maximum size of uploaded files
|
||||
MAX_ATTACHMENT_FILE_SIZE="100M"
|
||||
#BASE_CURRENCY="EUR"
|
||||
|
||||
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates
|
||||
# This must end with a slash!
|
||||
DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||
|
||||
# With this option you can configure, where users are enforced to give a change reason, which will be logged
|
||||
# This is a comma separated list of values, see documentation for available values
|
||||
# Leave this empty, to make all change reasons optional
|
||||
ENFORCE_CHANGE_COMMENTS_FOR=""
|
||||
|
||||
# Disable that if you do not want that Part-DB connects to GitHub to check for available updates, or if your server can not connect to the internet
|
||||
CHECK_FOR_UPDATES=1
|
||||
|
||||
###################################################################################
|
||||
# Email settings
|
||||
###################################################################################
|
||||
|
@ -78,21 +58,6 @@ EMAIL_SENDER_NAME="Part-DB Mailer"
|
|||
# Set this to 1 to allow reset of a password per email
|
||||
ALLOW_EMAIL_PW_RESET=0
|
||||
|
||||
######################################################################################
|
||||
# History/Eventlog settings
|
||||
######################################################################################
|
||||
# If you want to use full timetrave functionality all values below have to be set to 1
|
||||
|
||||
# Save which fields were changed in a ElementEdited log entry
|
||||
HISTORY_SAVE_CHANGED_FIELDS=1
|
||||
# Save the old data in the ElementEdited log entry (warning this could increase the database size in short time)
|
||||
HISTORY_SAVE_CHANGED_DATA=1
|
||||
# Save the data of an element that gets removed into log entry. This allows to undelete an element
|
||||
HISTORY_SAVE_REMOVED_DATA=1
|
||||
# Save the new data of an element that gets changed or added. This allows an easy comparison of the old and new data on the detail page
|
||||
# This option only becomes active when HISTORY_SAVE_CHANGED_DATA is set to 1
|
||||
HISTORY_SAVE_NEW_DATA=1
|
||||
|
||||
###################################################################################
|
||||
# Error pages settings
|
||||
###################################################################################
|
||||
|
@ -107,10 +72,10 @@ ERROR_PAGE_SHOW_HELP=1
|
|||
##################################################################################
|
||||
|
||||
# The default page size for the part table (set to -1 to show all parts on one page)
|
||||
TABLE_DEFAULT_PAGE_SIZE=50
|
||||
#TABLE_DEFAULT_PAGE_SIZE=50
|
||||
# Configure which columns will be visible by default in the parts table (and in which order).
|
||||
# This is a comma separated list of column names. See documentation for available values.
|
||||
TABLE_PARTS_DEFAULT_COLUMNS=name,description,category,footprint,manufacturer,storage_location,amount
|
||||
#TABLE_PARTS_DEFAULT_COLUMNS=name,description,category,footprint,manufacturer,storage_location,amount
|
||||
|
||||
##################################################################################
|
||||
# Info provider settings
|
||||
|
@ -143,8 +108,7 @@ PROVIDER_TME_CURRENCY=EUR
|
|||
PROVIDER_TME_LANGUAGE=en
|
||||
# The country to get results for
|
||||
PROVIDER_TME_COUNTRY=DE
|
||||
# [DEPRECATED] Set this to 1 to get gross prices (including VAT) instead of net prices
|
||||
# With private API keys, this option cannot be used anymore is ignored by Part-DB. The VAT inclusion depends on your TME account settings.
|
||||
# Set this to 1 to get gross prices (including VAT) instead of net prices
|
||||
PROVIDER_TME_GET_GROSS_PRICES=1
|
||||
|
||||
# Octopart / Nexar Provider:
|
||||
|
@ -216,35 +180,14 @@ PROVIDER_OEMSECRETS_SET_PARAM=1
|
|||
#If unset or set to any other value, no sorting is performed.
|
||||
PROVIDER_OEMSECRETS_SORT_CRITERIA=C
|
||||
|
||||
|
||||
# Reichelt provider:
|
||||
# Reichelt.com offers no official API, so this info provider webscrapes the website to extract info
|
||||
# It could break at any time, use it at your own risk
|
||||
# We dont require an API key for Reichelt, just set this to 1 to enable Reichelt support
|
||||
PROVIDER_REICHELT_ENABLED=0
|
||||
# The country to get prices for
|
||||
PROVIDER_REICHELT_COUNTRY=DE
|
||||
# The language to get results in (en, de, fr, nl, pl, it, es)
|
||||
PROVIDER_REICHELT_LANGUAGE=en
|
||||
# Include VAT in prices (set to 1 to include VAT, 0 to exclude VAT)
|
||||
PROVIDER_REICHELT_INCLUDE_VAT=1
|
||||
# The currency to get prices in (only for countries with countries other than EUR)
|
||||
PROVIDER_REICHELT_CURRENCY=EUR
|
||||
|
||||
# Pollin provider:
|
||||
# Pollin.de offers no official API, so this info provider webscrapes the website to extract info
|
||||
# It could break at any time, use it at your own risk
|
||||
# We dont require an API key for Pollin, just set this to 1 to enable Pollin support
|
||||
PROVIDER_POLLIN_ENABLED=0
|
||||
|
||||
##################################################################################
|
||||
# EDA integration related settings
|
||||
##################################################################################
|
||||
|
||||
# This value determines the depth of the category tree, that is visible inside KiCad
|
||||
# 0 means that only the top level categories are visible. Set to a value > 0 to show more levels.
|
||||
# Set to -1, to show all parts of Part-DB inside a single category in KiCad
|
||||
EDA_KICAD_CATEGORY_DEPTH=0
|
||||
# Set to -1, to show all parts of Part-DB inside a sigle cnategory in KiCad
|
||||
#EDA_KICAD_CATEGORY_DEPTH=0
|
||||
|
||||
###################################################################################
|
||||
# SAML Single sign on-settings
|
||||
|
|
|
@ -10,4 +10,6 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"
|
|||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||
|
||||
# Disable update checks, as tests would fail, when github is not reachable
|
||||
CHECK_FOR_UPDATES=0
|
||||
CHECK_FOR_UPDATES=0
|
||||
|
||||
INSTANCE_NAME="Part-DB"
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/.env.local.bak
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.16.1
|
||||
2.0.0-dev
|
|
@ -23,12 +23,6 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
|
|||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
|
||||
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
|
@ -52,12 +46,6 @@ export default class extends Controller {
|
|||
}
|
||||
return '<div>' + escape(data.label) + '</div>';
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
'clear_button': {},
|
||||
"restore_on_backspace": {}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ export default class extends Controller {
|
|||
|
||||
|
||||
let settings = {
|
||||
plugins: ["clear_button"],
|
||||
allowEmptyOption: true,
|
||||
selectOnTab: true,
|
||||
maxOptions: null,
|
||||
|
@ -50,7 +51,24 @@ export default class extends Controller {
|
|||
}
|
||||
};
|
||||
|
||||
//Load the drag_drop plugin if the select is ordered
|
||||
if (this.element.dataset.orderedValue) {
|
||||
settings.plugins.push('drag_drop');
|
||||
settings.plugins.push("caret_position");
|
||||
}
|
||||
|
||||
//If multiple items can be selected, enable the remove_button plugin
|
||||
if (this.element.multiple) {
|
||||
settings.plugins.push('remove_button');
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
|
||||
//If the select is ordered, we need to update the value field (with the decoded value from the orderedValue field)
|
||||
if (this.element.dataset.orderedValue) {
|
||||
const data = JSON.parse(this.element.dataset.orderedValue);
|
||||
this._tomSelect.setValue(data);
|
||||
}
|
||||
}
|
||||
|
||||
getTomSelect() {
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
import {Controller} from "@hotwired/stimulus";
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
// TODO: Merge with select_controller.js
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
|
|
|
@ -23,12 +23,6 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
|
|||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
|
||||
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
/**
|
||||
* This is the frontend controller for StaticFileAutocompleteType form element.
|
||||
* Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete.
|
||||
|
@ -52,13 +46,7 @@ export default class extends Controller {
|
|||
orderField: 'text',
|
||||
|
||||
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
|
||||
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING',
|
||||
plugins: {
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
'clear_button': {},
|
||||
'restore_on_backspace': {}
|
||||
}
|
||||
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING'
|
||||
};
|
||||
|
||||
if (this.element.dataset.url) {
|
||||
|
|
|
@ -24,9 +24,6 @@ import {Controller} from "@hotwired/stimulus";
|
|||
|
||||
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
|
||||
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
|
@ -40,15 +37,11 @@ export default class extends Controller {
|
|||
const allowAdd = this.element.getAttribute("data-allow-add") === "true";
|
||||
const addHint = this.element.getAttribute("data-add-hint") ?? "";
|
||||
|
||||
|
||||
|
||||
|
||||
let settings = {
|
||||
allowEmptyOption: true,
|
||||
selectOnTab: true,
|
||||
maxOptions: null,
|
||||
create: allowAdd ? this.createItem.bind(this) : false,
|
||||
createFilter: this.createFilter.bind(this),
|
||||
|
||||
// This three options allow us to paste element names with commas: (see issue #538)
|
||||
maxItems: 1,
|
||||
|
@ -88,17 +81,8 @@ export default class extends Controller {
|
|||
//Add callbacks to update validity
|
||||
onInitialize: this.updateValidity.bind(this),
|
||||
onChange: this.updateValidity.bind(this),
|
||||
|
||||
plugins: {
|
||||
"autoselect_typed": {},
|
||||
}
|
||||
};
|
||||
|
||||
//Add clear button plugin, if an empty option is present
|
||||
if (this.element.querySelector("option[value='']") !== null) {
|
||||
settings.plugins["clear_button"] = {};
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
//Do not do a sync here as this breaks the initial rendering of the empty option
|
||||
//this._tomSelect.sync();
|
||||
|
@ -129,31 +113,6 @@ export default class extends Controller {
|
|||
});
|
||||
}
|
||||
|
||||
createFilter(input) {
|
||||
|
||||
//Normalize the input (replace spacing around arrows)
|
||||
if (input.includes("->")) {
|
||||
const inputs = input.split("->");
|
||||
inputs.forEach((value, index) => {
|
||||
inputs[index] = value.trim();
|
||||
});
|
||||
input = inputs.join("->");
|
||||
} else {
|
||||
input = input.trim();
|
||||
}
|
||||
|
||||
const options = this._tomSelect.options;
|
||||
//Iterate over all options and check if the input is already present
|
||||
for (let index in options) {
|
||||
const option = options[index];
|
||||
if (option.path === input) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
updateValidity() {
|
||||
//Mark this input as invalid, if the selected option is disabled
|
||||
|
|
|
@ -23,21 +23,14 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
|
|||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
|
||||
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
let settings = {
|
||||
plugins: {
|
||||
remove_button:{},
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
remove_button:{
|
||||
}
|
||||
},
|
||||
persistent: false,
|
||||
selectOnTab: true,
|
||||
|
|
|
@ -22,13 +22,6 @@ import TomSelect from "tom-select";
|
|||
import katex from "katex";
|
||||
import "katex/dist/katex.css";
|
||||
|
||||
|
||||
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
|
||||
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller
|
||||
{
|
||||
|
@ -60,10 +53,7 @@ export default class extends Controller
|
|||
connect() {
|
||||
const settings = {
|
||||
plugins: {
|
||||
'autoselect_typed': {},
|
||||
'click_to_edit': {},
|
||||
'clear_button': {},
|
||||
'restore_on_backspace': {}
|
||||
clear_button:{}
|
||||
},
|
||||
persistent: false,
|
||||
maxItems: 1,
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
/**
|
||||
* Autoselect Typed plugin for Tomselect
|
||||
*
|
||||
* This plugin allows automatically selecting an option matching the typed text when the Tomselect element goes out of
|
||||
* focus (is blurred) and/or when the delimiter is typed.
|
||||
*
|
||||
* #select_on_blur option
|
||||
* Tomselect natively supports the "createOnBlur" option. This option picks up any remaining text in the input field
|
||||
* and uses it to create a new option and selects that option. It does behave a bit strangely though, in that it will
|
||||
* not select an already existing option when the input is blurred, so if you typed something that matches an option in
|
||||
* the list and then click outside the box (without pressing enter) the entered text is just removed (unless you have
|
||||
* allow duplicates on in which case it will create a new option).
|
||||
* This plugin fixes that, such that Tomselect will first try to select an option matching the remaining uncommitted
|
||||
* text and only when no matching option is found tries to create a new one (if createOnBlur and create is on)
|
||||
*
|
||||
* #select_on_delimiter option
|
||||
* Normally when typing the delimiter (space by default) Tomselect will try to create a new option (and select it) (if
|
||||
* create is on), but if the typed text matches an option (and allow duplicates is off) it refuses to react at all until
|
||||
* you press enter. With this option, the delimiter will also allow selecting an option, not just creating it.
|
||||
*/
|
||||
function select_current_input(self){
|
||||
if(self.isLocked){
|
||||
return
|
||||
}
|
||||
|
||||
const val = self.inputValue()
|
||||
//Do nothing if the input is empty
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
|
||||
if (self.options[val]) {
|
||||
self.addItem(val)
|
||||
self.setTextboxValue()
|
||||
}
|
||||
}
|
||||
|
||||
export default function(plugin_options_) {
|
||||
const plugin_options = Object.assign({
|
||||
//Autoselect the typed text when the input element goes out of focus
|
||||
select_on_blur: true,
|
||||
//Autoselect the typed text when the delimiter is typed
|
||||
select_on_delimiter: true,
|
||||
}, plugin_options_);
|
||||
|
||||
const self = this
|
||||
|
||||
if(plugin_options.select_on_blur) {
|
||||
this.hook("before", "onBlur", function () {
|
||||
select_current_input(self)
|
||||
})
|
||||
}
|
||||
|
||||
if(plugin_options.select_on_delimiter) {
|
||||
this.hook("before", "onKeyPress", function (e) {
|
||||
const character = String.fromCharCode(e.keyCode || e.which);
|
||||
if (self.settings.mode === 'multi' && character === self.settings.delimiter) {
|
||||
select_current_input(self)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/**
|
||||
* click_to_edit plugin for Tomselect
|
||||
*
|
||||
* This plugin allows editing (and selecting text in) any selected item by clicking it.
|
||||
*
|
||||
* Usually, when the user typed some text and created an item in Tomselect that item cannot be edited anymore. To make
|
||||
* a change, the item has to be deleted and retyped completely. There is also generally no way to copy text out of a
|
||||
* tomselect item. The "restore_on_backspace" plugin improves that somewhat, by allowing the user to edit an item after
|
||||
* pressing backspace. However, it is somewhat confusing to first have to focus the field an then hit backspace in order
|
||||
* to copy a piece of text. It may also not be immediately obvious for editing.
|
||||
* This plugin transforms an item into editable text when it is clicked, e.g. when the user tries to place the caret
|
||||
* within an item or when they try to drag across the text to highlight it.
|
||||
* It also plays nice with the remove_button plugin which still removes (deselects) an option entirely.
|
||||
*
|
||||
* It is recommended to also enable the autoselect_typed plugin when using this plugin. Without it, the text in the
|
||||
* input field (i.e. the item that was just clicked) is lost when the user clicks outside the field. Also, when the user
|
||||
* clicks an option (making it text) and then tries to enter another one by entering the delimiter (e.g. space) nothing
|
||||
* happens until enter is pressed or the text is changed from what it was.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return a dom element from either a dom query string, jQuery object, a dom element or html string
|
||||
* https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
|
||||
*
|
||||
* param query should be {}
|
||||
*/
|
||||
const getDom = query => {
|
||||
if (query.jquery) {
|
||||
return query[0];
|
||||
}
|
||||
if (query instanceof HTMLElement) {
|
||||
return query;
|
||||
}
|
||||
if (isHtmlString(query)) {
|
||||
var tpl = document.createElement('template');
|
||||
tpl.innerHTML = query.trim(); // Never return a text node of whitespace as the result
|
||||
return tpl.content.firstChild;
|
||||
}
|
||||
return document.querySelector(query);
|
||||
};
|
||||
const isHtmlString = arg => {
|
||||
if (typeof arg === 'string' && arg.indexOf('<') > -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function plugin(plugin_options_) {
|
||||
const self = this
|
||||
|
||||
const plugin_options = Object.assign({
|
||||
//If there is unsubmitted text in the input field, should that text be automatically used to select a matching
|
||||
//element? If this is off, clicking on item1 and then clicking on item2 will result in item1 being deselected
|
||||
auto_select_before_edit: true,
|
||||
//If there is unsubmitted text in the input field, should that text be automatically used to create a matching
|
||||
//element if no matching element was found or auto_select_before_edit is off?
|
||||
auto_create_before_edit: true,
|
||||
//customize this function to change which text the item is replaced with when clicking on it
|
||||
text: option => {
|
||||
return option[self.settings.labelField];
|
||||
}
|
||||
}, plugin_options_);
|
||||
|
||||
|
||||
self.hook('after', 'setupTemplates', () => {
|
||||
const orig_render_item = self.settings.render.item;
|
||||
self.settings.render.item = (data, escape) => {
|
||||
const item = getDom(orig_render_item.call(self, data, escape));
|
||||
|
||||
item.addEventListener('click', evt => {
|
||||
if (self.isLocked) {
|
||||
return;
|
||||
}
|
||||
const val = self.inputValue();
|
||||
|
||||
if (self.options[val]) {
|
||||
self.addItem(val)
|
||||
} else if (self.settings.create) {
|
||||
self.createItem();
|
||||
}
|
||||
const option = self.options[item.dataset.value]
|
||||
self.setTextboxValue(plugin_options.text.call(self, option));
|
||||
self.focus();
|
||||
self.removeItem(item);
|
||||
}
|
||||
);
|
||||
|
||||
return item;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
export { plugin as default };
|
|
@ -30,6 +30,7 @@
|
|||
"hshn/base64-encoded-file": "^5.0",
|
||||
"jbtronics/2fa-webauthn": "^v2.2.0",
|
||||
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
||||
"jbtronics/settings-bundle": "dev-master",
|
||||
"jfcherng/php-diff": "^6.14",
|
||||
"knpuniversity/oauth2-client-bundle": "^2.15",
|
||||
"league/csv": "^9.8.0",
|
||||
|
@ -54,8 +55,6 @@
|
|||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "6.4.*",
|
||||
"symfony/console": "6.4.*",
|
||||
"symfony/css-selector": "6.4.*",
|
||||
"symfony/dom-crawler": "6.4.*",
|
||||
"symfony/dotenv": "6.4.*",
|
||||
"symfony/expression-language": "6.4.*",
|
||||
"symfony/flex": "^v2.3.1",
|
||||
|
@ -73,7 +72,7 @@
|
|||
"symfony/runtime": "6.4.*",
|
||||
"symfony/security-bundle": "6.4.*",
|
||||
"symfony/serializer": "6.4.*",
|
||||
"symfony/string": "6.4.*",
|
||||
"symfony/string": "6.4.8",
|
||||
"symfony/translation": "6.4.*",
|
||||
"symfony/twig-bundle": "6.4.*",
|
||||
"symfony/ux-translator": "^2.10",
|
||||
|
@ -106,6 +105,7 @@
|
|||
"rector/rector": "^2.0.4",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/browser-kit": "6.4.*",
|
||||
"symfony/css-selector": "6.4.*",
|
||||
"symfony/debug-bundle": "6.4.*",
|
||||
"symfony/maker-bundle": "^1.13",
|
||||
"symfony/phpunit-bridge": "6.4.*",
|
||||
|
|
1080
composer.lock
generated
1080
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -31,5 +31,6 @@ return [
|
|||
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||
Jbtronics\SettingsBundle\JbtronicsSettingsBundle::class => ['all' => true],
|
||||
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
|
||||
];
|
||||
|
|
|
@ -34,8 +34,4 @@ api_platform:
|
|||
|
||||
keep_legacy_inflector: false
|
||||
# Need to be true, or some tests will fail
|
||||
use_symfony_listeners: true
|
||||
|
||||
serializer:
|
||||
# Change this to false later, to remove the hydra prefix on the API
|
||||
hydra_prefix: true
|
||||
use_symfony_listeners: true
|
|
@ -8,8 +8,9 @@ datatables:
|
|||
|
||||
# Set options, as documented at https://datatables.net/reference/option/
|
||||
options:
|
||||
lengthMenu : [[10, 25, 50, 100], [10, 25, 50, 100]] # We add the "All" option, when part tables are generated
|
||||
pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
|
||||
lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
|
||||
#pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
|
||||
pageLength: 50 #TODO
|
||||
dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>>
|
||||
<'card'
|
||||
rt
|
||||
|
|
2
config/packages/settings.yaml
Normal file
2
config/packages/settings.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
jbtronics_settings:
|
||||
default_storage_adapter: Jbtronics\SettingsBundle\Storage\PHPFileStorageAdapter
|
|
@ -1,11 +1,10 @@
|
|||
framework:
|
||||
default_locale: '%partdb.locale%'
|
||||
default_locale: 'en'
|
||||
# Just enable the locales we need for performance reasons.
|
||||
enabled_locale: '%partdb.locale_menu%'
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
fallbacks:
|
||||
- '%partdb.locale%'
|
||||
- 'en'
|
||||
providers:
|
||||
# crowdin:
|
||||
|
|
|
@ -6,16 +6,12 @@ twig:
|
|||
'%kernel.project_dir%/assets/css': css
|
||||
|
||||
globals:
|
||||
partdb_title: '%partdb.title%'
|
||||
default_currency: '%partdb.default_currency%'
|
||||
global_theme: '%partdb.global_theme%'
|
||||
allow_email_pw_reset: '%partdb.users.email_pw_reset%'
|
||||
locale_menu: '%partdb.locale_menu%'
|
||||
attachment_manager: '@App\Services\Attachments\AttachmentManager'
|
||||
label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper'
|
||||
error_page_admin_email: '%partdb.error_pages.admin_email%'
|
||||
error_page_show_help: '%partdb.error_pages.show_help%'
|
||||
sidebar_items: '%partdb.sidebar.items%'
|
||||
sidebar_tree_updater: '@App\Services\Trees\SidebarTreeUpdater'
|
||||
avatar_helper: '@App\Services\UserSystem\UserAvatarHelper'
|
||||
available_themes: '%partdb.available_themes%'
|
||||
|
|
|
@ -5,14 +5,10 @@ parameters:
|
|||
######################################################################################################################
|
||||
# Common
|
||||
######################################################################################################################
|
||||
partdb.locale: '%env(string:DEFAULT_LANG)%' # The default language to use serverwide
|
||||
partdb.timezone: '%env(string:DEFAULT_TIMEZONE)%' # The default timezone
|
||||
partdb.title: '%env(trim:string:INSTANCE_NAME)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage)
|
||||
partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used
|
||||
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
|
||||
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
|
||||
|
||||
# This is used as workaround for places where we can not access the settings directly (like the 2FA application names)
|
||||
partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage)
|
||||
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu
|
||||
partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all.
|
||||
|
||||
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
|
||||
|
||||
|
@ -22,11 +18,8 @@ parameters:
|
|||
# Users and Privacy
|
||||
######################################################################################################################
|
||||
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.
|
||||
|
||||
partdb.check_for_updates: '%env(bool:CHECK_FOR_UPDATES)' # Set to false, if Part-DB should not contact the GitHub API to check for updates
|
||||
|
||||
######################################################################################################################
|
||||
# Mail settings
|
||||
######################################################################################################################
|
||||
|
@ -36,11 +29,8 @@ parameters:
|
|||
######################################################################################################################
|
||||
# Attachments and files
|
||||
######################################################################################################################
|
||||
partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%' # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet!
|
||||
partdb.attachments.download_by_default: '%env(bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT)%' # If this is set the 'download external files' checkbox is set by default for new attachments (only if allow_downloads is set to true)
|
||||
partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder)
|
||||
partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/)
|
||||
partdb.attachments.max_file_size: '%env(string:MAX_ATTACHMENT_FILE_SIZE)%' # The maximum size of an attachment file (in bytes, you can use M for megabytes and G for gigabytes)
|
||||
|
||||
######################################################################################################################
|
||||
# Error pages
|
||||
|
@ -53,22 +43,6 @@ parameters:
|
|||
######################################################################################################################
|
||||
partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled
|
||||
|
||||
######################################################################################################################
|
||||
# Table settings
|
||||
######################################################################################################################
|
||||
partdb.table.default_page_size: '%env(int:TABLE_DEFAULT_PAGE_SIZE)%' # The default number of entries shown per page in tables
|
||||
partdb.table.parts.default_columns: '%env(trim:string:TABLE_PARTS_DEFAULT_COLUMNS)%' # The default columns in part tables and their order
|
||||
|
||||
######################################################################################################################
|
||||
# Sidebar
|
||||
######################################################################################################################
|
||||
# You can configures the default shown tree items in the sidebar here. You can add or remove entries here, to change the number of trees in the sidebar. The possible entries are: categories, locations, footprints, manufacturers, suppliers, devices, tools
|
||||
partdb.sidebar.items:
|
||||
- categories
|
||||
- devices
|
||||
- tools
|
||||
partdb.sidebar.root_expanded: true # If this is set to true, the root node of the sidebar is expanded by default
|
||||
partdb.sidebar.root_node_enable: true # Put all entities below a root node in the sidebar
|
||||
|
||||
######################################################################################################################
|
||||
# Miscellaneous
|
||||
|
@ -110,17 +84,8 @@ parameters:
|
|||
# Env default values
|
||||
######################################################################################################################
|
||||
|
||||
env(DEFAULT_LANG): 'en'
|
||||
env(DEFAULT_TIMEZONE): 'Europe/Berlin'
|
||||
env(INSTANCE_NAME): 'Part-DB'
|
||||
env(BASE_CURRENCY): 'EUR'
|
||||
env(USE_GRAVATAR): '0'
|
||||
env(MAX_ATTACHMENT_FILE_SIZE): '100M'
|
||||
|
||||
env(REDIRECT_TO_HTTPS): 0
|
||||
|
||||
env(ENFORCE_CHANGE_COMMENTS_FOR): ''
|
||||
|
||||
env(ERROR_PAGE_ADMIN_EMAIL): ''
|
||||
env(ERROR_PAGE_SHOW_HELP): 1
|
||||
|
||||
|
@ -132,8 +97,6 @@ parameters:
|
|||
env(EMAIL_SENDER_NAME): 'Part-DB Mailer'
|
||||
env(ALLOW_EMAIL_PW_RESET): 0
|
||||
|
||||
env(TABLE_DEFAULT_PAGE_SIZE): 50
|
||||
|
||||
env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server
|
||||
env(TRUSTED_HOSTS): '' # Trust all host names by default
|
||||
|
||||
|
@ -141,11 +104,4 @@ parameters:
|
|||
|
||||
env(SAML_ROLE_MAPPING): '{}'
|
||||
|
||||
env(HISTORY_SAVE_CHANGED_DATA): 1
|
||||
env(HISTORY_SAVE_CHANGED_FIELDS): 1
|
||||
env(HISTORY_SAVE_REMOVED_DATA): 1
|
||||
env(HISTORY_SAVE_NEW_DATA): 1
|
||||
|
||||
env(EDA_KICAD_CATEGORY_DEPTH): 0
|
||||
|
||||
env(DATABASE_EMULATE_NATURAL_SORT): 0
|
||||
|
|
|
@ -17,8 +17,6 @@ services:
|
|||
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 $base_currency: '%partdb.default_currency%'
|
||||
|
||||
_instanceof:
|
||||
App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface:
|
||||
|
@ -76,28 +74,10 @@ services:
|
|||
# Only the event classes specified here are saved to DB (set to []) to log all events
|
||||
$whitelist: []
|
||||
|
||||
App\EventListener\LogSystem\EventLoggerListener:
|
||||
arguments:
|
||||
$save_changed_fields: '%env(bool:HISTORY_SAVE_CHANGED_FIELDS)%'
|
||||
$save_changed_data: '%env(bool:HISTORY_SAVE_CHANGED_DATA)%'
|
||||
$save_removed_data: '%env(bool:HISTORY_SAVE_REMOVED_DATA)%'
|
||||
$save_new_data: '%env(bool:HISTORY_SAVE_NEW_DATA)%'
|
||||
|
||||
App\Form\AttachmentFormType:
|
||||
arguments:
|
||||
$allow_attachments_download: '%partdb.attachments.allow_downloads%'
|
||||
$max_file_size: '%partdb.attachments.max_file_size%'
|
||||
$download_by_default: '%partdb.attachments.download_by_default%'
|
||||
|
||||
App\Services\Attachments\AttachmentSubmitHandler:
|
||||
arguments:
|
||||
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
|
||||
$mimeTypes: '@mime_types'
|
||||
$max_upload_size: '%partdb.attachments.max_file_size%'
|
||||
|
||||
App\Services\LogSystem\EventCommentNeededHelper:
|
||||
arguments:
|
||||
$enforce_change_comments_for: '%partdb.enforce_change_comments_for%'
|
||||
|
||||
####################################################################################################################
|
||||
# Attachment system
|
||||
|
@ -156,29 +136,6 @@ services:
|
|||
tags:
|
||||
- { name: doctrine.orm.entity_listener }
|
||||
|
||||
####################################################################################################################
|
||||
# Price system
|
||||
####################################################################################################################
|
||||
App\Command\Currencies\UpdateExchangeRatesCommand:
|
||||
arguments:
|
||||
$base_current: '%partdb.default_currency%'
|
||||
|
||||
App\Form\Type\CurrencyEntityType:
|
||||
arguments:
|
||||
$base_currency: '%partdb.default_currency%'
|
||||
|
||||
App\Services\Parts\PricedetailHelper:
|
||||
arguments:
|
||||
$base_currency: '%partdb.default_currency%'
|
||||
|
||||
App\Services\Formatters\MoneyFormatter:
|
||||
arguments:
|
||||
$base_currency: '%partdb.default_currency%'
|
||||
|
||||
App\Services\Tools\ExchangeRateUpdater:
|
||||
arguments:
|
||||
$base_currency: '%partdb.default_currency%'
|
||||
|
||||
###################################################################################################################
|
||||
# User system
|
||||
####################################################################################################################
|
||||
|
@ -186,10 +143,6 @@ services:
|
|||
arguments:
|
||||
$demo_mode: '%partdb.demo_mode%'
|
||||
|
||||
App\EventSubscriber\UserSystem\SetUserTimezoneSubscriber:
|
||||
arguments:
|
||||
$default_timezone: '%partdb.timezone%'
|
||||
|
||||
App\Controller\SecurityController:
|
||||
arguments:
|
||||
$allow_email_pw_reset: '%partdb.users.email_pw_reset%'
|
||||
|
@ -203,10 +156,6 @@ services:
|
|||
tags:
|
||||
- { name: 'translation.extractor', alias: 'permissionExtractor'}
|
||||
|
||||
App\Services\UserSystem\UserAvatarHelper:
|
||||
arguments:
|
||||
$use_gravatar: '%partdb.users.use_gravatar%'
|
||||
|
||||
App\Form\Type\ThemeChoiceType:
|
||||
arguments:
|
||||
$available_themes: '%partdb.available_themes%'
|
||||
|
@ -222,9 +171,6 @@ services:
|
|||
####################################################################################################################
|
||||
# Table settings
|
||||
####################################################################################################################
|
||||
App\DataTables\PartsDataTable:
|
||||
arguments:
|
||||
$visible_columns: '%partdb.table.parts.default_columns%'
|
||||
|
||||
App\DataTables\Helpers\ColumnSortHelper:
|
||||
shared: false # Service has a state so not share it between different tables
|
||||
|
@ -246,14 +192,6 @@ services:
|
|||
$fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/'
|
||||
$tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/'
|
||||
|
||||
####################################################################################################################
|
||||
# Trees
|
||||
####################################################################################################################
|
||||
App\Services\Trees\TreeViewGenerator:
|
||||
arguments:
|
||||
$rootNodeExpandedByDefault: '%partdb.sidebar.root_expanded%'
|
||||
$rootNodeEnabled: '%partdb.sidebar.root_node_enable%'
|
||||
|
||||
####################################################################################################################
|
||||
# Part info provider system
|
||||
####################################################################################################################
|
||||
|
@ -261,11 +199,6 @@ services:
|
|||
arguments:
|
||||
$providers: !tagged_iterator 'app.info_provider'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\Element14Provider:
|
||||
arguments:
|
||||
$api_key: '%env(string:PROVIDER_ELEMENT14_KEY)%'
|
||||
$store_id: '%env(string:PROVIDER_ELEMENT14_STORE_ID)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\DigikeyProvider:
|
||||
arguments:
|
||||
$clientId: '%env(string:PROVIDER_DIGIKEY_CLIENT_ID)%'
|
||||
|
@ -273,18 +206,6 @@ services:
|
|||
$language: '%env(string:PROVIDER_DIGIKEY_LANGUAGE)%'
|
||||
$country: '%env(string:PROVIDER_DIGIKEY_COUNTRY)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\TMEClient:
|
||||
arguments:
|
||||
$secret: '%env(string:PROVIDER_TME_SECRET)%'
|
||||
$token: '%env(string:PROVIDER_TME_KEY)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\TMEProvider:
|
||||
arguments:
|
||||
$currency: '%env(string:PROVIDER_TME_CURRENCY)%'
|
||||
$country: '%env(string:PROVIDER_TME_COUNTRY)%'
|
||||
$language: '%env(string:PROVIDER_TME_LANGUAGE)%'
|
||||
$get_gross_prices: '%env(bool:PROVIDER_TME_GET_GROSS_PRICES)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\OctopartProvider:
|
||||
arguments:
|
||||
$clientId: '&env(string:PROVIDER_OCTOPART_CLIENT_ID)%'
|
||||
|
@ -294,27 +215,6 @@ services:
|
|||
$search_limit: '%env(int:PROVIDER_OCTOPART_SEARCH_LIMIT)%'
|
||||
$onlyAuthorizedSellers: '%env(bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\MouserProvider:
|
||||
arguments:
|
||||
$api_key: '%env(string:PROVIDER_MOUSER_KEY)%'
|
||||
$language: '%env(string:PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE)%'
|
||||
$options: '%env(string:PROVIDER_MOUSER_SEARCH_OPTION)%'
|
||||
$search_limit: '%env(int:PROVIDER_MOUSER_SEARCH_LIMIT)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\LCSCProvider:
|
||||
arguments:
|
||||
$enabled: '%env(bool:PROVIDER_LCSC_ENABLED)%'
|
||||
$currency: '%env(string:PROVIDER_LCSC_CURRENCY)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\OEMSecretsProvider:
|
||||
arguments:
|
||||
$api_key: '%env(string:PROVIDER_OEMSECRETS_KEY)%'
|
||||
$country_code: '%env(string:PROVIDER_OEMSECRETS_COUNTRY_CODE)%'
|
||||
$currency: '%env(PROVIDER_OEMSECRETS_CURRENCY)%'
|
||||
$zero_price: '%env(PROVIDER_OEMSECRETS_ZERO_PRICE)%'
|
||||
$set_param: '%env(PROVIDER_OEMSECRETS_SET_PARAM)%'
|
||||
$sort_criteria: '%env(PROVIDER_OEMSECRETS_SORT_CRITERIA)%'
|
||||
|
||||
|
||||
####################################################################################################################
|
||||
# API system
|
||||
|
@ -322,15 +222,6 @@ services:
|
|||
App\State\PartDBInfoProvider:
|
||||
arguments:
|
||||
$default_uri: '%partdb.default_uri%'
|
||||
$global_locale: '%partdb.locale%'
|
||||
$global_timezone: '%partdb.timezone%'
|
||||
|
||||
####################################################################################################################
|
||||
# EDA system
|
||||
####################################################################################################################
|
||||
App\Services\EDA\KiCadHelper:
|
||||
arguments:
|
||||
$category_depth: '%env(int:EDA_KICAD_CATEGORY_DEPTH)%'
|
||||
|
||||
####################################################################################################################
|
||||
# Symfony overrides
|
||||
|
@ -355,7 +246,6 @@ services:
|
|||
####################################################################################################################
|
||||
App\Controller\RedirectController:
|
||||
arguments:
|
||||
$default_locale: '%partdb.locale%'
|
||||
$enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%'
|
||||
|
||||
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
|
||||
|
@ -370,14 +260,6 @@ services:
|
|||
arguments:
|
||||
$project_dir: '%kernel.project_dir%'
|
||||
|
||||
App\Services\System\UpdateAvailableManager:
|
||||
arguments:
|
||||
$check_for_updates: '%partdb.check_for_updates%'
|
||||
|
||||
App\Services\System\BannerHelper:
|
||||
arguments:
|
||||
$partdb_banner: '%partdb.banner%'
|
||||
$project_dir: '%kernel.project_dir%'
|
||||
|
||||
App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper:
|
||||
arguments:
|
||||
|
|
|
@ -150,9 +150,9 @@ In the `serverVersion` parameter you can specify the version of the PostgreSQL s
|
|||
|
||||
The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly.
|
||||
|
||||
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `host` parameter.
|
||||
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `unix_socket` parameter.
|
||||
```shell
|
||||
DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql"
|
||||
DATABASE_URL="postgresql://db_user:db_password@localhost/db_name?serverVersion=12.19&charset=utf8&unix_socket=/var/run/postgresql/.s.PGSQL.5432"
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -201,10 +201,6 @@ You also have to create the database as described above in step 4.
|
|||
You can run the console commands described in README by
|
||||
executing `docker exec --user=www-data -it partdb bin/console [command]`
|
||||
|
||||
{: .warning }
|
||||
> If you run a root console inside the container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell.
|
||||
> Otherwise Part-DB console might use the wrong configuration to execute commands.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
*Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*:
|
||||
|
|
|
@ -25,12 +25,6 @@ is named `partdb`, you can execute the command `php bin/console cache:clear` wit
|
|||
docker exec --user=www-data partdb php bin/console cache:clear
|
||||
```
|
||||
|
||||
{: .warning }
|
||||
> If you run a root console inside the docker container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell.
|
||||
> Otherwise Part-DB console might use the wrong configuration to execute commands.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
## User management commands
|
||||
|
||||
* `php bin/console partdb:users:list`: List all users of this Part-DB instance
|
||||
|
@ -70,4 +64,4 @@ docker exec --user=www-data partdb php bin/console cache:clear
|
|||
## Database commands
|
||||
|
||||
* `php bin/console doctrine:migrations:migrate`: Migrate the database to the latest version
|
||||
* `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date
|
||||
* `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date
|
|
@ -127,9 +127,6 @@ You must create an organization there and create a "Production app". Most settin
|
|||
grant access to the "Product Information" API.
|
||||
You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below).
|
||||
|
||||
**Attention**: Currently only the "Product Information V3 (Deprecated)" is supported by Part-DB.
|
||||
Using "Product Information V4" will not work.
|
||||
|
||||
The following env configuration options are available:
|
||||
|
||||
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
|
||||
|
@ -235,26 +232,6 @@ The following env configuration options are available:
|
|||
completeness (prioritizing items with the most detailed information). If set to 'M', it further sorts by manufacturer name.
|
||||
If set to any other value, no sorting is performed.
|
||||
|
||||
### Reichelt
|
||||
|
||||
The reichelt provider uses webscraping from [reichelt.com](https://reichelt.com/) to get part information.
|
||||
This is not an official API and could break at any time. So use it at your own risk.
|
||||
|
||||
The following env configuration options are available:
|
||||
* `PROVIDER_REICHELT_ENABLED`: Set this to `1` to enable the Reichelt provider
|
||||
* `PROVIDER_REICHELT_CURRENCY`: The currency you want to get prices in. Only possible for countries which use Non-EUR (optional, default: `EUR`)
|
||||
* `PROVIDER_REICHELT_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
|
||||
* `PROVIDER_REICHELT_LANGUAGE`: The language you want to get the descriptions in (optional, default: `en`)
|
||||
* `PROVIDER_REICHELT_INCLUDE_VAT`: If set to `1`, the prices will be gross prices (including tax), otherwise net prices (optional, default: `1`)
|
||||
|
||||
### Pollin
|
||||
|
||||
The pollin provider uses webscraping from [pollin.de](https://www.pollin.de/) to get part information.
|
||||
This is not an official API and could break at any time. So use it at your own risk.
|
||||
|
||||
The following env configuration options are available:
|
||||
* `PROVIDER_POLLIN_ENABLED`: Set this to `1` to enable the Pollin provider
|
||||
|
||||
### Custom provider
|
||||
|
||||
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long
|
||||
|
|
|
@ -117,6 +117,6 @@ For a German keyboard layout, replace `[` with `0`, and `]` with `´`.
|
|||
| Key | Character |
|
||||
|--------------------------------|--------------------|
|
||||
| **Alt + [** (code 219) | © (Copyright char) |
|
||||
| **Alt + Shift + [** (code 219) | ® (Registered char) |
|
||||
| **Alt + Shift + [** (code 219) | (Registered char) |
|
||||
| **Alt + ]** (code 221) | ™ (Trademark char) |
|
||||
| **Alt + Shift + ]** (code 221) | ° (Degree char) |
|
||||
| **Alt + Shift + ]** (code 221) | (Degree char) |
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250220215048 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Split $path property for attachments into $internal_path and $external_path';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
//Create the new columns as nullable (that is easier modifying them)
|
||||
$this->addSql('ALTER TABLE attachments ADD internal_path VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE attachments ADD external_path VARCHAR(255) DEFAULT NULL');
|
||||
|
||||
//Copy the data from path to external_path and remove the path column
|
||||
$this->addSql('UPDATE attachments SET external_path=path');
|
||||
$this->addSql('ALTER TABLE attachments DROP path');
|
||||
|
||||
|
||||
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%MEDIA#%%\' ESCAPE \'#\'');
|
||||
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%BASE#%%\' ESCAPE \'#\'');
|
||||
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%SECURE#%%\' ESCAPE \'#\'');
|
||||
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%FOOTPRINTS#%%\' ESCAPE \'#\'');
|
||||
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%FOOTPRINTS3D#%%\' ESCAPE \'#\'');
|
||||
$this->addSql('UPDATE attachments SET external_path=NULL WHERE internal_path IS NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('UPDATE attachments SET external_path=internal_path WHERE internal_path IS NOT NULL');
|
||||
$this->addSql('ALTER TABLE attachments DROP internal_path');
|
||||
$this->addSql('ALTER TABLE attachments RENAME COLUMN external_path TO path');
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250222165240 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Migrate the old attachment class discriminator values from legacy Part-DB to the modern format, so that there is just one unified value';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
//Change the old discriminator values to the new ones
|
||||
$this->addSql("UPDATE attachments SET class_name = 'Part' WHERE class_name = 'PartDB\Part'");
|
||||
$this->addSql("UPDATE attachments SET class_name = 'Device' WHERE class_name = 'PartDB\Device'");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
//No down required, as the new format can also be read by older Part-DB version
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
116
src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php
Normal file
116
src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?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/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform;
|
||||
|
||||
use ApiPlatform\JsonSchema\Schema;
|
||||
use ApiPlatform\JsonSchema\SchemaFactoryInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
|
||||
/**
|
||||
* This decorator adds the properties given by DocumentedAPIProperty attributes on the classes to the schema.
|
||||
*/
|
||||
#[AsDecorator('api_platform.json_schema.schema_factory')]
|
||||
class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterface
|
||||
{
|
||||
|
||||
public function __construct(private readonly SchemaFactoryInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
public function buildSchema(
|
||||
string $className,
|
||||
string $format = 'json',
|
||||
string $type = Schema::TYPE_OUTPUT,
|
||||
Operation $operation = null,
|
||||
Schema $schema = null,
|
||||
array $serializerContext = null,
|
||||
bool $forceCollection = false
|
||||
): Schema {
|
||||
|
||||
|
||||
$schema = $this->decorated->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
|
||||
|
||||
//Check if there is are DocumentedAPIProperty attributes on the class
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
$attributes = $reflectionClass->getAttributes(DocumentedAPIProperty::class);
|
||||
foreach ($attributes as $attribute) {
|
||||
/** @var DocumentedAPIProperty $api_property */
|
||||
$api_property = $attribute->newInstance();
|
||||
$this->addPropertyToSchema($schema, $api_property->schemaName, $api_property->property,
|
||||
$api_property, $serializerContext ?? [], $format);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
private function addPropertyToSchema(Schema $schema, string $definitionName, string $normalizedPropertyName, DocumentedAPIProperty $propertyMetadata, array $serializerContext, string $format): void
|
||||
{
|
||||
$version = $schema->getVersion();
|
||||
$swagger = Schema::VERSION_SWAGGER === $version;
|
||||
|
||||
$propertySchema = [];
|
||||
|
||||
if (false === $propertyMetadata->writeable) {
|
||||
$propertySchema['readOnly'] = true;
|
||||
}
|
||||
if (!$swagger && false === $propertyMetadata->readable) {
|
||||
$propertySchema['writeOnly'] = true;
|
||||
}
|
||||
if (null !== $description = $propertyMetadata->description) {
|
||||
$propertySchema['description'] = $description;
|
||||
}
|
||||
|
||||
$deprecationReason = $propertyMetadata->deprecationReason;
|
||||
|
||||
// see https://github.com/json-schema-org/json-schema-spec/pull/737
|
||||
if (!$swagger && null !== $deprecationReason) {
|
||||
$propertySchema['deprecated'] = true;
|
||||
}
|
||||
|
||||
if (!empty($default = $propertyMetadata->default)) {
|
||||
if ($default instanceof \BackedEnum) {
|
||||
$default = $default->value;
|
||||
}
|
||||
$propertySchema['default'] = $default;
|
||||
}
|
||||
|
||||
if (!empty($example = $propertyMetadata->example)) {
|
||||
$propertySchema['example'] = $example;
|
||||
}
|
||||
|
||||
if (!isset($propertySchema['example']) && isset($propertySchema['default'])) {
|
||||
$propertySchema['example'] = $propertySchema['default'];
|
||||
}
|
||||
|
||||
$propertySchema['type'] = $propertyMetadata->type;
|
||||
$propertySchema['nullable'] = $propertyMetadata->nullable;
|
||||
|
||||
$propertySchema = new \ArrayObject($propertySchema);
|
||||
|
||||
$schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 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\ApiPlatform\DocumentedAPIProperties;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
|
||||
use ReflectionClass;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
|
||||
/**
|
||||
* This decorator adds the virtual properties defined by the DocumentedAPIProperty attribute to the property metadata
|
||||
* which then get picked up by the openapi schema generator
|
||||
*/
|
||||
#[AsDecorator('api_platform.metadata.property.metadata_factory')]
|
||||
class PropertyMetadataFactory implements PropertyMetadataFactoryInterface
|
||||
{
|
||||
public function __construct(private PropertyMetadataFactoryInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
public function create(string $resourceClass, string $property, array $options = []): ApiProperty
|
||||
{
|
||||
$metadata = $this->decorated->create($resourceClass, $property, $options);
|
||||
|
||||
//Only become active in the context of the openapi schema generation
|
||||
if (!isset($options['schema_type'])) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if (!class_exists($resourceClass)) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$refClass = new ReflectionClass($resourceClass);
|
||||
$attributes = $refClass->getAttributes(DocumentedAPIProperty::class);
|
||||
|
||||
//Look for the DocumentedAPIProperty attribute with the given property name
|
||||
foreach ($attributes as $attribute) {
|
||||
/** @var DocumentedAPIProperty $api_property */
|
||||
$api_property = $attribute->newInstance();
|
||||
//If attribute not matches the property name, skip it
|
||||
if ($api_property->property !== $property) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Return the virtual property
|
||||
return $api_property->toAPIProperty();
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 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\ApiPlatform\DocumentedAPIProperties;
|
||||
|
||||
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
|
||||
use ApiPlatform\Metadata\Property\PropertyNameCollection;
|
||||
use ReflectionClass;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
|
||||
/**
|
||||
* This decorator adds the virtual property names defined by the DocumentedAPIProperty attribute to the property name collection
|
||||
* which then get picked up by the openapi schema generator
|
||||
*/
|
||||
#[AsDecorator('api_platform.metadata.property.name_collection_factory')]
|
||||
class PropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
|
||||
{
|
||||
public function __construct(private readonly PropertyNameCollectionFactoryInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
public function create(string $resourceClass, array $options = []): PropertyNameCollection
|
||||
{
|
||||
// Get the default properties from the decorated service
|
||||
$propertyNames = $this->decorated->create($resourceClass, $options);
|
||||
|
||||
//Only become active in the context of the openapi schema generation
|
||||
if (!isset($options['schema_type'])) {
|
||||
return $propertyNames;
|
||||
}
|
||||
|
||||
if (!class_exists($resourceClass)) {
|
||||
return $propertyNames;
|
||||
}
|
||||
|
||||
$properties = iterator_to_array($propertyNames);
|
||||
|
||||
$refClass = new ReflectionClass($resourceClass);
|
||||
|
||||
foreach ($refClass->getAttributes(DocumentedAPIProperty::class) as $attribute) {
|
||||
/** @var DocumentedAPIProperty $instance */
|
||||
$instance = $attribute->newInstance();
|
||||
$properties[] = $instance->property;
|
||||
}
|
||||
|
||||
return new PropertyNameCollection($properties);
|
||||
}
|
||||
}
|
|
@ -21,9 +21,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform\DocumentedAPIProperties;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
namespace App\ApiPlatform;
|
||||
|
||||
/**
|
||||
* When this attribute is applied to a class, an property will be added to the API documentation using the given parameters.
|
||||
|
@ -66,55 +64,4 @@ final class DocumentedAPIProperty
|
|||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function toAPIProperty(bool $use_swagger = false): ApiProperty
|
||||
{
|
||||
$openApiContext = [];
|
||||
|
||||
if (false === $this->writeable) {
|
||||
$openApiContext['readOnly'] = true;
|
||||
}
|
||||
if (!$use_swagger && false === $this->readable) {
|
||||
$openApiContext['writeOnly'] = true;
|
||||
}
|
||||
if (null !== $description = $this->description) {
|
||||
$openApiContext['description'] = $description;
|
||||
}
|
||||
|
||||
$deprecationReason = $this->deprecationReason;
|
||||
|
||||
// see https://github.com/json-schema-org/json-schema-spec/pull/737
|
||||
if (!$use_swagger && null !== $deprecationReason) {
|
||||
$openApiContext['deprecated'] = true;
|
||||
}
|
||||
|
||||
if (!empty($default = $this->default)) {
|
||||
if ($default instanceof \BackedEnum) {
|
||||
$default = $default->value;
|
||||
}
|
||||
$openApiContext['default'] = $default;
|
||||
}
|
||||
|
||||
if (!empty($example = $this->example)) {
|
||||
$openApiContext['example'] = $example;
|
||||
}
|
||||
|
||||
if (!isset($openApiContext['example']) && isset($openApiContext['default'])) {
|
||||
$openApiContext['example'] = $openApiContext['default'];
|
||||
}
|
||||
|
||||
$openApiContext['type'] = $this->type;
|
||||
$openApiContext['nullable'] = $this->nullable;
|
||||
|
||||
|
||||
|
||||
return new ApiProperty(
|
||||
description: $this->description,
|
||||
readable: $this->readable,
|
||||
writable: $this->writeable,
|
||||
openapiContext: $openApiContext,
|
||||
types: $this->type,
|
||||
property: $this->property
|
||||
);
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ class EntityFilter extends AbstractFilter
|
|||
public function __construct(
|
||||
ManagerRegistry $managerRegistry,
|
||||
private readonly EntityFilterHelper $filter_helper,
|
||||
?LoggerInterface $logger = null,
|
||||
LoggerInterface $logger = null,
|
||||
?array $properties = null,
|
||||
?NameConverterInterface $nameConverter = null
|
||||
) {
|
||||
|
@ -50,7 +50,7 @@ class EntityFilter extends AbstractFilter
|
|||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
?Operation $operation = null,
|
||||
Operation $operation = null,
|
||||
array $context = []
|
||||
): void {
|
||||
if (
|
||||
|
|
|
@ -92,6 +92,12 @@ class EntityFilterHelper
|
|||
'type' => Type::BUILTIN_TYPE_STRING,
|
||||
'required' => false,
|
||||
'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.',
|
||||
'openapi' => [
|
||||
'example' => '',
|
||||
'allowReserved' => false,// if true, query parameters will be not percent-encoded
|
||||
'allowEmptyValue' => true,
|
||||
'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
|
||||
],
|
||||
];
|
||||
}
|
||||
return $description;
|
||||
|
|
|
@ -38,7 +38,7 @@ final class LikeFilter extends AbstractFilter
|
|||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
?Operation $operation = null,
|
||||
Operation $operation = null,
|
||||
array $context = []
|
||||
): void {
|
||||
// Otherwise filter is applied to order and page as well
|
||||
|
@ -67,6 +67,12 @@ final class LikeFilter extends AbstractFilter
|
|||
'type' => Type::BUILTIN_TYPE_STRING,
|
||||
'required' => false,
|
||||
'description' => 'Filter using a LIKE SQL expression. Use % as wildcard for multiple characters and _ for single characters. For example, to search for all items containing foo, use foo. To search for all items starting with foo, use foo%. To search for all items ending with foo, use %foo',
|
||||
'openapi' => [
|
||||
'example' => '',
|
||||
'allowReserved' => false,// if true, query parameters will be not percent-encoded
|
||||
'allowEmptyValue' => true,
|
||||
'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
|
||||
],
|
||||
];
|
||||
}
|
||||
return $description;
|
||||
|
|
|
@ -38,7 +38,7 @@ class PartStoragelocationFilter extends AbstractFilter
|
|||
public function __construct(
|
||||
ManagerRegistry $managerRegistry,
|
||||
private readonly EntityFilterHelper $filter_helper,
|
||||
?LoggerInterface $logger = null,
|
||||
LoggerInterface $logger = null,
|
||||
?array $properties = null,
|
||||
?NameConverterInterface $nameConverter = null
|
||||
) {
|
||||
|
@ -51,7 +51,7 @@ class PartStoragelocationFilter extends AbstractFilter
|
|||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
?Operation $operation = null,
|
||||
Operation $operation = null,
|
||||
array $context = []
|
||||
): void {
|
||||
//Do not check for mapping here, as we are using a virtual property
|
||||
|
|
|
@ -89,6 +89,12 @@ final class TagFilter extends AbstractFilter
|
|||
'type' => Type::BUILTIN_TYPE_STRING,
|
||||
'required' => false,
|
||||
'description' => 'Filter for tags of a part',
|
||||
'openapi' => [
|
||||
'example' => '',
|
||||
'allowReserved' => false,// if true, query parameters will be not percent-encoded
|
||||
'allowEmptyValue' => true,
|
||||
'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
|
||||
],
|
||||
];
|
||||
}
|
||||
return $description;
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 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\ApiPlatform;
|
||||
|
||||
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
|
||||
use ApiPlatform\Metadata\Property\PropertyNameCollection;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
/**
|
||||
* This decorator removes all camelCase property names from the property name collection, if a snake_case version exists.
|
||||
* This is a fix for https://github.com/Part-DB/Part-DB-server/issues/862, as the openapi schema generator wrongly collects
|
||||
* both camelCase and snake_case property names, which leads to duplicate properties in the schema.
|
||||
* This seems to come from the fact that the openapi schema generator uses no serializerContext, which seems then to collect
|
||||
* the getters too...
|
||||
*/
|
||||
#[AsDecorator('api_platform.metadata.property.name_collection_factory')]
|
||||
class NormalizePropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
|
||||
{
|
||||
public function __construct(private readonly PropertyNameCollectionFactoryInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
public function create(string $resourceClass, array $options = []): PropertyNameCollection
|
||||
{
|
||||
// Get the default properties from the decorated service
|
||||
$propertyNames = $this->decorated->create($resourceClass, $options);
|
||||
|
||||
//Only become active in the context of the openapi schema generation
|
||||
if (!isset($options['schema_type'])) {
|
||||
return $propertyNames;
|
||||
}
|
||||
|
||||
//If we are not in the jsonapi generator (which sets no serializer groups), return the property names as is
|
||||
if (isset($options['serializer_groups'])) {
|
||||
return $propertyNames;
|
||||
}
|
||||
|
||||
//Remove all camelCase property names from the collection, if a snake_case version exists
|
||||
$properties = iterator_to_array($propertyNames);
|
||||
|
||||
foreach ($properties as $property) {
|
||||
if (str_contains($property, '_')) {
|
||||
$camelized = u($property)->camel()->toString();
|
||||
|
||||
//If the camelized version exists, remove it from the collection
|
||||
$index = array_search($camelized, $properties, true);
|
||||
if ($index !== false) {
|
||||
unset($properties[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PropertyNameCollection($properties);
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Command\Currencies;
|
||||
|
||||
use App\Settings\SystemSettings\LocalizationSettings;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Services\Tools\ExchangeRateUpdater;
|
||||
|
@ -39,7 +40,7 @@ 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
|
||||
{
|
||||
public function __construct(protected string $base_current, protected EntityManagerInterface $em, protected ExchangeRateUpdater $exchangeRateUpdater)
|
||||
public function __construct(protected EntityManagerInterface $em, protected ExchangeRateUpdater $exchangeRateUpdater, private readonly LocalizationSettings $localizationSettings)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
@ -54,13 +55,13 @@ class UpdateExchangeRatesCommand extends Command
|
|||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
//Check for valid base current
|
||||
if (3 !== strlen($this->base_current)) {
|
||||
if (3 !== strlen($this->localizationSettings->baseCurrency)) {
|
||||
$io->error('Chosen Base current is not valid. Check your settings!');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->note('Update currency exchange rates with base currency: '.$this->base_current);
|
||||
$io->note('Update currency exchange rates with base currency: '.$this->localizationSettings->baseCurrency);
|
||||
|
||||
//Check what currencies we need to update:
|
||||
$iso_code = $input->getArgument('iso_code');
|
||||
|
|
|
@ -35,7 +35,7 @@ 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
|
||||
{
|
||||
public function __construct(protected EntityManagerInterface $entityManager, ?string $name = null)
|
||||
public function __construct(protected EntityManagerInterface $entityManager, string $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
|
|
@ -467,11 +467,6 @@ abstract class BaseAdminController extends AbstractController
|
|||
$this->denyAccessUnlessGranted('read', $entity);
|
||||
$entities = $em->getRepository($this->entity_class)->findAll();
|
||||
|
||||
if (count($entities) === 0) {
|
||||
$this->addFlash('error', 'entity.export.flash.error.no_entities');
|
||||
return $this->redirectToRoute($this->route_base.'_new');
|
||||
}
|
||||
|
||||
return $exporter->exportEntityFromRequest($entities, $request);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ use App\Entity\Attachments\Attachment;
|
|||
use App\Form\Filters\AttachmentFilterType;
|
||||
use App\Services\Attachments\AttachmentManager;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use App\Settings\BehaviorSettings\TableSettings;
|
||||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
use RuntimeException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
@ -51,15 +52,15 @@ class AttachmentFileController extends AbstractController
|
|||
$this->denyAccessUnlessGranted('show_private', $attachment);
|
||||
}
|
||||
|
||||
if (!$attachment->hasInternal()) {
|
||||
throw $this->createNotFoundException('The file for this attachment is external and not stored locally!');
|
||||
if ($attachment->isExternal()) {
|
||||
throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!');
|
||||
}
|
||||
|
||||
if (!$helper->isInternalFileExisting($attachment)) {
|
||||
if (!$helper->isFileExisting($attachment)) {
|
||||
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
|
||||
}
|
||||
|
||||
$file_path = $helper->toAbsoluteInternalFilePath($attachment);
|
||||
$file_path = $helper->toAbsoluteFilePath($attachment);
|
||||
$response = new BinaryFileResponse($file_path);
|
||||
|
||||
//Set header content disposition, so that the file will be downloaded
|
||||
|
@ -80,15 +81,15 @@ class AttachmentFileController extends AbstractController
|
|||
$this->denyAccessUnlessGranted('show_private', $attachment);
|
||||
}
|
||||
|
||||
if (!$attachment->hasInternal()) {
|
||||
throw $this->createNotFoundException('The file for this attachment is external and not stored locally!');
|
||||
if ($attachment->isExternal()) {
|
||||
throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!');
|
||||
}
|
||||
|
||||
if (!$helper->isInternalFileExisting($attachment)) {
|
||||
if (!$helper->isFileExisting($attachment)) {
|
||||
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
|
||||
}
|
||||
|
||||
$file_path = $helper->toAbsoluteInternalFilePath($attachment);
|
||||
$file_path = $helper->toAbsoluteFilePath($attachment);
|
||||
$response = new BinaryFileResponse($file_path);
|
||||
|
||||
//Set header content disposition, so that the file will be downloaded
|
||||
|
@ -98,7 +99,8 @@ class AttachmentFileController extends AbstractController
|
|||
}
|
||||
|
||||
#[Route(path: '/attachment/list', name: 'attachment_list')]
|
||||
public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder): Response
|
||||
public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder,
|
||||
TableSettings $tableSettings): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@attachments.list_attachments');
|
||||
|
||||
|
@ -110,7 +112,7 @@ class AttachmentFileController extends AbstractController
|
|||
|
||||
$filterForm->handleRequest($formRequest);
|
||||
|
||||
$table = $dataTableFactory->createFromType(AttachmentDataTable::class, ['filter' => $filter])
|
||||
$table = $dataTableFactory->createFromType(AttachmentDataTable::class, ['filter' => $filter], ['pageLength' => $tableSettings->fullDefaultPageSize])
|
||||
->handleRequest($request);
|
||||
|
||||
if ($table->isCallback()) {
|
||||
|
|
|
@ -38,6 +38,7 @@ use App\Services\LogSystem\LogEntryExtraFormatter;
|
|||
use App\Services\LogSystem\LogLevelHelper;
|
||||
use App\Services\LogSystem\LogTargetHelper;
|
||||
use App\Services\LogSystem\TimeTravel;
|
||||
use App\Settings\BehaviorSettings\TableSettings;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
|
@ -58,7 +59,7 @@ class LogController extends AbstractController
|
|||
}
|
||||
|
||||
#[Route(path: '/', name: 'log_view')]
|
||||
public function showLogs(Request $request, DataTableFactory $dataTable): Response
|
||||
public function showLogs(Request $request, DataTableFactory $dataTable, TableSettings $tableSettings): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@system.show_logs');
|
||||
|
||||
|
@ -72,7 +73,7 @@ class LogController extends AbstractController
|
|||
|
||||
$table = $dataTable->createFromType(LogDataTable::class, [
|
||||
'filter' => $filter,
|
||||
])
|
||||
], ['pageLength' => $tableSettings->fullDefaultPageSize])
|
||||
->handleRequest($request);
|
||||
|
||||
if ($table->isCallback()) {
|
||||
|
|
|
@ -112,9 +112,8 @@ class PartImportExportController extends AbstractController
|
|||
$ids = $request->query->get('ids', '');
|
||||
$parts = $this->partsTableActionHandler->idStringToArray($ids);
|
||||
|
||||
if (count($parts) === 0) {
|
||||
$this->addFlash('error', 'entity.export.flash.error.no_entities');
|
||||
return $this->redirectToRoute('homepage');
|
||||
if ($parts === []) {
|
||||
throw new \RuntimeException('No parts found!');
|
||||
}
|
||||
|
||||
//Ensure that we have access to the parts
|
||||
|
|
|
@ -35,6 +35,7 @@ use App\Exceptions\InvalidRegexException;
|
|||
use App\Form\Filters\PartFilterType;
|
||||
use App\Services\Parts\PartsTableActionHandler;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use App\Settings\BehaviorSettings\TableSettings;
|
||||
use Doctrine\DBAL\Exception\DriverException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
|
@ -47,7 +48,12 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
|
||||
class PartListsController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator)
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager,
|
||||
private readonly NodesListBuilder $nodesListBuilder,
|
||||
private readonly DataTableFactory $dataTableFactory,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly TableSettings $tableSettings
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -132,11 +138,7 @@ class PartListsController extends AbstractController
|
|||
|
||||
$filterForm->handleRequest($formRequest);
|
||||
|
||||
$table = $this->dataTableFactory->createFromType(
|
||||
PartsDataTable::class,
|
||||
array_merge(['filter' => $filter], $additional_table_vars),
|
||||
['lengthMenu' => PartsDataTable::LENGTH_MENU]
|
||||
)
|
||||
$table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(['filter' => $filter], $additional_table_vars), ['pageLength' => $this->tableSettings->fullDefaultPageSize])
|
||||
->handleRequest($request);
|
||||
|
||||
if ($table->isCallback()) {
|
||||
|
|
|
@ -31,6 +31,7 @@ use App\Form\ProjectSystem\ProjectBuildType;
|
|||
use App\Helpers\Projects\ProjectBuildRequest;
|
||||
use App\Services\ImportExportSystem\BOMImporter;
|
||||
use App\Services\ProjectSystem\ProjectBuildHelper;
|
||||
use App\Settings\BehaviorSettings\TableSettings;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use League\Csv\SyntaxError;
|
||||
|
@ -55,11 +56,12 @@ class ProjectController extends AbstractController
|
|||
}
|
||||
|
||||
#[Route(path: '/{id}/info', name: 'project_info', requirements: ['id' => '\d+'])]
|
||||
public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper): Response
|
||||
public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper, TableSettings $tableSettings): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('read', $project);
|
||||
|
||||
$table = $this->dataTableFactory->createFromType(ProjectBomEntriesDataTable::class, ['project' => $project])
|
||||
$table = $this->dataTableFactory->createFromType(ProjectBomEntriesDataTable::class, ['project' => $project],
|
||||
['pageLength' => $tableSettings->fullDefaultPageSize])
|
||||
->handleRequest($request);
|
||||
|
||||
if ($table->isCallback()) {
|
||||
|
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\Controller;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Settings\SystemSettings\LocalizationSettings;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
@ -35,7 +36,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
*/
|
||||
class RedirectController extends AbstractController
|
||||
{
|
||||
public function __construct(protected string $default_locale, protected TranslatorInterface $translator, protected bool $enforce_index_php)
|
||||
public function __construct(private readonly LocalizationSettings $localizationSettings, protected TranslatorInterface $translator, protected bool $enforce_index_php)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -46,7 +47,7 @@ class RedirectController extends AbstractController
|
|||
public function addLocalePart(Request $request): RedirectResponse
|
||||
{
|
||||
//By default, we use the global default locale
|
||||
$locale = $this->default_locale;
|
||||
$locale = $this->localizationSettings->locale;
|
||||
|
||||
//Check if a user has set a preferred language setting:
|
||||
$user = $this->getUser();
|
||||
|
|
73
src/Controller/SettingsController.php
Normal file
73
src/Controller/SettingsController.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 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\Controller;
|
||||
|
||||
use App\Settings\AppSettings;
|
||||
use Jbtronics\SettingsBundle\Form\SettingsFormFactoryInterface;
|
||||
use Jbtronics\SettingsBundle\Manager\SettingsManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
class SettingsController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly SettingsManagerInterface $settingsManager, private readonly SettingsFormFactoryInterface $settingsFormFactory)
|
||||
{}
|
||||
|
||||
#[Route("/settings", name: "system_settings")]
|
||||
public function systemSettings(Request $request, TagAwareCacheInterface $cache): Response
|
||||
{
|
||||
//Create a clone of the settings object
|
||||
$settings = $this->settingsManager->createTemporaryCopy(AppSettings::class);
|
||||
|
||||
//Create a form builder for the settings object
|
||||
$builder = $this->settingsFormFactory->createSettingsFormBuilder($settings);
|
||||
|
||||
//Add a submit button to the form
|
||||
$builder->add('submit', \Symfony\Component\Form\Extension\Core\Type\SubmitType::class, ['label' => 'save']);
|
||||
|
||||
//Create the form
|
||||
$form = $builder->getForm();
|
||||
$form->handleRequest($request);
|
||||
|
||||
//If the form was submitted and is valid, save the settings
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->settingsManager->mergeTemporaryCopy($settings);
|
||||
$this->settingsManager->save($settings);
|
||||
|
||||
//It might be possible, that the tree settings have changed, so clear the cache
|
||||
$cache->invalidateTags(['tree_treeview', 'sidebar_tree_update']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//Render the form
|
||||
return $this->render('settings/settings.html.twig', [
|
||||
'form' => $form
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ use App\Services\Doctrine\DBInfoHelper;
|
|||
use App\Services\Doctrine\NatsortDebugHelper;
|
||||
use App\Services\Misc\GitVersionInfo;
|
||||
use App\Services\System\UpdateAvailableManager;
|
||||
use App\Settings\AppSettings;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
@ -47,7 +48,8 @@ class ToolsController extends AbstractController
|
|||
|
||||
#[Route(path: '/server_infos', name: 'tools_server_infos')]
|
||||
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper, NatsortDebugHelper $natsortDebugHelper,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager): Response
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager,
|
||||
AppSettings $settings): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@system.server_infos');
|
||||
|
||||
|
@ -55,23 +57,23 @@ class ToolsController extends AbstractController
|
|||
//Part-DB section
|
||||
'git_branch' => $versionInfo->getGitBranchName(),
|
||||
'git_commit' => $versionInfo->getGitCommitHash(),
|
||||
'default_locale' => $this->getParameter('partdb.locale'),
|
||||
'default_timezone' => $this->getParameter('partdb.timezone'),
|
||||
'default_currency' => $this->getParameter('partdb.default_currency'),
|
||||
'default_theme' => $this->getParameter('partdb.global_theme'),
|
||||
'default_locale' => $settings->system->localization->locale,
|
||||
'default_timezone' => $settings->system->localization->timezone,
|
||||
'default_currency' => $settings->system->localization->baseCurrency,
|
||||
'default_theme' => $settings->system->customization->theme,
|
||||
'enabled_locales' => $this->getParameter('partdb.locale_menu'),
|
||||
'demo_mode' => $this->getParameter('partdb.demo_mode'),
|
||||
'use_gravatar' => $settings->system->privacy->useGravatar,
|
||||
'gdpr_compliance' => $this->getParameter('partdb.gdpr_compliance'),
|
||||
'use_gravatar' => $this->getParameter('partdb.users.use_gravatar'),
|
||||
'email_password_reset' => $this->getParameter('partdb.users.email_pw_reset'),
|
||||
'environment' => $this->getParameter('kernel.environment'),
|
||||
'is_debug' => $this->getParameter('kernel.debug'),
|
||||
'email_sender' => $this->getParameter('partdb.mail.sender_email'),
|
||||
'email_sender_name' => $this->getParameter('partdb.mail.sender_name'),
|
||||
'allow_attachments_downloads' => $this->getParameter('partdb.attachments.allow_downloads'),
|
||||
'allow_attachments_downloads' => $settings->system->attachments->allowDownloads,
|
||||
'detailed_error_pages' => $this->getParameter('partdb.error_pages.show_help'),
|
||||
'error_page_admin_email' => $this->getParameter('partdb.error_pages.admin_email'),
|
||||
'configured_max_file_size' => $this->getParameter('partdb.attachments.max_file_size'),
|
||||
'configured_max_file_size' => $settings->system->attachments->maxFileSize,
|
||||
'effective_max_file_size' => $attachmentSubmitHandler->getMaximumAllowedUploadSize(),
|
||||
'saml_enabled' => $this->getParameter('partdb.saml.enabled'),
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
|
|||
|
||||
$attachment = new PartAttachment();
|
||||
$attachment->setName('Test2');
|
||||
$attachment->setInternalPath('invalid');
|
||||
$attachment->setPath('invalid');
|
||||
$attachment->setShowInTable(true);
|
||||
$attachment->setAttachmentType($manager->find(AttachmentType::class, 1));
|
||||
$part->addAttachment($attachment);
|
||||
|
|
|
@ -54,7 +54,7 @@ class TwoStepORMAdapter extends ORMAdapter
|
|||
|
||||
private \Closure|null $query_modifier = null;
|
||||
|
||||
public function __construct(?ManagerRegistry $registry = null)
|
||||
public function __construct(ManagerRegistry $registry = null)
|
||||
{
|
||||
parent::__construct($registry);
|
||||
$this->detailQueryCallable = static function (QueryBuilder $qb, array $ids): never {
|
||||
|
|
|
@ -50,8 +50,8 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
{
|
||||
$dataTable->add('dont_matter', RowClassColumn::class, [
|
||||
'render' => function ($value, Attachment $context): string {
|
||||
//Mark attachments yellow which have an internal file linked that doesn't exist
|
||||
if($context->hasInternal() && !$this->attachmentHelper->isInternalFileExisting($context)){
|
||||
//Mark attachments with missing files yellow
|
||||
if(!$this->attachmentHelper->isFileExisting($context)){
|
||||
return 'table-warning';
|
||||
}
|
||||
|
||||
|
@ -64,8 +64,8 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
'className' => 'no-colvis',
|
||||
'render' => function ($value, Attachment $context): string {
|
||||
if ($context->isPicture()
|
||||
&& $this->attachmentHelper->isInternalFileExisting($context)) {
|
||||
|
||||
&& !$context->isExternal()
|
||||
&& $this->attachmentHelper->isFileExisting($context)) {
|
||||
$title = htmlspecialchars($context->getName());
|
||||
if ($context->getFilename()) {
|
||||
$title .= ' ('.htmlspecialchars($context->getFilename()).')';
|
||||
|
@ -93,6 +93,26 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
$dataTable->add('name', TextColumn::class, [
|
||||
'label' => 'attachment.edit.name',
|
||||
'orderField' => 'NATSORT(attachment.name)',
|
||||
'render' => function ($value, Attachment $context) {
|
||||
//Link to external source
|
||||
if ($context->isExternal()) {
|
||||
return sprintf(
|
||||
'<a href="%s" class="link-external">%s</a>',
|
||||
htmlspecialchars((string) $context->getURL()),
|
||||
htmlspecialchars($value)
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->attachmentHelper->isFileExisting($context)) {
|
||||
return sprintf(
|
||||
'<a href="%s" target="_blank" data-no-ajax>%s</a>',
|
||||
$this->entityURLGenerator->viewURL($context),
|
||||
htmlspecialchars($value)
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
]);
|
||||
|
||||
$dataTable->add('attachment_type', TextColumn::class, [
|
||||
|
@ -116,60 +136,25 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
|||
),
|
||||
]);
|
||||
|
||||
$dataTable->add('internal_link', TextColumn::class, [
|
||||
'label' => 'attachment.table.internal_file',
|
||||
$dataTable->add('filename', TextColumn::class, [
|
||||
'label' => $this->translator->trans('attachment.table.filename'),
|
||||
'propertyPath' => 'filename',
|
||||
'orderField' => 'NATSORT(attachment.original_filename)',
|
||||
'render' => function ($value, Attachment $context) {
|
||||
if ($this->attachmentHelper->isInternalFileExisting($context)) {
|
||||
return sprintf(
|
||||
'<a href="%s" target="_blank" data-no-ajax>%s</a>',
|
||||
$this->entityURLGenerator->viewURL($context),
|
||||
htmlspecialchars($value)
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
]);
|
||||
|
||||
$dataTable->add('external_link', TextColumn::class, [
|
||||
'label' => 'attachment.table.external_link',
|
||||
'propertyPath' => 'host',
|
||||
'orderField' => 'attachment.external_path',
|
||||
'render' => function ($value, Attachment $context) {
|
||||
if ($context->hasExternal()) {
|
||||
return sprintf(
|
||||
'<a href="%s" class="link-external" title="%s" target="_blank" rel="noopener">%s</a>',
|
||||
htmlspecialchars((string) $context->getExternalPath()),
|
||||
htmlspecialchars((string) $context->getExternalPath()),
|
||||
htmlspecialchars($value),
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
]);
|
||||
|
||||
$dataTable->add('filesize', TextColumn::class, [
|
||||
'label' => $this->translator->trans('attachment.table.filesize'),
|
||||
'render' => function ($value, Attachment $context) {
|
||||
if (!$context->hasInternal()) {
|
||||
if ($context->isExternal()) {
|
||||
return sprintf(
|
||||
'<span class="badge bg-primary">
|
||||
<i class="fas fa-globe fa-fw"></i>%s
|
||||
</span>',
|
||||
$this->translator->trans('attachment.external_only')
|
||||
$this->translator->trans('attachment.external')
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->attachmentHelper->isInternalFileExisting($context)) {
|
||||
return sprintf(
|
||||
'<span class="badge bg-secondary">
|
||||
<i class="fas fa-hdd fa-fw"></i> %s
|
||||
</span>',
|
||||
$this->attachmentHelper->getHumanFileSize($context)
|
||||
);
|
||||
if ($this->attachmentHelper->isFileExisting($context)) {
|
||||
return $this->attachmentHelper->getHumanFileSize($context);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
|
|
|
@ -45,9 +45,6 @@ class AttachmentFilter implements FilterInterface
|
|||
public readonly DateTimeConstraint $lastModified;
|
||||
public readonly DateTimeConstraint $addedDate;
|
||||
|
||||
public readonly TextConstraint $originalFileName;
|
||||
public readonly TextConstraint $externalLink;
|
||||
|
||||
|
||||
public function __construct(NodesListBuilder $nodesListBuilder)
|
||||
{
|
||||
|
@ -58,9 +55,6 @@ class AttachmentFilter implements FilterInterface
|
|||
$this->lastModified = new DateTimeConstraint('attachment.lastModified');
|
||||
$this->addedDate = new DateTimeConstraint('attachment.addedDate');
|
||||
$this->showInTable = new BooleanConstraint('attachment.show_in_table');
|
||||
$this->originalFileName = new TextConstraint('attachment.original_filename');
|
||||
$this->externalLink = new TextConstraint('attachment.external_path');
|
||||
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
|
|
|
@ -45,7 +45,7 @@ abstract class AbstractConstraint implements FilterInterface
|
|||
* @var string The property where this BooleanConstraint should apply to
|
||||
*/
|
||||
protected string $property,
|
||||
?string $identifier = null)
|
||||
string $identifier = null)
|
||||
{
|
||||
$this->identifier = $identifier ?? $this->generateParameterIdentifier($property);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class BooleanConstraint extends AbstractConstraint
|
|||
{
|
||||
public function __construct(
|
||||
string $property,
|
||||
?string $identifier = null,
|
||||
string $identifier = null,
|
||||
/** @var bool|null The value of our constraint */
|
||||
protected ?bool $value = null
|
||||
)
|
||||
|
|
|
@ -34,7 +34,7 @@ class DateTimeConstraint extends AbstractConstraint
|
|||
|
||||
public function __construct(
|
||||
string $property,
|
||||
?string $identifier = null,
|
||||
string $identifier = null,
|
||||
/**
|
||||
* The value1 used for comparison (this is the main one used for all mono-value comparisons)
|
||||
*/
|
||||
|
|
|
@ -46,7 +46,7 @@ class EntityConstraint extends AbstractConstraint
|
|||
public function __construct(protected ?NodesListBuilder $nodesListBuilder,
|
||||
protected string $class,
|
||||
string $property,
|
||||
?string $identifier = null,
|
||||
string $identifier = null,
|
||||
protected ?AbstractDBElement $value = null,
|
||||
protected ?string $operator = null)
|
||||
{
|
||||
|
|
|
@ -31,7 +31,7 @@ class NumberConstraint extends AbstractConstraint
|
|||
|
||||
public function __construct(
|
||||
string $property,
|
||||
?string $identifier = null,
|
||||
string $identifier = null,
|
||||
/**
|
||||
* The value1 used for comparison (this is the main one used for all mono-value comparisons)
|
||||
*/
|
||||
|
|
|
@ -28,7 +28,7 @@ use Doctrine\ORM\QueryBuilder;
|
|||
|
||||
class LessThanDesiredConstraint extends BooleanConstraint
|
||||
{
|
||||
public function __construct(?string $property = null, ?string $identifier = null, ?bool $default_value = null)
|
||||
public function __construct(string $property = null, string $identifier = null, ?bool $default_value = null)
|
||||
{
|
||||
parent::__construct($property ?? '(
|
||||
SELECT COALESCE(SUM(ld_partLot.amount), 0.0)
|
||||
|
|
|
@ -30,7 +30,7 @@ class TagsConstraint extends AbstractConstraint
|
|||
{
|
||||
final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE'];
|
||||
|
||||
public function __construct(string $property, ?string $identifier = null,
|
||||
public function __construct(string $property, string $identifier = null,
|
||||
protected ?string $value = null,
|
||||
protected ?string $operator = '')
|
||||
{
|
||||
|
|
|
@ -32,7 +32,7 @@ class TextConstraint extends AbstractConstraint
|
|||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function __construct(string $property, ?string $identifier = null, /**
|
||||
public function __construct(string $property, string $identifier = null, /**
|
||||
* @var string|null The value to compare to
|
||||
*/
|
||||
protected ?string $value = null, /**
|
||||
|
|
|
@ -72,7 +72,8 @@ class ColumnSortHelper
|
|||
* Apply the visibility configuration to the given DataTable and configure the columns.
|
||||
* @param DataTable $dataTable
|
||||
* @param string|array $visible_columns Either a list or a comma separated string of column names, which should
|
||||
* be visible by default. If a column is not listed here, it will be hidden by default.
|
||||
* be visible by default. If a column is not listed here, it will be hidden by default. If an array of enum values are passed,
|
||||
* their value will be used as the column name.
|
||||
* @return void
|
||||
*/
|
||||
public function applyVisibilityAndConfigureColumns(DataTable $dataTable, string|array $visible_columns,
|
||||
|
@ -83,6 +84,14 @@ class ColumnSortHelper
|
|||
$visible_columns = array_map(trim(...), explode(",", $visible_columns));
|
||||
}
|
||||
|
||||
//If $visible_columns is a list of enum values, convert them to the column names
|
||||
foreach ($visible_columns as &$value) {
|
||||
if ($value instanceof \BackedEnum) {
|
||||
$value = $value->value;
|
||||
}
|
||||
}
|
||||
unset ($value);
|
||||
|
||||
$processed_columns = [];
|
||||
|
||||
//First add all columns which visibility is not configurable
|
||||
|
|
|
@ -45,6 +45,7 @@ use App\Entity\Parts\PartLot;
|
|||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Services\EntityURLGenerator;
|
||||
use App\Services\Formatters\AmountFormatter;
|
||||
use App\Settings\BehaviorSettings\TableSettings;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
|
||||
|
@ -57,16 +58,14 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
|
||||
final class PartsDataTable implements DataTableTypeInterface
|
||||
{
|
||||
const LENGTH_MENU = [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]];
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityURLGenerator $urlGenerator,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly AmountFormatter $amountFormatter,
|
||||
private readonly PartDataTableHelper $partDataTableHelper,
|
||||
private readonly Security $security,
|
||||
private readonly string $visible_columns,
|
||||
private readonly ColumnSortHelper $csh,
|
||||
private readonly TableSettings $tableSettings,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -246,7 +245,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
]);
|
||||
|
||||
//Apply the user configured order and visibility and add the columns to the table
|
||||
$this->csh->applyVisibilityAndConfigureColumns($dataTable, $this->visible_columns,
|
||||
$this->csh->applyVisibilityAndConfigureColumns($dataTable, $this->tableSettings->partsDefaultColumns,
|
||||
"TABLE_PARTS_DEFAULT_COLUMNS");
|
||||
|
||||
$dataTable->addOrderBy('name')
|
||||
|
|
|
@ -33,24 +33,23 @@ use ApiPlatform\Metadata\Get;
|
|||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\ApiPlatform\DocumentedAPIProperties\DocumentedAPIProperty;
|
||||
use App\ApiPlatform\DocumentedAPIProperty;
|
||||
use App\ApiPlatform\Filter\EntityFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\ApiPlatform\HandleAttachmentsUploadsProcessor;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\EntityListeners\AttachmentDeleteListener;
|
||||
use App\Repository\AttachmentRepository;
|
||||
use App\Validator\Constraints\Selectable;
|
||||
use App\EntityListeners\AttachmentDeleteListener;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Validator\Constraints\Selectable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Serializer\Annotation\SerializedName;
|
||||
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* Class Attachment.
|
||||
|
@ -79,16 +78,11 @@ use function in_array;
|
|||
denormalizationContext: ['groups' => ['attachment:write', 'attachment:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
processor: HandleAttachmentsUploadsProcessor::class,
|
||||
)]
|
||||
//This property is added by the denormalizer in order to resolve the placeholder
|
||||
#[DocumentedAPIProperty(
|
||||
schemaName: 'Attachment-Read', property: 'internal_path', type: 'string', nullable: false,
|
||||
description: 'The URL to the internally saved copy of the file, if one exists',
|
||||
example: '/media/part/2/bc547-6508afa5a79c8.pdf'
|
||||
)]
|
||||
#[DocumentedAPIProperty(
|
||||
schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true,
|
||||
description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.'
|
||||
)]
|
||||
#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'media_url', type: 'string', nullable: true,
|
||||
description: 'The URL to the file, where the attachment file can be downloaded. This can be an internal or external URL.',
|
||||
example: '/media/part/2/bc547-6508afa5a79c8.pdf')]
|
||||
#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true,
|
||||
description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.')]
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name"])]
|
||||
#[ApiFilter(EntityFilter::class, properties: ["attachment_type"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||
|
@ -97,8 +91,8 @@ use function in_array;
|
|||
#[DiscriminatorMap(typeProperty: '_type', mapping: self::API_DISCRIMINATOR_MAP)]
|
||||
abstract class Attachment extends AbstractNamedDBElement
|
||||
{
|
||||
private const ORM_DISCRIMINATOR_MAP = ['Part' => PartAttachment::class, 'Device' => ProjectAttachment::class,
|
||||
'AttachmentType' => AttachmentTypeAttachment::class,
|
||||
private const ORM_DISCRIMINATOR_MAP = ['PartDB\Part' => PartAttachment::class, 'Part' => PartAttachment::class,
|
||||
'PartDB\Device' => ProjectAttachment::class, 'Device' => ProjectAttachment::class, 'AttachmentType' => AttachmentTypeAttachment::class,
|
||||
'Category' => CategoryAttachment::class, 'Footprint' => FootprintAttachment::class, 'Manufacturer' => ManufacturerAttachment::class,
|
||||
'Currency' => CurrencyAttachment::class, 'Group' => GroupAttachment::class, 'MeasurementUnit' => MeasurementUnitAttachment::class,
|
||||
'Storelocation' => StorageLocationAttachment::class, 'Supplier' => SupplierAttachment::class,
|
||||
|
@ -125,6 +119,10 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
*/
|
||||
final public const MODEL_EXTS = ['x3d'];
|
||||
|
||||
/**
|
||||
* When the path begins with one of the placeholders.
|
||||
*/
|
||||
final public const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%', '%SECURE%'];
|
||||
|
||||
/**
|
||||
* @var array placeholders for attachments which using built in files
|
||||
|
@ -154,21 +152,10 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
protected ?string $original_filename = null;
|
||||
|
||||
/**
|
||||
* @var string|null If a copy of the file is stored internally, the path to the file relative to a placeholder
|
||||
* path like %MEDIA%
|
||||
* @var string The path to the file relative to a placeholder path like %MEDIA%
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, nullable: true)]
|
||||
protected ?string $internal_path = null;
|
||||
|
||||
|
||||
/**
|
||||
* @var string|null The path to the external source if the file is stored externally or was downloaded from an
|
||||
* external source. Null if there is no external source.
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['attachment:read'])]
|
||||
#[ApiProperty(example: 'http://example.com/image.jpg')]
|
||||
protected ?string $external_path = null;
|
||||
#[ORM\Column(name: 'path', type: Types::STRING)]
|
||||
protected string $path = '';
|
||||
|
||||
/**
|
||||
* @var string the name of this element
|
||||
|
@ -250,7 +237,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
|
||||
/**
|
||||
* Check if this attachment is a picture (analyse the file's extension).
|
||||
* If the link is only external and doesn't contain an extension, it is assumed that this is true.
|
||||
* If the link is external, it is assumed that this is true.
|
||||
*
|
||||
* @return bool * true if the file extension is a picture extension
|
||||
* * otherwise false
|
||||
|
@ -258,67 +245,54 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
#[Groups(['attachment:read'])]
|
||||
public function isPicture(): bool
|
||||
{
|
||||
if($this->hasInternal()){
|
||||
|
||||
$extension = pathinfo($this->getInternalPath(), PATHINFO_EXTENSION);
|
||||
|
||||
return in_array(strtolower($extension), static::PICTURE_EXTS, true);
|
||||
|
||||
}
|
||||
if ($this->hasExternal()) {
|
||||
if ($this->isExternal()) {
|
||||
//Check if we can extract a file extension from the URL
|
||||
$extension = pathinfo(parse_url($this->getExternalPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
||||
$extension = pathinfo(parse_url($this->path, PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
||||
|
||||
//If no extension is found or it is known picture extension, we assume that this is a picture extension
|
||||
return $extension === '' || in_array(strtolower($extension), static::PICTURE_EXTS, true);
|
||||
}
|
||||
//File doesn't have an internal, nor an external copy. This shouldn't happen, but it certainly isn't a picture...
|
||||
return false;
|
||||
|
||||
$extension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
|
||||
|
||||
return in_array(strtolower($extension), static::PICTURE_EXTS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this attachment is a 3D model and therefore can be directly shown to user.
|
||||
* If no internal copy exists, false is returned (3D Models must be internal).
|
||||
* If the attachment is external, false is returned (3D Models must be internal).
|
||||
*/
|
||||
#[Groups(['attachment:read'])]
|
||||
#[SerializedName('3d_model')]
|
||||
public function is3DModel(): bool
|
||||
{
|
||||
//We just assume that 3D Models are internally saved, otherwise we get problems loading them.
|
||||
if (!$this->hasInternal()) {
|
||||
if ($this->isExternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$extension = pathinfo($this->getInternalPath(), PATHINFO_EXTENSION);
|
||||
$extension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
|
||||
|
||||
return in_array(strtolower($extension), static::MODEL_EXTS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this attachment has a path to an external file
|
||||
* Checks if the attachment file is externally saved (the database saves an URL).
|
||||
*
|
||||
* @return bool true, if there is a path to an external file
|
||||
* @phpstan-assert-if-true non-empty-string $this->external_path
|
||||
* @phpstan-assert-if-true non-empty-string $this->getExternalPath())
|
||||
* @return bool true, if the file is saved externally
|
||||
*/
|
||||
#[Groups(['attachment:read'])]
|
||||
public function hasExternal(): bool
|
||||
public function isExternal(): bool
|
||||
{
|
||||
return $this->external_path !== null && $this->external_path !== '';
|
||||
}
|
||||
//When path is empty, this attachment can not be external
|
||||
if ($this->path === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this attachment has a path to an internal file.
|
||||
* Does not check if the file exists.
|
||||
*
|
||||
* @return bool true, if there is a path to an internal file
|
||||
* @phpstan-assert-if-true non-empty-string $this->internal_path
|
||||
* @phpstan-assert-if-true non-empty-string $this->getInternalPath())
|
||||
*/
|
||||
#[Groups(['attachment:read'])]
|
||||
public function hasInternal(): bool
|
||||
{
|
||||
return $this->internal_path !== null && $this->internal_path !== '';
|
||||
//After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode
|
||||
$tmp = explode('/', $this->path);
|
||||
|
||||
return !in_array($tmp[0], array_merge(static::INTERNAL_PLACEHOLDER, static::BUILTIN_PLACEHOLDER), true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -331,12 +305,8 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
#[SerializedName('private')]
|
||||
public function isSecure(): bool
|
||||
{
|
||||
if ($this->internal_path === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode
|
||||
$tmp = explode('/', $this->internal_path);
|
||||
$tmp = explode('/', $this->path);
|
||||
|
||||
return '%SECURE%' === $tmp[0];
|
||||
}
|
||||
|
@ -350,11 +320,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
#[Groups(['attachment:read'])]
|
||||
public function isBuiltIn(): bool
|
||||
{
|
||||
if ($this->internal_path === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return static::checkIfBuiltin($this->internal_path);
|
||||
return static::checkIfBuiltin($this->path);
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
|
@ -366,13 +332,13 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
/**
|
||||
* Returns the extension of the file referenced via the attachment.
|
||||
* For a path like %BASE/path/foo.bar, bar will be returned.
|
||||
* If this attachment is only external null is returned.
|
||||
* If this attachment is external null is returned.
|
||||
*
|
||||
* @return string|null the file extension in lower case
|
||||
*/
|
||||
public function getExtension(): ?string
|
||||
{
|
||||
if (!$this->hasInternal()) {
|
||||
if ($this->isExternal()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -380,7 +346,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
return strtolower(pathinfo($this->original_filename, PATHINFO_EXTENSION));
|
||||
}
|
||||
|
||||
return strtolower(pathinfo($this->getInternalPath(), PATHINFO_EXTENSION));
|
||||
return strtolower(pathinfo($this->getPath(), PATHINFO_EXTENSION));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -395,54 +361,52 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
}
|
||||
|
||||
/**
|
||||
* The URL to the external file, or the path to the built-in file, but not paths to uploaded files.
|
||||
* The URL to the external file, or the path to the built-in file.
|
||||
* Returns null, if the file is not external (and not builtin).
|
||||
* The output of this function is such, that no changes occur when it is fed back into setURL().
|
||||
* Required for the Attachment form field.
|
||||
*/
|
||||
#[Groups(['attachment:read'])]
|
||||
#[SerializedName('url')]
|
||||
public function getURL(): ?string
|
||||
{
|
||||
if($this->hasExternal()){
|
||||
return $this->getExternalPath();
|
||||
if (!$this->isExternal() && !$this->isBuiltIn()) {
|
||||
return null;
|
||||
}
|
||||
if($this->isBuiltIn()){
|
||||
return $this->getInternalPath();
|
||||
}
|
||||
return null;
|
||||
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostname where the external file is stored.
|
||||
* Returns null, if there is no external path.
|
||||
* Returns null, if the file is not external.
|
||||
*/
|
||||
public function getHost(): ?string
|
||||
{
|
||||
if (!$this->hasExternal()) {
|
||||
if (!$this->isExternal()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parse_url($this->getExternalPath(), PHP_URL_HOST);
|
||||
return parse_url((string) $this->getURL(), PHP_URL_HOST);
|
||||
}
|
||||
|
||||
public function getInternalPath(): ?string
|
||||
/**
|
||||
* Get the filepath, relative to %BASE%.
|
||||
*
|
||||
* @return string A string like %BASE/path/foo.bar
|
||||
*/
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->internal_path;
|
||||
}
|
||||
|
||||
public function getExternalPath(): ?string
|
||||
{
|
||||
return $this->external_path;
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename of the attachment.
|
||||
* For a path like %BASE/path/foo.bar, foo.bar will be returned.
|
||||
*
|
||||
* If there is no internal copy of the file, null will be returned.
|
||||
* If the path is a URL (can be checked via isExternal()), null will be returned.
|
||||
*/
|
||||
public function getFilename(): ?string
|
||||
{
|
||||
if (!$this->hasInternal()) {
|
||||
if ($this->isExternal()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -451,7 +415,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
return $this->original_filename;
|
||||
}
|
||||
|
||||
return pathinfo($this->getInternalPath(), PATHINFO_BASENAME);
|
||||
return pathinfo($this->getPath(), PATHINFO_BASENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -524,12 +488,15 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the path to a file hosted internally. If you set this path to a file that was not downloaded from the
|
||||
* external source in external_path, make sure to reset external_path.
|
||||
* Sets the filepath (with relative placeholder) for this attachment.
|
||||
*
|
||||
* @param string $path the new filepath of the attachment
|
||||
*
|
||||
* @return Attachment
|
||||
*/
|
||||
public function setInternalPath(?string $internal_path): self
|
||||
public function setPath(string $path): self
|
||||
{
|
||||
$this->internal_path = $internal_path;
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -545,61 +512,35 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets up the paths using a user provided string which might contain an external path or a builtin path. Allows
|
||||
* resetting the external path if an internal path exists. Resets any other paths if a (nonempty) new path is set.
|
||||
* Sets the url associated with this attachment.
|
||||
* If the url is empty nothing is changed, to not override the file path.
|
||||
*
|
||||
* @return Attachment
|
||||
*/
|
||||
#[Groups(['attachment:write'])]
|
||||
#[SerializedName('url')]
|
||||
#[ApiProperty(description: 'Set the path of the attachment here.
|
||||
Provide either an external URL, a path to a builtin file (like %FOOTPRINTS%/Active/ICs/IC_DFS.png) or an empty
|
||||
string if the attachment has an internal file associated and you\'d like to reset the external source.
|
||||
If you set a new (nonempty) file path any associated internal file will be removed!')]
|
||||
public function setURL(?string $url): self
|
||||
{
|
||||
//Don't allow the user to set an empty external path if the internal path is empty already
|
||||
if (($url === null || $url === "") && !$this->hasInternal()) {
|
||||
//Do nothing if the URL is empty
|
||||
if ($url === null || $url === '') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
//The URL field can also contain the special builtin internal paths, so we need to distinguish here
|
||||
if ($this::checkIfBuiltin($url)) {
|
||||
$this->setInternalPath($url);
|
||||
//make sure the external path isn't still pointing to something unrelated
|
||||
$this->setExternalPath(null);
|
||||
} else {
|
||||
$this->setExternalPath($url);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the path to a file hosted on an external server. Setting the external path to a (nonempty) value different
|
||||
* from the the old one _clears_ the internal path, so that the external path reflects where any associated internal
|
||||
* file came from.
|
||||
*/
|
||||
public function setExternalPath(?string $external_path): self
|
||||
{
|
||||
//If we only clear the external path, don't reset the internal path, since that could be confusing
|
||||
if($external_path === null || $external_path === '') {
|
||||
$this->external_path = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
$external_path = trim($external_path);
|
||||
$url = trim($url);
|
||||
//Escape spaces in URL
|
||||
$external_path = str_replace(' ', '%20', $external_path);
|
||||
$url = str_replace(' ', '%20', $url);
|
||||
|
||||
if($this->external_path === $external_path) {
|
||||
//Nothing changed, nothing to do
|
||||
return $this;
|
||||
//Only set if the URL is not empty
|
||||
if ($url !== '') {
|
||||
if (str_contains($url, '%BASE%') || str_contains($url, '%MEDIA%')) {
|
||||
throw new InvalidArgumentException('You can not reference internal files via the url field! But nice try!');
|
||||
}
|
||||
|
||||
$this->path = $url;
|
||||
//Reset internal filename
|
||||
$this->original_filename = null;
|
||||
}
|
||||
|
||||
$this->external_path = $external_path;
|
||||
$this->internal_path = null;
|
||||
//Reset internal filename
|
||||
$this->original_filename = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -610,17 +551,12 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
/**
|
||||
* Checks if the given path is a path to a builtin resource.
|
||||
*
|
||||
* @param string|null $path The path that should be checked
|
||||
* @param string $path The path that should be checked
|
||||
*
|
||||
* @return bool true if the path is pointing to a builtin resource
|
||||
*/
|
||||
public static function checkIfBuiltin(?string $path): bool
|
||||
public static function checkIfBuiltin(string $path): bool
|
||||
{
|
||||
//An empty path can't be a builtin
|
||||
if ($path === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode
|
||||
$tmp = explode('/', $path);
|
||||
//Builtins must have a %PLACEHOLDER% construction
|
||||
|
|
|
@ -162,7 +162,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement
|
|||
*
|
||||
* @return string the link to the article
|
||||
*/
|
||||
public function getAutoProductUrl(?string $partnr = null): string
|
||||
public function getAutoProductUrl(string $partnr = null): string
|
||||
{
|
||||
if (is_string($partnr)) {
|
||||
return str_replace('%PARTNUMBER%', $partnr, $this->auto_product_url);
|
||||
|
|
|
@ -54,7 +54,7 @@ class OAuthToken extends AbstractNamedDBElement implements AccessTokenInterface
|
|||
*/
|
||||
private const DEFAULT_EXPIRATION_TIME = 3600;
|
||||
|
||||
public function __construct(string $name, ?string $refresh_token, ?string $token = null, ?\DateTimeImmutable $expires_at = null)
|
||||
public function __construct(string $name, ?string $refresh_token, ?string $token = null, \DateTimeImmutable $expires_at = null)
|
||||
{
|
||||
//If token is given, you also have to give the expires_at date
|
||||
if ($token !== null && $expires_at === null) {
|
||||
|
|
|
@ -197,7 +197,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
|||
/**
|
||||
* @var string|null The language/locale the user prefers
|
||||
*/
|
||||
#[Assert\Language]
|
||||
#[Assert\Locale]
|
||||
#[Groups(['full', 'import', 'user:read'])]
|
||||
#[ORM\Column(name: 'config_language', type: Types::STRING, nullable: true)]
|
||||
protected ?string $language = '';
|
||||
|
|
|
@ -52,8 +52,8 @@ class AttachmentDeleteListener
|
|||
#[PreUpdate]
|
||||
public function preUpdateHandler(Attachment $attachment, PreUpdateEventArgs $event): void
|
||||
{
|
||||
if ($event->hasChangedField('internal_path')) {
|
||||
$old_path = $event->getOldValue('internal_path');
|
||||
if ($event->hasChangedField('path')) {
|
||||
$old_path = $event->getOldValue('path');
|
||||
|
||||
//Dont delete file if the attachment uses a builtin ressource:
|
||||
if (Attachment::checkIfBuiltin($old_path)) {
|
||||
|
|
|
@ -39,6 +39,8 @@ use App\Services\LogSystem\EventCommentHelper;
|
|||
use App\Services\LogSystem\EventLogger;
|
||||
use App\Services\LogSystem\EventUndoHelper;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use App\Settings\SystemSettings\HistorySettings;
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Event\PostFlushEventArgs;
|
||||
|
@ -74,14 +76,15 @@ class EventLoggerListener
|
|||
];
|
||||
|
||||
protected const MAX_STRING_LENGTH = 2000;
|
||||
protected bool $save_new_data;
|
||||
|
||||
public function __construct(protected EventLogger $logger, protected SerializerInterface $serializer, protected EventCommentHelper $eventCommentHelper,
|
||||
protected bool $save_changed_fields, protected bool $save_changed_data, protected bool $save_removed_data, bool $save_new_data,
|
||||
protected PropertyAccessorInterface $propertyAccessor, protected EventUndoHelper $eventUndoHelper)
|
||||
public function __construct(
|
||||
protected EventLogger $logger,
|
||||
protected SerializerInterface $serializer,
|
||||
protected EventCommentHelper $eventCommentHelper,
|
||||
private readonly HistorySettings $settings,
|
||||
protected PropertyAccessorInterface $propertyAccessor,
|
||||
protected EventUndoHelper $eventUndoHelper)
|
||||
{
|
||||
//This option only makes sense if save_changed_data is true
|
||||
$this->save_new_data = $save_new_data && $save_changed_data;
|
||||
}
|
||||
|
||||
public function onFlush(OnFlushEventArgs $eventArgs): void
|
||||
|
@ -200,14 +203,14 @@ class EventLoggerListener
|
|||
if ($this->eventUndoHelper->isUndo()) {
|
||||
$log->setUndoneEvent($this->eventUndoHelper->getUndoneEvent(), $this->eventUndoHelper->getMode());
|
||||
}
|
||||
if ($this->save_removed_data) {
|
||||
if ($this->settings->saveRemovedData) {
|
||||
//The 4th param is important here, as we delete the element...
|
||||
$this->saveChangeSet($entity, $log, $em, true);
|
||||
}
|
||||
$this->logger->logFromOnFlush($log);
|
||||
|
||||
//Check if we have to log CollectionElementDeleted entries
|
||||
if ($this->save_changed_data) {
|
||||
if ($this->settings->saveOldData) {
|
||||
$metadata = $em->getClassMetadata($entity::class);
|
||||
$mappings = $metadata->getAssociationMappings();
|
||||
//Check if class is whitelisted for CollectionElementDeleted entry
|
||||
|
@ -243,9 +246,9 @@ class EventLoggerListener
|
|||
}
|
||||
|
||||
$log = new ElementEditedLogEntry($entity);
|
||||
if ($this->save_changed_data) {
|
||||
if ($this->settings->saveOldData) {
|
||||
$this->saveChangeSet($entity, $log, $em);
|
||||
} elseif ($this->save_changed_fields) {
|
||||
} elseif ($this->settings->saveChangedFields) {
|
||||
$changed_fields = array_keys($uow->getEntityChangeSet($entity));
|
||||
//Remove lastModified field, as this is always changed (gives us no additional info)
|
||||
$changed_fields = array_diff($changed_fields, ['lastModified']);
|
||||
|
@ -313,7 +316,7 @@ class EventLoggerListener
|
|||
$changeSet = $uow->getEntityChangeSet($entity);
|
||||
$old_data = array_combine(array_keys($changeSet), array_column($changeSet, 0));
|
||||
//If save_new_data is enabled, we extract it from the change set
|
||||
if ($this->save_new_data) {
|
||||
if ($this->settings->saveNewData && $this->settings->saveOldData) { //Only useful if we save old data too
|
||||
$new_data = array_combine(array_keys($changeSet), array_column($changeSet, 1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\EventSubscriber\UserSystem;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Settings\SystemSettings\LocalizationSettings;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\ControllerEvent;
|
||||
|
@ -33,7 +34,7 @@ use Symfony\Component\HttpKernel\KernelEvents;
|
|||
*/
|
||||
final class SetUserTimezoneSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(private readonly string $default_timezone, private readonly Security $security)
|
||||
public function __construct(private readonly LocalizationSettings $localizationSettings, private readonly Security $security)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -48,8 +49,8 @@ final class SetUserTimezoneSubscriber implements EventSubscriberInterface
|
|||
}
|
||||
|
||||
//Fill with default value if needed
|
||||
if (null === $timezone && $this->default_timezone !== '') {
|
||||
$timezone = $this->default_timezone;
|
||||
if (null === $timezone && $this->localizationSettings !== '') {
|
||||
$timezone = $this->localizationSettings->timezone;
|
||||
}
|
||||
|
||||
//If timezone was configured anywhere set it, otherwise just use the one from php.ini
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace App\Form\AdminPages;
|
|||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\UserSystem\Group;
|
||||
use App\Services\LogSystem\EventCommentType;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
|
@ -152,7 +153,7 @@ class BaseEntityAdminForm extends AbstractType
|
|||
$builder->add('log_comment', TextType::class, [
|
||||
'label' => 'edit.log_comment',
|
||||
'mapped' => false,
|
||||
'required' => $this->eventCommentNeededHelper->isCommentNeeded($is_new ? 'datastructure_create': 'datastructure_edit'),
|
||||
'required' => $this->eventCommentNeededHelper->isCommentNeeded($is_new ? EventCommentType::DATASTRUCTURE_CREATE: EventCommentType::DATASTRUCTURE_EDIT),
|
||||
'empty_data' => null,
|
||||
]);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
use App\Settings\SystemSettings\LocalizationSettings;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Form\Type\BigDecimalMoneyType;
|
||||
|
@ -32,7 +33,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
|
||||
class CurrencyAdminForm extends BaseEntityAdminForm
|
||||
{
|
||||
public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, private readonly string $base_currency)
|
||||
public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, private readonly LocalizationSettings $localizationSettings)
|
||||
{
|
||||
parent::__construct($security, $eventCommentNeededHelper);
|
||||
}
|
||||
|
@ -51,7 +52,7 @@ class CurrencyAdminForm extends BaseEntityAdminForm
|
|||
$builder->add('exchange_rate', BigDecimalMoneyType::class, [
|
||||
'required' => false,
|
||||
'label' => 'currency.edit.exchange_rate',
|
||||
'currency' => $this->base_currency,
|
||||
'currency' => $this->localizationSettings->baseCurrency,
|
||||
'scale' => 6,
|
||||
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||
]);
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
use App\Settings\SystemSettings\LocalizationSettings;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
|
@ -32,7 +33,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
|||
|
||||
class SupplierForm extends CompanyForm
|
||||
{
|
||||
public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, protected string $base_currency)
|
||||
public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, private readonly LocalizationSettings $localizationSettings)
|
||||
{
|
||||
parent::__construct($security, $eventCommentNeededHelper);
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ class SupplierForm extends CompanyForm
|
|||
|
||||
$builder->add('shipping_costs', BigDecimalMoneyType::class, [
|
||||
'required' => false,
|
||||
'currency' => $this->base_currency,
|
||||
'currency' => $this->localizationSettings->baseCurrency,
|
||||
'scale' => 3,
|
||||
'label' => 'supplier.shipping_costs.label',
|
||||
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Settings\SystemSettings\AttachmentsSettings;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
|
@ -54,9 +55,7 @@ class AttachmentFormType extends AbstractType
|
|||
protected Security $security,
|
||||
protected AttachmentSubmitHandler $submitHandler,
|
||||
protected TranslatorInterface $translator,
|
||||
protected bool $allow_attachments_download,
|
||||
protected bool $download_by_default,
|
||||
protected string $max_file_size
|
||||
protected AttachmentsSettings $settings,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -108,7 +107,7 @@ class AttachmentFormType extends AbstractType
|
|||
'required' => false,
|
||||
'label' => 'attachment.edit.download_url',
|
||||
'mapped' => false,
|
||||
'disabled' => !$this->allow_attachments_download,
|
||||
'disabled' => !$this->settings->allowDownloads,
|
||||
]);
|
||||
|
||||
$builder->add('file', FileType::class, [
|
||||
|
@ -177,7 +176,7 @@ class AttachmentFormType extends AbstractType
|
|||
|
||||
//If the attachment should be downloaded by default (and is download allowed at all), register a listener,
|
||||
// which sets the downloadURL checkbox to true for new attachments
|
||||
if ($this->download_by_default && $this->allow_attachments_download) {
|
||||
if ($this->settings->downloadByDefault && $this->settings->allowDownloads) {
|
||||
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event): void {
|
||||
$form = $event->getForm();
|
||||
$attachment = $form->getData();
|
||||
|
@ -204,7 +203,7 @@ class AttachmentFormType extends AbstractType
|
|||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Attachment::class,
|
||||
'max_file_size' => $this->max_file_size,
|
||||
'max_file_size' => $this->settings->maxFileSize,
|
||||
'allow_builtins' => true,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -100,15 +100,6 @@ class AttachmentFilterType extends AbstractType
|
|||
'label' => 'attachment.edit.show_in_table'
|
||||
]);
|
||||
|
||||
$builder->add('originalFileName', TextConstraintType::class, [
|
||||
'label' => 'attachment.file_name'
|
||||
]);
|
||||
|
||||
$builder->add('externalLink', TextConstraintType::class, [
|
||||
'label' => 'attachment.table.external_link'
|
||||
]);
|
||||
|
||||
|
||||
$builder->add('lastModified', DateTimeConstraintType::class, [
|
||||
'label' => 'lastModified'
|
||||
]);
|
||||
|
|
49
src/Form/History/EnforceEventCommentTypesType.php
Normal file
49
src/Form/History/EnforceEventCommentTypesType.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 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\Form\History;
|
||||
|
||||
use App\Services\LogSystem\EventCommentType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* The type for the "enforceComments" setting in the HistorySettings.
|
||||
*/
|
||||
class EnforceEventCommentTypesType extends AbstractType
|
||||
{
|
||||
public function getParent(): string
|
||||
{
|
||||
return EnumType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'multiple' => true,
|
||||
'class' => EventCommentType::class,
|
||||
'empty_data' => [],
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ use App\Form\Type\SIUnitType;
|
|||
use App\Form\Type\StructuralEntityType;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\LogSystem\EventCommentNeededHelper;
|
||||
use App\Services\LogSystem\EventCommentType;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
|
@ -265,7 +266,7 @@ class PartBaseType extends AbstractType
|
|||
$builder->add('log_comment', TextType::class, [
|
||||
'label' => 'edit.log_comment',
|
||||
'mapped' => false,
|
||||
'required' => $this->event_comment_needed_helper->isCommentNeeded($new_part ? 'part_create' : 'part_edit'),
|
||||
'required' => $this->event_comment_needed_helper->isCommentNeeded($new_part ? EventCommentType::PART_CREATE : EventCommentType::PART_EDIT),
|
||||
'empty_data' => null,
|
||||
]);
|
||||
|
||||
|
|
60
src/Form/SelectTypeOrderExtension.php
Normal file
60
src/Form/SelectTypeOrderExtension.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 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\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class SelectTypeOrderExtension extends AbstractTypeExtension
|
||||
{
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [
|
||||
ChoiceType::class,
|
||||
EnumType::class
|
||||
];
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('ordered', false);
|
||||
$resolver->setDefault('by_reference', function (Options $options) {
|
||||
//Disable by_reference if the field is ordered (otherwise the order will be lost)
|
||||
return !$options['ordered'];
|
||||
});
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
//Pass the data in ordered form to the frontend controller, so it can make the items appear in the correct order.
|
||||
if ($options['ordered']) {
|
||||
$view->vars['attr']['data-ordered-value'] = json_encode($form->getViewData(), JSON_THROW_ON_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ namespace App\Form\Type;
|
|||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Form\Type\Helper\StructuralEntityChoiceHelper;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use App\Settings\SystemSettings\LocalizationSettings;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Intl\Currencies;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
@ -36,7 +37,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
|||
*/
|
||||
class CurrencyEntityType extends StructuralEntityType
|
||||
{
|
||||
public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper, protected ?string $base_currency)
|
||||
public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper, private readonly LocalizationSettings $localizationSettings)
|
||||
{
|
||||
parent::__construct($em, $builder, $translator, $choiceHelper);
|
||||
}
|
||||
|
@ -57,7 +58,7 @@ class CurrencyEntityType extends StructuralEntityType
|
|||
|
||||
$resolver->setDefault('empty_message', function (Options $options) {
|
||||
//By default, we use the global base currency:
|
||||
$iso_code = $this->base_currency;
|
||||
$iso_code = $this->localizationSettings->baseCurrency;
|
||||
|
||||
if ($options['base_currency']) { //Allow to override it
|
||||
$iso_code = $options['base_currency'];
|
||||
|
|
53
src/Form/Type/LocaleSelectType.php
Normal file
53
src/Form/Type/LocaleSelectType.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 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\Form\Type;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\LocaleType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* A locale select field that uses the preferred languages from the configuration.
|
||||
|
||||
*/
|
||||
class LocaleSelectType extends AbstractType
|
||||
{
|
||||
|
||||
public function __construct(#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages)
|
||||
{
|
||||
|
||||
}
|
||||
public function getParent(): string
|
||||
{
|
||||
return LocaleType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'preferred_choices' => $this->preferred_languages,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Type\LocaleSelectType;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\UserSystem\Group;
|
||||
|
@ -35,7 +36,6 @@ use App\Form\Type\ThemeChoiceType;
|
|||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ResetType;
|
||||
|
@ -140,11 +140,10 @@ class UserAdminForm extends AbstractType
|
|||
])
|
||||
|
||||
//Config section
|
||||
->add('language', LanguageType::class, [
|
||||
->add('language', LocaleSelectType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => 'user_settings.language.placeholder',
|
||||
'label' => 'user.language_select',
|
||||
'preferred_choices' => ['en', 'de'],
|
||||
'disabled' => !$this->security->isGranted('change_user_settings', $entity),
|
||||
])
|
||||
->add('timezone', TimezoneType::class, [
|
||||
|
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Type\LocaleSelectType;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Form\Type\CurrencyEntityType;
|
||||
|
@ -33,7 +34,6 @@ use Symfony\Component\Form\Event\PreSetDataEvent;
|
|||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ResetType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
|
@ -47,7 +47,7 @@ class UserSettingsType extends AbstractType
|
|||
{
|
||||
public function __construct(protected Security $security,
|
||||
protected bool $demo_mode,
|
||||
#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages)
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -107,12 +107,11 @@ class UserSettingsType extends AbstractType
|
|||
'mode' => 'markdown-full',
|
||||
'disabled' => !$this->security->isGranted('edit_infos', $options['data']) || $this->demo_mode,
|
||||
])
|
||||
->add('language', LanguageType::class, [
|
||||
->add('language', LocaleSelectType::class, [
|
||||
'disabled' => $this->demo_mode,
|
||||
'required' => false,
|
||||
'placeholder' => 'user_settings.language.placeholder',
|
||||
'label' => 'user.language_select',
|
||||
'preferred_choices' => $this->preferred_languages,
|
||||
])
|
||||
->add('timezone', TimezoneType::class, [
|
||||
'disabled' => $this->demo_mode,
|
||||
|
|
|
@ -62,7 +62,7 @@ trait WithPermPresetsTrait
|
|||
return json_encode($user->getPermissions());
|
||||
}
|
||||
|
||||
public function setContainer(?ContainerInterface $container = null): void
|
||||
public function setContainer(ContainerInterface $container = null): void
|
||||
{
|
||||
if ($container !== null) {
|
||||
$this->container = $container;
|
||||
|
|
|
@ -58,15 +58,15 @@ class AttachmentRepository extends DBElementRepository
|
|||
{
|
||||
$qb = $this->createQueryBuilder('attachment');
|
||||
$qb->select('COUNT(attachment)')
|
||||
->where('attachment.internal_path LIKE :like ESCAPE \'#\'');
|
||||
$qb->setParameter('like', '#%SECURE#%%');
|
||||
->where('attachment.path LIKE :like');
|
||||
$qb->setParameter('like', '\\%SECURE\\%%');
|
||||
$query = $qb->getQuery();
|
||||
|
||||
return (int) $query->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the count of all external attachments (attachments containing an external path).
|
||||
* Gets the count of all external attachments (attachments only containing a URL).
|
||||
*
|
||||
* @throws NoResultException
|
||||
* @throws NonUniqueResultException
|
||||
|
@ -75,15 +75,17 @@ class AttachmentRepository extends DBElementRepository
|
|||
{
|
||||
$qb = $this->createQueryBuilder('attachment');
|
||||
$qb->select('COUNT(attachment)')
|
||||
->andWhere('attaachment.internal_path IS NULL')
|
||||
->where('attachment.external_path IS NOT NULL');
|
||||
->where('ILIKE(attachment.path, :http) = TRUE')
|
||||
->orWhere('ILIKE(attachment.path, :https) = TRUE');
|
||||
$qb->setParameter('http', 'http://%');
|
||||
$qb->setParameter('https', 'https://%');
|
||||
$query = $qb->getQuery();
|
||||
|
||||
return (int) $query->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the count of all attachments where a user uploaded a file or a file was downloaded from an external source.
|
||||
* Gets the count of all attachments where a user uploaded a file.
|
||||
*
|
||||
* @throws NoResultException
|
||||
* @throws NonUniqueResultException
|
||||
|
@ -92,12 +94,12 @@ class AttachmentRepository extends DBElementRepository
|
|||
{
|
||||
$qb = $this->createQueryBuilder('attachment');
|
||||
$qb->select('COUNT(attachment)')
|
||||
->where('attachment.internal_path LIKE :base ESCAPE \'#\'')
|
||||
->orWhere('attachment.internal_path LIKE :media ESCAPE \'#\'')
|
||||
->orWhere('attachment.internal_path LIKE :secure ESCAPE \'#\'');
|
||||
$qb->setParameter('secure', '#%SECURE#%%');
|
||||
$qb->setParameter('base', '#%BASE#%%');
|
||||
$qb->setParameter('media', '#%MEDIA#%%');
|
||||
->where('attachment.path LIKE :base')
|
||||
->orWhere('attachment.path LIKE :media')
|
||||
->orWhere('attachment.path LIKE :secure');
|
||||
$qb->setParameter('secure', '\\%SECURE\\%%');
|
||||
$qb->setParameter('base', '\\%BASE\\%%');
|
||||
$qb->setParameter('media', '\\%MEDIA\\%%');
|
||||
$query = $qb->getQuery();
|
||||
|
||||
return (int) $query->getSingleScalarResult();
|
||||
|
|
|
@ -160,7 +160,7 @@ class LogEntryRepository extends DBElementRepository
|
|||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
*/
|
||||
public function getLogsOrderedByTimestamp(string $order = 'DESC', ?int $limit = null, ?int $offset = null): array
|
||||
public function getLogsOrderedByTimestamp(string $order = 'DESC', int $limit = null, int $offset = null): array
|
||||
{
|
||||
return $this->findBy([], ['timestamp' => $order], $limit, $offset);
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ class ApiTokenAuthenticator implements AuthenticatorInterface
|
|||
/**
|
||||
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3
|
||||
*/
|
||||
private function getAuthenticateHeader(?string $errorDescription = null): string
|
||||
private function getAuthenticateHeader(string $errorDescription = null): string
|
||||
{
|
||||
$data = [
|
||||
'realm' => $this->realm,
|
||||
|
|
|
@ -47,7 +47,7 @@ class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
|
|||
) {
|
||||
}
|
||||
|
||||
public function start(Request $request, ?AuthenticationException $authException = null): Response
|
||||
public function start(Request $request, AuthenticationException $authException = null): Response
|
||||
{
|
||||
//Check if the request is an API request
|
||||
if ($this->isJSONRequest($request)) {
|
||||
|
|
|
@ -116,10 +116,10 @@ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterf
|
|||
* Maps a list of SAML roles to a local group ID.
|
||||
* The first available mapping will be used (so the order of the $map is important, first match wins).
|
||||
* @param array $roles The list of SAML roles
|
||||
* @param array|null $map The mapping from SAML roles. If null, the global mapping will be used.
|
||||
* @param array $map|null The mapping from SAML roles. If null, the global mapping will be used.
|
||||
* @return int|null The ID of the local group or null if no mapping was found.
|
||||
*/
|
||||
public function mapSAMLRolesToLocalGroupID(array $roles, ?array $map = null): ?int
|
||||
public function mapSAMLRolesToLocalGroupID(array $roles, array $map = null): ?int
|
||||
{
|
||||
$map ??= $this->saml_role_mapping;
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ class AttachmentNormalizer implements NormalizerInterface, NormalizerAwareInterf
|
|||
{
|
||||
}
|
||||
|
||||
public function normalize(mixed $object, ?string $format = null, array $context = []): array|null
|
||||
public function normalize(mixed $object, string $format = null, array $context = []): array|null
|
||||
{
|
||||
if (!$object instanceof Attachment) {
|
||||
throw new \InvalidArgumentException('This normalizer only supports Attachment objects!');
|
||||
|
@ -52,19 +52,15 @@ class AttachmentNormalizer implements NormalizerInterface, NormalizerAwareInterf
|
|||
$context[self::ALREADY_CALLED] = true;
|
||||
|
||||
$data = $this->normalizer->normalize($object, $format, $context);
|
||||
$data['internal_path'] = $this->attachmentURLGenerator->getInternalViewURL($object);
|
||||
|
||||
$data['media_url'] = $this->attachmentURLGenerator->getViewURL($object);
|
||||
//Add thumbnail url if the attachment is a picture
|
||||
$data['thumbnail_url'] = $object->isPicture() ? $this->attachmentURLGenerator->getThumbnailURL($object) : null;
|
||||
|
||||
//For backwards compatibility reasons
|
||||
//Deprecated: Use internal_path and external_path instead
|
||||
$data['media_url'] = $data['internal_path'] ?? $object->getExternalPath();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
|
||||
public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
|
||||
{
|
||||
// avoid recursion: only call once per object
|
||||
if (isset($context[self::ALREADY_CALLED])) {
|
||||
|
|
|
@ -33,12 +33,12 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
|||
class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||
{
|
||||
|
||||
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
|
||||
public function supportsNormalization($data, string $format = null, array $context = []): bool
|
||||
{
|
||||
return $data instanceof BigNumber;
|
||||
}
|
||||
|
||||
public function normalize($object, ?string $format = null, array $context = []): string
|
||||
public function normalize($object, string $format = null, array $context = []): string
|
||||
{
|
||||
if (!$object instanceof BigNumber) {
|
||||
throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!');
|
||||
|
@ -58,7 +58,7 @@ class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface
|
|||
];
|
||||
}
|
||||
|
||||
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): BigNumber|null
|
||||
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): BigNumber|null
|
||||
{
|
||||
if (!is_a($type, BigNumber::class, true)) {
|
||||
throw new \InvalidArgumentException('This normalizer only supports BigNumber objects!');
|
||||
|
@ -67,7 +67,7 @@ class BigNumberNormalizer implements NormalizerInterface, DenormalizerInterface
|
|||
return $type::of($data);
|
||||
}
|
||||
|
||||
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
|
||||
public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
|
||||
{
|
||||
//data must be a string or a number (int, float, etc.) and the type must be BigNumber or BigDecimal
|
||||
return (is_string($data) || is_numeric($data)) && (is_subclass_of($type, BigNumber::class));
|
||||
|
|
|
@ -63,13 +63,13 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm
|
|||
{
|
||||
}
|
||||
|
||||
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
|
||||
public function supportsNormalization($data, string $format = null, array $context = []): bool
|
||||
{
|
||||
//We only remove the type field for CSV export
|
||||
return !isset($context[self::ALREADY_CALLED]) && $format === 'csv' && $data instanceof Part ;
|
||||
}
|
||||
|
||||
public function normalize($object, ?string $format = null, array $context = []): array
|
||||
public function normalize($object, string $format = null, array $context = []): array
|
||||
{
|
||||
if (!$object instanceof Part) {
|
||||
throw new \InvalidArgumentException('This normalizer only supports Part objects!');
|
||||
|
@ -117,7 +117,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Norm
|
|||
return $data;
|
||||
}
|
||||
|
||||
public function denormalize($data, string $type, ?string $format = null, array $context = []): ?Part
|
||||
public function denormalize($data, string $type, string $format = null, array $context = []): ?Part
|
||||
{
|
||||
$this->normalizeKeys($data);
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ class StructuralElementDenormalizer implements DenormalizerInterface, Denormaliz
|
|||
{
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool
|
||||
public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
|
||||
{
|
||||
//Only denormalize if we are doing a file import operation
|
||||
if (!($context['partdb_import'] ?? false)) {
|
||||
|
@ -78,7 +78,7 @@ class StructuralElementDenormalizer implements DenormalizerInterface, Denormaliz
|
|||
* @return AbstractStructuralDBElement|null
|
||||
* @phpstan-return T|null
|
||||
*/
|
||||
public function denormalize($data, string $type, ?string $format = null, array $context = []): ?AbstractStructuralDBElement
|
||||
public function denormalize($data, string $type, string $format = null, array $context = []): ?AbstractStructuralDBElement
|
||||
{
|
||||
//Do not use API Platform's denormalizer
|
||||
$context[SkippableItemNormalizer::DISABLE_ITEM_NORMALIZER] = true;
|
||||
|
|
|
@ -36,7 +36,7 @@ class StructuralElementFromNameDenormalizer implements DenormalizerInterface
|
|||
{
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool
|
||||
public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
|
||||
{
|
||||
//Only denormalize if we are doing a file import operation
|
||||
if (!($context['partdb_import'] ?? false)) {
|
||||
|
@ -51,7 +51,7 @@ class StructuralElementFromNameDenormalizer implements DenormalizerInterface
|
|||
* @phpstan-param class-string<T> $type
|
||||
* @phpstan-return T|null
|
||||
*/
|
||||
public function denormalize($data, string $type, ?string $format = null, array $context = []): AbstractStructuralDBElement|null
|
||||
public function denormalize($data, string $type, string $format = null, array $context = []): AbstractStructuralDBElement|null
|
||||
{
|
||||
//Retrieve the repository for the given type
|
||||
/** @var StructuralDBElementRepository<T> $repo */
|
||||
|
|
|
@ -38,7 +38,7 @@ class StructuralElementNormalizer implements NormalizerInterface
|
|||
{
|
||||
}
|
||||
|
||||
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
|
||||
public function supportsNormalization($data, string $format = null, array $context = []): bool
|
||||
{
|
||||
//Only normalize if we are doing a file export operation
|
||||
if (!($context['partdb_export'] ?? false)) {
|
||||
|
@ -48,7 +48,7 @@ class StructuralElementNormalizer implements NormalizerInterface
|
|||
return $data instanceof AbstractStructuralDBElement;
|
||||
}
|
||||
|
||||
public function normalize($object, ?string $format = null, array $context = []): mixed
|
||||
public function normalize($object, string $format = null, array $context = []): mixed
|
||||
{
|
||||
if (!$object instanceof AbstractStructuralDBElement) {
|
||||
throw new \InvalidArgumentException('This normalizer only supports AbstractStructural objects!');
|
||||
|
|
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