mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-20 16:52:41 +01:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39009a71d5 | ||
|
|
0430178fe2 | ||
|
|
cf9df883c9 | ||
|
|
198befe2bc | ||
|
|
7195bd6cd6 | ||
|
|
a5fa2da80c | ||
|
|
593d37f37c | ||
|
|
2ddd6753ca | ||
|
|
9537c4f210 | ||
|
|
e0ce6ba165 | ||
|
|
ee50ce26f8 | ||
|
|
94a6de4a90 | ||
|
|
d5902314c3 | ||
|
|
60125534ec | ||
|
|
48385cadc9 | ||
|
|
ba6abe6ca7 | ||
|
|
79ad243bf4 | ||
|
|
5ab21e019d | ||
|
|
d8469efba2 | ||
|
|
316b09ddf3 | ||
|
|
866ef73774 | ||
|
|
138d5c6e0f | ||
|
|
4bed50d894 | ||
|
|
55943f5d8f | ||
|
|
133652c296 | ||
|
|
b9331ac1ef | ||
|
|
08f7b2cc87 | ||
|
|
1a2bdaf8e5 | ||
|
|
d81dec78ae | ||
|
|
f78bd03521 | ||
|
|
6aa16272d8 | ||
|
|
e80f7c08ab | ||
|
|
675f05f0fb | ||
|
|
b1f23e1684 | ||
|
|
d612164885 | ||
|
|
b257e1d5f7 | ||
|
|
f26776ecd5 | ||
|
|
bf4a23652c | ||
|
|
e7681aedb1 | ||
|
|
098fcb29fb | ||
|
|
eb46ea19e3 | ||
|
|
99ee05a90f | ||
|
|
fd31f983af | ||
|
|
80bae4167f | ||
|
|
eaee4af715 | ||
|
|
7d4723c3e4 | ||
|
|
33a0981981 | ||
|
|
b62dc1241d | ||
|
|
e2270aec38 | ||
|
|
73346fcdaf | ||
|
|
7b112512a9 | ||
|
|
0e5613b57b | ||
|
|
e66ff40733 | ||
|
|
73d61f7440 | ||
|
|
dedb3071d6 | ||
|
|
a43ee52086 | ||
|
|
97ccb0cb21 | ||
|
|
1fb334b0ca | ||
|
|
fa4af99525 | ||
|
|
b3153dac68 | ||
|
|
c981476706 | ||
|
|
1a3e5ec705 | ||
|
|
aaff0835a3 | ||
|
|
9bf814d4cd | ||
|
|
b5c0f37f88 |
@@ -28,12 +28,13 @@
|
||||
PassEnv APP_ENV APP_DEBUG APP_SECRET
|
||||
PassEnv TRUSTED_PROXIES TRUSTED_HOSTS LOCK_DSN
|
||||
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR
|
||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI
|
||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES
|
||||
PassEnv MAILER_DSN ALLOW_EMAIL_PW_RESET EMAIL_SENDER_EMAIL EMAIL_SENDER_NAME
|
||||
PassEnv HISTORY_SAVE_CHANGED_FIELDS HISTORY_SAVE_CHANGED_DATA HISTORY_SAVE_REMOVED_DATA HISTORY_SAVE_NEW_DATA
|
||||
PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP
|
||||
PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER
|
||||
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAMLP_SP_PRIVATE_KEY
|
||||
# In old version the SAML sp private key env, was wrongly named SAMLP_SP_PRIVATE_KEY, keep it for backward compatibility
|
||||
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAML_SP_PRIVATE_KEY SAMLP_SP_PRIVATE_KEY
|
||||
PassEnv TABLE_DEFAULT_PAGE_SIZE
|
||||
|
||||
PassEnv PROVIDER_DIGIKEY_CLIENT_ID PROVIDER_DIGIKEY_SECRET PROVIDER_DIGIKEY_CURRENCY PROVIDER_DIGIKEY_LANGUAGE PROVIDER_DIGIKEY_COUNTRY
|
||||
|
||||
5
.env
5
.env
@@ -44,6 +44,9 @@ DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||
# 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
|
||||
###################################################################################
|
||||
@@ -172,7 +175,7 @@ SAML_SP_ENTITY_ID="https://partdb.changeme.invalid/sp"
|
||||
# The public certificate of the SAML SP
|
||||
SAML_SP_X509_CERT="MIIC..."
|
||||
# The private key of the SAML SP
|
||||
SAMLP_SP_PRIVATE_KEY="MIIE..."
|
||||
SAML_SP_PRIVATE_KEY="MIIE..."
|
||||
|
||||
|
||||
######################################################################################
|
||||
|
||||
2
.github/workflows/assets_artifact_build.yml
vendored
2
.github/workflows/assets_artifact_build.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
APP_ENV: prod
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
|
||||
10
.github/workflows/docker_build.yml
vendored
10
.github/workflows/docker_build.yml
vendored
@@ -17,11 +17,11 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
@@ -49,12 +49,12 @@ jobs:
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: 'arm64,arm'
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
|
||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
if: matrix.db-type == 'sqlite'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
@@ -65,8 +65,13 @@ export default class extends Controller {
|
||||
localStorage.setItem( this.getStateSaveKey(), JSON.stringify(data) );
|
||||
}
|
||||
|
||||
stateLoadCallback(settings) {
|
||||
const data = JSON.parse( localStorage.getItem(this.getStateSaveKey()) );
|
||||
stateLoadCallback() {
|
||||
const json = localStorage.getItem(this.getStateSaveKey());
|
||||
if(json === null || json === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = JSON.parse(json);
|
||||
|
||||
if (data) {
|
||||
//Do not save the start value (current page), as we want to always start at the first page on a page reload
|
||||
@@ -90,6 +95,19 @@ export default class extends Controller {
|
||||
//Add url info, as the one available in the history is not enough, as Turbo may have not changed it yet
|
||||
settings.url = this.element.dataset.dtUrl;
|
||||
|
||||
//Add initial_order info to the settings, so that the order on the initial page load is the one saved in the state
|
||||
const saved_state = this.stateLoadCallback();
|
||||
if (saved_state !== null) {
|
||||
const raw_order = saved_state.order;
|
||||
|
||||
settings.initial_order = raw_order.map((order) => {
|
||||
return {
|
||||
column: order[0],
|
||||
dir: order[1]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let options = {
|
||||
colReorder: true,
|
||||
responsive: true,
|
||||
@@ -221,4 +239,16 @@ export default class extends Controller {
|
||||
return this.element.dataset.select ?? false;
|
||||
}
|
||||
|
||||
invertSelection() {
|
||||
//Do nothing if the datatable is not selectable
|
||||
if(!this.isSelectable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Invert the selected rows on the datatable
|
||||
const selected_rows = this._dt.rows({selected: true});
|
||||
this._dt.rows().select();
|
||||
selected_rows.deselect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ export default class extends Controller {
|
||||
selectOnTab: true,
|
||||
maxOptions: null,
|
||||
create: allowAdd ? this.createItem.bind(this) : false,
|
||||
createFilter: /\D/, //Must contain a non-digit character, otherwise they would be recognized as DB ID
|
||||
|
||||
searchField: [
|
||||
{field: "text", weight : 2},
|
||||
@@ -72,7 +71,8 @@ export default class extends Controller {
|
||||
|
||||
createItem(input, callback) {
|
||||
callback({
|
||||
value: input,
|
||||
//$%$ is a special value prefix, that is used to identify items, that are not yet in the DB
|
||||
value: '$%$' + input,
|
||||
text: input,
|
||||
not_in_db_yet: true,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
/**
|
||||
* This controller is used on a checkbox, which toggles the max value of all number input fields
|
||||
*/
|
||||
export default class extends Controller {
|
||||
|
||||
_checkbox;
|
||||
|
||||
getCheckbox() {
|
||||
if (this._checkbox) {
|
||||
return this._checkbox;
|
||||
}
|
||||
|
||||
//Find the checkbox inside the controller element
|
||||
this._checkbox = this.element.querySelector('input[type="checkbox"]');
|
||||
return this._checkbox;
|
||||
}
|
||||
|
||||
connect() {
|
||||
//Add event listener to the checkbox
|
||||
this.getCheckbox().addEventListener('change', this.toggleInputLimits.bind(this));
|
||||
}
|
||||
|
||||
toggleInputLimits() {
|
||||
//Find all input fields with the data-toggle-input-limits-target="max"
|
||||
const inputFields = document.querySelectorAll("input[type='number']");
|
||||
|
||||
inputFields.forEach((inputField) => {
|
||||
//Ensure that the input field has either a max or a data-max attribute
|
||||
if (!inputField.hasAttribute('max') && !inputField.hasAttribute('data-max')) {
|
||||
return;
|
||||
}
|
||||
|
||||
//If the checkbox is checked, rename the max attribute to data-max
|
||||
if (this.getCheckbox().checked) {
|
||||
inputField.setAttribute('data-max', inputField.getAttribute('max'));
|
||||
inputField.removeAttribute('max');
|
||||
} else {
|
||||
//If the checkbox is not checked, rename the data-max attribute back to max
|
||||
inputField.setAttribute('max', inputField.getAttribute('data-max'));
|
||||
inputField.removeAttribute('data-max');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ th.select-checkbox {
|
||||
/** Fix datatables select-checkbox position */
|
||||
table.dataTable tr.selected td.select-checkbox:after
|
||||
{
|
||||
margin-top: -25px !important;
|
||||
margin-top: -20px !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
method: config.method,
|
||||
data: {
|
||||
_dt: config.name,
|
||||
_init: true
|
||||
_init: true,
|
||||
order: config.initial_order ?? undefined,
|
||||
}
|
||||
}).done(function(data) {
|
||||
var baseState;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"doctrine/dbal": "^3.4.6",
|
||||
"doctrine/doctrine-bundle": "^2.0",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "dev-entity-level-commit-order#44d2a83 as 2.15.3",
|
||||
"doctrine/orm": "^2.16",
|
||||
"dompdf/dompdf": "dev-master#87bea32efe0b0db309e1d31537201f64d5508280 as v2.0.3",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"florianv/swap": "^4.0",
|
||||
@@ -95,7 +95,7 @@
|
||||
"phpstan/phpstan-strict-rules": "^1.5",
|
||||
"phpstan/phpstan-symfony": "^1.1.7",
|
||||
"psalm/plugin-symfony": "^v5.0.1",
|
||||
"rector/rector": "^0.17.0",
|
||||
"rector/rector": "^0.18.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/browser-kit": "6.3.*",
|
||||
"symfony/css-selector": "6.3.*",
|
||||
@@ -104,7 +104,7 @@
|
||||
"symfony/phpunit-bridge": "6.3.*",
|
||||
"symfony/stopwatch": "6.3.*",
|
||||
"symfony/web-profiler-bundle": "6.3.*",
|
||||
"symplify/easy-coding-standard": "^11.0",
|
||||
"symplify/easy-coding-standard": "^12.0",
|
||||
"vimeo/psalm": "^5.6.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
||||
1183
composer.lock
generated
1183
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,9 @@ framework:
|
||||
csrf_protection: true
|
||||
handle_all_throwables: true
|
||||
|
||||
# We set this header by ourself, so we can disable it here
|
||||
disallow_search_engine_index: false
|
||||
|
||||
# Must be set to true, to enable the change of HTTP method via _method parameter, otherwise our delete routines does not work anymore
|
||||
# TODO: Rework delete routines to work without _method parameter as it is not recommended anymore (see https://github.com/symfony/symfony/issues/45278)
|
||||
http_method_override: true
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
|
||||
|
||||
# Define a parameter here, so we can access it later in the default fallback
|
||||
parameters:
|
||||
saml.sp.privateKey: '%env(string:SAML_SP_PRIVATE_KEY)%'
|
||||
|
||||
nbgrp_onelogin_saml:
|
||||
onelogin_settings:
|
||||
default:
|
||||
@@ -22,7 +26,9 @@ nbgrp_onelogin_saml:
|
||||
url: '%partdb.default_uri%logout'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_SP_X509_CERT)%'
|
||||
privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
# Before the env variable was wrongly named "SAMLP_SP_PRIVATE_KEY".
|
||||
# For compatibility reasons we keep it and only fallback to the new name if the old one is not set. This may be removed in the future.
|
||||
privateKey: '%env(string:default:saml.sp.privateKey:string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
|
||||
# Optional settings
|
||||
#baseurl: 'http://myapp.com'
|
||||
|
||||
@@ -11,7 +11,7 @@ parameters:
|
||||
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
|
||||
partdb.locale_menu: ['en', 'de', 'fr', 'ru', 'ja'] # The languages that are shown in user drop down menu
|
||||
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja'] # 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
|
||||
@@ -23,6 +23,8 @@ parameters:
|
||||
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
|
||||
######################################################################################################################
|
||||
|
||||
@@ -251,6 +251,8 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
label: "perm.server_infos"
|
||||
manage_oauth_tokens:
|
||||
label: "Manage OAuth tokens"
|
||||
show_updates:
|
||||
label: "perm.system.show_available_updates"
|
||||
|
||||
attachments:
|
||||
label: "perm.part.attachments"
|
||||
|
||||
@@ -315,6 +315,10 @@ services:
|
||||
arguments:
|
||||
$project_dir: '%kernel.project_dir%'
|
||||
|
||||
App\Services\System\UpdateAvailableManager:
|
||||
arguments:
|
||||
$check_for_updates: '%partdb.check_for_updates%'
|
||||
|
||||
####################################################################################################################
|
||||
# Monolog
|
||||
####################################################################################################################
|
||||
|
||||
@@ -36,6 +36,7 @@ The following configuration options can only be changed by the server administra
|
||||
* `datastructure_edit`: Edit operation of a existing datastructure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...)
|
||||
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new versions, or if your server can not connect to the internet.
|
||||
|
||||
### E-Mail settings
|
||||
* `MAILER_DSN`: You can configure the mail provider which should be used for email delivery (see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587`
|
||||
|
||||
@@ -101,7 +101,7 @@ The basic configuration of Part-DB is done by a `.env.local` file in the main di
|
||||
cp .env .env.local
|
||||
```
|
||||
|
||||
In your `.env.local` you can configure Part-DB according to your wishes. A full list of configuration options can be found [here]({% link configuration.md %}.
|
||||
In your `.env.local` you can configure Part-DB according to your wishes. A full list of configuration options can be found [here](../configuration.md).
|
||||
Other configuration options like the default language or default currency can be found in `config/parameters.yaml`.
|
||||
|
||||
Please check that the `partdb.default_currency` value in `config/parameters.yaml` matches your mainly used currency, as this can not be changed after creating price informations.
|
||||
|
||||
@@ -143,7 +143,7 @@ The reverse is also possible: If you have existing SAML users and want them to b
|
||||
> It is recommended that you let the original admin user (ID: 2) be a local user, so you can still login to Part-DB if the SAML identity provider is not available.
|
||||
|
||||
## Advanced SAML configuration
|
||||
You can find some more advanced SAML configuration options in the `config/packages/hslavich_onelogin_saml.yaml` file. Refer to the file for more information.
|
||||
You can find some more advanced SAML configuration options in the `config/packages/nbgrp_onelogin_saml.yaml` file. Refer to the file for more information.
|
||||
Normally you don't have to change anything here.
|
||||
|
||||
Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep your changes. On docker containers you have to configure a volume mapping for it.
|
||||
|
||||
@@ -77,13 +77,15 @@ Part-DB caches the search results internally, so if you have searched for a part
|
||||
Following env configuration options are available:
|
||||
|
||||
* `PROVIDER_OCTOPART_CLIENT_ID`: The client ID you got from Nexar (mandatory)
|
||||
* `PROVIDER_OCTOPART_CLIENT_SECRET`: The client secret you got from Nexar (mandatory)
|
||||
* `PROVIDER_OCTOPART_SECRET`: The client secret you got from Nexar (mandatory)
|
||||
* `PROVIDER_OCTOPART_CURRENCY`: The currency you want to get prices in if available (optional, 3 letter ISO-code, default: `EUR`). If an offer is only available in a certain currency,
|
||||
Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert it to your preferred currency.
|
||||
* `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code, default: `DE`). To get correct prices, you have to set this and the currency setting to the correct value.
|
||||
* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This affects how quickly your monthly limit is used up.
|
||||
* `PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS`: If set to `true`, only offers from [authorized sellers](https://octopart.com/authorized) will be returned (optional, default: `false`).
|
||||
|
||||
**Attention**: If you change the octopart clientID after you have already used the provider, you have to remove the OAuth token in the Part-DB database. Remove the entry in the table `oauth_tokens` with the name `ip_octopart_oauth`.
|
||||
|
||||
### Digi-Key
|
||||
The Digi-Key provider uses the [Digi-Key API](https://developer.digikey.com/) to search for parts and getting shopping information from [Digi-Key](https://www.digikey.com/).
|
||||
To use it you have to create an account at Digi-Key and get an API key on the [Digi-Key API page](https://developer.digikey.com/).
|
||||
@@ -92,7 +94,7 @@ You will get an Client ID and a Client Secret, which you have to enter in the Pa
|
||||
|
||||
Following env configuration options are available:
|
||||
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
|
||||
* `PROVIDER_DIGIKEY_CLIENT_SECRET`: The client secret you got from Digi-Key (mandatory)
|
||||
* `PROVIDER_DIGIKEY_SECRET`: The client secret you got from Digi-Key (mandatory)
|
||||
* `PROVIDER_DIGIKEY_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`)
|
||||
* `PROVIDER_DIGIKEY_LANGUAGE`: The language you want to get the descriptions in (optional, default: `en`)
|
||||
* `PROVIDER_DIGIKEY_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
|
||||
@@ -108,8 +110,8 @@ To use it you have to create an account at TME and get an API key on the [TME AP
|
||||
You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see below).
|
||||
|
||||
Following env configuration options are available:
|
||||
* `PROVIDER_TME_API_KEY`: The API key you got from TME (mandatory)
|
||||
* `PROVIDER_TME_API_SECRET`: The API secret you got from TME (mandatory)
|
||||
* `PROVIDER_TME_KEY`: The API key you got from TME (mandatory)
|
||||
* `PROVIDER_TME_SECRET`: The API secret you got from TME (mandatory)
|
||||
* `PROVIDER_TME_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`)
|
||||
* `PROVIDER_TME_LANGUAGE`: The language you want to get the descriptions in (`en`, `de` and `pl`) (optional, default: `en`)
|
||||
* `PROVIDER_TME_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
|
||||
@@ -137,4 +139,4 @@ To reduce the number of API calls against the providers, the results are cached:
|
||||
* The product details are cached for 4 days
|
||||
|
||||
If you need a fresh result, you can clear the cache by running `php .\bin\console cache:pool:clear info_provider.cache` on the command line.
|
||||
The default `php bin/console cache:clear` also clears the result cache, as it clears all caches.
|
||||
The default `php bin/console cache:clear` also clears the result cache, as it clears all caches.
|
||||
|
||||
@@ -72,4 +72,11 @@ See the configuration reference for more information.
|
||||
|
||||
## Personal stocks and stock locations
|
||||
For makerspaces and universities with a lot of users, where each user can have his own stock, which only he should be able to access, you can assign
|
||||
the user as "owner" of a part lot. This way, only him is allowed to add or remove parts from this lot.
|
||||
the user as "owner" of a part lot. This way, only him is allowed to add or remove parts from this lot.
|
||||
|
||||
## Update notfications
|
||||
Part-DB can show you a notification that there is a newer version than currently installed available. The notification will be shown on the homepage and the server info page.
|
||||
It is only be shown to users which has the `Show available Part-DB updates` permission.
|
||||
|
||||
For the notification to work, Part-DB queries the GitHub API every 2 days to check for new releases. No data is sent to GitHub besides the metadata required for the connection (so the public IP address of your computer running Part-DB).
|
||||
If you don't want Part-DB to query the GitHub API, or if your server can not reach the internet, you can disable the update notifications by setting the `CHECK_FOR_UPDATES` option to `false`.
|
||||
@@ -69,6 +69,12 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
|
||||
//For attachments
|
||||
$this->addSql('DELETE FROM `attachements` WHERE attachements.class_name = "Part" AND (SELECT COUNT(parts.id) FROM parts WHERE parts.id = attachements.element_id) = 0;');
|
||||
|
||||
//Add perms_labels column to groups table if not existing (it was not created in certain legacy versions)
|
||||
//This prevents the migration failing (see https://github.com/Part-DB/Part-DB-server/issues/366 and https://github.com/Part-DB/Part-DB-server/issues/67)
|
||||
if (!$this->doesColumnExist('groups', 'perms_labels')) {
|
||||
$this->addSql('ALTER TABLE `groups` ADD `perms_labels` SMALLINT NOT NULL AFTER `perms_tools`');
|
||||
}
|
||||
|
||||
/**************************************************************************************************************
|
||||
* Doctrine generated SQL
|
||||
**************************************************************************************************************/
|
||||
|
||||
68
package.json
68
package.json
@@ -30,38 +30,38 @@
|
||||
"build": "encore production --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ckeditor/ckeditor5-alignment": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-autoformat": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-basic-styles": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-block-quote": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-code-block": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
|
||||
"@ckeditor/ckeditor5-editor-classic": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-essentials": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-find-and-replace": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-font": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-heading": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-highlight": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-horizontal-line": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-html-embed": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-html-support": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-image": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-indent": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-link": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-list": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-media-embed": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-paragraph": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-paste-from-office": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-remove-format": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-source-editing": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-special-characters": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-table": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-upload": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-watchdog": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-word-count": "^38.0.1",
|
||||
"@ckeditor/ckeditor5-alignment": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-autoformat": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-basic-styles": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-block-quote": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-code-block": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-dev-translations": "^38.4.0",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^38.4.0",
|
||||
"@ckeditor/ckeditor5-editor-classic": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-essentials": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-find-and-replace": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-font": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-heading": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-highlight": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-horizontal-line": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-html-embed": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-html-support": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-image": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-indent": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-link": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-list": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-media-embed": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-paragraph": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-paste-from-office": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-remove-format": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-source-editing": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-special-characters": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-table": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-upload": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-watchdog": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-word-count": "^39.0.1",
|
||||
"@jbtronics/bs-treeview": "^1.0.1",
|
||||
"@zxcvbn-ts/core": "^3.0.2",
|
||||
"@zxcvbn-ts/language-common": "^3.0.3",
|
||||
@@ -81,13 +81,13 @@
|
||||
"datatables.net-responsive-bs5": "^2.2.3",
|
||||
"datatables.net-select-bs5": "^1.2.7",
|
||||
"dompurify": "^3.0.3",
|
||||
"emoji.json": "^14.0.0",
|
||||
"emoji.json": "^15.0.0",
|
||||
"exports-loader": "^3.0.0",
|
||||
"html5-qrcode": "^2.2.1",
|
||||
"json-formatter-js": "^2.3.4",
|
||||
"jszip": "^3.2.0",
|
||||
"katex": "^0.16.0",
|
||||
"marked": "^5.1.0",
|
||||
"marked": "^7.0.4",
|
||||
"marked-gfm-heading-id": "^3.0.4",
|
||||
"marked-mangle": "^1.0.1",
|
||||
"pdfmake": "^0.2.2",
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace App\Controller;
|
||||
use App\DataTables\LogDataTable;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\Misc\GitVersionInfo;
|
||||
use App\Services\System\UpdateAvailableManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
@@ -62,8 +63,11 @@ class HomepageController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route(path: '/', name: 'homepage')]
|
||||
public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager): Response
|
||||
public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager,
|
||||
UpdateAvailableManager $updateAvailableManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('HAS_ACCESS_PERMISSIONS');
|
||||
|
||||
if ($this->isGranted('@tools.lastActivity')) {
|
||||
$table = $this->dataTable->createFromType(
|
||||
LogDataTable::class,
|
||||
@@ -97,6 +101,9 @@ class HomepageController extends AbstractController
|
||||
'git_commit' => $versionInfo->getGitCommitHash(),
|
||||
'show_first_steps' => $show_first_steps,
|
||||
'datatable' => $table,
|
||||
'new_version_available' => $updateAvailableManager->isUpdateAvailable(),
|
||||
'new_version' => $updateAvailableManager->getLatestVersionString(),
|
||||
'new_version_url' => $updateAvailableManager->getLatestVersionUrl(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ class PartController extends AbstractController
|
||||
public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler, ProjectBuildPartHelper $projectBuildPartHelper,
|
||||
#[MapEntity(mapping: ['id' => 'id'])] ?Part $part = null,
|
||||
#[MapEntity(mapping: ['id' => 'project_id'])] ?Project $project = null): Response
|
||||
#[MapEntity(mapping: ['project_id' => 'id'])] ?Project $project = null): Response
|
||||
{
|
||||
|
||||
if ($part instanceof Part) {
|
||||
|
||||
@@ -170,7 +170,7 @@ class SecurityController extends AbstractController
|
||||
$this->addFlash('success', 'pw_reset.new_pw.success');
|
||||
|
||||
$repo = $em->getRepository(User::class);
|
||||
$u = $repo->findOneBy(['name' => $data['username']]);
|
||||
$u = $repo->findByUsername($data['username']);
|
||||
$event = new SecurityEvent($u);
|
||||
/** @var EventDispatcher $eventDispatcher */
|
||||
$eventDispatcher->dispatch($event, SecurityEvents::PASSWORD_RESET);
|
||||
|
||||
@@ -28,6 +28,7 @@ use App\Services\Attachments\AttachmentURLGenerator;
|
||||
use App\Services\Attachments\BuiltinAttachmentsFinder;
|
||||
use App\Services\Misc\GitVersionInfo;
|
||||
use App\Services\Misc\DBInfoHelper;
|
||||
use App\Services\System\UpdateAvailableManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -47,7 +48,7 @@ class ToolsController extends AbstractController
|
||||
|
||||
#[Route(path: '/server_infos', name: 'tools_server_infos')]
|
||||
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@system.server_infos');
|
||||
|
||||
@@ -90,6 +91,11 @@ class ToolsController extends AbstractController
|
||||
'db_size' => $DBInfoHelper->getDatabaseSize(),
|
||||
'db_name' => $DBInfoHelper->getDatabaseName() ?? 'Unknown',
|
||||
'db_user' => $DBInfoHelper->getDatabaseUsername() ?? 'Unknown',
|
||||
|
||||
//New version section
|
||||
'new_version_available' => $updateAvailableManager->isUpdateAvailable(),
|
||||
'new_version' => $updateAvailableManager->getLatestVersionString(),
|
||||
'new_version_url' => $updateAvailableManager->getLatestVersionUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
#[Route(path: '/user')]
|
||||
class UserController extends BaseAdminController
|
||||
@@ -79,7 +80,8 @@ class UserController extends BaseAdminController
|
||||
*/
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'user_edit')]
|
||||
#[Route(path: '/{id}/', requirements: ['id' => '\d+'])]
|
||||
public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response
|
||||
public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper,
|
||||
PermissionSchemaUpdater $permissionSchemaUpdater, ValidatorInterface $validator, ?string $timestamp = null): Response
|
||||
{
|
||||
//Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet)
|
||||
$permissionSchemaUpdater->userUpgradeSchemaRecursively($entity);
|
||||
@@ -108,7 +110,7 @@ class UserController extends BaseAdminController
|
||||
|
||||
$this->addFlash('success', 'user.edit.reset_success');
|
||||
} else {
|
||||
$this->addFlash('danger', 'csfr_invalid');
|
||||
$this->addFlash('error', 'csfr_invalid');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,15 +122,25 @@ class UserController extends BaseAdminController
|
||||
|
||||
$permissionPresetsHelper->applyPreset($entity, $preset);
|
||||
|
||||
$em->flush();
|
||||
//Ensure that the user is valid after applying the preset
|
||||
$errors = $validator->validate($entity);
|
||||
if (count($errors) > 0) {
|
||||
$this->addFlash('error', 'validator.noLockout');
|
||||
//Refresh the entity to remove the changes
|
||||
$em->refresh($entity);
|
||||
} else {
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', 'user.edit.permission_success');
|
||||
$this->addFlash('success', 'user.edit.permission_success');
|
||||
|
||||
//We need to stop the execution here, or our permissions changes will be overwritten by the form values
|
||||
return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]);
|
||||
//We need to stop the execution here, or our permissions changes will be overwritten by the form values
|
||||
return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]);
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
$this->addFlash('error', 'csfr_invalid');
|
||||
}
|
||||
|
||||
$this->addFlash('danger', 'csfr_invalid');
|
||||
}
|
||||
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
|
||||
@@ -54,6 +54,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
|
||||
$user = new User();
|
||||
$user->setName('user');
|
||||
$user->setNeedPwChange(false);
|
||||
$user->setEmail('user@invalid.invalid');
|
||||
$user->setFirstName('Test')->setLastName('User');
|
||||
$user->setPassword($this->encoder->hashPassword($user, 'test'));
|
||||
$user->setGroup($this->getReference(GroupFixtures::USERS));
|
||||
@@ -66,6 +67,9 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
|
||||
$manager->persist($noread);
|
||||
|
||||
$manager->flush();
|
||||
|
||||
//Ensure that the anonymous user has the ID 0
|
||||
$manager->getRepository(User::class)->changeID($anonymous, User::ID_ANONYMOUS);
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
|
||||
@@ -97,7 +97,6 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||
*/
|
||||
#[Assert\NotBlank(message: 'validator.attachment.name_not_blank')]
|
||||
#[Groups(['simple', 'extended', 'full'])]
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
protected string $name = '';
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,10 +84,10 @@ class Part extends AttachmentContainingDBElement
|
||||
* Overridden properties
|
||||
* (They are defined here and not in a trait, to avoid conflicts).
|
||||
****************************************************************/
|
||||
|
||||
/**
|
||||
* @var string The name of this part
|
||||
*/
|
||||
#[ORM\Column(type: Types::STRING)]
|
||||
protected string $name = '';
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ final class PermissionData implements \JsonSerializable
|
||||
/**
|
||||
* The current schema version of the permission data
|
||||
*/
|
||||
public const CURRENT_SCHEMA_VERSION = 2;
|
||||
public const CURRENT_SCHEMA_VERSION = 3;
|
||||
|
||||
/**
|
||||
* Creates a new Permission Data Instance using the given data.
|
||||
|
||||
@@ -69,6 +69,10 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface
|
||||
#[ORM\EntityListeners([TreeCacheInvalidationListener::class])]
|
||||
#[ORM\Table('`users`')]
|
||||
#[ORM\Index(name: 'user_idx_username', columns: ['name'])]
|
||||
#[ORM\AttributeOverrides([
|
||||
new ORM\AttributeOverride(name: 'name', column: new ORM\Column(type: Types::STRING, length: 180, unique: true))
|
||||
])]
|
||||
|
||||
#[NoLockout()]
|
||||
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface,
|
||||
BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface, SamlUserInterface
|
||||
@@ -125,11 +129,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
#[ORM\Column(type: Types::JSON)]
|
||||
protected ?array $backupCodes = [];
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
protected ?int $id = null;
|
||||
|
||||
/**
|
||||
* @var Group|null the group this user belongs to
|
||||
* DO NOT PUT A fetch eager here! Otherwise, you can not unset the group of a user! This seems to be some kind of bug in doctrine. Maybe this is fixed in future versions.
|
||||
@@ -212,7 +211,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Regex('/^[\w\.\+\-\$]+$/', message: 'user.invalid_username')]
|
||||
#[ORM\Column(type: Types::STRING, length: 180, unique: true)]
|
||||
protected string $name = '';
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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\EventListener;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||
|
||||
#[AsEventListener]
|
||||
class DisallowSearchEngineIndexingRequestListener
|
||||
{
|
||||
private const HEADER_NAME = 'X-Robots-Tag';
|
||||
|
||||
private readonly bool $enabled;
|
||||
|
||||
public function __construct(#[Autowire(param: 'partdb.demo_mode')] bool $demo_mode)
|
||||
{
|
||||
// Disable this listener in demo mode
|
||||
$this->enabled = !$demo_mode;
|
||||
}
|
||||
|
||||
public function __invoke(ResponseEvent $event): void
|
||||
{
|
||||
//Skip if disabled
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$event->getResponse()->headers->has(self::HEADER_NAME)) {
|
||||
$event->getResponse()->headers->set(self::HEADER_NAME, 'noindex');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,15 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface
|
||||
'disabled' => !$this->security->isGranted('@parts_stock.withdraw'),
|
||||
]);
|
||||
|
||||
$builder->add('dontCheckQuantity', CheckboxType::class, [
|
||||
'label' => 'project.build.dont_check_quantity',
|
||||
'help' => 'project.build.dont_check_quantity.help',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'data-controller' => 'pages--dont-check-quantity-checkbox'
|
||||
]
|
||||
]);
|
||||
|
||||
$builder->add('comment', TextType::class, [
|
||||
'label' => 'part.info.withdraw_modal.comment',
|
||||
'help' => 'part.info.withdraw_modal.comment.hint',
|
||||
@@ -124,6 +133,7 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface
|
||||
}
|
||||
|
||||
$forms['comment']->setData($data->getComment());
|
||||
$forms['dontCheckQuantity']->setData($data->isDontCheckQuantity());
|
||||
$forms['addBuildsToBuildsPart']->setData($data->getAddBuildsToBuildsPart());
|
||||
if (isset($forms['buildsPartLot'])) {
|
||||
$forms['buildsPartLot']->setData($data->getBuildsPartLot());
|
||||
@@ -150,6 +160,8 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface
|
||||
}
|
||||
|
||||
$data->setComment($forms['comment']->getData());
|
||||
$data->setDontCheckQuantity($forms['dontCheckQuantity']->getData());
|
||||
|
||||
if (isset($forms['buildsPartLot'])) {
|
||||
$lot = $forms['buildsPartLot']->getData();
|
||||
if (!$lot) { //When the user selected "Create new lot", create a new lot
|
||||
|
||||
@@ -136,9 +136,10 @@ class StructuralEntityChoiceHelper
|
||||
if ($element->getID() === null) {
|
||||
if ($element instanceof AbstractStructuralDBElement) {
|
||||
//Must be the same as the separator in the choice_loader, otherwise this will not work!
|
||||
return $element->getFullPath('->');
|
||||
return '$%$' . $element->getFullPath('->');
|
||||
}
|
||||
return $element->getName();
|
||||
// '$%$' is the indicator prefix for a new entity
|
||||
return '$%$' . $element->getName();
|
||||
}
|
||||
|
||||
return $element->getID();
|
||||
|
||||
@@ -22,8 +22,8 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace App\Form\Type\Helper;
|
||||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Repository\StructuralDBElementRepository;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -34,7 +34,7 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
||||
{
|
||||
private ?string $additional_element = null;
|
||||
|
||||
private ?AbstractStructuralDBElement $starting_element = null;
|
||||
private ?AbstractNamedDBElement $starting_element = null;
|
||||
|
||||
public function __construct(private readonly Options $options, private readonly NodesListBuilder $builder, private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
@@ -108,19 +108,19 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
||||
|
||||
/**
|
||||
* Gets the initial value used to populate the field.
|
||||
* @return AbstractStructuralDBElement|null
|
||||
* @return AbstractNamedDBElement|null
|
||||
*/
|
||||
public function getStartingElement(): ?AbstractStructuralDBElement
|
||||
public function getStartingElement(): ?AbstractNamedDBElement
|
||||
{
|
||||
return $this->starting_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial value used to populate the field. This will always be an allowed value.
|
||||
* @param AbstractStructuralDBElement|null $starting_element
|
||||
* @param AbstractNamedDBElement|null $starting_element
|
||||
* @return StructuralEntityChoiceLoader
|
||||
*/
|
||||
public function setStartingElement(?AbstractStructuralDBElement $starting_element): StructuralEntityChoiceLoader
|
||||
public function setStartingElement(?AbstractNamedDBElement $starting_element): StructuralEntityChoiceLoader
|
||||
{
|
||||
$this->starting_element = $starting_element;
|
||||
return $this;
|
||||
|
||||
@@ -51,11 +51,14 @@ class StructuralEntityType extends AbstractType
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event) {
|
||||
//When the data contains non-digit characters, we assume that the user entered a new element.
|
||||
//When the data starts with "$%$", we assume that the user entered a new element.
|
||||
//In that case we add the new element to our choice_loader
|
||||
|
||||
$data = $event->getData();
|
||||
if (null === $data || !is_string($data) || $data === "" || ctype_digit($data)) {
|
||||
if (is_string($data) && str_starts_with($data, '$%$')) {
|
||||
//Extract the real name from the data
|
||||
$data = substr($data, 3);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ use App\Entity\UserSystem\User;
|
||||
use App\Form\Type\CurrencyEntityType;
|
||||
use App\Form\Type\RichTextEditorType;
|
||||
use App\Form\Type\ThemeChoiceType;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Event\PreSetDataEvent;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
@@ -44,7 +45,9 @@ use Symfony\Component\Validator\Constraints\File;
|
||||
|
||||
class UserSettingsType extends AbstractType
|
||||
{
|
||||
public function __construct(protected Security $security, protected bool $demo_mode)
|
||||
public function __construct(protected Security $security,
|
||||
protected bool $demo_mode,
|
||||
#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -90,7 +93,7 @@ class UserSettingsType extends AbstractType
|
||||
],
|
||||
'constraints' => [
|
||||
new File([
|
||||
'maxSize' => '2M',
|
||||
'maxSize' => '5M',
|
||||
]),
|
||||
],
|
||||
])
|
||||
@@ -109,7 +112,7 @@ class UserSettingsType extends AbstractType
|
||||
'required' => false,
|
||||
'placeholder' => 'user_settings.language.placeholder',
|
||||
'label' => 'user.language_select',
|
||||
'preferred_choices' => ['en', 'de'],
|
||||
'preferred_choices' => $this->preferred_languages,
|
||||
])
|
||||
->add('timezone', TimezoneType::class, [
|
||||
'disabled' => $this->demo_mode,
|
||||
|
||||
@@ -47,6 +47,8 @@ final class ProjectBuildRequest
|
||||
|
||||
private bool $add_build_to_builds_part = false;
|
||||
|
||||
private bool $dont_check_quantity = false;
|
||||
|
||||
/**
|
||||
* @param Project $project The project that should be build
|
||||
* @param int $number_of_builds The number of builds that should be created
|
||||
@@ -283,4 +285,26 @@ final class ProjectBuildRequest
|
||||
{
|
||||
return $this->number_of_builds;
|
||||
}
|
||||
|
||||
/**
|
||||
* If Set to true, the given withdraw amounts are used without any checks for requirements.
|
||||
* @return bool
|
||||
*/
|
||||
public function isDontCheckQuantity(): bool
|
||||
{
|
||||
return $this->dont_check_quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true, the given withdraw amounts are used without any checks for requirements.
|
||||
* @param bool $dont_check_quantity
|
||||
* @return $this
|
||||
*/
|
||||
public function setDontCheckQuantity(bool $dont_check_quantity): ProjectBuildRequest
|
||||
{
|
||||
$this->dont_check_quantity = $dont_check_quantity;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
106
src/Helpers/TrinaryLogicHelper.php
Normal file
106
src/Helpers/TrinaryLogicHelper.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?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\Helpers;
|
||||
|
||||
/**
|
||||
* Helper functions for logic operations with trinary logic.
|
||||
* True and false are represented as classical boolean values, undefined is represented as null.
|
||||
*/
|
||||
class TrinaryLogicHelper
|
||||
{
|
||||
|
||||
/**
|
||||
* Implements the trinary logic NOT.
|
||||
* @param bool|null $a
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function not(?bool $a): ?bool
|
||||
{
|
||||
if ($a === null) {
|
||||
return null;
|
||||
}
|
||||
return !$a;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the trinary logic OR of the given parameters. At least one parameter is required.
|
||||
* @param bool|null ...$args
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function or(?bool ...$args): ?bool
|
||||
{
|
||||
if (count($args) === 0) {
|
||||
throw new \LogicException('At least one parameter is required.');
|
||||
}
|
||||
|
||||
// The trinary or is the maximum of the integer representation of the parameters.
|
||||
return self::intToBool(
|
||||
max(array_map(self::boolToInt(...), $args))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the trinary logic AND of the given parameters. At least one parameter is required.
|
||||
* @param bool|null ...$args
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function and(?bool ...$args): ?bool
|
||||
{
|
||||
if (count($args) === 0) {
|
||||
throw new \LogicException('At least one parameter is required.');
|
||||
}
|
||||
|
||||
// The trinary and is the minimum of the integer representation of the parameters.
|
||||
return self::intToBool(
|
||||
min(array_map(self::boolToInt(...), $args))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the trinary bool to an integer, where true is 1, false is -1 and null is 0.
|
||||
* @param bool|null $a
|
||||
* @return int
|
||||
*/
|
||||
private static function boolToInt(?bool $a): int
|
||||
{
|
||||
if ($a === null) {
|
||||
return 0;
|
||||
}
|
||||
return $a ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the integer to a trinary bool, where 1 is true, -1 is false and 0 is null.
|
||||
* @param int $a
|
||||
* @return bool|null
|
||||
*/
|
||||
private static function intToBool(int $a): ?bool
|
||||
{
|
||||
if ($a === 0) {
|
||||
return null;
|
||||
}
|
||||
return $a > 0;
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,24 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration
|
||||
return $result > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a column exists in a table.
|
||||
* @return bool Returns true, if the column exists
|
||||
* @throws Exception
|
||||
*/
|
||||
public function doesColumnExist(string $table, string $column_name): bool
|
||||
{
|
||||
$db_type = $this->getDatabaseType();
|
||||
if ($db_type !== 'mysql') {
|
||||
throw new \RuntimeException('This method is only supported for MySQL/MariaDB databases!');
|
||||
}
|
||||
|
||||
$sql = "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '$table' AND COLUMN_NAME = '$column_name'";
|
||||
$result = (int) $this->connection->fetchOne($sql);
|
||||
|
||||
return $result > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database type of the used database.
|
||||
* @return string|null Returns 'mysql' for MySQL/MariaDB and 'sqlite' for SQLite. Returns null if unknown type
|
||||
|
||||
@@ -42,6 +42,7 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU
|
||||
/**
|
||||
* Returns the anonymous user.
|
||||
* The result is cached, so the database is only called once, after the anonymous user was found.
|
||||
* @return User|null The user if it is existing, null if no one matched the criteria
|
||||
*/
|
||||
public function getAnonymousUser(): ?User
|
||||
{
|
||||
@@ -54,6 +55,30 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU
|
||||
return $this->anonymous_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user by its username.
|
||||
* @param string $username
|
||||
* @return User|null
|
||||
*/
|
||||
public function findByUsername(string $username): ?User
|
||||
{
|
||||
if ($username === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$qb = $this->createQueryBuilder('u');
|
||||
$qb->select('u')
|
||||
->where('u.name = (:name)');
|
||||
|
||||
$qb->setParameter('name', $username);
|
||||
|
||||
try {
|
||||
return $qb->getQuery()->getOneOrNullResult();
|
||||
} catch (NonUniqueResultException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user by its name or its email. Useful for login or password reset purposes.
|
||||
*
|
||||
|
||||
45
src/Security/Voter/HasAccessPermissionsVoter.php
Normal file
45
src/Security/Voter/HasAccessPermissionsVoter.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Security\Voter;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
|
||||
/**
|
||||
* This voter implements a virtual role, which can be used if the user has any permission set to allowed.
|
||||
* We use this to restrict access to the homepage.
|
||||
*/
|
||||
class HasAccessPermissionsVoter extends ExtendedVoter
|
||||
{
|
||||
public const ROLE = "HAS_ACCESS_PERMISSIONS";
|
||||
|
||||
protected function voteOnUser(string $attribute, $subject, User $user): bool
|
||||
{
|
||||
return $this->resolver->hasAnyPermissionSetToAllowInherited($user);
|
||||
}
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return $attribute === self::ROLE;
|
||||
}
|
||||
}
|
||||
@@ -214,13 +214,13 @@ class EntityURLGenerator
|
||||
/**
|
||||
* Generates an URL to a page, where this entity can be edited.
|
||||
*
|
||||
* @param mixed $entity The entity for which the edit link should be generated
|
||||
* @param AbstractDBElement $entity The entity for which the edit link should be generated
|
||||
*
|
||||
* @return string the URL to the edit page
|
||||
*
|
||||
* @throws EntityNotSupportedException If the method is not supported for the given Entity
|
||||
*/
|
||||
public function editURL(mixed $entity): string
|
||||
public function editURL(AbstractDBElement $entity): string
|
||||
{
|
||||
$map = [
|
||||
Part::class => 'part_edit',
|
||||
@@ -244,13 +244,14 @@ class EntityURLGenerator
|
||||
/**
|
||||
* Generates an URL to a page, where a entity of this type can be created.
|
||||
*
|
||||
* @param mixed $entity The entity for which the link should be generated
|
||||
* @param AbstractDBElement|string $entity The entity (or the entity class) for which the link should be generated
|
||||
* @phpstan-param AbstractDBElement|class-string<AbstractDBElement> $entity
|
||||
*
|
||||
* @return string the URL to the page
|
||||
*
|
||||
* @throws EntityNotSupportedException If the method is not supported for the given Entity
|
||||
*/
|
||||
public function createURL(mixed $entity): string
|
||||
public function createURL(AbstractDBElement|string $entity): string
|
||||
{
|
||||
$map = [
|
||||
Part::class => 'part_new',
|
||||
@@ -352,21 +353,26 @@ class EntityURLGenerator
|
||||
* Throws an exception if the entity class is not known to the map.
|
||||
*
|
||||
* @param array $map The map that should be used for determing the controller
|
||||
* @param mixed $entity The entity for which the controller name should be determined
|
||||
* @param AbstractDBElement|string $entity The entity for which the controller name should be determined
|
||||
* @phpstan-param AbstractDBElement|class-string<AbstractDBElement> $entity
|
||||
*
|
||||
* @return string The name of the controller fitting the entity class
|
||||
*
|
||||
* @throws EntityNotSupportedException
|
||||
*/
|
||||
protected function mapToController(array $map, mixed $entity): string
|
||||
protected function mapToController(array $map, string|AbstractDBElement $entity): string
|
||||
{
|
||||
$class = $entity::class;
|
||||
if (is_string($entity)) { //If a class name was already passed, then use it directly
|
||||
$class = $entity;
|
||||
} else { //Otherwise get the class name from the entity
|
||||
$class = $entity::class;
|
||||
}
|
||||
|
||||
//Check if we have an direct mapping for the given class
|
||||
if (!array_key_exists($class, $map)) {
|
||||
//Check if we need to check inheritance by looping through our map
|
||||
foreach (array_keys($map) as $key) {
|
||||
if (is_a($entity, $key)) {
|
||||
if (is_a($entity, $key, true)) {
|
||||
return $map[$key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,8 +265,7 @@ class PKDatastructureImporter
|
||||
{
|
||||
$count = $this->importElementsWithCategory($data, Storelocation::class, 'storagelocation');
|
||||
|
||||
//Footprints have both attachments and images
|
||||
$this->importAttachments($data, 'storagelocationimage', Storelocation::class, 'footprint_id', StorelocationAttachment::class);
|
||||
$this->importAttachments($data, 'storagelocationimage', Storelocation::class, 'storageLocation_id', StorelocationAttachment::class);
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
143
src/Services/System/UpdateAvailableManager.php
Normal file
143
src/Services/System/UpdateAvailableManager.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?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\Services\System;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shivas\VersioningBundle\Service\VersionManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Version\Version;
|
||||
|
||||
/**
|
||||
* This class checks if a new version of Part-DB is available.
|
||||
*/
|
||||
class UpdateAvailableManager
|
||||
{
|
||||
|
||||
private const API_URL = 'https://api.github.com/repos/Part-DB/Part-DB-server/releases/latest';
|
||||
private const CACHE_KEY = 'uam_latest_version';
|
||||
private const CACHE_TTL = 60 * 60 * 24 * 2; // 2 day
|
||||
|
||||
public function __construct(private readonly HttpClientInterface $httpClient,
|
||||
private readonly CacheInterface $updateCache, private readonly VersionManagerInterface $versionManager,
|
||||
private readonly bool $check_for_updates, private readonly LoggerInterface $logger,
|
||||
#[Autowire(param: 'kernel.debug')] private readonly bool $is_dev_mode)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest version of Part-DB as string (e.g. "1.2.3").
|
||||
* This value is cached for 2 days.
|
||||
* @return string
|
||||
*/
|
||||
public function getLatestVersionString(): string
|
||||
{
|
||||
return $this->getLatestVersionInfo()['version'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest version of Part-DB as Version object.
|
||||
*/
|
||||
public function getLatestVersion(): Version
|
||||
{
|
||||
return Version::fromString($this->getLatestVersionString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL to the latest version of Part-DB on GitHub.
|
||||
* @return string
|
||||
*/
|
||||
public function getLatestVersionUrl(): string
|
||||
{
|
||||
return $this->getLatestVersionInfo()['url'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a new version of Part-DB is available. This value is cached for 2 days.
|
||||
* @return bool
|
||||
*/
|
||||
public function isUpdateAvailable(): bool
|
||||
{
|
||||
//If we don't want to check for updates, we can return false
|
||||
if (!$this->check_for_updates) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$latestVersion = $this->getLatestVersion();
|
||||
$currentVersion = $this->versionManager->getVersion();
|
||||
|
||||
return $latestVersion->isGreaterThan($currentVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest version info. The value is cached for 2 days.
|
||||
* @return array
|
||||
* @phpstan-return array{version: string, url: string}
|
||||
*/
|
||||
private function getLatestVersionInfo(): array
|
||||
{
|
||||
//If we don't want to check for updates, we can return dummy data
|
||||
if (!$this->check_for_updates) {
|
||||
return [
|
||||
'version' => '0.0.1',
|
||||
'url' => 'update-checking-disabled'
|
||||
];
|
||||
}
|
||||
|
||||
return $this->updateCache->get(self::CACHE_KEY, function (ItemInterface $item) {
|
||||
$item->expiresAfter(self::CACHE_TTL);
|
||||
try {
|
||||
$response = $this->httpClient->request('GET', self::API_URL);
|
||||
$result = $response->toArray();
|
||||
$tag_name = $result['tag_name'];
|
||||
|
||||
// Remove the leading 'v' from the tag name
|
||||
$version = substr($tag_name, 1);
|
||||
|
||||
return [
|
||||
'version' => $version,
|
||||
'url' => $result['html_url'],
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
//When we are in dev mode, throw the exception, otherwise just silently log it
|
||||
if ($this->is_dev_mode) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
//In the case of an error, try it again after half of the cache time
|
||||
$item->expiresAfter(self::CACHE_TTL / 2);
|
||||
|
||||
$this->logger->error('Checking for updates failed: ' . $e->getMessage());
|
||||
|
||||
return [
|
||||
'version' => '0.0.1',
|
||||
'url' => 'update-checking-error'
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,8 @@ class TreeViewGenerator
|
||||
//When we use the newEdit type, add the New Element node.
|
||||
if ('newEdit' === $mode) {
|
||||
//Generate the url for the new node
|
||||
$href = $this->urlGenerator->createURL(new $class());
|
||||
//DO NOT try to create an object from the class, as this might be an proxy, which can not be easily initialized, so just pass the class_name directly
|
||||
$href = $this->urlGenerator->createURL($class);
|
||||
$new_node = new TreeViewNode($this->translator->trans('entity.tree.new'), $href);
|
||||
//When the id of the selected element is null, then we have a new element, and we need to select "new" node
|
||||
if (!$selectedElement instanceof AbstractDBElement || null === $selectedElement->getID()) {
|
||||
|
||||
@@ -97,8 +97,7 @@ class PasswordResetManager
|
||||
{
|
||||
//Try to find the user
|
||||
$repo = $this->em->getRepository(User::class);
|
||||
/** @var User|null $user */
|
||||
$user = $repo->findOneBy(['name' => $username]);
|
||||
$user = $repo->findByUsername($username);
|
||||
|
||||
//If no user matching the name, show an error message
|
||||
if (!$user instanceof User) {
|
||||
|
||||
@@ -271,6 +271,27 @@ class PermissionManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function checks if the given user has any permission set to allow, either directly or inherited.
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAnyPermissionSetToAllowInherited(User $user): bool
|
||||
{
|
||||
//Iterate over all permissions
|
||||
foreach ($this->permission_structure['perms'] as $perm_key => $permission) {
|
||||
//Iterate over all operations of the permission
|
||||
foreach ($permission['operations'] as $op_key => $op) {
|
||||
//Check if the user has the permission set to allow
|
||||
if ($this->inherit($user, $perm_key, $op_key) === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function generatePermissionStructure()
|
||||
{
|
||||
$cache = new ConfigCache($this->cache_file, $this->kernel_debug_enabled);
|
||||
|
||||
@@ -107,6 +107,8 @@ class PermissionPresetsHelper
|
||||
|
||||
//Allow to manage Oauth tokens
|
||||
$this->permissionResolver->setPermission($perm_holder, 'system', 'manage_oauth_tokens', PermissionData::ALLOW);
|
||||
//Allow to show updates
|
||||
$this->permissionResolver->setPermission($perm_holder, 'system', 'show_updates', PermissionData::ALLOW);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace App\Services\UserSystem;
|
||||
use App\Entity\UserSystem\Group;
|
||||
use App\Entity\UserSystem\PermissionData;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Helpers\TrinaryLogicHelper;
|
||||
use App\Security\Interfaces\HasPermissionsInterface;
|
||||
|
||||
/**
|
||||
@@ -138,4 +139,22 @@ class PermissionSchemaUpdater
|
||||
$holder->getPermissions()->removePermission('devices');
|
||||
}
|
||||
}
|
||||
|
||||
private function upgradeSchemaToVersion3(HasPermissionsInterface $holder): void //@phpstan-ignore-line This is called via reflection
|
||||
{
|
||||
$permissions = $holder->getPermissions();
|
||||
|
||||
//If the system.show_updates permission is not defined yet, set it to true, if the user can view server info, server logs or edit users or groups
|
||||
if (!$permissions->isPermissionSet('system', 'show_updates')) {
|
||||
|
||||
$new_value = TrinaryLogicHelper::or(
|
||||
$permissions->getPermissionValue('system', 'server_infos'),
|
||||
$permissions->getPermissionValue('system', 'show_logs'),
|
||||
$permissions->getPermissionValue('users', 'edit'),
|
||||
$permissions->getPermissionValue('groups', 'edit')
|
||||
);
|
||||
|
||||
$permissions->setPermissionValue('system', 'show_updates', $new_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +69,12 @@ class ValidProjectBuildRequestValidator extends ConstraintValidator
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if ($withdraw_sum > $needed_amount) {
|
||||
if ($withdraw_sum > $needed_amount && $value->isDontCheckQuantity() === false) {
|
||||
$this->buildViolationForLot($lot, 'validator.project_build.lot_bigger_than_needed')
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if ($withdraw_sum < $needed_amount) {
|
||||
if ($withdraw_sum < $needed_amount && $value->isDontCheckQuantity() === false) {
|
||||
$this->buildViolationForLot($lot, 'validator.project_build.lot_smaller_than_needed')
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
{# <span id="select_count"></span> #}
|
||||
|
||||
<div class="input-group">
|
||||
<button class="btn btn-outline-secondary" type="button" {{ stimulus_action('elements/datatables/parts', 'invertSelection')}}
|
||||
title="{% trans %}part_list.action.invert_selection{% endtrans %}" ><i class="fa-solid fa-arrow-right-arrow-left"></i></button>
|
||||
<span class="input-group-text">
|
||||
<span class="badge bg-primary">{% trans with {'%count%': '<span ' ~ stimulus_target('elements/datatables/parts', 'selectCount') ~ '></span>'} %}part_list.action.part_count{% endtrans %}</span>
|
||||
</span>
|
||||
@@ -75,7 +77,7 @@
|
||||
{# This is left empty, as this will be filled by Javascript #}
|
||||
</select>
|
||||
|
||||
<button type="submit" class="btn btn-secondary">{% trans %}part_list.action.submit{% endtrans %}</button>
|
||||
<button type="submit" class="btn btn-primary">{% trans %}part_list.action.submit{% endtrans %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
9
templates/components/new_version.macro.html.twig
Normal file
9
templates/components/new_version.macro.html.twig
Normal file
@@ -0,0 +1,9 @@
|
||||
{% macro new_version_alert(is_available, new_version, new_version_url) %}
|
||||
{% if is_available %}
|
||||
<div class="alert alert-success" role="alert">
|
||||
<h5><i class="fa-solid fa-champagne-glasses"></i> {% trans %}update_manager.new_version_available.title{% endtrans %}</h5>
|
||||
{% trans %}update_manager.new_version_available.text{% endtrans %}: <b><a href="{{ new_version_url }}" class="alert-link link-external" target="_blank">{% trans %}version.caption{% endtrans %} {{ new_version }}</a></b>
|
||||
<br><small>{% trans %}update_manager.new_version_available.only_administrators_can_see{% endtrans %}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -1,6 +1,13 @@
|
||||
{% extends "base.html.twig" %}
|
||||
|
||||
{% import "components/new_version.macro.html.twig" as nv %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if is_granted('@system.show_updates') %}
|
||||
{{ nv.new_version_alert(new_version_available, new_version, new_version_url) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="rounded p-4 bg-body-secondary">
|
||||
<h1 class="display-3">{{ partdb_title }}</h1>
|
||||
<h4>
|
||||
|
||||
@@ -74,6 +74,9 @@
|
||||
</table>
|
||||
|
||||
{{ form_row(form.comment) }}
|
||||
<div {{ stimulus_controller('pages/dont_check_quantity_checkbox') }}>
|
||||
{{ form_row(form.dontCheckQuantity) }}
|
||||
</div>
|
||||
|
||||
{{ form_row(form.addBuildsToBuildsPart) }}
|
||||
{% if form.buildsPartLot is defined %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "main_card.html.twig" %}
|
||||
{% import "components/new_version.macro.html.twig" as nv %}
|
||||
|
||||
{% block title %}{% trans %}tools.server_infos.title{% endtrans %}{% endblock %}
|
||||
|
||||
@@ -6,6 +7,12 @@
|
||||
<i class="fas fa-database"></i> {% trans %}tools.server_infos.title{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block before_card %}
|
||||
{% if is_granted('@system.show_updates') %}
|
||||
{{ nv.new_version_alert(new_version_available, new_version, new_version_url) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||
|
||||
87
tests/Helpers/TrinaryLogicHelperTest.php
Normal file
87
tests/Helpers/TrinaryLogicHelperTest.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Helpers;
|
||||
|
||||
use App\Helpers\TrinaryLogicHelper;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class TrinaryLogicHelperTest extends TestCase
|
||||
{
|
||||
|
||||
public function testNot()
|
||||
{
|
||||
$this->assertTrue(TrinaryLogicHelper::not(false));
|
||||
$this->assertFalse(TrinaryLogicHelper::not(true));
|
||||
$this->assertNull(TrinaryLogicHelper::not(null));
|
||||
}
|
||||
|
||||
public function testOr(): void
|
||||
{
|
||||
$this->assertFalse(TrinaryLogicHelper::or(false, false));
|
||||
$this->assertNull(TrinaryLogicHelper::or(null, false));
|
||||
$this->assertTrue(TrinaryLogicHelper::or(false, true));
|
||||
|
||||
$this->assertNull(TrinaryLogicHelper::or(null, false));
|
||||
$this->assertNull(TrinaryLogicHelper::or(null, null));
|
||||
$this->assertTrue(TrinaryLogicHelper::or(false, true));
|
||||
|
||||
$this->assertTrue(TrinaryLogicHelper::or(true, false));
|
||||
$this->assertTrue(TrinaryLogicHelper::or(true, null));
|
||||
$this->assertTrue(TrinaryLogicHelper::or(true, true));
|
||||
|
||||
//Should work for longer arrays too
|
||||
$this->assertTrue(TrinaryLogicHelper::or(true, true, false, null));
|
||||
$this->assertNull(TrinaryLogicHelper::or(false, false, false, false, null));
|
||||
$this->assertFalse(TrinaryLogicHelper::or(false, false, false));
|
||||
|
||||
//Test for one argument
|
||||
$this->assertTrue(TrinaryLogicHelper::or(true));
|
||||
$this->assertFalse(TrinaryLogicHelper::or(false));
|
||||
$this->assertNull(TrinaryLogicHelper::or(null));
|
||||
|
||||
}
|
||||
|
||||
public function testAnd(): void
|
||||
{
|
||||
$this->assertFalse(TrinaryLogicHelper::and(false, false));
|
||||
$this->assertFalse(TrinaryLogicHelper::and(false, null));
|
||||
$this->assertFalse(TrinaryLogicHelper::and(false, true));
|
||||
$this->assertFalse(TrinaryLogicHelper::and(null, false));
|
||||
$this->assertNull(TrinaryLogicHelper::and(null, null));
|
||||
$this->assertNull(TrinaryLogicHelper::and(null, true));
|
||||
$this->assertFalse(TrinaryLogicHelper::and(true, false));
|
||||
$this->assertNull(TrinaryLogicHelper::and(true, null));
|
||||
$this->assertTrue(TrinaryLogicHelper::and(true, true));
|
||||
|
||||
|
||||
//Should work for longer arrays too
|
||||
$this->assertFalse(TrinaryLogicHelper::and(true, true, false, null));
|
||||
$this->assertFalse(TrinaryLogicHelper::and(false, false, false, false, null));
|
||||
$this->assertFalse(TrinaryLogicHelper::and(false, false, false));
|
||||
$this->assertNull(TrinaryLogicHelper::and(true, true, null));
|
||||
$this->assertTrue(TrinaryLogicHelper::and(true, true, true));
|
||||
|
||||
//Test for one argument
|
||||
$this->assertTrue(TrinaryLogicHelper::and(true));
|
||||
$this->assertFalse(TrinaryLogicHelper::and(false));
|
||||
$this->assertNull(TrinaryLogicHelper::and(null));
|
||||
}
|
||||
}
|
||||
86
tests/Repository/UserRepositoryTest.php
Normal file
86
tests/Repository/UserRepositoryTest.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Repository;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Repository\UserRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class UserRepositoryTest extends WebTestCase
|
||||
{
|
||||
|
||||
private $entityManager;
|
||||
/**
|
||||
* @var UserRepository
|
||||
*/
|
||||
private $repo;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$kernel = self::bootKernel();
|
||||
|
||||
$this->entityManager = $kernel->getContainer()
|
||||
->get('doctrine')
|
||||
->getManager();
|
||||
|
||||
$this->repo = $this->entityManager->getRepository(User::class);
|
||||
}
|
||||
|
||||
|
||||
public function testGetAnonymousUser()
|
||||
{
|
||||
$user = $this->repo->getAnonymousUser();
|
||||
|
||||
$this->assertInstanceOf(User::class, $user);
|
||||
$this->assertSame(User::ID_ANONYMOUS, $user->getId());
|
||||
$this->assertSame('anonymous', $user->getUsername());
|
||||
}
|
||||
|
||||
public function testFindByEmailOrName()
|
||||
{
|
||||
//Test for email
|
||||
$u = $this->repo->findByEmailOrName('user@invalid.invalid');
|
||||
$this->assertInstanceOf(User::class, $u);
|
||||
$this->assertSame('user', $u->getUsername());
|
||||
|
||||
//Test for name
|
||||
$u = $this->repo->findByEmailOrName('user');
|
||||
$this->assertInstanceOf(User::class, $u);
|
||||
$this->assertSame('user', $u->getUsername());
|
||||
|
||||
//Check what happens for unknown user
|
||||
$u = $this->repo->findByEmailOrName('unknown');
|
||||
$this->assertNull($u);
|
||||
|
||||
}
|
||||
|
||||
public function testFindByUsername()
|
||||
{
|
||||
$u = $this->repo->findByUsername('user');
|
||||
$this->assertInstanceOf(User::class, $u);
|
||||
$this->assertSame('user', $u->getUsername());
|
||||
|
||||
//Check what happens for unknown user
|
||||
$u = $this->repo->findByEmailOrName('unknown');
|
||||
$this->assertNull($u);
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,12 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class PermissionManagerTest extends WebTestCase
|
||||
{
|
||||
protected $user_withoutGroup;
|
||||
protected ?User $user_withoutGroup = null;
|
||||
|
||||
protected $user;
|
||||
protected $group;
|
||||
protected ?User $user = null;
|
||||
protected ?Group $group = null;
|
||||
|
||||
/**
|
||||
* @var PermissionManager
|
||||
*/
|
||||
protected $service;
|
||||
protected ?PermissionManager $service = null;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -294,4 +291,34 @@ class PermissionManagerTest extends WebTestCase
|
||||
$this->assertTrue($this->service->dontInherit($user, 'parts', 'edit'));
|
||||
$this->assertTrue($this->service->dontInherit($user, 'categories', 'read'));
|
||||
}
|
||||
|
||||
public function testHasAnyPermissionSetToAllowInherited(): void
|
||||
{
|
||||
//For empty user this should return false
|
||||
$user = new User();
|
||||
$this->assertFalse($this->service->hasAnyPermissionSetToAllowInherited($user));
|
||||
|
||||
//If all permissions are set to false this should return false
|
||||
$this->service->setAllPermissions($user, false);
|
||||
$this->assertFalse($this->service->hasAnyPermissionSetToAllowInherited($user));
|
||||
|
||||
//If all permissions are set to null this should return false
|
||||
$this->service->setAllPermissions($user, null);
|
||||
$this->assertFalse($this->service->hasAnyPermissionSetToAllowInherited($user));
|
||||
|
||||
//If all permissions are set to true this should return true
|
||||
$this->service->setAllPermissions($user, true);
|
||||
$this->assertTrue($this->service->hasAnyPermissionSetToAllowInherited($user));
|
||||
|
||||
//The test data should return true
|
||||
$this->assertTrue($this->service->hasAnyPermissionSetToAllowInherited($this->user));
|
||||
$this->assertTrue($this->service->hasAnyPermissionSetToAllowInherited($this->user_withoutGroup));
|
||||
|
||||
//Create a user with a group
|
||||
$user = new User();
|
||||
$user->setGroup($this->group);
|
||||
//This should return true
|
||||
$this->assertTrue($this->service->hasAnyPermissionSetToAllowInherited($user));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,4 +110,17 @@ class PermissionSchemaUpdaterTest extends WebTestCase
|
||||
self::assertEquals(PermissionData::INHERIT, $user->getPermissions()->getPermissionValue('projects', 'edit'));
|
||||
self::assertEquals(PermissionData::DISALLOW, $user->getPermissions()->getPermissionValue('projects', 'delete'));
|
||||
}
|
||||
|
||||
public function testUpgradeSchemaToVersion3(): void
|
||||
{
|
||||
$perm_data = new PermissionData();
|
||||
$perm_data->setSchemaVersion(2);
|
||||
$perm_data->setPermissionValue('system', 'server_infos', PermissionData::ALLOW);
|
||||
|
||||
$user = new TestPermissionHolder($perm_data);
|
||||
|
||||
//After the upgrade the server.show_updates should be set to ALLOW
|
||||
self::assertTrue($this->service->upgradeSchema($user, 3));
|
||||
self::assertSame(PermissionData::ALLOW, $user->getPermissions()->getPermissionValue('system', 'show_updates'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
<note priority="1">Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:4</note>
|
||||
<note priority="1">templates\AdminPages\DeviceAdmin.html.twig:4</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>project.caption</source>
|
||||
<target>Projekte</target>
|
||||
</segment>
|
||||
@@ -163,7 +163,7 @@
|
||||
<note category="file-source" priority="1">Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:8</note>
|
||||
<note category="state" priority="1">new</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>project.edit</source>
|
||||
<target>Bearbeite Projekt</target>
|
||||
</segment>
|
||||
@@ -173,7 +173,7 @@
|
||||
<note category="file-source" priority="1">Part-DB1\templates\AdminPages\DeviceAdmin.html.twig:12</note>
|
||||
<note category="state" priority="1">new</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>project.new</source>
|
||||
<target>Neues Projekt</target>
|
||||
</segment>
|
||||
@@ -4050,7 +4050,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
<note priority="1">templates\base.html.twig:202</note>
|
||||
<note priority="1">templates\base.html.twig:230</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>project.labelp</source>
|
||||
<target>Projekte</target>
|
||||
</segment>
|
||||
@@ -5974,7 +5974,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
<note category="file-source" priority="1">Part-DB1\src\Services\ElementTypeNameGenerator.php:82</note>
|
||||
<note priority="1">Part-DB1\src\Services\ElementTypeNameGenerator.php:82</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>project.label</source>
|
||||
<target>Projekt</target>
|
||||
</segment>
|
||||
@@ -6165,7 +6165,7 @@ Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertr
|
||||
<note priority="1">Part-DB1\src\Services\Trees\ToolsTreeBuilder.php:138</note>
|
||||
<note priority="1">src\Services\ToolsTreeBuilder.php:66</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>tree.tools.edit.projects</source>
|
||||
<target>Projekte</target>
|
||||
</segment>
|
||||
@@ -7974,7 +7974,7 @@ Element 3</target>
|
||||
<note priority="1">obsolete</note>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>perm.projects</source>
|
||||
<target>Projekte</target>
|
||||
</segment>
|
||||
@@ -9600,7 +9600,7 @@ Element 3</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="dU7EyhM" name="entity.info.parts_count_recursive">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>entity.info.parts_count_recursive</source>
|
||||
<target>Bauteile mit diesem Element oder dessen Kindelementen</target>
|
||||
</segment>
|
||||
@@ -11575,5 +11575,47 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön
|
||||
<target>Quelle</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="21tIbM2" name="update_manager.new_version_available.title">
|
||||
<segment state="translated">
|
||||
<source>update_manager.new_version_available.title</source>
|
||||
<target>Neue Version verfügbar</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id=".c.eoDV" name="update_manager.new_version_available.text">
|
||||
<segment state="translated">
|
||||
<source>update_manager.new_version_available.text</source>
|
||||
<target>Eine neue Version von Part-DB ist verfügbar. Mehr Informationen gibt es hier</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="KOFGqJw" name="update_manager.new_version_available.only_administrators_can_see">
|
||||
<segment state="translated">
|
||||
<source>update_manager.new_version_available.only_administrators_can_see</source>
|
||||
<target>Nur Administratoren können diese Nachricht sehen.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="IFkvJpC" name="perm.system.show_available_updates">
|
||||
<segment state="translated">
|
||||
<source>perm.system.show_available_updates</source>
|
||||
<target>Verfügbare Part-DB Updates anzeigen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="VVpmfIj" name="project.build.dont_check_quantity">
|
||||
<segment state="translated">
|
||||
<source>project.build.dont_check_quantity</source>
|
||||
<target>Mengen nicht überprüfen</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="AzYSIiX" name="project.build.dont_check_quantity.help">
|
||||
<segment state="translated">
|
||||
<source>project.build.dont_check_quantity.help</source>
|
||||
<target>Wenn diese Option gewählt wird, werden die gewählten Mengen aus dem Lager entfernt, egal ob mehr oder weniger Bauteile sind, als für den Bau des Projekts eigentlich benötigt werden.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="tfOeMsC" name="part_list.action.invert_selection">
|
||||
<segment state="translated">
|
||||
<source>part_list.action.invert_selection</source>
|
||||
<target>Auswahl umkehren</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -731,10 +731,10 @@
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>user.edit.tfa.disable_tfa_message</source>
|
||||
<target><![CDATA[This will disable <b>all active two-factor authentication methods of the user</b> and delete the <b>backup codes</b>!
|
||||
<br>
|
||||
The user will have to set up all two-factor authentication methods again and print new backup codes! <br><br>
|
||||
<b>Only do this if you are absolutely sure about the identity of the user (seeking help), otherwise the account could be compromised by an attacker!</b>]]></target>
|
||||
<target>This will disable <b>all active two-factor authentication methods of the user</b> and delete the <b>backup codes</b>!
|
||||
<br>
|
||||
The user will have to set up all two-factor authentication methods again and print new backup codes! <br><br>
|
||||
<b>Only do this if you are absolutely sure about the identity of the user (seeking help), otherwise the account could be compromised by an attacker!</b></target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="02HvwiS" name="user.edit.tfa.disable_tfa.btn">
|
||||
@@ -1512,7 +1512,7 @@ Sub elements will be moved upwards.</target>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>label_generator.common</source>
|
||||
<target>Common</target>
|
||||
<target>Comune</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="WLpYniK" name="label_generator.advanced">
|
||||
@@ -11326,67 +11326,67 @@ Element 3</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="DGczoY6" name="tfa_u2f.add_key.registration_error">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>tfa_u2f.add_key.registration_error</source>
|
||||
<target>An error occurred during the registration of the security key. Try again or use another security key!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ie0Ca0l" name="log.target_type.none">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>log.target_type.none</source>
|
||||
<target>None</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="R2nX4ip" name="ui.darkmode.light">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>ui.darkmode.light</source>
|
||||
<target>Light</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3NHpuW3" name="ui.darkmode.dark">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>ui.darkmode.dark</source>
|
||||
<target>Dark</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="4TGOK5_" name="ui.darkmode.auto">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>ui.darkmode.auto</source>
|
||||
<target>Auto (decide based on system settings)</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="9N0N8aL" name="label_generator.no_lines_given">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>label_generator.no_lines_given</source>
|
||||
<target>No text content given! The labels will remain empty.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="RdFvZsb" name="user.password_strength.very_weak">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>user.password_strength.very_weak</source>
|
||||
<target>Very weak</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="IBjmblZ" name="user.password_strength.weak">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>user.password_strength.weak</source>
|
||||
<target>Weak</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="qSm_ID0" name="user.password_strength.medium">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>user.password_strength.medium</source>
|
||||
<target>Medium</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="aWAaADS" name="user.password_strength.strong">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>user.password_strength.strong</source>
|
||||
<target>Strong</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Wa9CStW" name="user.password_strength.very_strong">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>user.password_strength.very_strong</source>
|
||||
<target>Very strong</target>
|
||||
</segment>
|
||||
@@ -11579,5 +11579,47 @@ Please note, that you can not impersonate a disabled user. If you try you will g
|
||||
<target>Provider</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="21tIbM2" name="update_manager.new_version_available.title">
|
||||
<segment state="translated">
|
||||
<source>update_manager.new_version_available.title</source>
|
||||
<target>New version available</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id=".c.eoDV" name="update_manager.new_version_available.text">
|
||||
<segment state="translated">
|
||||
<source>update_manager.new_version_available.text</source>
|
||||
<target>A new version of Part-DB is available. Check it out here</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="KOFGqJw" name="update_manager.new_version_available.only_administrators_can_see">
|
||||
<segment state="translated">
|
||||
<source>update_manager.new_version_available.only_administrators_can_see</source>
|
||||
<target>Only administrators can see this message.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="IFkvJpC" name="perm.system.show_available_updates">
|
||||
<segment state="translated">
|
||||
<source>perm.system.show_available_updates</source>
|
||||
<target>Show available Part-DB updates</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="VVpmfIj" name="project.build.dont_check_quantity">
|
||||
<segment state="translated">
|
||||
<source>project.build.dont_check_quantity</source>
|
||||
<target>Do not check quantities</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="AzYSIiX" name="project.build.dont_check_quantity.help">
|
||||
<segment state="translated">
|
||||
<source>project.build.dont_check_quantity.help</source>
|
||||
<target>If this option is selected, the given withdraw quantities are used as given, no matter if more or less parts are actually required to build this project.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="tfOeMsC" name="part_list.action.invert_selection">
|
||||
<segment state="translated">
|
||||
<source>part_list.action.invert_selection</source>
|
||||
<target>Invert selection</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -9068,31 +9068,31 @@ exemple de ville</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="1HcqCmo" name="currency.edit.update_rate">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>currency.edit.update_rate</source>
|
||||
<target>Taux de rafraîchissement</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="jSf6Wmz" name="currency.edit.exchange_rate_update.unsupported_currency">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>currency.edit.exchange_rate_update.unsupported_currency</source>
|
||||
<target>Devise non prise en charge</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="D481NZD" name="currency.edit.exchange_rate_update.generic_error">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>currency.edit.exchange_rate_update.generic_error</source>
|
||||
<target>Erreur générique</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="E_M7mZ5" name="currency.edit.exchange_rate_updated.success">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>currency.edit.exchange_rate_updated.success</source>
|
||||
<target>Succès</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="nfBdkzp" name="homepage.forum.text">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>homepage.forum.text</source>
|
||||
<target>Si vous avez des questions à propos de Part-DB , rendez vous sur <a href="%href%" class="link-external" target="_blank">Github</a></target>
|
||||
</segment>
|
||||
|
||||
11627
translations/messages.it.xlf
Normal file
11627
translations/messages.it.xlf
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8829,7 +8829,7 @@ Exampletown</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="nfBdkzp" name="homepage.forum.text">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>homepage.forum.text</source>
|
||||
<target>Part-DBについての質問は、<a href="%href%" class="link-external" target="_blank">GitHub</a> にスレッドがあります。</target>
|
||||
</segment>
|
||||
|
||||
@@ -9070,7 +9070,7 @@
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="nfBdkzp" name="homepage.forum.text">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>homepage.forum.text</source>
|
||||
<target>Все вопросы по Part-DB в ветке обсуждения на <a href="%href%" class="link-external" target="_blank">mikrocontroller.net</a></target>
|
||||
</segment>
|
||||
|
||||
@@ -1860,6 +1860,20 @@
|
||||
<target>库存</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="t2TNwOq" name="Unknown">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\templates\Parts\info\_extended_infos.html.twig:13</note>
|
||||
<note category="file-source" priority="1">Part-DB1\templates\Parts\info\_extended_infos.html.twig:28</note>
|
||||
<note category="file-source" priority="1">Part-DB1\templates\Parts\info\_extended_infos.html.twig:50</note>
|
||||
<note priority="1">Part-DB1\templates\Parts\info\_extended_infos.html.twig:13</note>
|
||||
<note priority="1">Part-DB1\templates\Parts\info\_extended_infos.html.twig:28</note>
|
||||
<note priority="1">Part-DB1\templates\Parts\info\_extended_infos.html.twig:50</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>Unknown</source>
|
||||
<target>未知</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3tdTZVD" name="part.supplier.name">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\templates\Parts\info\_order_infos.html.twig:5</note>
|
||||
@@ -1880,6 +1894,16 @@
|
||||
<target>元件编号</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="qNZM46r" name="delete.caption">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\templates\Parts\info\_order_infos.html.twig:72</note>
|
||||
<note priority="1">Part-DB1\templates\Parts\info\_order_infos.html.twig:72</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>delete.caption</source>
|
||||
<target>删除</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="upshmqD" name="part_lots.location_unknown">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\templates\Parts\info\_part_lots.html.twig:24</note>
|
||||
|
||||
17
translations/security.it.xlf
Normal file
17
translations/security.it.xlf
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="it">
|
||||
<file id="security.en">
|
||||
<unit id="aazoCks" name="user.login_error.user_disabled">
|
||||
<segment state="translated">
|
||||
<source>user.login_error.user_disabled</source>
|
||||
<target>Il tuo account è disabilitato! Contatta un amministratore se ritieni che non sia corretto.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Dpb9AmY" name="saml.error.cannot_login_local_user_per_saml">
|
||||
<segment state="translated">
|
||||
<source>saml.error.cannot_login_local_user_per_saml</source>
|
||||
<target>Non è possibile accedere come utente locale tramite SSO! Usare invece la password locale.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -192,7 +192,7 @@
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6vIlN5q" name="validator.part_lot.only_existing">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.only_existing</source>
|
||||
<target>Der Lagerort wurde als "nur bestehende Teile" markiert, daher können keine neuen Teile hinzugefügt werden.</target>
|
||||
</segment>
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
<notes>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.noneofitschild.self</source>
|
||||
<target>Un élément ne peut pas être son propre parent.</target>
|
||||
</segment>
|
||||
@@ -180,25 +180,25 @@
|
||||
<notes>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.noneofitschild.children</source>
|
||||
<target>Le parent ne peut pas être un de ses propres enfants.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6vIlN5q" name="validator.part_lot.only_existing">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.only_existing</source>
|
||||
<target>L'emplacement de stockage a été marqué comme "uniquement existant", donc aucun nouveau composant ne peut être ajouté.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="R6Ov4Yt" name="validator.part_lot.location_full">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.location_full</source>
|
||||
<target>L'emplacement de stockage est plein, c'est pourquoi aucun nouveau composant ne peut être ajouté.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="BNQk2e7" name="validator.part_lot.single_part">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.single_part</source>
|
||||
<target>L'emplacement de stockage a été marqué comme "Composant seul", par conséquent aucun nouveau composant ne peut être ajouté.</target>
|
||||
</segment>
|
||||
|
||||
321
translations/validators.it.xlf
Normal file
321
translations/validators.it.xlf
Normal file
@@ -0,0 +1,321 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="it">
|
||||
<file id="validators.en">
|
||||
<unit id="xevSdCK" name="part.master_attachment.must_be_picture">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Attachments\AttachmentContainingDBElement.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Attachments\AttachmentType.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Base\AbstractCompany.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Base\AbstractPartsContainingDBElement.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Base\AbstractStructuralDBElement.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Devices\Device.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\LabelSystem\LabelProfile.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Category.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Footprint.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Manufacturer.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\MeasurementUnit.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Part.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Part.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Storelocation.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Supplier.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\PriceInformations\Currency.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\UserSystem\Group.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\UserSystem\User.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Attachments\AttachmentType.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Base\AbstractCompany.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Base\AbstractPartsContainingDBElement.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Base\AbstractStructuralDBElement.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Devices\Device.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Category.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Footprint.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Manufacturer.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\MeasurementUnit.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Part.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Storelocation.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Supplier.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\PriceInformations\Currency.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\UserSystem\Group.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\UserSystem\User.php:0</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>part.master_attachment.must_be_picture</source>
|
||||
<target>L'anteprima di un allegato deve essere un'immagine valida!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="VJHTkxx" name="structural.entity.unique_name">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Attachments\AttachmentType.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Base\AbstractCompany.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Base\AbstractPartsContainingDBElement.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Base\AbstractStructuralDBElement.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Devices\Device.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Category.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Footprint.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Manufacturer.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\MeasurementUnit.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Storelocation.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parts\Supplier.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\PriceInformations\Currency.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\UserSystem\Group.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Attachments\AttachmentType.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Base\AbstractCompany.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Base\AbstractPartsContainingDBElement.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Base\AbstractStructuralDBElement.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Devices\Device.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Category.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Footprint.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Manufacturer.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\MeasurementUnit.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Storelocation.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\Parts\Supplier.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\PriceInformations\Currency.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\UserSystem\Group.php:0</note>
|
||||
<note priority="1">src\Entity\AttachmentType.php:0</note>
|
||||
<note priority="1">src\Entity\Category.php:0</note>
|
||||
<note priority="1">src\Entity\Company.php:0</note>
|
||||
<note priority="1">src\Entity\Device.php:0</note>
|
||||
<note priority="1">src\Entity\Footprint.php:0</note>
|
||||
<note priority="1">src\Entity\Group.php:0</note>
|
||||
<note priority="1">src\Entity\Manufacturer.php:0</note>
|
||||
<note priority="1">src\Entity\PartsContainingDBElement.php:0</note>
|
||||
<note priority="1">src\Entity\Storelocation.php:0</note>
|
||||
<note priority="1">src\Entity\StructuralDBElement.php:0</note>
|
||||
<note priority="1">src\Entity\Supplier.php:0</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>structural.entity.unique_name</source>
|
||||
<target>Un elemento con questo nome esiste già a questo livello!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3ODUtpU" name="parameters.validator.min_lesser_typical">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\AbstractParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\AttachmentTypeParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\CategoryParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\CurrencyParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\DeviceParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\FootprintParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\GroupParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\ManufacturerParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\MeasurementUnitParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\PartParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\SupplierParameter.php:0</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parameters.validator.min_lesser_typical</source>
|
||||
<target>Il valore deve essere inferiore o uguale al valore tipico ({{ compared_value }}).</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="jDBA_WW" name="parameters.validator.min_lesser_max">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\AbstractParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\AttachmentTypeParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\CategoryParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\CurrencyParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\DeviceParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\FootprintParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\GroupParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\ManufacturerParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\MeasurementUnitParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\PartParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\SupplierParameter.php:0</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parameters.validator.min_lesser_max</source>
|
||||
<target>Il valore deve essere inferiore al valore massimo ({{ compared_value }}).</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ygK_e_X" name="parameters.validator.max_greater_typical">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\AbstractParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\AttachmentTypeParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\CategoryParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\CurrencyParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\DeviceParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\FootprintParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\GroupParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\ManufacturerParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\MeasurementUnitParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\PartParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\StorelocationParameter.php:0</note>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\Parameters\SupplierParameter.php:0</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>parameters.validator.max_greater_typical</source>
|
||||
<target>Il valore deve essere maggiore o uguale al valore tipico ({{ compared_value }}).</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="isXL.ie" name="validator.user.username_already_used">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\UserSystem\User.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\UserSystem\User.php:0</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>validator.user.username_already_used</source>
|
||||
<target>Esiste già un utente con questo nome</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="NcM463r" name="user.invalid_username">
|
||||
<notes>
|
||||
<note category="file-source" priority="1">Part-DB1\src\Entity\UserSystem\User.php:0</note>
|
||||
<note priority="1">Part-DB1\src\Entity\UserSystem\User.php:0</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>user.invalid_username</source>
|
||||
<target>Il nome utente deve contenere solo lettere, numeri, trattini bassi, punti, più o meno!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="lZvhKYu" name="validator.noneofitschild.self">
|
||||
<notes>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>validator.noneofitschild.self</source>
|
||||
<target>Un elemento non può essere il proprio elemento padre!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="pr07aV4" name="validator.noneofitschild.children">
|
||||
<notes>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment state="translated">
|
||||
<source>validator.noneofitschild.children</source>
|
||||
<target>Un elemento figlio non può essere anche elemento padre!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ayNr6QK" name="validator.select_valid_category">
|
||||
<segment state="translated">
|
||||
<source>validator.select_valid_category</source>
|
||||
<target>Selezionare una categoria valida.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6vIlN5q" name="validator.part_lot.only_existing">
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.only_existing</source>
|
||||
<target>Questa ubicazione è stata contrassegnata come "solo parti esistenti", quindi non è possibile aggiungere nuove parti.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3xoKOIS" name="validator.part_lot.location_full.no_increase">
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.location_full.no_increase</source>
|
||||
<target>Questa ubicazione è piena. La quantità non può essere superiore a {{old_amount}}.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="R6Ov4Yt" name="validator.part_lot.location_full">
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.location_full</source>
|
||||
<target>Questa ubicazione è piena, non è possibile aggiungere nuovi componenti.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="BNQk2e7" name="validator.part_lot.single_part">
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.single_part</source>
|
||||
<target>L'ubicazione è stata contrassegnata come "singolo componente", quindi non vi si possono aggiungere componenti.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="4gPskOG" name="validator.attachment.must_not_be_null">
|
||||
<segment state="translated">
|
||||
<source>validator.attachment.must_not_be_null</source>
|
||||
<target>Bisogna selezionare un tipo di file!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="cDDVrWT" name="validator.orderdetail.supplier_must_not_be_null">
|
||||
<segment state="translated">
|
||||
<source>validator.orderdetail.supplier_must_not_be_null</source>
|
||||
<target>Bisogna selezionare un fornitore!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="k5DDdB4" name="validator.measurement_unit.use_si_prefix_needs_unit">
|
||||
<segment state="translated">
|
||||
<source>validator.measurement_unit.use_si_prefix_needs_unit</source>
|
||||
<target>Per attivare i prefissi SI, è necessario impostare un simbolo di unità!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="DuzIOCr" name="part.ipn.must_be_unique">
|
||||
<segment state="translated">
|
||||
<source>part.ipn.must_be_unique</source>
|
||||
<target>Il codice interno (IPN) deve essere univoco. Il valore {{value}} è già in uso!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Z4Kuuo2" name="validator.project.bom_entry.name_or_part_needed">
|
||||
<segment state="translated">
|
||||
<source>validator.project.bom_entry.name_or_part_needed</source>
|
||||
<target>È necessario selezionare un componente o assegnare un nome ad una voce BOM che non indica un componente!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="WF_v4ih" name="project.bom_entry.name_already_in_bom">
|
||||
<segment state="translated">
|
||||
<source>project.bom_entry.name_already_in_bom</source>
|
||||
<target>Esiste già una voce BOM con questo nome!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="5v4p85H" name="project.bom_entry.part_already_in_bom">
|
||||
<segment state="translated">
|
||||
<source>project.bom_entry.part_already_in_bom</source>
|
||||
<target>Questo componente esiste già nella BOM!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="3lM32Tw" name="project.bom_entry.mountnames_quantity_mismatch">
|
||||
<segment state="translated">
|
||||
<source>project.bom_entry.mountnames_quantity_mismatch</source>
|
||||
<target>La quantità dei nomi delle parti deve coincidere con la quantità prevista in BOM!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="x47D5WT" name="project.bom_entry.can_not_add_own_builds_part">
|
||||
<segment state="translated">
|
||||
<source>project.bom_entry.can_not_add_own_builds_part</source>
|
||||
<target>Non è possibile aggiungere un componente di produzione interno del progetto alla lista dei materiali (BOM).</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="2x2XDI_" name="project.bom_has_to_include_all_subelement_parts">
|
||||
<segment state="translated">
|
||||
<source>project.bom_has_to_include_all_subelement_parts</source>
|
||||
<target>Il progetto BOM (lista dei materiali) deve contenere tutti i componenti di produzione dei sottoprogetti. Manca il componente %part_name% del progetto %project_name%!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="U9b1EzD" name="project.bom_entry.price_not_allowed_on_parts">
|
||||
<segment state="translated">
|
||||
<source>project.bom_entry.price_not_allowed_on_parts</source>
|
||||
<target>Non è possibile definire un prezzo per le voci BOM (lista dei materiali). Definisci invece i prezzi nella scheda del componente.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ID056SR" name="validator.project_build.lot_bigger_than_needed">
|
||||
<segment state="translated">
|
||||
<source>validator.project_build.lot_bigger_than_needed</source>
|
||||
<target>E' stato selezionato più del necessario per il prelievo. Rimuovere la quantità superflua.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6hV5UqD" name="validator.project_build.lot_smaller_than_needed">
|
||||
<segment state="translated">
|
||||
<source>validator.project_build.lot_smaller_than_needed</source>
|
||||
<target>E' stato selezionato meno del necessario per la costruzione! Aggiungere la quantità necessaria.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="G9ZKt.4" name="part.name.must_match_category_regex">
|
||||
<segment state="translated">
|
||||
<source>part.name.must_match_category_regex</source>
|
||||
<target>Il nome del componente non corrisponde all'espressione regolare specificata dalla categoria: %regex%</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="m8kMFhf" name="validator.attachment.name_not_blank">
|
||||
<segment state="translated">
|
||||
<source>validator.attachment.name_not_blank</source>
|
||||
<target>Seleziona un valore, o carica un file per usare automaticamente il suo nome di file come nome per quell'allegato.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="nwGaNBW" name="validator.part_lot.owner_must_match_storage_location_owner">
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.owner_must_match_storage_location_owner</source>
|
||||
<target>Il proprietario di questo stock di componenti e quello dell'ubicazione scelta devono corrispondere (%owner_name%)!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="HXSz3nQ" name="validator.part_lot.owner_must_not_be_anonymous">
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.owner_must_not_be_anonymous</source>
|
||||
<target>Il proprietario non può essere un utente anonimo!</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -171,7 +171,7 @@
|
||||
<notes>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.noneofitschild.self</source>
|
||||
<target>要素は自身の親とすることはできません。</target>
|
||||
</segment>
|
||||
@@ -180,25 +180,25 @@
|
||||
<notes>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.noneofitschild.children</source>
|
||||
<target>要素は自身の子とすることはできません。</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6vIlN5q" name="validator.part_lot.only_existing">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.only_existing</source>
|
||||
<target>新しい部品を追加できません。保管場所は「既存の部品のみ」とマークされています。</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="R6Ov4Yt" name="validator.part_lot.location_full">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.location_full</source>
|
||||
<target>新しい部品を追加できません。保管場所が満杯とマークされています。</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="BNQk2e7" name="validator.part_lot.single_part">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.single_part</source>
|
||||
<target>新しい部品を追加できません。保管場所は「1つの部品のみ」とマークされています。</target>
|
||||
</segment>
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
<notes>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.noneofitschild.self</source>
|
||||
<target>Элемент не может быть собственным родителем</target>
|
||||
</segment>
|
||||
@@ -180,25 +180,25 @@
|
||||
<notes>
|
||||
<note category="state" priority="1">obsolete</note>
|
||||
</notes>
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.noneofitschild.children</source>
|
||||
<target>Родитель не может быть дочерним по отношению к себе</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="6vIlN5q" name="validator.part_lot.only_existing">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.only_existing</source>
|
||||
<target>Вы не можете добавлять новые компоненты в хранилище которое помечено как "только существующие".</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="R6Ov4Yt" name="validator.part_lot.location_full">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.location_full</source>
|
||||
<target>Вы не можете добавлять новые компоненты в хранилище которое отмечено как "полное".</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="BNQk2e7" name="validator.part_lot.single_part">
|
||||
<segment>
|
||||
<segment state="translated">
|
||||
<source>validator.part_lot.single_part</source>
|
||||
<target>Вы не можете добавлять новые компоненты в хранилище которое отмечено как "единственный компонент".</target>
|
||||
</segment>
|
||||
|
||||
@@ -24,7 +24,7 @@ var Encore = require('@symfony/webpack-encore');
|
||||
const zlib = require('zlib');
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
const CKEditorWebpackPlugin = require( '@ckeditor/ckeditor5-dev-webpack-plugin' );
|
||||
const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' );
|
||||
const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );
|
||||
|
||||
// Manually configure the runtime environment if not already configured yet by the "encore" command.
|
||||
@@ -120,7 +120,7 @@ Encore
|
||||
// uncomment if you're having problems with a jQuery plugin
|
||||
.autoProvidejQuery()
|
||||
|
||||
.addPlugin( new CKEditorWebpackPlugin( {
|
||||
.addPlugin( new CKEditorTranslationsPlugin( {
|
||||
// See https://ckeditor.com/docs/ckeditor5/latest/features/ui-language.html
|
||||
language: 'en',
|
||||
addMainLanguageTranslationsToAllAssets: true,
|
||||
|
||||
Reference in New Issue
Block a user