Compare commits

...

65 Commits

Author SHA1 Message Date
Jan Böhmer
39009a71d5 Bumped version to 1.7.2 2023-09-24 19:54:35 +02:00
Jan Böhmer
0430178fe2 Fixed issue when the data is null. 2023-09-24 15:42:44 +02:00
Jan Böhmer
cf9df883c9 Updated dependencies 2023-09-24 15:36:07 +02:00
Jan Böhmer
198befe2bc Allow to dynamically create elements with purely numeric names in a selector type
Before this was not possible, as this was messed up with the DB ids. Now we prefix the new created values with a special prefix, to mark them as new.

This fixes issue #381
2023-09-24 15:28:35 +02:00
Jan Böhmer
7195bd6cd6 Increased user avatar max file size from 2M to 5M 2023-09-24 14:46:51 +02:00
Jan Böhmer
a5fa2da80c Show the languages from the language selector as preffered in language select on user settings page 2023-09-24 14:45:12 +02:00
Jan Böhmer
593d37f37c Added italien to language selector navbar menu 2023-09-24 14:42:21 +02:00
Jan Böhmer
2ddd6753ca Merge remote-tracking branch 'origin/l10n_master' 2023-09-24 14:40:31 +02:00
Jan Böhmer
9537c4f210 New translations messages.en.xlf (Italian) 2023-09-24 12:50:18 +02:00
Jan Böhmer
e0ce6ba165 New translations messages.en.xlf (Italian) 2023-09-24 11:50:16 +02:00
Jan Böhmer
ee50ce26f8 Merge remote-tracking branch 'origin/master' 2023-09-23 23:08:55 +02:00
Jan Böhmer
94a6de4a90 Fixed wrong literal in italian translation, which caused an exception. 2023-09-23 23:08:46 +02:00
Jan Böhmer
d5902314c3 New Crowdin updates (#378)
* New translations messages.en.xlf (French)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations validators.en.xlf (Italian)

* New translations security.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)
2023-09-23 23:06:06 +02:00
Jan Böhmer
60125534ec New Crowdin updates (#370)
* New translations validators.en.xlf (French)

* New translations messages.en.xlf (German)

* New translations validators.en.xlf (German)

* New translations messages.en.xlf (Italian)

* New translations validators.en.xlf (Italian)

* New translations security.en.xlf (Italian)

* New translations messages.en.xlf (Japanese)

* New translations validators.en.xlf (Japanese)

* New translations messages.en.xlf (Russian)

* New translations validators.en.xlf (Russian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (English)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)
2023-09-18 19:41:01 +02:00
dependabot[bot]
48385cadc9 Bump docker/build-push-action from 4 to 5 (#375)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 19:40:49 +02:00
dependabot[bot]
ba6abe6ca7 Bump docker/setup-buildx-action from 2 to 3 (#374)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 19:40:43 +02:00
dependabot[bot]
79ad243bf4 Bump actions/checkout from 3 to 4 (#361)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 19:40:33 +02:00
dependabot[bot]
5ab21e019d Bump docker/metadata-action from 4 to 5 (#376)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4 to 5.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md)
- [Commits](https://github.com/docker/metadata-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 19:40:23 +02:00
dependabot[bot]
d8469efba2 Bump docker/setup-qemu-action from 2 to 3 (#377)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 19:40:13 +02:00
Jan Böhmer
316b09ddf3 Bumped version to 1.7.1 2023-09-09 23:20:09 +02:00
Jan Böhmer
866ef73774 Upgraded dependencies 2023-09-09 23:19:44 +02:00
Jan Böhmer
138d5c6e0f Merge remote-tracking branch 'origin/l10n_master' 2023-09-09 23:05:29 +02:00
Jan Böhmer
4bed50d894 Allow database migration from legacy versions even if the perms_label column is missing in the groups table
This fixes issue #366 and #67
2023-09-09 23:04:50 +02:00
Jan Böhmer
55943f5d8f Fixed wrong env documentation for TME and digikey provider
This fixes issue #359
2023-08-28 12:08:47 +02:00
Jan Böhmer
133652c296 Fixed PHPstan issues 2023-08-23 22:05:16 +02:00
Jan Böhmer
b9331ac1ef Prevent indexing through search engines, if we are not in demo mode 2023-08-23 22:01:39 +02:00
Jan Böhmer
08f7b2cc87 New translations security.en.xlf (English) 2023-08-23 21:51:37 +02:00
Jan Böhmer
1a2bdaf8e5 New translations validators.en.xlf (English) 2023-08-23 21:51:36 +02:00
Jan Böhmer
d81dec78ae New translations messages.en.xlf (English) 2023-08-23 21:51:35 +02:00
Jan Böhmer
f78bd03521 New translations messages.en.xlf (German) 2023-08-23 21:51:29 +02:00
Jan Böhmer
6aa16272d8 Merge remote-tracking branch 'origin/l10n_master' 2023-08-23 21:24:45 +02:00
Jan Böhmer
e80f7c08ab Bumped version to 1.7.0 2023-08-23 21:24:09 +02:00
Jan Böhmer
675f05f0fb Updated dependencies 2023-08-23 21:23:34 +02:00
Jan Böhmer
b1f23e1684 Added some documentation about the update notification 2023-08-23 21:06:10 +02:00
Jan Böhmer
d612164885 Added that clearing the octopart oauth token is required after changing octopart clientID
See discussion in issue #329. Maybe we will implement a better (more automatic) way to solve this.
2023-08-23 21:00:04 +02:00
Jan Böhmer
b257e1d5f7 New translations messages.en.xlf (German) 2023-08-23 20:51:29 +02:00
Théophile Bornon
f26776ecd5 Fix wrong environment variable name (#355)
For Octopart, the secret must be set inside the PROVIDER_OCTOPART_SECRET instead of PROVIDER_OCTOPART_CLIENT_SECRET
2023-08-23 20:46:26 +02:00
Jan Böhmer
bf4a23652c New translations messages.en.xlf (Chinese Simplified) 2023-08-22 08:00:22 +02:00
Jan Böhmer
e7681aedb1 New translations messages.en.xlf (English) 2023-08-21 23:20:48 +02:00
Jan Böhmer
098fcb29fb Upgraded dependencies 2023-08-21 23:17:42 +02:00
Jan Böhmer
eb46ea19e3 Make update checking mechanism more resilient against connection errors 2023-08-21 23:11:12 +02:00
Jan Böhmer
99ee05a90f Allow to configure update checking utility via env 2023-08-21 22:57:45 +02:00
Jan Böhmer
fd31f983af Fixed positioning of the part row selection checkboxes 2023-08-21 22:52:11 +02:00
Jan Böhmer
80bae4167f Added button to inverse part selection in tables
Fix issue #346
2023-08-21 22:49:02 +02:00
Andy
eaee4af715 Update installation_guide-debian.md (#352)
Fixed a typo: (link to configuration.md)
2023-08-21 17:06:12 +02:00
Jan Böhmer
7d4723c3e4 New translations messages.en.xlf (English) 2023-08-20 23:30:41 +02:00
Jan Böhmer
33a0981981 Added possibility to ignore the checks of withdraw amount when building projects
This fixes #349
2023-08-20 23:23:18 +02:00
Jan Böhmer
b62dc1241d Fix parameter mapping on part creation dialog to fix add builds part
This fixes issue #348
2023-08-20 20:30:38 +02:00
Jan Böhmer
e2270aec38 Upgraded further JS packages 2023-08-20 13:03:09 +02:00
Jan Böhmer
73346fcdaf Upgraded dependencies 2023-08-20 12:51:50 +02:00
Jan Böhmer
7b112512a9 Prevent that an administrator can lockout himself accidentally out of the user interface by using one of the permission presets 2023-08-20 12:42:56 +02:00
Jan Böhmer
0e5613b57b Forbid access to homepage if a user has no allow permission
This allows to block access to everything (even the homepage) for anonymous access. This fixes issue #290
2023-08-20 12:33:08 +02:00
Jan Böhmer
e66ff40733 Use the column order stored in localStorage during the initial datatables ajax call.
This way we still have the right ordering when changing pages. This fixes issue #345
2023-08-20 00:41:44 +02:00
Jan Böhmer
73d61f7440 Fixed PartKeepr import for storagelocation attachments
This should fix issue #334
2023-08-19 23:52:22 +02:00
Jan Böhmer
dedb3071d6 New translations messages.en.xlf (English) 2023-08-05 00:10:52 +02:00
Jan Böhmer
a43ee52086 Fixed static analysis issues 2023-08-05 00:07:42 +02:00
Jan Böhmer
97ccb0cb21 Allow to globally disable update checking/connection with Github 2023-08-04 23:55:41 +02:00
Jan Böhmer
1fb334b0ca Show a notification on homepage and server info page if there is a new version available. 2023-08-04 23:49:26 +02:00
Jan Böhmer
fa4af99525 RELEASE v1.6.1 2023-08-01 16:21:25 +02:00
Jan Böhmer
b3153dac68 Fixed static analysis issue and added test for UserRepository 2023-08-01 16:20:31 +02:00
Jan Böhmer
c981476706 Use proper way of overriding doctrine attributes
In older versions doctrine allowed overriding attributes, by simply redifining them in subclasses. In 2.16 this throws an exception. We now use the proper way using the AttributeOverrides attribute
2023-08-01 15:55:21 +02:00
Jan Böhmer
1a3e5ec705 Updated dependencies
The commit order changes were merged into doctrines main branch, so we can now use the official release instead of the development branch again.
2023-08-01 15:34:37 +02:00
Jan Böhmer
aaff0835a3 Renmed SAMLP_SP_PRIVATE_KEY to SAML_SP_PRIVATE_KEY
Now it matches the documented value and follows the naming schema. The old env name is still valid, to maintain backwards compatibility.

Fixes issue #339
2023-08-01 15:31:40 +02:00
Jan Böhmer
9bf814d4cd Fixed error when StructuralEntityChoice type was used for non structural entities. 2023-08-01 15:14:32 +02:00
Jan Böhmer
b5c0f37f88 Fixed exception on visiting certain group edit pages.
This fixes issue #340
2023-08-01 15:06:57 +02:00
80 changed files with 14607 additions and 1641 deletions

View File

@@ -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
View File

@@ -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..."
######################################################################################

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1 +1 @@
1.6.0
1.7.2

View File

@@ -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();
}
}

View File

@@ -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,
});

View File

@@ -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');
}
});
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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'

View File

@@ -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
######################################################################################################################

View File

@@ -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"

View File

@@ -315,6 +315,10 @@ services:
arguments:
$project_dir: '%kernel.project_dir%'
App\Services\System\UpdateAvailableManager:
arguments:
$check_for_updates: '%partdb.check_for_updates%'
####################################################################################################################
# Monolog
####################################################################################################################

View File

@@ -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`

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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`.

View File

@@ -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
**************************************************************************************************************/

View File

@@ -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",

View File

@@ -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(),
]);
}
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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(),
]);
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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 = '';
/**

View File

@@ -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 = '';
/**

View File

@@ -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.

View File

@@ -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 = '';
/**

View File

@@ -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');
}
}
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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

View File

@@ -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.
*

View 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;
}
}

View File

@@ -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];
}
}

View File

@@ -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;
}

View 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'
];
}
});
}
}

View File

@@ -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()) {

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}

View File

@@ -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>

View 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 %}

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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">

View 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));
}
}

View 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);
}
}

View File

@@ -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));
}
}

View File

@@ -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'));
}
}

View File

@@ -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>

View File

@@ -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 &lt;b&gt;all active two-factor authentication methods of the user&lt;/b&gt; and delete the &lt;b&gt;backup codes&lt;/b&gt;!
&lt;br&gt;
The user will have to set up all two-factor authentication methods again and print new backup codes! &lt;br&gt;&lt;br&gt;
&lt;b&gt;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!&lt;/b&gt;</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>

View File

@@ -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 &lt;a href="%href%" class="link-external" target="_blank"&gt;Github&lt;/a&gt;</target>
</segment>

11627
translations/messages.it.xlf Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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についての質問は、&lt;a href="%href%" class="link-external" target="_blank"&gt;GitHub&lt;/a&gt; にスレッドがあります。</target>
</segment>

View File

@@ -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 в ветке обсуждения на &lt;a href="%href%" class="link-external" target="_blank"&gt;mikrocontroller.net&lt;/a&gt;</target>
</segment>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

1768
yarn.lock

File diff suppressed because it is too large Load Diff