Compare commits

...

305 commits

Author SHA1 Message Date
d-buchmann
c44535990b
Fix typo and copy-paste error (#942)
Some checks failed
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
2025-05-23 18:09:56 +02:00
Jan Böhmer
b8d5b83eee Bumped version 1.17.1
Some checks failed
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
2025-05-18 22:54:26 +02:00
Jan Böhmer
00da2dedc3 Ignore phpstan issue 2025-05-18 22:54:03 +02:00
Jan Böhmer
4ce1de079e Updated dependencies 2025-05-18 22:41:05 +02:00
Jan Böhmer
6b9c125de4 Added console command to sanitize SVG files 2025-05-18 22:38:43 +02:00
Jan Böhmer
2c4f44e808 Sanatize SVG files when uploading 2025-05-18 21:00:19 +02:00
Jan Böhmer
2b694731ad Added content-security policy for SVG files in webserver config 2025-05-18 20:38:53 +02:00
Michael
7e34535e62
Added Datamatrix and C93 label twigs (#931)
Some checks failed
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
* Added Datamatrix and C93 label twigs

* Added new barcode placeholders to ckeditor plugin

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2025-05-11 00:46:38 +02:00
Jan Böhmer
0bb831fe88 Updated dependencies 2025-05-11 00:33:29 +02:00
Jan Böhmer
42a32ce142 Merge remote-tracking branch 'origin/l10n_master' 2025-05-11 00:32:34 +02:00
Jan Böhmer
23f58b7bf4 New translations security.en.xlf (French) 2025-05-02 08:23:16 +02:00
Jan Böhmer
4e9101fded New translations messages.en.xlf (Italian) 2025-03-30 19:01:21 +02:00
Jan Böhmer
9c700c77a8 New translations messages.en.xlf (English) 2025-03-30 17:21:15 +02:00
Jan Böhmer
cb1f674332 Removed now obsolete notice about requiring digikey v3 api in docs.
Some checks failed
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
2025-03-30 16:24:33 +02:00
Jan Böhmer
6823d94ffb New translations messages.en.xlf (English) 2025-03-30 16:21:19 +02:00
Jan Böhmer
60ab992360 Bumped to version 1.17.0 2025-03-30 16:06:47 +02:00
Jan Böhmer
f9e769a6e3 Fixed phpstan issue 2025-03-30 15:01:28 +02:00
Jan Böhmer
f802c6c176 Exclude automigration-backup folder from clean attachments folder 2025-03-30 14:50:52 +02:00
Jan Böhmer
dedadf0c10 Merge remote-tracking branch 'origin/master' 2025-03-30 14:47:53 +02:00
Jan Böhmer
c8375def1a Added an database automigration feature to the docker image 2025-03-30 14:47:48 +02:00
Jan Böhmer
62ebcde2de
New Crowdin updates (#899)
* New translations messages.en.xlf (English)

* New translations messages.en.xlf (German)
2025-03-30 14:23:21 +02:00
Jan Böhmer
594a5779dc Specify that we mean a column in drop statement. This is more correct
Some checks are pending
Build assets artifact / Build assets artifact (push) Waiting to run
Docker Image Build / docker (push) Waiting to run
Docker Image Build (FrankenPHP) / docker (push) Waiting to run
Static analysis / Static analysis (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Waiting to run
This should help with more strict sql servers like in issue #900
2025-03-29 20:57:58 +01:00
Jan Böhmer
c0ef64fb64 Use updated version of translation-editor-bundle 2025-03-29 16:24:32 +01:00
Jan Böhmer
48c70c3bb4 Added way to batch edit the location of parts with a single stock 2025-03-29 16:21:10 +01:00
Jan Böhmer
68124a340b Updated dependencies
Some checks are pending
Build assets artifact / Build assets artifact (push) Waiting to run
Docker Image Build / docker (push) Waiting to run
Docker Image Build (FrankenPHP) / docker (push) Waiting to run
Static analysis / Static analysis (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Waiting to run
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Waiting to run
2025-03-29 13:45:53 +01:00
Jan Böhmer
0b5003fcf6 We are in development of 1.17 2025-03-29 13:41:30 +01:00
Jan Böhmer
956ece60af Added documentation for attachments download command 2025-03-29 13:35:29 +01:00
Jan Böhmer
53da45d7d7 Added command to download all external-only attachments to the local file system 2025-03-29 13:33:35 +01:00
Jan Böhmer
57f0432a87 Fixed typo in attachmentrepository 2025-03-29 12:52:43 +01:00
Jan Böhmer
fb535ec6f7 Added tests for latex formatted units 2025-03-29 12:37:17 +01:00
Jan Böhmer
4e1b1a4ffa Render units of parameters in upstanding latex
Fixes issue #856
2025-03-29 12:33:18 +01:00
Jan Böhmer
5b111d80f1 Invalidate kicad category cache, when parts get changed, as this might affect the visibility of categories too
Related to #885
2025-03-29 12:01:26 +01:00
Jan Böhmer
03e1105a8e Fixed phpstan issues
Some checks failed
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
2025-03-27 23:11:49 +01:00
Jan Böhmer
059a9683db Fixed problem that global_theme setting was not respected
This fixes issue #880
2025-03-27 21:47:52 +01:00
Jan Böhmer
1daf6f01f4 Fixed error 500 if internal attachment path was not resolvable to an URL
This fixes issue #898
2025-03-27 21:40:51 +01:00
Daniel Carrasco
d3b225771c
Modified the DigiKey Provider to works with the V4 API (#875)
* Modified the DigiKey Provider to works with the V4 API

* Correclty apply the MarketPlaceFilter option to digikey v4 API

* Show the packe type (Tape&Reel, Box, etc.) as footprint in digikey provider search

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2025-03-27 21:26:18 +01:00
Jan Böhmer
7275db27e7 Manually filter mouser search results to fix the edgecase, that the API returned multiple results for an exact part number
This fixes issue #888 and issue #616
2025-03-27 21:06:50 +01:00
Jan Böhmer
49ee9131d0 Use composer/ca-bundle instead of system CA for element14 provider
This is a workaround for debian systems, where the required root CA is missing as trusted CA in the system CAs. This fixes issue #891 and #866
2025-03-27 20:59:22 +01:00
Jan Böhmer
e75e0c4c0b Add a link to the category part info as category description in KiCAD.
This also fixes issue #878
2025-03-27 20:34:32 +01:00
Jan Böhmer
e94d4a7752 Merge remote-tracking branch 'origin/l10n_master'
Some checks failed
Static analysis / Static analysis (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, mysql) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, postgres) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.1, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.2, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.3, sqlite) (push) Has been cancelled
PHPUnit Tests / PHPUnit and coverage Test (PHP 8.4, sqlite) (push) Has been cancelled
Build assets artifact / Build assets artifact (push) Has been cancelled
Docker Image Build / docker (push) Has been cancelled
Docker Image Build (FrankenPHP) / docker (push) Has been cancelled
2025-03-23 19:52:02 +01:00
Jan Böhmer
95206f427d We are in development of 1.16.2-dev now 2025-03-23 19:51:58 +01:00
Jan Böhmer
2d7f88522a Improved attachment download compatibility
Added more headers to make it appear more like a browser request, and try to use TLS1.3, if we get a 403 (which is useful for digikey).

Commit cherry picked from @Treeed
2025-03-23 19:51:21 +01:00
Jan Böhmer
f5c17bc7c8 Fixed Pollin provider exception, if product top features panel does not exist 2025-03-23 19:43:28 +01:00
Jan Böhmer
63e222ed40 Upgraded dependencies 2025-03-23 19:41:40 +01:00
Jan Böhmer
6963ee3b8d New translations messages.en.xlf (Spanish) 2025-03-11 14:02:14 +01:00
Jan Böhmer
bb5e42bf63 New translations security.en.xlf (Spanish) 2025-03-11 14:02:10 +01:00
Jan Böhmer
c48f778648
Update console_commands.md 2025-03-07 11:30:50 +01:00
Jan Böhmer
616aad6403
Added hint to docker install docs, that you need to run sudo with -E command
That should prevent isssues like in #882 and #871
2025-03-07 11:29:58 +01:00
Jan Böhmer
bcc7547d6f
New Crowdin updates (#874)
* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (English)
2025-03-02 21:14:42 +01:00
Jan Böhmer
5a1a6e9217
Bumped version 1.16.1 2025-02-26 12:28:07 +01:00
Jan Böhmer
eae1fcecab
New translations security.en.xlf (Chinese Simplified) (#870) 2025-02-26 12:27:11 +01:00
Marc
b53989bb9d
Update LCSCProvider.php: fix error in query string (#873)
Fix typo in query string 'prodctCode' -> 'productCode' introduced in Commit 80527e3
2025-02-25 13:32:36 +01:00
Jan Böhmer
467d50bd31 Use slightly different migration method for attachment system migration
This maybe help with issue #871
2025-02-25 00:32:04 +01:00
Jan Böhmer
1935258978 Fixed phpstan issue 2025-02-23 22:55:59 +01:00
Jan Böhmer
2b5030c69f Bumped version to 1.16.0 2025-02-23 22:40:02 +01:00
Jan Böhmer
6537502696
New Crowdin updates (#867)
* New translations messages.en.xlf (English)

* New translations messages.en.xlf (German)
2025-02-23 17:30:33 +01:00
Jan Böhmer
0ba352ab0b Updated composer dependencies 2025-02-23 17:28:39 +01:00
Jan Böhmer
5d3f861728 Use newer version of farnell/element14 api to get the correct links to product pages. Also we can now retrieve a more detailed description, which will be put into the notes field 2025-02-22 23:29:57 +01:00
Jan Böhmer
319b69f6c7 Added an workaround for issue #862 2025-02-22 22:59:55 +01:00
Jan Böhmer
c4ba28e3a0 Heavily refactored the property metadata attribute logic
The new method is much more universal and fixes issue #862
2025-02-22 22:19:38 +01:00
Jan Böhmer
b38ef8ecea Revert "Fixed type error introduced with api-platform upgrade"
This reverts commit a54c2db9b9.
2025-02-22 21:12:58 +01:00
Jan Böhmer
cb0817666d Revert "Use the modular api-platform packages instead of the monolitic api-platform/core package"
This reverts commit 17caf476bf.
2025-02-22 21:12:54 +01:00
Jan Böhmer
a54c2db9b9 Fixed type error introduced with api-platform upgrade 2025-02-22 19:59:12 +01:00
Jan Böhmer
17caf476bf Use the modular api-platform packages instead of the monolitic api-platform/core package 2025-02-22 19:48:36 +01:00
Jan Böhmer
42cb590c75 Fixed deprecations with api platform 2025-02-22 19:35:49 +01:00
Jan Böhmer
6fd05e1456 Fixed migration 2025-02-22 19:23:28 +01:00
Jan Böhmer
bec45d60e5 Fixed migration for postgresql 2025-02-22 18:03:03 +01:00
Jan Böhmer
019e67a676 Migrate legacy attachment discriminator class values to modern format, so that we can make the discriminator map unique and fix a deprecation with doctrine 2025-02-22 17:58:20 +01:00
Jan Böhmer
f146d88aa5 Added additional filters to attachment datatable 2025-02-22 17:48:26 +01:00
Jan Böhmer
48be9a8098 Made attachment datatable sortable by internal filename and external url 2025-02-22 17:41:41 +01:00
Treeed
29f92d9bd3
Split attachment paths (#848)
* fixed attachment statistics for sqlite

* Split attachment path into internal and external path, so the external source URL can be retained after a file is downloaded

* Make internal and external path for attachments nullable, to make clear that they have no internal or external path

* Added migrations for nullable columns for postgres and mysql

* Added migration for nullable internal and external pathes for sqlite

* Added translations

* Fixed upload error

* Restrict length of filename badge in attachment edit view

* Improved margins with badges in attachment edit

* Added a link to view external version from attachment edit

* Let media_url  stay in API attachments responses for backward compatibility

---------

Co-authored-by: jona <a@b.c>
Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2025-02-22 17:29:14 +01:00
Jan Böhmer
ebb977e99f Updated dependencies 2025-02-20 22:10:57 +01:00
Jan Böhmer
94bcd3d2d3 Fixed static analysis issue 2025-02-20 20:35:00 +01:00
Jan Böhmer
a9bd4c5606 Use better parameter value parsing for pollin and reichelt parameters 2025-02-20 19:45:05 +01:00
Jan Böhmer
8d9dde0032 Show the desired part, when searching for the pollin part number 2025-02-20 19:39:35 +01:00
Jan Böhmer
b2d4333fff Merge branch 'pollin_provider' 2025-02-20 19:36:36 +01:00
Jan Böhmer
afd5e2b95b Added documentation for pollin info provider 2025-02-20 19:36:31 +01:00
Jan Böhmer
085adf8d71 Throw an exception message, when trying to use an info provider which is not active 2025-02-20 19:35:16 +01:00
Jan Böhmer
9b35b60d61 Use correct price for one unit if there are multiple prices availble
We can still not parse the bulk prices correctly completly
2025-02-20 19:32:31 +01:00
Jan Böhmer
742e57cc5c Added basic provider for pollin 2025-02-20 19:20:14 +01:00
Jan Böhmer
a5961668fe Disable info provider result caching when in debug mode 2025-02-20 16:59:16 +01:00
Jan Böhmer
e7394c165a Undo change to cache expiration 2025-02-20 16:57:16 +01:00
Jan Böhmer
7be966122f Added missing info hint how to enable reichelt provider 2025-02-20 16:56:21 +01:00
Jan Böhmer
d176b68fd2 Merge branch 'reichelt_provider' 2025-02-20 16:46:58 +01:00
Jan Böhmer
1e80be1376 Added documentation for reichelt info provider 2025-02-20 16:46:47 +01:00
Jan Böhmer
3585b08d4b Pass reichelt env through docker container 2025-02-20 16:38:07 +01:00
Jan Böhmer
c51e0eb68f Allow to get prices in non-EUR currencies from reichelt 2025-02-20 16:37:13 +01:00
Jan Böhmer
d05c0579a2 Fixed problem that first batch price was not included 2025-02-20 16:33:37 +01:00
Jan Böhmer
5e40519bc5 Allow to select if VAT should be included or not 2025-02-20 16:29:37 +01:00
Jan Böhmer
d13752114c Allow to configure the reichelt info provider via env vars 2025-02-20 16:06:10 +01:00
Jan Böhmer
90e1b809fe Allow to change language and country 2025-02-20 15:59:35 +01:00
Jan Böhmer
32b4e6812d Undo change to cache expiration 2025-02-20 15:40:26 +01:00
Jan Böhmer
a798aa9c24 Allow to extract MPN from reichelt provider 2025-02-20 15:39:35 +01:00
Jan Böhmer
f1c28b9f46 Allow to parse batch prices 2025-02-20 15:14:59 +01:00
Jan Böhmer
39bc400376 Added basic price info retrieval from reichelt 2025-02-20 00:32:03 +01:00
Jan Böhmer
e287918121 Extract category from reichelt provider 2025-02-20 00:24:46 +01:00
Jan Böhmer
e0bf8e5fbc Allow reichelt provider to parse parameters 2025-02-20 00:18:05 +01:00
Jan Böhmer
376c7e7a6f Allow to parse ranges for ParameterDTO which just contain two dots 2025-02-20 00:17:53 +01:00
Jan Böhmer
5612a790fb Added basic way to retrieve simple part infos and datasheet 2025-02-19 23:55:58 +01:00
Jan Böhmer
80527e35c3 Added basic reichelt search capabilities 2025-02-19 00:44:49 +01:00
Jan Böhmer
f592ab6395 Updated KiCAD library lists to latest KICAD lib 2025-02-16 21:48:30 +01:00
Jan Böhmer
d7c741c652 Disable create option for input selects if an entity with this name already exists 2025-02-16 21:14:57 +01:00
Jan Böhmer
9502f30e1b Add cllear button to entity select type 2025-02-16 20:36:46 +01:00
Jan Böhmer
7286c4bbef Fixed bug in autoselect_typed plugin 2025-02-16 20:24:13 +01:00
Jan Böhmer
a976f97dbb Apply click_to_edit plugin where reasonable 2025-02-16 20:24:01 +01:00
Jan Böhmer
2c9b8c7dea Merge remote-tracking branch 'origin/l10n_master' 2025-02-16 19:48:57 +01:00
Jan Böhmer
787decf4e2 Updated dependencies 2025-02-16 19:48:51 +01:00
Treeed
2fc70b8bdd
Highlightable attachment paths (#849)
* made autocomplete controller allow selecting text and autocommit typed text on blur

* moved click_to_edit and autoselect_typed into separate plugins

---------

Co-authored-by: jona <a@b.c>
2025-02-16 19:46:29 +01:00
Jan Böhmer
64491a9772 New translations security.en.xlf (Dutch) 2025-02-10 17:11:09 +01:00
Jan Böhmer
b724b05de6 Fixed "implicitly marking parameter as nullable" deprecations in PHP 8.4 fixed 2025-02-08 00:19:20 +01:00
Marc
d94c4af1be
Document that only Digikey API v3 is supported (#858) 2025-02-04 21:41:03 +01:00
Nico Felbinger
8f0f5a5eb4
Fix postgresl unix socket example (#852)
* Fix postgresl unix socket example

* Fix user defaulting to root
2025-02-02 21:09:10 +01:00
Treeed
edf50a71d1
fixed attachment statistics for sqlite (#847)
Co-authored-by: jona <a@b.c>
2025-02-02 21:05:47 +01:00
Jan Böhmer
d0937218b9 Bumped version to 1.15.2 2025-02-01 23:47:14 +01:00
Jan Böhmer
3247a97217 Removed "All" page length option for log tables, as this easily causes an out of memory situation on the server
You can easily have thousands of log entries, making it impossible to view them all at once.

This fixes #835
2025-02-01 23:44:07 +01:00
Jan Böhmer
edd254ee06 Merge remote-tracking branch 'origin/l10n_master' 2025-02-01 23:14:50 +01:00
Jan Böhmer
42ecb83155 Rename duplicate parameters and attachments when importing from an info provider
This fixes issue #840
2025-02-01 23:14:42 +01:00
Jan Böhmer
56f801c058 New translations messages.en.xlf (English) 2025-02-01 21:30:23 +01:00
Marc
2d3d05e956
Added character symbols for degree and registered in keybindings.md (#845) 2025-02-01 21:02:02 +01:00
Marc
4321e51bf5
Update messages.de.xlf (#841)
Removed "es" in german translation of string tfa_trustedDevices.explanation
2025-02-01 20:58:55 +01:00
Jan Böhmer
be04730906
New Crowdin updates (#837)
* New translations security.en.xlf (Russian)

* New translations validators.en.xlf (Russian)

* New translations messages.en.xlf (Russian)

* New translations messages.en.xlf (English)

* New translations messages.en.xlf (Italian)
2025-02-01 20:58:35 +01:00
Jan Böhmer
aa06e1df04 Fixed exception when trying to export an empty entity list
Fixes issue #836
2025-02-01 20:57:27 +01:00
Jan Böhmer
fd7a0156bc Disable GET_GROSS_PRICES option for TME info provider when using private key. Otherwise we receive an error.
This fixes issue #838
2025-02-01 20:20:33 +01:00
Jan Böhmer
1e19ff24ba Updated dependencies 2025-02-01 20:01:33 +01:00
Jan Böhmer
0f4238291f Bumped version to 1.15.1 2025-01-07 21:56:13 +01:00
Jan Böhmer
03a2a10efd Updated dependencies 2025-01-07 16:51:34 +01:00
Jan Böhmer
04310aa2f8 Removed WebpackAutoPathSubscriber, as it seems it not necessary anymore with encore 5 2025-01-07 16:39:51 +01:00
Jan Böhmer
e8ca11a5cf Do not use a slash prefix for .setPublicPath() in webpack config. Webpack encore 5 seemed to have changed something, so that it does not get overriden anymore
We just get a warning, which we can ignore.
2025-01-07 16:14:21 +01:00
Jan Böhmer
24137b30a5 Added tests for RedirectController that simulates the situation of a reverse proxy and subdirectory 2025-01-07 15:52:28 +01:00
Jan Böhmer
4421917333 Revert "Removed baarcode scanner inbox, as we now have enough performance to apply the decoder to the full image"
This reverts commit de0832bece.
2025-01-06 00:49:16 +01:00
Jan Böhmer
25c8660c2e Bumped version to 1.15.0 2025-01-06 00:31:19 +01:00
Jan Böhmer
190e87390d
New Crowdin updates (#804)
* New translations security.en.xlf (Italian)

* New translations messages.en.xlf (German)

* New translations security.en.xlf (German)
2025-01-06 00:29:40 +01:00
Jan Böhmer
c9aefdd862 Start with an empty selection of category when a new part is created, so that the user has to manually select it 2025-01-06 00:29:26 +01:00
Jan Böhmer
3ad088663f Fixed phpstan issue 2025-01-06 00:21:04 +01:00
Jan Böhmer
a29e87e5ac Fixed phpunit tests 2025-01-06 00:19:31 +01:00
Jan Böhmer
de0832bece Removed baarcode scanner inbox, as we now have enough performance to apply the decoder to the full image 2025-01-06 00:08:42 +01:00
Jan Böhmer
614697ba84 Use zxing-wasm polyfill even if native barcode detector API is available as the polyfill is sometimes better than the native on
See discussion #808 for more infos
2025-01-05 23:43:17 +01:00
Jan Böhmer
6bdf3d891a Include the JSON encoded response, when throwing an unknown response format in mouser provider
Should help to debug issue #820
2025-01-05 22:15:12 +01:00
Jan Böhmer
f75704f77c Allow users to save a new label profiles directly from the label generator dialog
This fixes issue #806
2025-01-05 22:00:07 +01:00
Jan Böhmer
9d09543eb9 Added a button to quickly go to all label profiles editor
Related to issue #806
2025-01-05 17:50:15 +01:00
Jan Böhmer
a6116398a8 Only run PartDenormalizer when importing files, otherwise it causes problems with API platform
This fixes issue #800
2025-01-05 17:37:02 +01:00
Jan Böhmer
39763b84d5 Updated dependencies 2025-01-05 15:37:21 +01:00
Jan Böhmer
8502df08fa Added test for autodetection of EIGP114 barcodes 2025-01-04 18:43:50 +01:00
Jan Böhmer
bf2a776403 Do the EIGP114 detection after userdefined barcode detection, so that users can override it on their partLots 2025-01-04 18:41:27 +01:00
Jan Böhmer
052190c69b Do not trim user_barcode for partLots 2025-01-04 18:40:18 +01:00
Jan Böhmer
8826ba6729 Fixed CSP rules to allow the WASM loading for the barcode scanner 2025-01-04 18:23:57 +01:00
Jan Böhmer
39b5240934 We are in development of version 1.15.0 2025-01-04 18:14:15 +01:00
Jan Böhmer
ddc1c286d9 Merge branch 'detector-api-polyfill' 2025-01-04 18:10:36 +01:00
Jan Böhmer
22fba37d28 Pause the camera and scanner, when navigating away from the scanner page, so that the camera feed is closed 2025-01-04 18:10:26 +01:00
Jan Böhmer
0c627a5636 Use forked version of html5-qrcode to avoid including zxing-js which will never be used then 2025-01-04 18:09:56 +01:00
Jan Böhmer
53dcd24216 Improved scanning FPS to 10 2025-01-04 16:57:41 +01:00
Jan Böhmer
4b09a321ad Polyfill BarcodeDetector API for improved performance of Html5QRcodeScanner 2025-01-04 16:49:17 +01:00
Treeed
9e85b70c17
Added capability to scan Digikey barcodes and open the local part part page based on the result (#811)
* added capability to scan digikey barcodes and open the local part page based on the digikey part number or manufacturer part number

* had replaced one too many doublequotes

* Generalized interpretation of format06 barcodes, added ids for mouser

* Renamed vendor_barcode to user_barcode in entities

* Added a own class to parse EIGP114 barcodes

* Added tests to EIGP114Barcode parser

* Refactored code

* Changed BarcodeRedirector to support the new Barcode EIGP114BarcodeScanResult class

* Added possibility to just show all information contained in a barcode

* Dont require trailer for EIGP114 barcodes, as digikey does not seem to put them onto their  barcodes

* Fixed inspection issues

---------

Co-authored-by: jona <a@b.c>
Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2025-01-04 01:20:51 +01:00
Jan Böhmer
9c99217dee Fixed test errors 2025-01-03 20:21:17 +01:00
Jan Böhmer
afc1dbdd4b Updated recipe for symfony flex 2025-01-03 17:45:13 +01:00
Jan Böhmer
20f58fc07d Updated symfony cli recipe
This adds an error message if no composer dependencies are installed
2025-01-03 17:42:49 +01:00
Jan Böhmer
e6b78dd213 Updated dependencies 2025-01-03 17:41:37 +01:00
Jan Böhmer
63893ffabe Allow to automatically map categories from info providers to local categories using the "alternative names" system
This partially addresses issues discussed in discussion #808
2024-12-31 18:31:20 +01:00
Jan Böhmer
c9e519d0b5 Show a warning flash, when creating a part from an info provider that has no category autoprovided
This partially fixes the problems described in discussion #808
2024-12-31 18:25:28 +01:00
Jan Böhmer
273bde90f2 Use the providerID as keyword when searching infos for a part with already existing providerreference and preselect the info provider if possible 2024-12-31 18:13:15 +01:00
Treeed
92e4976396
Show when parts from info provider already exist (#810)
* added button to show existing part with same manufacturer and mpn in provider list

* added button to edit existing part in provider list

* added docstring and comments

* replaced unnecessary double quotes

* Introduced a new twig variable localPart to split up the result

* Highlight a row, if the part is already existing

* Made buttons translatable

* Improved styling of the buttons and added a badge to show a hint

* Extracted database queries for part matching into its own service and optimized the query reducing the required queries by factor 2

* Allow to find existing parts via the stored providerReference

This should allow the database to more quickly find entries

* Allow to use part name and manufacturer alternative names for mapping

* Added a button to update a local part from the info provider and moved some buttons into dropdown menu

---------

Co-authored-by: jona <a@b.c>
Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2024-12-31 18:03:36 +01:00
Jan Böhmer
e9efbff912 Upgraded typescript version 2024-12-29 20:15:05 +01:00
Jan Böhmer
41089c08f8 Upgraded exports-loader and webpack-cli 2024-12-29 20:08:59 +01:00
Jan Böhmer
9e23e606f8 Updated marked and compression-webpack plugin 2024-12-29 19:59:31 +01:00
Jan Böhmer
b3f0fd368a Updated webpack encore to 5.0.0 2024-12-29 19:50:02 +01:00
Jan Böhmer
12bd5472e2 Fixed sizing of text in label editor input 2024-12-29 19:47:19 +01:00
Jan Böhmer
ef64779759 Updated CKeditor5 to v41 2024-12-29 19:31:04 +01:00
Jan Böhmer
b3d8076ddf Fixed fixture loading process 2024-12-29 19:06:58 +01:00
Jan Böhmer
f775203608 Use new syntax for getReference() in data fixtures 2024-12-29 13:46:22 +01:00
Jan Böhmer
a6083688e4 Upgraded frontend dependencies 2024-12-29 13:43:32 +01:00
Jan Böhmer
50689cd4e6 Updated datatables-bundle 2024-12-29 13:41:30 +01:00
Jan Böhmer
255fcbac1c Removed direct dependency to phpstan-phpdoc-parser, so that it can get removed when updating the webauthn bundle 2024-12-29 13:38:35 +01:00
Jan Böhmer
7f8ffa56e5 Removed (hopefully) unused and unnecessary dependencies 2024-12-29 13:34:10 +01:00
Jan Böhmer
9a2a5f30a3 Updated doctrine fixtures bundle 2024-12-29 13:20:12 +01:00
Jan Böhmer
a9f444cbb4 Added return types to ExponentialNumberTypes for futureproofing the implementation 2024-12-29 13:14:26 +01:00
Jan Böhmer
164efb0551 Fixed phpstan error with NodesListBuilder 2024-12-29 13:14:00 +01:00
Jan Böhmer
a37b8cbb15 Fixed introduced bug in node flattening 2024-12-28 23:19:55 +01:00
Jan Böhmer
946032a101 Fixed phpstan analysis issues and bad code that showed up with phpstan 2.0 2024-12-28 23:08:08 +01:00
Jan Böhmer
a273acbecd Updated PHPstan configuration 2024-12-28 15:12:18 +01:00
Jan Böhmer
0ceee1582e Updated phpstan to 2.0 2024-12-28 14:56:44 +01:00
Jan Böhmer
04a0369d56 Added proxmox LXC installation method to docs 2024-12-25 19:21:30 +01:00
Jan Böhmer
31a288b44d Added recommendation on installation methods 2024-12-25 19:13:28 +01:00
Jan Böhmer
502dc3aa1c Added kubernetes as available installation method. 2024-12-25 19:12:09 +01:00
Jan Böhmer
6874d7ca55 Upgraded dependencies 2024-12-23 20:34:42 +01:00
Jan Böhmer
da8f669aed Test for PHP 8.4 2024-12-23 20:26:31 +01:00
Vedran Vekic
0f92a69b03
Rewrite LCSC datasheet URL (#795) 2024-12-10 14:32:23 +01:00
Bryce Nagaj
8faa3251c4
Update verbiage (#788) 2024-12-10 14:30:47 +01:00
Marc
56fc14003c
Update information_provider_system.md (#791)
Fixed minor typo in Octopart variable: PROVIDER_OCOTPART_COUNTRY
2024-12-10 14:30:16 +01:00
Jan Böhmer
b3499e4ea5
Fixed (wrongly) failing test on IPAnonymizer 2024-12-02 11:01:39 +01:00
Jan Böhmer
07b1ff9bf5 Bumped version to 1.14.5 2024-12-02 01:00:35 +01:00
Jan Böhmer
5bbf24c92e
New translations security.en.xlf (English) (#786) 2024-12-02 00:22:45 +01:00
Jan Böhmer
eea8b3e679 Merge remote-tracking branch 'origin/master' 2024-12-02 00:22:20 +01:00
Jan Böhmer
e223078af9 Added a custom function to make PostgresSQL searches case insensitive
This is required only for postgres as every other database is case invariant by default. But to achieve a portable way, we implement it via a custom DQL function.

This fixes issue #784
2024-12-02 00:17:54 +01:00
dependabot[bot]
b554d0d851
Bump codecov/codecov-action from 4 to 5 (#774)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: codecov/codecov-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>
2024-12-01 23:16:37 +01:00
Jan Böhmer
b1ba26e0b9 Allow usernames to contain @-signs as long as it is not at the begininng
This allows to use email addresses as usernames and fixes issue #772
2024-12-01 23:05:27 +01:00
Jan Böhmer
ca8ad760d7 Allow SAML users to access the API
This fixes issue #765.
2024-12-01 22:54:22 +01:00
Jan Böhmer
80129c0a88 Use PHP 8.3 as docker image, and allow for longer error log lines
This fixes issue #781
2024-12-01 22:34:05 +01:00
Jan Böhmer
7530e62dfa Do not format error messages as JSON, this makes log files easier to read and reduces line length which can makes problems on FPM
See issue #781
2024-12-01 22:33:32 +01:00
Jan Böhmer
baf8977578 Correctly handle IP addresses containing RFC 4007 scoping 2024-12-01 19:19:04 +01:00
Jan Böhmer
c7bf843312 Updated dependencies 2024-12-01 18:48:22 +01:00
Jan Böhmer
ce6fee1682 Merge remote-tracking branch 'origin/dependabot/composer/symfony/http-client-6.4.15' 2024-11-18 15:43:25 +01:00
Jan Böhmer
2653fad488 Merge remote-tracking branch 'origin/dependabot/composer/symfony/security-http-6.4.15' 2024-11-18 15:43:16 +01:00
Jan Böhmer
dd54c46a29 Merge remote-tracking branch 'origin/l10n_master' 2024-11-18 15:43:10 +01:00
dependabot[bot]
724a0e21d3
Bump symfony/security-http from 6.4.14 to 6.4.15
Bumps [symfony/security-http](https://github.com/symfony/security-http) from 6.4.14 to 6.4.15.
- [Release notes](https://github.com/symfony/security-http/releases)
- [Changelog](https://github.com/symfony/security-http/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/security-http/compare/v6.4.14...v6.4.15)

---
updated-dependencies:
- dependency-name: symfony/security-http
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-13 18:39:42 +00:00
dependabot[bot]
578277d11f
Bump symfony/http-client from 6.4.14 to 6.4.15
Bumps [symfony/http-client](https://github.com/symfony/http-client) from 6.4.14 to 6.4.15.
- [Release notes](https://github.com/symfony/http-client/releases)
- [Changelog](https://github.com/symfony/http-client/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-client/compare/v6.4.14...v6.4.15)

---
updated-dependencies:
- dependency-name: symfony/http-client
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-13 16:49:40 +00:00
Jan Böhmer
22258e3183 New translations validators.en.xlf (Croatian) 2024-11-10 14:30:20 +01:00
Jan Böhmer
0234463b68 Bumped version to 1.14.4 2024-11-08 23:32:34 +01:00
Jan Böhmer
ef412eef92 Fixed tests 2024-11-08 23:32:14 +01:00
Marc
76ebd22eab
Fixed Typos and mistranslations in GDPR mode (DSGVO Modus) (#757)
Fixed Typo enviroment

Co-authored-by: Marc Kreidler <kreidler@nedo.com>
2024-11-08 23:22:18 +01:00
Jan Böhmer
5b0ca8e346 Updated dependencies 2024-11-08 23:15:40 +01:00
Jan Böhmer
0b6b10c27b Bumped version to 1.14.3 2024-11-05 00:11:39 +01:00
Jan Böhmer
6225d2c9b3 Implemented an easy to use APIFilter for tags
This makes the process of filters more easily and intuitive. This fixes issue #750
2024-11-05 00:07:25 +01:00
Jan Böhmer
01fc6524a4 Added a aggregate function for storelocation sorting to avoid exceptions on Postgres
Actually this was not good on other DB types too, but they just ignored the problems.

This fixes issue #734
2024-11-04 23:46:45 +01:00
Jan Böhmer
2575e6a160 Improved size and position of back to top button to avoid overlapping with buttons, etc.
This should fix issue #737
2024-11-03 23:58:06 +01:00
Jan Böhmer
484ba5ebd7 If user password set command is run in non-interactive mode, show a warning message if no password is inputted
Related to issue #748
2024-11-03 23:39:04 +01:00
Jan Böhmer
b42d98e9f8 Increase font-weight of the <dl> element in part info page to match its look to the <h5> element 2024-11-03 23:21:58 +01:00
Sam Edwards
65b2f045ac
Responsive tweaks (#755)
* Change datatables markup to be responsive with BS5

* Responsive tweaks to single part info
2024-11-03 23:14:52 +01:00
Jan Böhmer
5e76451d46 Try to guess the character encodings and convert it to UTF-8 on importing
This should fix issue #749
2024-11-03 22:27:24 +01:00
Jan Böhmer
a873ad3316 Replace all unicode characters with ASCII chars in FilenameSanatizer to make filenames more sanatized 2024-11-03 22:06:42 +01:00
Jan Böhmer
b1e03f49ee Pass the docker envs for oemsecrets to Part-DB
This fixes issue #742
2024-11-03 21:46:53 +01:00
Jan Böhmer
011e23f8e6 Added polish to language selector 2024-11-03 20:04:43 +01:00
Jan Böhmer
646cd8cf22 Merge remote-tracking branch 'origin/l10n_master' 2024-11-03 20:03:28 +01:00
Jan Böhmer
52ac8a70d5 Updated dependencies 2024-11-03 20:03:24 +01:00
Jan Böhmer
e020334b73 New translations messages.en.xlf (Polish) 2024-10-19 22:40:21 +02:00
Jan Böhmer
7698e83f0b New translations messages.en.xlf (Polish) 2024-10-19 21:40:19 +02:00
Jan Böhmer
dd56f5e0c8 New translations messages.en.xlf (Polish) 2024-10-19 19:30:24 +02:00
Jan Böhmer
92c32eef74
New Crowdin updates (#738)
* New translations validators.en.xlf (Polish)

* New translations messages.en.xlf (Polish)
2024-10-19 19:24:55 +02:00
Jan Böhmer
08770c7dc5 Bumped version to 1.14.2 2024-10-17 00:20:48 +02:00
Priit Laes
808a94e4df
Document APP_SECRET and PostgreSQL specific bits in configuration variables (#727)
* docs: Mention APP_SECRET

* docs: Add PostgreSQL specific bits to DATABASE_URL description
2024-10-16 23:59:53 +02:00
Jan Böhmer
490086d531 Use the same translation for the panel with enabled search options, like in the checkbox options in navbar 2024-10-16 23:59:23 +02:00
Jan Böhmer
2ef3fbb81b Merge remote-tracking branch 'origin/l10n_master' 2024-10-16 23:57:07 +02:00
Jan Böhmer
7d834ac8d7 Include the query part of the request, when generating the url for the datatables via a custom twig function.
This fixes issue #735, as without this the query gets not passed to the datatable
2024-10-16 23:57:02 +02:00
Jan Böhmer
15ad0ec9c0 Updated dependencies 2024-10-16 23:40:48 +02:00
Jan Böhmer
f0b78e8b2c New translations validators.en.xlf (Italian) 2024-10-16 13:50:37 +02:00
Jan Böhmer
e616faa76f New translations messages.en.xlf (Italian) 2024-10-16 13:50:36 +02:00
Jan Böhmer
8159f4d8ee Bumped version to 1.14.1 2024-10-13 23:43:55 +02:00
Jan Böhmer
021c576468 Exclude the translation dumper fix files, to avoid phpstan issues, which we cannot control 2024-10-13 23:23:48 +02:00
Jan Böhmer
1b2339a82c Merge remote-tracking branch 'origin/l10n_master' 2024-10-13 23:22:24 +02:00
Jan Böhmer
2b6bb3f773 New translations messages.en.xlf (German) 2024-10-13 23:20:14 +02:00
Jan Böhmer
abc5c61a06 Fixed problem, that search field and search options did not close when clicking outside
This fixes issue #701. For the search field this was caused by algolia/autocomplete lib, which do not support multiple autocomplete fields on a single page. If initailly loaded on the homepage, which features a second autocomplete, this one "steals" the input listening, and the one in the navbar do not close anymore when clicking outside.
Custom code which triggers the closing of the autocomplete manually when clicking outside, was added as a workaround.
2024-10-13 23:19:03 +02:00
Jan Böhmer
7145bce605 Construct the correct current path, when serving from a subdirectory
This fixes issue #274
2024-10-13 22:49:42 +02:00
Jan Böhmer
bb92e5e9ee New translations validators.en.xlf (German) 2024-10-13 21:40:20 +02:00
Jan Böhmer
0c47aa226c Fixed imports of parameters on parts
It was missing the required serialization group. This fixes issue #718
2024-10-13 21:35:31 +02:00
Jan Böhmer
76e945bbbd Fixed issue that the document could not be scrolled anymore, when redirected from a modal
This fixes issue #696
2024-10-13 21:23:57 +02:00
Jan Böhmer
4a6ec2581d Removed wrongly used controller for merge modal 2024-10-13 20:59:28 +02:00
Jan Böhmer
3d75bf5f9f Added translation for the confirmation code field in the authenticator app 2FA setup section 2024-10-13 20:46:12 +02:00
Jan Böhmer
c27648b89b New translations validators.en.xlf (English) 2024-10-13 20:40:16 +02:00
Jan Böhmer
ccf67c0662 Added translation if authentication confirmation code is wrong 2024-10-13 20:35:56 +02:00
Jan Böhmer
ca116cae91 Keeep the segment annotations in the translation files, when editing them from inside the application 2024-10-13 20:30:56 +02:00
Jan Böhmer
a29d933f99 Fixed 2FA TOTP for non-admins, while also retaining validation of auth code
This fixes issue #717
2024-10-13 20:29:22 +02:00
Jan Böhmer
49acf3e0cf Fixed problem preventing non-admins to add TOTP 2FA to their account
This was caused by the no-lockout constraint, which was accidentially triggered here
2024-10-13 20:13:03 +02:00
Jan Böhmer
234b5abb96 Merge remote-tracking branch 'origin/master' 2024-10-13 19:56:29 +02:00
Jan Böhmer
839bcf91d6 Updated dependencies. 2024-10-13 19:56:21 +02:00
Jan Böhmer
58ed57fab7
New translations messages.en.xlf (English) (#703) 2024-09-12 21:52:34 +02:00
Jan Böhmer
fa42997733 Bumped version to 1.14.0 2024-09-09 21:42:29 +02:00
Jan Böhmer
ac416141d0 Merge remote-tracking branch 'origin/master' 2024-09-09 21:42:15 +02:00
Jan Böhmer
c629a85b14 Updated dependencies 2024-09-09 21:42:03 +02:00
Jan Böhmer
7ccfea208f
New Crowdin updates (#695)
* New translations messages.en.xlf (English)

* New translations validators.en.xlf (German)

* New translations messages.en.xlf (German)

* New translations messages.en.xlf (Italian)
2024-09-09 21:38:07 +02:00
Jan Böhmer
f3c802bcff Made parameter type fields wider to fit more digits 2024-09-09 21:36:05 +02:00
Jan Böhmer
574583bd6a Do not round values of parameters, we can now use the full double precision
This fixes issue #681
2024-09-09 21:33:28 +02:00
Jan Böhmer
84c54d0b25 Removed NumberType fixes, as these is now part of the upstream symfony 2024-09-09 21:13:44 +02:00
Jan Böhmer
86d3f87694 [Digikey provider] Do not try to interpret certain parameters (like packages) as numbers
This fixes issue #682
2024-09-09 20:44:09 +02:00
André Lademann
ddd7252051
Increase image size in list view #688 (#689) 2024-09-09 20:29:25 +02:00
Jan Böhmer
b4e8136618 Fixed problem with undeleting elements containing an embedded and propertly restore the infos of the embed
This fixes issue #685
2024-09-09 20:26:26 +02:00
Jan Böhmer
c2638991f2 Added documentation for OEMSecrets info provider 2024-09-09 17:02:45 +02:00
Jan Böhmer
8554be9abd Show number of results for info provider search and show a notice, if no results were found 2024-09-09 16:41:19 +02:00
Jan Böhmer
87a518703f Escape spaces in unnwrapped urls to avoid invalid URLs 2024-09-09 16:23:12 +02:00
Jan Böhmer
dd03ca943d Fixed phpstan issues 2024-09-09 14:52:18 +02:00
Jan Böhmer
6997861811 [OEMSecrets provider] Extract real URLs and remove tracking parts 2024-09-09 14:52:09 +02:00
Pasquale D'Orsi
1cc1530b20
OEMSecrets provider interface v.1.0 (#679)
* OEMSecrets provider interface v.1.0

New class for interacting with the OEMSecrets (https://www.oemsecrets.com) API version 3.0.1.

* Refactored info provider to be stateless and independent from session, optimized Part-DB API usage, and fixed PHPStan issues.

Refactored info provider to be stateless and independent from session, now use Psr\Cache, fixed issues identified by PHPStan, additional minor enhancements and bug fixes.

* Prefix cache keys with oemsecrets_ to avoid key collissions

* Use uniqid with more entropy to reduce probability of collisions

* Made $resultData local as it is only used inside searchByKeyword

* Use the parameter name $id from interface declaration for getDetails to avoid problems with named arguments

* Use unicode modifier for preg_match to avoid problems when parameters contain non-unicode strings

* Various small code quality improvements

* Try to retrieve the part from the API in getDetails, if the DTO was not cached before

* Improved code formatting

* Channged OEMSecret default country to DE to be consistent with other default values

* Do not call gc_collect_cycles in the loop to process the results, but only after all processBatch calls

---------

Co-authored-by: Jan Böhmer <mail@jan-boehmer.de>
2024-09-09 00:59:44 +02:00
Jan Böhmer
98597fb3aa Use new repository pathes (Part-DB-server) instead of the Part-DB-symfony ones 2024-09-08 20:05:06 +02:00
Jan Böhmer
283a445198 Use jbtronics/translation-editor bundle instead of php-translation/symfony-bundle for profiler translation editing
This new bundle has less dependencies and less overhead
2024-09-08 20:03:33 +02:00
Jan Böhmer
7db44f0ec5 Upgraded dependencies 2024-09-08 19:54:31 +02:00
Jan Böhmer
abb5395cae Use "log-bin-trust-function-creators" option for mysql in recommended docker-compose file
This avoids errors, while creating the MySQL functions for the natural sort: "1419 You do not have the SUPER privilege an
  d binary logging is enabled"
2024-09-08 19:46:55 +02:00
Jan Böhmer
8c8b44baef Use debian bookworm, PHP 8.2 and node 20 for the docker image by default 2024-09-08 19:40:43 +02:00
Jan Böhmer
7366a33fe5 Apply the PHP_VERSION arg also to the partdb-entrypoint during build, to make it really version independent 2024-09-08 19:40:19 +02:00
Jan Böhmer
ad02d7e525
New Crowdin updates (#692)
* New translations messages.en.xlf (English)

* New translations validators.en.xlf (English)

* New translations security.en.xlf (English)

* New translations validators.en.xlf (Italian)
2024-09-08 19:14:02 +02:00
David Girón
b5a0189f29
feat(docker): Refactor Dockerfile (#683)
* reorder nodejs/yarn install, separate packages per line

* reduce run actions and reorganize commands

* simplify file creation, copy in one layer only

* fix lint LegacyKeyValueFormat

* arg php_version to run different version

* reorder copy from generated config

* update dockerfile-frankenphp
2024-09-08 19:13:13 +02:00
Jan Böhmer
756152dd68 Bumped to version 1.13.3 2024-08-24 15:58:46 +02:00
Jan Böhmer
173a8ee680 Improved assymmetric padding in datatables footer 2024-08-24 15:55:45 +02:00
Jan Böhmer
b99777cde1 Return a 404 message, instead of creating an 500 Runtime exception, when a file associated with an attachment is not existing.
This fails more gracefully, and do not pollute log files.
2024-08-24 15:49:45 +02:00
Jan Böhmer
8193e7a68e Allow to show attachment IDs in attachment table 2024-08-24 15:48:50 +02:00
Jan Böhmer
f18c024daa Remove -> prefix if no element is selected yet 2024-08-24 15:35:30 +02:00
Jan Böhmer
f6577a8f33 Allow to create sub elements for existing elements, by typing "->"
This fixes issue #666 and #560
2024-08-24 15:31:44 +02:00
Jan Böhmer
7fc3153dde Fixed filter logic for exclusion of entities. Before parts with null values as property value were wrongly not shown
This fixes  issue #658
2024-08-23 22:58:04 +02:00
Jan Böhmer
5231dbd6e7 Remove project path in twig label error messages to prevent information leakage 2024-08-23 22:28:29 +02:00
Jan Böhmer
77671550a7 Fail gracefully, when an exception occurs during rendering of the example labels for label profiles
This fixes issue #671
2024-08-23 22:15:29 +02:00
Jan Böhmer
e231404128 Load HTMLExtension in SandboxedTwig, so that the data_uri filter can be used in twig labels
This fixes issue #665
2024-08-23 22:06:37 +02:00
Jan Böhmer
6650e2da3d Updated dependencies 2024-08-23 21:57:37 +02:00
frank-f
fd521acaa4
Update LCSCProvider field for real datasheet URL (#670) 2024-08-21 17:35:55 +02:00
Jan Böhmer
a169623866 Bump version to 1.13.2 2024-07-28 23:20:47 +02:00
Jan Böhmer
21c3c45150 Introduced a custom ExponentialNumberType for parameter values
This type shows small values in exponential notation instead of rounding it to zero.
2024-07-28 23:18:36 +02:00
Jan Böhmer
1aee0a91c0 Workaround symfonys issue with NumberType and negative exponential e-notation number
This fixes issue #649
2024-07-28 22:52:44 +02:00
Jan Böhmer
27a28d4adc Fixed the NatSortKey function where regex characters were improperly escaped in the old migration
This caused a "range out of order in character class" error and propably affected the functionality of the sort function
2024-07-28 17:08:55 +02:00
Jan Böhmer
d6ff22fc44 Show in server info page which natural sorting method is used
This should ease debugging
2024-07-28 14:13:34 +02:00
Jan Böhmer
5ede61118c Use paragonie/sodium_compat v1 as v2 do not support 32-bit PHP anymore 2024-07-28 13:31:27 +02:00
Jan Böhmer
228549ff51 Include pgsql extensions and client in docker images 2024-07-28 13:12:42 +02:00
Jan Böhmer
1ec5cbc301 Merge remote-tracking branch 'origin/l10n_master' 2024-07-28 13:02:58 +02:00
Jan Böhmer
3011cb8fae Updated dependencies 2024-07-28 13:02:39 +02:00
Jan Böhmer
ff78c3c9a7 New translations security.en.xlf (Vietnamese) 2024-07-10 03:32:31 +02:00
Jan Böhmer
d5980b7620 New translations messages.en.xlf (Spanish) 2024-07-05 11:30:31 +02:00
Jan Böhmer
a8e1171108 Bumped to version 1.13.1 2024-06-23 21:14:00 +02:00
Jan Böhmer
19e5d302f4 Fixed detection on mariadb natsort capabilities on distributions which use the 5.5.5- prefix for MariaDB version 2024-06-23 21:13:37 +02:00
Jan Böhmer
f3bf4ca838 New translations validators.en.xlf (English) 2024-06-22 23:02:49 +02:00
Jan Böhmer
740985d68f New translations security.en.xlf (Croatian) 2024-06-14 23:40:19 +02:00
287 changed files with 49713 additions and 15614 deletions

View file

@ -39,8 +39,50 @@ if [ -d /var/www/html/var/db ]; then
fi
fi
# Start PHP-FPM
service php8.1-fpm start
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile)
service phpPHP_VERSION-fpm start
# Run migrations if automigration is enabled via env variable DB_AUTOMIGRATE
if [ "$DB_AUTOMIGRATE" = "true" ]; then
echo "Waiting for database to be ready..."
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(sudo -E -u www-data php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do
if [ $? -eq 255 ]; then
# If the Doctrine command exits with 255, an unrecoverable error occurred
ATTEMPTS_LEFT_TO_REACH_DATABASE=0
break
fi
sleep 1
ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1))
echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left."
done
if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then
echo "The database is not up or not reachable:"
echo "$DATABASE_ERROR"
exit 1
else
echo "The database is now ready and reachable"
fi
# Check if there are any available migrations to do, by executing doctrine:migrations:up-to-date
# and checking if the exit code is 0 (up to date) or 1 (not up to date)
if sudo -E -u www-data php bin/console doctrine:migrations:up-to-date --no-interaction; then
echo "Database is up to date, no migrations necessary."
else
echo "Migrations available..."
echo "Do backup of database..."
sudo -E -u www-data mkdir -p /var/www/html/uploads/.automigration-backup/
# Backup the database
sudo -E -u www-data php bin/console partdb:backup -n --database /var/www/html/uploads/.automigration-backup/backup-$(date +%Y-%m-%d_%H-%M-%S).zip
# Check if there are any migration files
sudo -E -u www-data php bin/console doctrine:migrations:migrate --no-interaction
fi
fi
# first arg is `-f` or `--some-option` (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/docker-php-entrypoint)
if [ "${1#-}" != "$1" ]; then

View file

@ -43,6 +43,9 @@
PassEnv PROVIDER_OCTOPART_CLIENT_ID PROVIDER_OCTOPART_SECRET PROVIDER_OCTOPART_CURRENCY PROVIDER_OCTOPART_COUNTRY PROVIDER_OCTOPART_SEARCH_LIMIT PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS
PassEnv PROVIDER_MOUSER_KEY PROVIDER_MOUSER_SEARCH_OPTION PROVIDER_MOUSER_SEARCH_LIMIT PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE
PassEnv PROVIDER_LCSC_ENABLED PROVIDER_LCSC_CURRENCY
PassEnv PROVIDER_OEMSECRETS_KEY PROVIDER_OEMSECRETS_COUNTRY_CODE PROVIDER_OEMSECRETS_CURRENCY PROVIDER_OEMSECRETS_ZERO_PRICE PROVIDER_OEMSECRETS_SET_PARAM PROVIDER_OEMSECRETS_SORT_CRITERIA
PassEnv PROVIDER_REICHELT_ENABLED PROVIDER_REICHELT_CURRENCY PROVIDER_REICHELT_COUNTRY PROVIDER_REICHELT_LANGUAGE PROVIDER_REICHELT_INCLUDE_VAT
PassEnv PROVIDER_POLLIN_ENABLED
PassEnv EDA_KICAD_CATEGORY_DEPTH
# For most configuration files from conf-available/, which are

58
.env
View file

@ -143,7 +143,8 @@ PROVIDER_TME_CURRENCY=EUR
PROVIDER_TME_LANGUAGE=en
# The country to get results for
PROVIDER_TME_COUNTRY=DE
# Set this to 1 to get gross prices (including VAT) instead of net prices
# [DEPRECATED] Set this to 1 to get gross prices (including VAT) instead of net prices
# With private API keys, this option cannot be used anymore is ignored by Part-DB. The VAT inclusion depends on your TME account settings.
PROVIDER_TME_GET_GROSS_PRICES=1
# Octopart / Nexar Provider:
@ -181,6 +182,61 @@ PROVIDER_LCSC_ENABLED=0
# The currency to get prices in (e.g. EUR, USD, etc.)
PROVIDER_LCSC_CURRENCY=EUR
# Oemsecrets Provider API 3.0.1:
# You can get your API key from https://www.oemsecrets.com/api
PROVIDER_OEMSECRETS_KEY=
# The country you want the output for
PROVIDER_OEMSECRETS_COUNTRY_CODE=DE
# Available country code are:
# AD, AE, AQ, AR, AT, AU, BE, BO, BR, BV, BY, CA, CH, CL, CN, CO, CZ, DE, DK, EC, EE, EH,
# ES, FI, FK, FO, FR, GB, GE, GF, GG, GI, GL, GR, GS, GY, HK, HM, HR, HU, IE, IM, IN, IS,
# IT, JM, JP, KP, KR, KZ, LI, LK, LT, LU, MC, MD, ME, MK, MT, NL, NO, NZ, PE, PH, PL, PT,
# PY, RO, RS, RU, SB, SD, SE, SG, SI, SJ, SK, SM, SO, SR, SY, SZ, TC, TF, TG, TH, TJ, TK,
# TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VE, VG, VI, VN, VU, WF, YE,
# ZA, ZM, ZW
#
# The currency you want the prices to be displayed in
PROVIDER_OEMSECRETS_CURRENCY=EUR
# Available currency are:AUD, CAD, CHF, CNY, DKK, EUR, GBP, HKD, ILS, INR, JPY, KRW, NOK,
# NZD, RUB, SEK, SGD, TWD, USD
#
# If PROVIDER_OEMSECRETS_ZERO_PRICE is set to 0, distributors with zero prices
# will be discarded from the creation of a new part (set to 1 otherwise)
PROVIDER_OEMSECRETS_ZERO_PRICE=0
#
# When PROVIDER_OEMSECRETS_SET_PARAM is set to 1 the parameters for the part are generated
# from the description transforming unstructured descriptions into structured parameters;
# each parameter in description should have the form: "...;name1:value1;name2:value2"
PROVIDER_OEMSECRETS_SET_PARAM=1
#
# This environment variable determines the sorting criteria for product results.
# The sorting process first arranges items based on the provided keyword.
# Then, if set to 'C', it further sorts by completeness (prioritizing items with the most
# detailed information). If set to 'M', it further sorts by manufacturer name.
#If unset or set to any other value, no sorting is performed.
PROVIDER_OEMSECRETS_SORT_CRITERIA=C
# Reichelt provider:
# Reichelt.com offers no official API, so this info provider webscrapes the website to extract info
# It could break at any time, use it at your own risk
# We dont require an API key for Reichelt, just set this to 1 to enable Reichelt support
PROVIDER_REICHELT_ENABLED=0
# The country to get prices for
PROVIDER_REICHELT_COUNTRY=DE
# The language to get results in (en, de, fr, nl, pl, it, es)
PROVIDER_REICHELT_LANGUAGE=en
# Include VAT in prices (set to 1 to include VAT, 0 to exclude VAT)
PROVIDER_REICHELT_INCLUDE_VAT=1
# The currency to get prices in (only for countries with countries other than EUR)
PROVIDER_REICHELT_CURRENCY=EUR
# Pollin provider:
# Pollin.de offers no official API, so this info provider webscrapes the website to extract info
# It could break at any time, use it at your own risk
# We dont require an API key for Pollin, just set this to 1 to enable Pollin support
PROVIDER_POLLIN_ENABLED=0
##################################################################################
# EDA integration related settings
##################################################################################

0
.env.dev Normal file
View file

View file

@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: [ '8.1', '8.2', '8.3' ]
php-versions: [ '8.1', '8.2', '8.3', '8.4' ]
db-type: [ 'mysql', 'sqlite', 'postgres' ]
env:
@ -126,7 +126,7 @@ jobs:
run: ./bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
env_vars: PHP_VERSION,DB_TYPE
token: ${{ secrets.CODECOV_TOKEN }}

View file

@ -1,22 +1,64 @@
FROM debian:bullseye-slim
ARG BASE_IMAGE=debian:bookworm-slim
ARG PHP_VERSION=8.3
FROM ${BASE_IMAGE} AS base
ARG PHP_VERSION
# Install needed dependencies for PHP build
#RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \
# libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libjpeg62-turbo-dev libonig-dev libxslt-dev libwebp-dev vim \
# && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-certificates curl zip mariadb-client \
RUN apt-get update && apt-get -y install \
apt-transport-https \
lsb-release \
ca-certificates \
curl \
zip \
mariadb-client \
postgresql-client \
&& curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \
&& sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \
&& apt-get update && apt-get upgrade -y \
&& apt-get install -y apache2 php8.1 php8.1-fpm php8.1-opcache php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath php8.1-intl php8.1-zip php8.1-xsl php8.1-sqlite3 php8.1-mysql gpg sudo \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
ENV APACHE_CONFDIR /etc/apache2
ENV APACHE_ENVVARS $APACHE_CONFDIR/envvars
&& apt-get install -y \
apache2 \
php${PHP_VERSION} \
php${PHP_VERSION}-fpm \
php${PHP_VERSION}-opcache \
php${PHP_VERSION}-curl \
php${PHP_VERSION}-gd \
php${PHP_VERSION}-mbstring \
php${PHP_VERSION}-xml \
php${PHP_VERSION}-bcmath \
php${PHP_VERSION}-intl \
php${PHP_VERSION}-zip \
php${PHP_VERSION}-xsl \
php${PHP_VERSION}-sqlite3 \
php${PHP_VERSION}-mysql \
php${PHP_VERSION}-pgsql \
gpg \
sudo \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* \
# Create workdir and set permissions if directory does not exists
RUN mkdir -p /var/www/html && chown -R www-data:www-data /var/www/html
&& mkdir -p /var/www/html \
&& chown -R www-data:www-data /var/www/html \
# delete the "index.html" that installing Apache drops in here
&& rm -rvf /var/www/html/*
# Install node and yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get update && apt-get install -y \
nodejs \
yarn \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
# Install composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
ENV APACHE_CONFDIR=/etc/apache2
ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars
# Configure apache 2 (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/Dockerfile)
# generically convert lines like
@ -27,8 +69,6 @@ RUN mkdir -p /var/www/html && chown -R www-data:www-data /var/www/html
# so that they can be overridden at runtime ("-e APACHE_RUN_USER=...")
RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \
set -eux; . "$APACHE_ENVVARS"; \
# delete the "index.html" that installing Apache drops in here
rm -rvf /var/www/html/*; \
\
# logs should go to stdout / stderr
ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
@ -36,82 +76,87 @@ RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"
ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR";
# Enable php-fpm
RUN a2enmod proxy_fcgi setenvif && a2enconf php8.1-fpm
# ---
FROM scratch AS apache-config
ARG PHP_VERSION
# Configure php-fpm to log to stdout of the container (stdout of PID 1)
# We have to use /proc/1/fd/1 because /dev/stdout or /proc/self/fd/1 does not point to the container stdout (because we use apache as entrypoint)
# We also disable the clear_env option to allow the use of environment variables in php-fpm
RUN { \
echo '[global]'; \
echo 'error_log = /proc/1/fd/1'; \
echo; \
echo '[www]'; \
echo 'access.log = /proc/1/fd/1'; \
echo 'catch_workers_output = yes'; \
echo 'decorate_workers_output = no'; \
echo 'clear_env = no'; \
} | tee "/etc/php/8.1/fpm/pool.d/zz-docker.conf"
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/pool.d/zz-docker.conf
[global]
error_log = /proc/1/fd/1
[www]
access.log = /proc/1/fd/1
catch_workers_output = yes
decorate_workers_output = no
clear_env = no
EOF
# PHP files should be handled by PHP, and should be preferred over any other file type
RUN { \
echo '<FilesMatch \.php$>'; \
echo '\tSetHandler application/x-httpd-php'; \
echo '</FilesMatch>'; \
echo; \
echo 'DirectoryIndex disabled'; \
echo 'DirectoryIndex index.php index.html'; \
echo; \
echo '<Directory /var/www/>'; \
echo '\tOptions -Indexes'; \
echo '\tAllowOverride All'; \
echo '</Directory>'; \
} | tee "$APACHE_CONFDIR/conf-available/docker-php.conf" \
&& a2enconf docker-php
COPY <<EOF /etc/apache2/conf-available/docker-php.conf
<FilesMatch \\.php$>
SetHandler application/x-httpd-php
</FilesMatch>
DirectoryIndex disabled
DirectoryIndex index.php index.html
<Directory /var/www/>
Options -Indexes
AllowOverride All
</Directory>
EOF
# Enable opcache and configure it recommended for symfony (see https://symfony.com/doc/current/performance.html)
RUN \
{ \
echo 'opcache.memory_consumption=256'; \
echo 'opcache.max_accelerated_files=20000'; \
echo 'opcache.validate_timestamp=0'; \
# Configure Realpath cache for performance
echo 'realpath_cache_size=4096K'; \
echo 'realpath_cache_ttl=600'; \
} > /etc/php/8.1/fpm/conf.d/symfony-recommended.ini
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/symfony-recommended.ini
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamp=0
# Configure Realpath cache for performance
realpath_cache_size=4096K
realpath_cache_ttl=600
EOF
# Increase upload limit and enable preloading
RUN \
{ \
echo 'upload_max_filesize=256M'; \
echo 'post_max_size=300M'; \
echo 'opcache.preload_user=www-data'; \
echo 'opcache.preload=/var/www/html/config/preload.php'; \
} > /etc/php/8.1/fpm/conf.d/partdb.ini
COPY <<EOF /etc/php/${PHP_VERSION}/fpm/conf.d/partdb.ini
upload_max_filesize=256M
post_max_size=300M
opcache.preload_user=www-data
opcache.preload=/var/www/html/config/preload.php
log_limit=8096
EOF
# Install node and yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - && apt-get update && apt-get install -y nodejs yarn && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
# Install composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# ---
FROM base
ARG PHP_VERSION
# Set working dir
WORKDIR /var/www/html
COPY --from=apache-config / /
COPY --chown=www-data:www-data . .
# Setup apache2
RUN a2dissite 000-default.conf
COPY ./.docker/symfony.conf /etc/apache2/sites-available/symfony.conf
RUN a2ensite symfony.conf
RUN a2enmod rewrite
RUN a2dissite 000-default.conf && \
a2ensite symfony.conf && \
# Enable php-fpm
a2enmod proxy_fcgi setenvif && \
a2enconf php${PHP_VERSION}-fpm && \
a2enconf docker-php && \
a2enmod rewrite
# Install composer and yarn dependencies for Part-DB
USER www-data
RUN composer install -a --no-dev && composer clear-cache
RUN yarn install --network-timeout 600000 && yarn build && yarn cache clean && rm -rf node_modules/
RUN composer install -a --no-dev && \
composer clear-cache
RUN yarn install --network-timeout 600000 && \
yarn build && \
yarn cache clean && \
rm -rf node_modules/
# Use docker env to output logs to stdout
ENV APP_ENV=docker
@ -119,10 +164,12 @@ ENV DATABASE_URL="sqlite:///%kernel.project_dir%/uploads/app.db"
USER root
# Copy entrypoint to /usr/local/bin and make it executable
RUN cp ./.docker/partdb-entrypoint.sh /usr/local/bin/partdb-entrypoint.sh && chmod +x /usr/local/bin/partdb-entrypoint.sh
# Copy apache2-foreground to /usr/local/bin and make it executable
RUN cp ./.docker/apache2-foreground /usr/local/bin/apache2-foreground && chmod +x /usr/local/bin/apache2-foreground
# Replace the php version placeholder in the entry point, with our php version
RUN sed -i "s/PHP_VERSION/${PHP_VERSION}/g" ./.docker/partdb-entrypoint.sh
# Copy entrypoint and apache2-foreground to /usr/local/bin and make it executable
RUN install ./.docker/partdb-entrypoint.sh /usr/local/bin && \
install ./.docker/apache2-foreground /usr/local/bin
ENTRYPOINT ["partdb-entrypoint.sh"]
CMD ["apache2-foreground"]

View file

@ -1,11 +1,25 @@
FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream
RUN apt-get update && apt-get -y install curl zip mariadb-client file acl git gettext ca-certificates gnupg \
RUN apt-get update && apt-get -y install \
curl \
ca-certificates \
mariadb-client \
postgresql-client \
file \
acl \
git \
gettext \
gnupg \
zip \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
# Create workdir and set permissions if directory does not exists
RUN mkdir -p /app
WORKDIR /app
# Install node and yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get update && apt-get install -y \
nodejs yarn \
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
# Install PHP
RUN set -eux; \
@ -17,6 +31,7 @@ RUN set -eux; \
zip \
pdo_mysql \
pdo_sqlite \
pdo_pgsql \
gd \
bcmath \
xsl \
@ -32,15 +47,13 @@ ENV FRANKENPHP_CONFIG="import worker.Caddyfile"
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# Install node and yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - && apt-get update && apt-get install -y nodejs yarn && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
# Install composer
ENV COMPOSER_ALLOW_SUPERUSER=1
#COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Create workdir and set permissions if directory does not exists
WORKDIR /app
# prevent the reinstallation of vendors at every changes in the source code
COPY --link composer.* symfony.* ./
RUN set -eux; \
@ -57,7 +70,10 @@ RUN set -eux; \
composer run-script --no-dev post-install-cmd; \
chmod +x bin/console; sync;
RUN yarn install --network-timeout 600000 && yarn build && yarn cache clean && rm -rf node_modules/
RUN yarn install --network-timeout 600000 && \
yarn build && \
yarn cache clean && \
rm -rf node_modules/
# Use docker env to output logs to stdout
ENV APP_ENV=docker

View file

@ -1 +1 @@
1.13.0
1.17.1

View file

@ -128,6 +128,8 @@ const PLACEHOLDERS = [
['[[BARCODE_QR]]', 'QR code linking to this element'],
['[[BARCODE_C128]]', 'Code 128 barcode linking to this element'],
['[[BARCODE_C39]]', 'Code 39 barcode linking to this element'],
['[[BARCODE_C93]]', 'Code 93 barcode linking to this element'],
['[[BARCODE_DATAMATRIX]]', 'Datamatrix code linking to this element'],
]
},
{

View file

@ -69,6 +69,8 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
'QR code linking to this element': 'QR Code verknüpft mit diesem Element',
'Code 128 barcode linking to this element': 'Code 128 Barcode verknüpft mit diesem Element',
'Code 39 barcode linking to this element': 'Code 39 Barcode verknüpft mit diesem Element',
'Code 93 barcode linking to this element': 'Code 93 Barcode verknüpft mit diesem Element',
'Datamatrix code linking to this element': 'Datamatrix Code verknüpft mit diesem Element',
'Location ID': 'Lagerort ID',
'Name': 'Name',

View file

@ -23,6 +23,12 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller {
_tomSelect;
@ -46,6 +52,12 @@ export default class extends Controller {
}
return '<div>' + escape(data.label) + '</div>';
}
},
plugins: {
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
"restore_on_backspace": {}
}
};

View file

@ -53,6 +53,7 @@ export default class extends Controller {
const config = {
language: language,
licenseKey: "GPL",
}
const watchdog = new EditorWatchdog();

View file

@ -186,5 +186,15 @@ export default class extends Controller {
];
},
});
//Try to find the input field and register a defocus handler. This is necessarry, as by default the autocomplete
//lib has problems when multiple inputs are present on the page. (see https://github.com/algolia/autocomplete/issues/1216)
const inputs = this.element.getElementsByClassName('aa-Input');
for (const input of inputs) {
input.addEventListener('blur', () => {
this._autocomplete.setIsOpen(false);
});
}
}
}

View file

@ -23,6 +23,12 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
/**
* This is the frontend controller for StaticFileAutocompleteType form element.
* Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete.
@ -46,7 +52,13 @@ export default class extends Controller {
orderField: 'text',
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING'
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING',
plugins: {
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
'restore_on_backspace': {}
}
};
if (this.element.dataset.url) {

View file

@ -24,6 +24,8 @@ import {Controller} from "@hotwired/stimulus";
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller {
_tomSelect;
@ -38,11 +40,15 @@ export default class extends Controller {
const allowAdd = this.element.getAttribute("data-allow-add") === "true";
const addHint = this.element.getAttribute("data-add-hint") ?? "";
let settings = {
allowEmptyOption: true,
selectOnTab: true,
maxOptions: null,
create: allowAdd ? this.createItem.bind(this) : false,
createFilter: this.createFilter.bind(this),
// This three options allow us to paste element names with commas: (see issue #538)
maxItems: 1,
@ -58,7 +64,21 @@ export default class extends Controller {
render: {
item: this.renderItem.bind(this),
option: this.renderOption.bind(this),
option_create: function(data, escape) {
option_create: (data, escape) => {
//If the input starts with "->", we prepend the current selected value, for easier extension of existing values
//This here handles the display part, while the createItem function handles the actual creation
if (data.input.startsWith("->")) {
//Get current selected value
const current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim();
//Prepend it to the input
if (current) {
data.input = current + " " + data.input;
} else {
//If there is no current value, we remove the "->"
data.input = data.input.substring(2);
}
}
return '<div class="create"><i class="fa-solid fa-plus fa-fw"></i>&nbsp;<strong>' + escape(data.input) + '</strong>&hellip;&nbsp;' +
'<small class="text-muted float-end">(' + addHint +')</small>' +
'</div>';
@ -68,14 +88,39 @@ export default class extends Controller {
//Add callbacks to update validity
onInitialize: this.updateValidity.bind(this),
onChange: this.updateValidity.bind(this),
plugins: {
"autoselect_typed": {},
}
};
//Add clear button plugin, if an empty option is present
if (this.element.querySelector("option[value='']") !== null) {
settings.plugins["clear_button"] = {};
}
this._tomSelect = new TomSelect(this.element, settings);
//Do not do a sync here as this breaks the initial rendering of the empty option
//this._tomSelect.sync();
}
createItem(input, callback) {
//If the input starts with "->", we prepend the current selected value, for easier extension of existing values
if (input.startsWith("->")) {
//Get current selected value
let current = this._tomSelect.getItem(this._tomSelect.getValue()).textContent.replaceAll("→", "->").trim();
//Replace no break spaces with normal spaces
current = current.replaceAll("\u00A0", " ");
//Prepend it to the input
if (current) {
input = current + " " + input;
} else {
//If there is no current value, we remove the "->"
input = input.substring(2);
}
}
callback({
//$%$ is a special value prefix, that is used to identify items, that are not yet in the DB
value: '$%$' + input,
@ -84,6 +129,31 @@ export default class extends Controller {
});
}
createFilter(input) {
//Normalize the input (replace spacing around arrows)
if (input.includes("->")) {
const inputs = input.split("->");
inputs.forEach((value, index) => {
inputs[index] = value.trim();
});
input = inputs.join("->");
} else {
input = input.trim();
}
const options = this._tomSelect.options;
//Iterate over all options and check if the input is already present
for (let index in options) {
const option = options[index];
if (option.path === input) {
return false;
}
}
return true;
}
updateValidity() {
//Mark this input as invalid, if the selected option is disabled

View file

@ -23,14 +23,21 @@ import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
export default class extends Controller {
_tomSelect;
connect() {
let settings = {
plugins: {
remove_button:{
}
remove_button:{},
'autoselect_typed': {},
'click_to_edit': {},
},
persistent: false,
selectOnTab: true,

View file

@ -20,7 +20,7 @@
import {Controller} from "@hotwired/stimulus";
//import * as ZXing from "@zxing/library";
import {Html5QrcodeScanner, Html5Qrcode} from "html5-qrcode";
import {Html5QrcodeScanner, Html5Qrcode} from "@part-db/html5-qrcode";
/* stimulusFetch: 'lazy' */
export default class extends Controller {
@ -50,7 +50,7 @@ export default class extends Controller {
});
this._scanner = new Html5QrcodeScanner(this.element.id, {
fps: 2,
fps: 10,
qrbox: qrboxFunction,
experimentalFeatures: {
//This option improves reading quality on android chrome
@ -61,6 +61,11 @@ export default class extends Controller {
this._scanner.render(this.onScanSuccess.bind(this));
}
disconnect() {
this._scanner.pause();
this._scanner.clear();
}
onScanSuccess(decodedText, decodedResult) {
//Put our decoded Text into the input box
document.getElementById('scan_dialog_input').value = decodedText;

View file

@ -25,9 +25,20 @@ import "katex/dist/katex.css";
export default class extends Controller {
static targets = ["input", "preview"];
static values = {
unit: {type: Boolean, default: false} //Render as upstanding (non-italic) text, useful for units
}
updatePreview()
{
katex.render(this.inputTarget.value, this.previewTarget, {
let value = "";
if (this.unitValue) {
value = "\\mathrm{" + this.inputTarget.value + "}";
} else {
value = this.inputTarget.value;
}
katex.render(value, this.previewTarget, {
throwOnError: false,
});
}

View file

@ -22,6 +22,13 @@ import TomSelect from "tom-select";
import katex from "katex";
import "katex/dist/katex.css";
import TomSelect_click_to_edit from '../../tomselect/click_to_edit/click_to_edit'
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
TomSelect.define('click_to_edit', TomSelect_click_to_edit)
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
/* stimulusFetch: 'lazy' */
export default class extends Controller
{
@ -53,7 +60,10 @@ export default class extends Controller
connect() {
const settings = {
plugins: {
clear_button:{}
'autoselect_typed': {},
'click_to_edit': {},
'clear_button': {},
'restore_on_backspace': {}
},
persistent: false,
maxItems: 1,

View file

@ -112,3 +112,10 @@ ul.structural_link li a:hover {
background-color: var(--bs-success);
border-color: var(--bs-success);
}
/***********************************************
* Katex rendering with same height as text
***********************************************/
.katex-same-height-as-text .katex {
font-size: 1.0em;
}

View file

@ -51,7 +51,6 @@
.part-table-image {
max-height: 40px;
object-fit: contain;
width: 100%;
}
.part-info-image {

View file

@ -108,8 +108,8 @@ body {
.back-to-top {
cursor: pointer;
position: fixed;
bottom: 20px;
right: 20px;
bottom: 60px;
right: 40px;
display:none;
z-index: 1030;
}

View file

@ -63,10 +63,6 @@ table.dataTable > tbody > tr.selected > td > a {
margin-block-end: 0;
}
.card-footer-table {
padding-top: 0;
}
table.dataTable {
margin-top: 0 !important;
}

View file

@ -24,9 +24,8 @@
/** Should be the same settings, as in label_style.css */
.ck-html-label .ck-content {
font-family: "DejaVu Sans Mono", monospace;
font-size: 12px;
font-size: 12pt;
line-height: 1.0;
font-size-adjust: 1.5;
}
.ck-html-label .ck-content p {

View file

@ -44,4 +44,18 @@ import "./register_events";
import "./tristate_checkboxes";
//Define jquery globally
window.$ = window.jQuery = require("jquery")
window.$ = window.jQuery = require("jquery");
//Use the local WASM file for the ZXing library
import {
setZXingModuleOverrides,
} from "barcode-detector/pure";
import wasmFile from "../../node_modules/zxing-wasm/dist/reader/zxing_reader.wasm";
setZXingModuleOverrides({
locateFile: (path, prefix) => {
if (path.endsWith(".wasm")) {
return wasmFile;
}
return prefix + path;
},
});

View file

@ -21,6 +21,7 @@
import {Dropdown} from "bootstrap";
import ClipboardJS from "clipboard";
import {Modal} from "bootstrap";
class RegisterEventHelper {
constructor() {
@ -31,9 +32,11 @@ class RegisterEventHelper {
//Initialize ClipboardJS
this.registerLoadHandler(() => {
new ClipboardJS('.btn');
})
});
this.registerModalDropRemovalOnFormSubmit();
}
registerModalDropRemovalOnFormSubmit() {
@ -43,6 +46,15 @@ class RegisterEventHelper {
if (back_drop) {
back_drop.remove();
}
//Remove scroll-lock if it is still active
if (document.body.classList.contains('modal-open')) {
document.body.classList.remove('modal-open');
//Remove the padding-right and overflow:hidden from the body
document.body.style.paddingRight = '';
document.body.style.overflow = '';
}
});
}

View file

@ -0,0 +1,63 @@
/**
* Autoselect Typed plugin for Tomselect
*
* This plugin allows automatically selecting an option matching the typed text when the Tomselect element goes out of
* focus (is blurred) and/or when the delimiter is typed.
*
* #select_on_blur option
* Tomselect natively supports the "createOnBlur" option. This option picks up any remaining text in the input field
* and uses it to create a new option and selects that option. It does behave a bit strangely though, in that it will
* not select an already existing option when the input is blurred, so if you typed something that matches an option in
* the list and then click outside the box (without pressing enter) the entered text is just removed (unless you have
* allow duplicates on in which case it will create a new option).
* This plugin fixes that, such that Tomselect will first try to select an option matching the remaining uncommitted
* text and only when no matching option is found tries to create a new one (if createOnBlur and create is on)
*
* #select_on_delimiter option
* Normally when typing the delimiter (space by default) Tomselect will try to create a new option (and select it) (if
* create is on), but if the typed text matches an option (and allow duplicates is off) it refuses to react at all until
* you press enter. With this option, the delimiter will also allow selecting an option, not just creating it.
*/
function select_current_input(self){
if(self.isLocked){
return
}
const val = self.inputValue()
//Do nothing if the input is empty
if (!val) {
return
}
if (self.options[val]) {
self.addItem(val)
self.setTextboxValue()
}
}
export default function(plugin_options_) {
const plugin_options = Object.assign({
//Autoselect the typed text when the input element goes out of focus
select_on_blur: true,
//Autoselect the typed text when the delimiter is typed
select_on_delimiter: true,
}, plugin_options_);
const self = this
if(plugin_options.select_on_blur) {
this.hook("before", "onBlur", function () {
select_current_input(self)
})
}
if(plugin_options.select_on_delimiter) {
this.hook("before", "onKeyPress", function (e) {
const character = String.fromCharCode(e.keyCode || e.which);
if (self.settings.mode === 'multi' && character === self.settings.delimiter) {
select_current_input(self)
}
})
}
}

View file

@ -0,0 +1,93 @@
/**
* click_to_edit plugin for Tomselect
*
* This plugin allows editing (and selecting text in) any selected item by clicking it.
*
* Usually, when the user typed some text and created an item in Tomselect that item cannot be edited anymore. To make
* a change, the item has to be deleted and retyped completely. There is also generally no way to copy text out of a
* tomselect item. The "restore_on_backspace" plugin improves that somewhat, by allowing the user to edit an item after
* pressing backspace. However, it is somewhat confusing to first have to focus the field an then hit backspace in order
* to copy a piece of text. It may also not be immediately obvious for editing.
* This plugin transforms an item into editable text when it is clicked, e.g. when the user tries to place the caret
* within an item or when they try to drag across the text to highlight it.
* It also plays nice with the remove_button plugin which still removes (deselects) an option entirely.
*
* It is recommended to also enable the autoselect_typed plugin when using this plugin. Without it, the text in the
* input field (i.e. the item that was just clicked) is lost when the user clicks outside the field. Also, when the user
* clicks an option (making it text) and then tries to enter another one by entering the delimiter (e.g. space) nothing
* happens until enter is pressed or the text is changed from what it was.
*/
/**
* Return a dom element from either a dom query string, jQuery object, a dom element or html string
* https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
*
* param query should be {}
*/
const getDom = query => {
if (query.jquery) {
return query[0];
}
if (query instanceof HTMLElement) {
return query;
}
if (isHtmlString(query)) {
var tpl = document.createElement('template');
tpl.innerHTML = query.trim(); // Never return a text node of whitespace as the result
return tpl.content.firstChild;
}
return document.querySelector(query);
};
const isHtmlString = arg => {
if (typeof arg === 'string' && arg.indexOf('<') > -1) {
return true;
}
return false;
};
function plugin(plugin_options_) {
const self = this
const plugin_options = Object.assign({
//If there is unsubmitted text in the input field, should that text be automatically used to select a matching
//element? If this is off, clicking on item1 and then clicking on item2 will result in item1 being deselected
auto_select_before_edit: true,
//If there is unsubmitted text in the input field, should that text be automatically used to create a matching
//element if no matching element was found or auto_select_before_edit is off?
auto_create_before_edit: true,
//customize this function to change which text the item is replaced with when clicking on it
text: option => {
return option[self.settings.labelField];
}
}, plugin_options_);
self.hook('after', 'setupTemplates', () => {
const orig_render_item = self.settings.render.item;
self.settings.render.item = (data, escape) => {
const item = getDom(orig_render_item.call(self, data, escape));
item.addEventListener('click', evt => {
if (self.isLocked) {
return;
}
const val = self.inputValue();
if (self.options[val]) {
self.addItem(val)
} else if (self.settings.create) {
self.createItem();
}
const option = self.options[item.dataset.value]
self.setTextboxValue(plugin_options.text.call(self, option));
self.focus();
self.removeItem(item);
}
);
return item;
}
});
}
export { plugin as default };

View file

@ -4,6 +4,10 @@
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_dir(dirname(__DIR__).'/vendor')) {
throw new LogicException('Dependencies are missing. Try running "composer install".');
}
//Increase xdebug.max_nesting_level to 1000 if required (see issue #411)
//Check if xdebug extension is active, and xdebug.max_nesting_level is set to 256 or lower
if (extension_loaded('xdebug') && ((int) ini_get('xdebug.max_nesting_level')) <= 256) {

View file

@ -1,4 +1,5 @@
{
"name": "part-db/part-db-server",
"type": "project",
"license": "AGPL-3.0-or-later",
"require": {
@ -14,9 +15,9 @@
"api-platform/core": "^3.1",
"beberlei/doctrineextensions": "^1.2",
"brick/math": "0.12.1 as 0.11.0",
"composer/ca-bundle": "^1.3",
"composer/ca-bundle": "^1.5",
"composer/package-versions-deprecated": "^1.11.99.5",
"doctrine/data-fixtures": "^1.6.6",
"doctrine/data-fixtures": "^2.0.0",
"doctrine/dbal": "^4.0.0",
"doctrine/doctrine-bundle": "^2.0",
"doctrine/doctrine-migrations-bundle": "^3.0",
@ -39,12 +40,10 @@
"nelmio/cors-bundle": "^2.3",
"nelmio/security-bundle": "^3.0",
"nyholm/psr7": "^1.1",
"ocramius/proxy-manager": "2.2.*",
"omines/datatables-bundle": "^0.8.0",
"omines/datatables-bundle": "^0.9.1",
"paragonie/sodium_compat": "^1.21",
"part-db/label-fonts": "^1.0",
"php-translation/symfony-bundle": "^0.14.0",
"phpdocumentor/reflection-docblock": "^5.2",
"phpstan/phpdoc-parser": "^1.23",
"rhukster/dom-sanitizer": "^1.0",
"runtime/frankenphp-symfony": "^0.2.0",
"s9e/text-formatter": "^2.1",
"scheb/2fa-backup-code": "^6.8.0",
@ -56,6 +55,8 @@
"symfony/apache-pack": "^1.0",
"symfony/asset": "6.4.*",
"symfony/console": "6.4.*",
"symfony/css-selector": "6.4.*",
"symfony/dom-crawler": "6.4.*",
"symfony/dotenv": "6.4.*",
"symfony/expression-language": "6.4.*",
"symfony/flex": "^v2.3.1",
@ -69,7 +70,6 @@
"symfony/process": "6.4.*",
"symfony/property-access": "6.4.*",
"symfony/property-info": "6.4.*",
"symfony/proxy-manager-bridge": "6.4.*",
"symfony/rate-limiter": "6.4.*",
"symfony/runtime": "6.4.*",
"symfony/security-bundle": "6.4.*",
@ -91,23 +91,22 @@
"twig/intl-extra": "^3.8",
"twig/markdown-extra": "^3.8",
"twig/string-extra": "^3.8",
"web-auth/webauthn-symfony-bundle": "^4.0.0",
"webmozart/assert": "^1.4"
"web-auth/webauthn-symfony-bundle": "^4.0.0"
},
"require-dev": {
"dama/doctrine-test-bundle": "^v8.0.0",
"doctrine/doctrine-fixtures-bundle": "^3.2",
"ekino/phpstan-banned-code": "^v1.0.0",
"doctrine/doctrine-fixtures-bundle": "^4.0.0",
"ekino/phpstan-banned-code": "^v3.0.0",
"jbtronics/translation-editor-bundle": "^1.0",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.4.7",
"phpstan/phpstan-doctrine": "^1.2.11",
"phpstan/phpstan-strict-rules": "^1.5",
"phpstan/phpstan-symfony": "^1.1.7",
"phpstan/phpstan": "^2.0.4",
"phpstan/phpstan-doctrine": "^2.0.1",
"phpstan/phpstan-strict-rules": "^2.0.1",
"phpstan/phpstan-symfony": "^2.0.0",
"phpunit/phpunit": "^9.5",
"rector/rector": "^1.1.1",
"rector/rector": "^2.0.4",
"roave/security-advisories": "dev-latest",
"symfony/browser-kit": "6.4.*",
"symfony/css-selector": "6.4.*",
"symfony/debug-bundle": "6.4.*",
"symfony/maker-bundle": "^1.13",
"symfony/phpunit-bridge": "6.4.*",

4750
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,6 @@ return [
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true],
Translation\Bundle\TranslationBundle::class => ['all' => true],
Florianv\SwapBundle\FlorianvSwapBundle::class => ['all' => true],
Nelmio\SecurityBundle\NelmioSecurityBundle::class => ['all' => true],
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
@ -32,4 +31,5 @@ return [
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
];

View file

@ -35,3 +35,7 @@ api_platform:
keep_legacy_inflector: false
# Need to be true, or some tests will fail
use_symfony_listeners: true
serializer:
# Change this to false later, to remove the hydra prefix on the API
hydra_prefix: true

View file

@ -8,15 +8,14 @@ datatables:
# Set options, as documented at https://datatables.net/reference/option/
options:
lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
lengthMenu : [[10, 25, 50, 100], [10, 25, 50, 100]] # We add the "All" option, when part tables are generated
pageLength: '%partdb.table.default_page_size%' # Set to -1 to disable pagination (i.e. show all rows) by default
#dom: "<'row' <'col-sm-12' tr>><'row' <'col-sm-6'l><'col-sm-6 text-right'pif>>"
dom: " <'row'<'col mb-2 input-group' B l> <'col mb-2' <'pull-end' p>>>
dom: " <'row' <'col mb-2 input-group flex-nowrap' B l > <'col-auto mb-2' < p >>>
<'card'
rt
<'card-footer card-footer-table text-muted' i >
>
<'row'<'col mt-2 input-group' B l> <'col mt-2' <'pull-right' p>>>"
<'row' <'col mt-2 input-group flex-nowrap' B l > <'col-auto mt-2' < p >>>"
pagingType: 'simple_numbers'
searching: true
stateSave: true

View file

@ -1,5 +0,0 @@
translation:
symfony_profiler:
enabled: true
webui:
enabled: true

View file

@ -57,6 +57,7 @@ doctrine:
field2: App\Doctrine\Functions\Field2
natsort: App\Doctrine\Functions\Natsort
array_position: App\Doctrine\Functions\ArrayPosition
ilike: App\Doctrine\Functions\ILike
when@test:
doctrine:

View file

@ -50,7 +50,6 @@ when@prod:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false
@ -74,7 +73,6 @@ when@docker:
type: stream
path: "php://stderr"
level: debug
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false

View file

@ -51,12 +51,16 @@ nelmio_security:
img-src:
- '*'
- 'data:'
# Required for be able to load pictures in the QR code scanner
- 'blob:'
style-src:
- 'self'
- 'unsafe-inline'
- 'data:'
script-src:
- 'self'
# Required for loading the Wasm for the barcode scanner:
- 'wasm-unsafe-eval'
object-src:
- 'self'
- 'data:'

View file

@ -1,11 +0,0 @@
translation:
locales: ["en", "de"]
edit_in_place:
enabled: false
config_name: app
configs:
app:
dirs: ["%kernel.project_dir%/templates", "%kernel.project_dir%/src"]
output_dir: "%kernel.project_dir%/translations"
excluded_names: ["*TestCase.php", "*Test.php"]
excluded_dirs: [cache, data, logs]

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', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh'] # The languages that are shown in user drop down menu
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu
partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all.
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails

View file

@ -1,6 +0,0 @@
_translation_webui:
resource: '@TranslationBundle/Resources/config/routing_webui.yaml'
prefix: /admin
_translation_profiler:
resource: '@TranslationBundle/Resources/config/routing_symfony_profiler.yaml'

View file

@ -0,0 +1,3 @@
when@dev:
translation_editor:
resource: '@JbtronicsTranslationEditorBundle/config/routes.php'

View file

@ -1,3 +0,0 @@
_translation_edit_in_place:
resource: '@TranslationBundle/Resources/config/routing_edit_in_place.yaml'
prefix: /admin

View file

@ -306,6 +306,16 @@ services:
$enabled: '%env(bool:PROVIDER_LCSC_ENABLED)%'
$currency: '%env(string:PROVIDER_LCSC_CURRENCY)%'
App\Services\InfoProviderSystem\Providers\OEMSecretsProvider:
arguments:
$api_key: '%env(string:PROVIDER_OEMSECRETS_KEY)%'
$country_code: '%env(string:PROVIDER_OEMSECRETS_COUNTRY_CODE)%'
$currency: '%env(PROVIDER_OEMSECRETS_CURRENCY)%'
$zero_price: '%env(PROVIDER_OEMSECRETS_ZERO_PRICE)%'
$set_param: '%env(PROVIDER_OEMSECRETS_SET_PARAM)%'
$sort_criteria: '%env(PROVIDER_OEMSECRETS_SORT_CRITERIA)%'
####################################################################################################################
# API system
####################################################################################################################

View file

@ -23,7 +23,7 @@ each other so that it does not matter which one of your 1000 things of Part you
A part entity has many fields, which can be used to describe it better. Most of the fields are optional:
* **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a
name you thought of yourself. The name have to be unique in a single category.
name you thought of yourself. Each name needs to be unique and must exist in a single category.
* **Description**: A short (single-line) description of what this part is/does. For longer information, you should use
the comment field or the specifications
* **Category** (Required): The category (see there) to which this part belongs to.

View file

@ -32,11 +32,16 @@ options listed, see `.env` file for the full list of possible env variables.
### General options
* `DATABASE_URL`: Configures the database which Part-DB uses. For mysql use a string in the form
of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here
(e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). For SQLite use the following format to specify the
* `DATABASE_URL`: Configures the database which Part-DB uses:
* For MySQL (or MariaDB) use a string in the form of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here
(e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`).
* For SQLite use the following format to specify the
absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as
placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`)
* For Postgresql use a string in the form of `DATABASE_URL=postgresql://user:password@127.0.0.1:5432/part-db?serverVersion=x.y`.
Please note that **`serverVersion=x.y`** variable is required due to dependency of Symfony framework.
* `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection
is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate
bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates.
@ -86,6 +91,10 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
* `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.
* `APP_SECRET`: This variable is a configuration parameter used for various security-related purposes,
particularly for securing and protecting various aspects of your application. It's a secret key that is used for
cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this
value should be handled as confidential data and not shared publicly.
### E-Mail settings

View file

@ -150,9 +150,9 @@ In the `serverVersion` parameter you can specify the version of the PostgreSQL s
The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly.
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `unix_socket` parameter.
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `host` parameter.
```shell
DATABASE_URL="postgresql://db_user:db_password@localhost/db_name?serverVersion=12.19&charset=utf8&unix_socket=/var/run/postgresql/.s.PGSQL.5432"
DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql"
```

View file

@ -7,3 +7,5 @@ has_children: true
# Installation
Below you can find some guides to install Part-DB.
For the hobbyists without much experience, we recommend the docker installation or direct installation on debian.

View file

@ -48,6 +48,12 @@ services:
# In docker env logs will be redirected to stderr
- APP_ENV=docker
# Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
# folder (under .automigration-backup), so you can restore it, if the migration fails.
# This feature is currently experimental, so use it at your own risk!
# - DB_AUTOMIGRATE=true
# You can configure Part-DB using environment variables
# Below you can find the most essential ones predefined
# However you can add any other environment configuration you want here
@ -130,6 +136,12 @@ services:
# In docker env logs will be redirected to stderr
- APP_ENV=docker
# Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
# folder (under .automigration-backup), so you can restore it, if the migration fails.
# This feature is currently experimental, so use it at your own risk!
# - DB_AUTOMIGRATE=true
# You can configure Part-DB using environment variables
# Below you can find the most essential ones predefined
# However you can add add any other environment configuration you want here
@ -158,7 +170,7 @@ services:
container_name: partdb_database
image: mysql:8.0
restart: unless-stopped
command: --default-authentication-plugin=mysql_native_password
command: --default-authentication-plugin=mysql_native_password --log-bin-trust-function-creators=1
environment:
# Change this Password
MYSQL_ROOT_PASSWORD: SECRET_ROOT_PASSWORD
@ -201,6 +213,10 @@ You also have to create the database as described above in step 4.
You can run the console commands described in README by
executing `docker exec --user=www-data -it partdb bin/console [command]`
{: .warning }
> If you run a root console inside the container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell.
> Otherwise Part-DB console might use the wrong configuration to execute commands.
## Troubleshooting
*Login is not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*:

View file

@ -0,0 +1,42 @@
---
title: Kubernetes / Helm
layout: default
parent: Installation
nav_order: 5
---
# Kubernetes / Helm Charts
If you are using Kubernetes, you can use the [helm charts](https://helm.sh/) provided in this [repository](https://github.com/Part-DB/helm-charts).
## Usage
[Helm](https://helm.sh) must be installed to use the charts. Please refer to
Helm's [documentation](https://helm.sh/docs) to get started.
Once Helm has been set up correctly, add the repo as follows:
`helm repo add part-db https://part-db.github.io/helm-charts`
If you had already added this repo earlier, run `helm repo update` to retrieve
the latest versions of the packages. You can then run `helm search repo
part-db` to see the charts.
To install the part-db chart:
helm install my-part-db part-db/part-db
To uninstall the chart:
helm delete my-part-db
This repository is also available at [ArtifactHUB](https://artifacthub.io/packages/search?repo=part-db).
## Configuration
See the README in the [chart directory](https://github.com/Part-DB/helm-charts/tree/main/charts/part-db) for more
information on the available configuration options.
## Bugreports
If you find issues related to the helm charts, please open an issue in the [helm-charts repository](https://github.com/Part-DB/helm-charts).

View file

@ -53,6 +53,11 @@ server {
return 404;
}
# Set Content-Security-Policy for svg files, to block embedded javascript in there
location ~* \.svg$ {
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';";
}
error_log /var/log/nginx/parts.error.log;
access_log /var/log/nginx/parts.access.log;

View file

@ -0,0 +1,31 @@
---
title: Proxmox VE LXC
layout: default
parent: Installation
nav_order: 6
---
# Proxmox VE LXC
{: .warning }
> The proxmox VE LXC script for Part-DB is developed and maintained by [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/)
> and not by the Part-DB developers. Keep in mind that the script is not officially supported by the Part-DB developers.
If you are using Proxmox VE you can use the scripts provided by [Proxmox VE Helper-Scripts community](https://community-scripts.github.io/ProxmoxVE/scripts?id=part-db)
to easily install Part-DB in a LXC container.
## Usage
To create a new LXC container with Part-DB, you can use the following command in the Proxmox VE shell:
```bash
bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/part-db.sh)"
```
The same command can be used to update an existing Part-DB container.
See the [helper script website](https://community-scripts.github.io/ProxmoxVE/scripts?id=part-db) for more information.
## Bugreports
If you find issues related to the proxmox VE LXC script, please open an issue in the [Proxmox VE Helper-Scripts repository](https://github.com/community-scripts/ProxmoxVE).

View file

@ -25,6 +25,12 @@ is named `partdb`, you can execute the command `php bin/console cache:clear` wit
docker exec --user=www-data partdb php bin/console cache:clear
```
{: .warning }
> If you run a root console inside the docker container, and wanna execute commands on the webserver behalf, be sure to use `sudo -E` command (with the `-E` flag) to preserve env variables from the current shell.
> Otherwise Part-DB console might use the wrong configuration to execute commands.
## Troubleshooting
## User management commands
* `php bin/console partdb:users:list`: List all users of this Part-DB instance
@ -65,3 +71,9 @@ docker exec --user=www-data partdb php bin/console cache:clear
* `php bin/console doctrine:migrations:migrate`: Migrate the database to the latest version
* `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date
## Attachment commands
* `php bin/console partdb:attachments:download`: Download all attachments, which are not already downloaded, to the
local filesystem. This is useful to create local backups of the attachments, no matter what happens on the remote and
also makes pictures thumbnails available for the frontend for them

View file

@ -107,7 +107,7 @@ The following env configuration options are available:
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,
* `PROVIDER_OCTOPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code,
default: `DE`). To get the 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.
@ -212,6 +212,46 @@ An API key is not required, it is enough to enable the provider using the follow
* `PROVIDER_LCSC_ENABLED`: Set this to `1` to enable the LCSC provider
* `PROVIDER_LCSC_CURRENCY`: The currency you want to get prices in (see LCSC webshop for available currencies, default: `EUR`)
### OEMsecrets
The oemsecrets provider uses the [oemsecrets API](https://www.oemsecrets.com/) to search for parts and getting shopping
information from them. Similar to octopart it aggregates offers from different distributors.
You can apply for a free API key on the [oemsecrets API page](https://www.oemsecrets.com/api/) and put the key you get
in the Part-DB env configuration (see below).
The following env configuration options are available:
* `PROVIDER_OEMSECRETS_KEY`: The API key you got from oemsecrets (mandatory)
* `PROVIDER_OEMSECRETS_COUNTRY_CODE`: The two-letter code of the country you want to get the prices for
* `PROVIDER_OEMSECRETS_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`)
* `PROVIDER_OEMSECRETS_ZERO_PRICE`: If set to `1`, parts with a price of 0 will be included in the search results, otherwise
they will be excluded (optional, default: `0`)
* `PROVIDER_OEMSECRETS_SET_PARAM`: If set to `1`, the provider will try to extract parameters from the part description
* `PROVIDER_OEMSECRETS_SORT_CRITERIA`: The criteria to sort the search results by. If set to 'C', it further sorts by
completeness (prioritizing items with the most detailed information). If set to 'M', it further sorts by manufacturer name.
If set to any other value, no sorting is performed.
### Reichelt
The reichelt provider uses webscraping from [reichelt.com](https://reichelt.com/) to get part information.
This is not an official API and could break at any time. So use it at your own risk.
The following env configuration options are available:
* `PROVIDER_REICHELT_ENABLED`: Set this to `1` to enable the Reichelt provider
* `PROVIDER_REICHELT_CURRENCY`: The currency you want to get prices in. Only possible for countries which use Non-EUR (optional, default: `EUR`)
* `PROVIDER_REICHELT_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
* `PROVIDER_REICHELT_LANGUAGE`: The language you want to get the descriptions in (optional, default: `en`)
* `PROVIDER_REICHELT_INCLUDE_VAT`: If set to `1`, the prices will be gross prices (including tax), otherwise net prices (optional, default: `1`)
### Pollin
The pollin provider uses webscraping from [pollin.de](https://www.pollin.de/) to get part information.
This is not an official API and could break at any time. So use it at your own risk.
The following env configuration options are available:
* `PROVIDER_POLLIN_ENABLED`: Set this to `1` to enable the Pollin provider
### Custom provider
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long

View file

@ -117,6 +117,6 @@ For a German keyboard layout, replace `[` with `0`, and `]` with `´`.
| Key | Character |
|--------------------------------|--------------------|
| **Alt + [** (code 219) | © (Copyright char) |
| **Alt + Shift + [** (code 219) | (Registered char) |
| **Alt + Shift + [** (code 219) | ® (Registered char) |
| **Alt + ]** (code 221) | ™ (Trademark char) |
| **Alt + Shift + ]** (code 221) | (Degree char) |
| **Alt + Shift + ]** (code 221) | ° (Degree char) |

View file

@ -343,6 +343,7 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme
$this->addSql('ALTER TABLE webauthn_keys CHANGE public_key_credential_id public_key_credential_id LONGTEXT NOT NULL, CHANGE transports transports LONGTEXT NOT NULL, CHANGE trust_path trust_path JSON NOT NULL, CHANGE aaguid aaguid TINYTEXT NOT NULL, CHANGE credential_public_key credential_public_key LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL, CHANGE last_time_used last_time_used DATETIME DEFAULT NULL');
// Add the natural sort emulation function to the database (based on this stackoverflow: https://stackoverflow.com/questions/153633/natural-sort-in-mysql/58154535#58154535)
//This version here is wrong, and will be replaced by the correct version in the next migration (we need to use nowdoc instead of heredoc, otherwise the slashes will be wrongly escaped!!)
$this->addSql(<<<EOD
CREATE DEFINER=CURRENT_USER FUNCTION `NatSortKey`(`s` VARCHAR(1000) CHARSET utf8mb4, `n` INT) RETURNS varchar(3500) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci
DETERMINISTIC
@ -411,7 +412,7 @@ final class Version20240606203053 extends AbstractMultiPlatformMigration impleme
DECLARE x,y varchar(1000); # need to be same length as input s
DECLARE r varchar(3500) DEFAULT ''; # return value: needs to be same length as return type
DECLARE suf varchar(101); # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components)
DECLARE suf varchar(1001); # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components)
DECLARE i,j,k int UNSIGNED;
IF n<=0 THEN SET n := -1; END IF; # n<=0 means "process all numbers"
LOOP

View file

@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240728145604 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Update the Natural Sorting function for MySQL';
}
public function mySQLUp(Schema $schema): void
{
//Remove the old function
$this->addSql('DROP FUNCTION IF EXISTS NatSortKey');
//The difference to the original function is the correct length of the suf variable and correct escaping
//We now use heredoc syntax to avoid escaping issues with the \ (which resulted in "range out of order in character class").
$this->addSql(<<<'EOD'
CREATE DEFINER=CURRENT_USER FUNCTION `NatSortKey`(`s` VARCHAR(1000) CHARSET utf8mb4, `n` INT) RETURNS varchar(3500) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci
DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
/****
Converts numbers in the input string s into a format such that sorting results in a nat-sort.
Numbers of up to 359 digits (before the decimal point, if one is present) are supported. Sort results are undefined if the input string contains numbers longer than this.
For n>0, only the first n numbers in the input string will be converted for nat-sort (so strings that differ only after the first n numbers will not nat-sort amongst themselves).
Total sort-ordering is preserved, i.e. if s1!=s2, then NatSortKey(s1,n)!=NatSortKey(s2,n), for any given n.
Numbers may contain ',' as a thousands separator, and '.' as a decimal point. To reverse these (as appropriate for some European locales), the code would require modification.
Numbers preceded by '+' sort with numbers not preceded with either a '+' or '-' sign.
Negative numbers (preceded with '-') sort before positive numbers, but are sorted in order of ascending absolute value (so -7 sorts BEFORE -1001).
Numbers with leading zeros sort after the same number with no (or fewer) leading zeros.
Decimal-part-only numbers (like .75) are recognised, provided the decimal point is not immediately preceded by either another '.', or by a letter-type character.
Numbers with thousand separators sort after the same number without them.
Thousand separators are only recognised in numbers with no leading zeros that don't immediately follow a ',', and when they format the number correctly.
(When not recognised as a thousand separator, a ',' will instead be treated as separating two distinct numbers).
Version-number-like sequences consisting of 3 or more numbers separated by '.' are treated as distinct entities, and each component number will be nat-sorted.
The entire entity will sort after any number beginning with the first component (so e.g. 10.2.1 sorts after both 10 and 10.995, but before 11)
Note that The first number component in an entity like this is also permitted to contain thousand separators.
To achieve this, numbers within the input string are prefixed and suffixed according to the following format:
- The number is prefixed by a 2-digit base-36 number representing its length, excluding leading zeros. If there is a decimal point, this length only includes the integer part of the number.
- A 3-character suffix is appended after the number (after the decimals if present).
- The first character is a space, or a '+' sign if the number was preceded by '+'. Any preceding '+' sign is also removed from the front of the number.
- This is followed by a 2-digit base-36 number that encodes the number of leading zeros and whether the number was expressed in comma-separated form (e.g. 1,000,000.25 vs 1000000.25)
- The value of this 2-digit number is: (number of leading zeros)*2 + (1 if comma-separated, 0 otherwise)
- For version number sequences, each component number has the prefix in front of it, and the separating dots are removed.
Then there is a single suffix that consists of a ' ' or '+' character, followed by a pair base-36 digits for each number component in the sequence.
e.g. here is how some simple sample strings get converted:
'Foo055' --> 'Foo0255 02'
'Absolute zero is around -273 centigrade' --> 'Absolute zero is around -03273 00 centigrade'
'The $1,000,000 prize' --> 'The $071000000 01 prize'
'+99.74 degrees' --> '0299.74+00 degrees'
'I have 0 apples' --> 'I have 00 02 apples'
'.5 is the same value as 0000.5000' --> '00.5 00 is the same value as 00.5000 08'
'MariaDB v10.3.0018' --> 'MariaDB v02100130218 000004'
The restriction to numbers of up to 359 digits comes from the fact that the first character of the base-36 prefix MUST be a decimal digit, and so the highest permitted prefix value is '9Z' or 359 decimal.
The code could be modified to handle longer numbers by increasing the size of (both) the prefix and suffix.
A higher base could also be used (by replacing CONV() with a custom function), provided that the collation you are using sorts the "digits" of the base in the correct order, starting with 0123456789.
However, while the maximum number length may be increased this way, note that the technique this function uses is NOT applicable where strings may contain numbers of unlimited length.
The function definition does not specify the charset or collation to be used for string-type parameters or variables: The default database charset & collation at the time the function is defined will be used.
This is to make the function code more portable. However, there are some important restrictions:
- Collation is important here only when comparing (or storing) the output value from this function, but it MUST order the characters " +0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in that order for the natural sort to work.
This is true for most collations, but not all of them, e.g. in Lithuanian 'Y' comes before 'J' (according to Wikipedia).
To adapt the function to work with such collations, replace CONV() in the function code with a custom function that emits "digits" above 9 that are characters ordered according to the collation in use.
- For efficiency, the function code uses LENGTH() rather than CHAR_LENGTH() to measure the length of strings that consist only of digits 0-9, '.', and ',' characters.
This works for any single-byte charset, as well as any charset that maps standard ASCII characters to single bytes (such as utf8 or utf8mb4).
If using a charset that maps these characters to multiple bytes (such as, e.g. utf16 or utf32), you MUST replace all instances of LENGTH() in the function definition with CHAR_LENGTH()
Length of the output:
Each number converted adds 5 characters (2 prefix + 3 suffix) to the length of the string. n is the maximum count of numbers to convert;
This parameter is provided as a means to limit the maximum output length (to input length + 5*n).
If you do not require the total-ordering property, you could edit the code to use suffixes of 1 character (space or plus) only; this would reduce the maximum output length for any given n.
Since a string of length L has at most ((L+1) DIV 2) individual numbers in it (every 2nd character a digit), for n<=0 the maximum output length is (inputlength + 5*((inputlength+1) DIV 2))
So for the current input length of 100, the maximum output length is 350.
If changing the input length, the output length must be modified according to the above formula. The DECLARE statements for x,y,r, and suf must also be modified, as the code comments indicate.
****/
DECLARE x,y varchar(1000); # need to be same length as input s
DECLARE r varchar(3500) DEFAULT ''; # return value: needs to be same length as return type
DECLARE suf varchar(1001); # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components)
DECLARE i,j,k int UNSIGNED;
IF n<=0 THEN SET n := -1; END IF; # n<=0 means "process all numbers"
LOOP
SET i := REGEXP_INSTR(s,'\\d'); # find position of next digit
IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF; # no more numbers to process -> we're done
SET n := n-1, suf := ' ';
IF i>1 THEN
IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF; # Allow decimal number (but not version string) to begin with a '.', provided preceding char is neither another '.', nor a member of the unicode character classes: "Alphabetic", "Letter", "Block=Letterlike Symbols" "Number", "Mark", "Join_Control"
IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF; # move any preceding '+' into the suffix, so equal numbers with and without preceding "+" signs sort together
SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i); # add everything before the number to r and strip it from the start of s; preceding '+' is dropped (not included in either r or s)
END IF;
SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+')); # capture the number + following decimals (including multiple consecutive '.<digits>' sequences)
SET s := SUBSTRING(s,CHAR_LENGTH(x)+1); # NOTE: CHAR_LENGTH() can be safely used instead of CHAR_LENGTH() here & below PROVIDED we're using a charset that represents digits, ',' and '.' characters using single bytes (e.g. latin1, utf8)
SET i := INSTR(x,'.');
IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF; # move any following decimals into y
SET i := CHAR_LENGTH(x);
SET x := REPLACE(x,',','');
SET j := CHAR_LENGTH(x);
SET x := TRIM(LEADING '0' FROM x); # strip leading zeros
SET k := CHAR_LENGTH(x);
SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0')); # (j-k)*2 + IF(i=j,0,1) = (count of leading zeros)*2 + (1 if there are thousands-separators, 0 otherwise) Note the first term is bounded to <= base-36 'ZY' as it must fit within 2 characters
SET i := LOCATE('.',y,2);
IF i=0 THEN
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf); # k = count of digits in number, bounded to be <= '9Z' base-36
ELSE # encode a version number (like 3.12.707, etc)
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36
WHILE CHAR_LENGTH(y)>0 AND n!=0 DO
IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF;
SET j := CHAR_LENGTH(x);
SET x := TRIM(LEADING '0' FROM x); # strip leading zeros
SET k := CHAR_LENGTH(x);
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x); # k = count of digits in number, bounded to be <= '9Z' base-36
SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0')); # (j-k)*2 = (count of leading zeros)*2, bounded to fit within 2 base-36 digits
SET n := n-1;
END WHILE;
SET r := CONCAT(r,y,suf);
END IF;
END LOOP;
END
EOD
);
}
public function mySQLDown(Schema $schema): void
{
//Not needed
}
public function sqLiteUp(Schema $schema): void
{
//Not needed
}
public function sqLiteDown(Schema $schema): void
{
//Not needed
}
public function postgreSQLUp(Schema $schema): void
{
//Not needed
}
public function postgreSQLDown(Schema $schema): void
{
//Not needed
}
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250220215048 extends AbstractMigration
{
public function getDescription(): string
{
return 'Split $path property for attachments into $internal_path and $external_path';
}
public function up(Schema $schema): void
{
//Create the new columns as nullable (that is easier modifying them)
$this->addSql('ALTER TABLE attachments ADD internal_path VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE attachments ADD external_path VARCHAR(255) DEFAULT NULL');
//Copy the data from path to external_path and remove the path column
$this->addSql('UPDATE attachments SET external_path=path');
$this->addSql('ALTER TABLE attachments DROP COLUMN path');
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%MEDIA#%%\' ESCAPE \'#\'');
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%BASE#%%\' ESCAPE \'#\'');
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%SECURE#%%\' ESCAPE \'#\'');
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%FOOTPRINTS#%%\' ESCAPE \'#\'');
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%FOOTPRINTS3D#%%\' ESCAPE \'#\'');
$this->addSql('UPDATE attachments SET external_path=NULL WHERE internal_path IS NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('UPDATE attachments SET external_path=internal_path WHERE internal_path IS NOT NULL');
$this->addSql('ALTER TABLE attachments DROP COLUMN internal_path');
$this->addSql('ALTER TABLE attachments RENAME COLUMN external_path TO path');
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250222165240 extends AbstractMigration
{
public function getDescription(): string
{
return 'Migrate the old attachment class discriminator values from legacy Part-DB to the modern format, so that there is just one unified value';
}
public function up(Schema $schema): void
{
//Change the old discriminator values to the new ones
$this->addSql("UPDATE attachments SET class_name = 'Part' WHERE class_name = 'PartDB\Part'");
$this->addSql("UPDATE attachments SET class_name = 'Device' WHERE class_name = 'PartDB\Device'");
}
public function down(Schema $schema): void
{
//No down required, as the new format can also be read by older Part-DB version
}
}

View file

@ -9,7 +9,7 @@
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
"@symfony/webpack-encore": "^4.1.0",
"@symfony/webpack-encore": "^5.0.0",
"bootstrap": "^5.1.3",
"core-js": "^3.23.0",
"intl-messageformat": "^10.2.5",
@ -18,7 +18,7 @@
"regenerator-runtime": "^0.13.9",
"webpack": "^5.74.0",
"webpack-bundle-analyzer": "^4.3.0",
"webpack-cli": "^4.10.0",
"webpack-cli": "^5.1.0",
"webpack-notifier": "^1.15.0"
},
"license": "AGPL-3.0-or-later",
@ -33,50 +33,52 @@
"@algolia/autocomplete-js": "^1.17.0",
"@algolia/autocomplete-plugin-recent-searches": "^1.17.0",
"@algolia/autocomplete-theme-classic": "^1.17.0",
"@ckeditor/ckeditor5-alignment": "^41.0.0",
"@ckeditor/ckeditor5-autoformat": "^41.0.0",
"@ckeditor/ckeditor5-basic-styles": "^41.0.0",
"@ckeditor/ckeditor5-block-quote": "^41.0.0",
"@ckeditor/ckeditor5-code-block": "^41.0.0",
"@ckeditor/ckeditor5-dev-translations": "^39.1.0",
"@ckeditor/ckeditor5-dev-utils": "^39.1.0",
"@ckeditor/ckeditor5-editor-classic": "^41.0.0",
"@ckeditor/ckeditor5-essentials": "^41.0.0",
"@ckeditor/ckeditor5-find-and-replace": "^41.0.0",
"@ckeditor/ckeditor5-font": "^41.0.0",
"@ckeditor/ckeditor5-heading": "^41.0.0",
"@ckeditor/ckeditor5-highlight": "^41.0.0",
"@ckeditor/ckeditor5-horizontal-line": "^41.0.0",
"@ckeditor/ckeditor5-html-embed": "^41.0.0",
"@ckeditor/ckeditor5-html-support": "^41.0.0",
"@ckeditor/ckeditor5-image": "^41.0.0",
"@ckeditor/ckeditor5-indent": "^41.0.0",
"@ckeditor/ckeditor5-link": "^41.0.0",
"@ckeditor/ckeditor5-list": "^41.0.0",
"@ckeditor/ckeditor5-markdown-gfm": "^41.0.0",
"@ckeditor/ckeditor5-media-embed": "^41.0.0",
"@ckeditor/ckeditor5-paragraph": "^41.0.0",
"@ckeditor/ckeditor5-paste-from-office": "^41.0.0",
"@ckeditor/ckeditor5-remove-format": "^41.0.0",
"@ckeditor/ckeditor5-source-editing": "^41.0.0",
"@ckeditor/ckeditor5-special-characters": "^41.0.0",
"@ckeditor/ckeditor5-table": "^41.0.0",
"@ckeditor/ckeditor5-theme-lark": "^41.0.0",
"@ckeditor/ckeditor5-upload": "^41.0.0",
"@ckeditor/ckeditor5-watchdog": "^41.0.0",
"@ckeditor/ckeditor5-word-count": "^41.0.0",
"@ckeditor/ckeditor5-alignment": "^44.0.0",
"@ckeditor/ckeditor5-autoformat": "^44.0.0",
"@ckeditor/ckeditor5-basic-styles": "^44.0.0",
"@ckeditor/ckeditor5-block-quote": "^44.0.0",
"@ckeditor/ckeditor5-code-block": "^44.0.0",
"@ckeditor/ckeditor5-dev-translations": "^43.0.1",
"@ckeditor/ckeditor5-dev-utils": "^43.0.1",
"@ckeditor/ckeditor5-editor-classic": "^44.0.0",
"@ckeditor/ckeditor5-essentials": "^44.0.0",
"@ckeditor/ckeditor5-find-and-replace": "^44.0.0",
"@ckeditor/ckeditor5-font": "^44.0.0",
"@ckeditor/ckeditor5-heading": "^44.0.0",
"@ckeditor/ckeditor5-highlight": "^44.0.0",
"@ckeditor/ckeditor5-horizontal-line": "^44.0.0",
"@ckeditor/ckeditor5-html-embed": "^44.0.0",
"@ckeditor/ckeditor5-html-support": "^44.0.0",
"@ckeditor/ckeditor5-image": "^44.0.0",
"@ckeditor/ckeditor5-indent": "^44.0.0",
"@ckeditor/ckeditor5-link": "^44.0.0",
"@ckeditor/ckeditor5-list": "^44.0.0",
"@ckeditor/ckeditor5-markdown-gfm": "^44.0.0",
"@ckeditor/ckeditor5-media-embed": "^44.0.0",
"@ckeditor/ckeditor5-paragraph": "^44.0.0",
"@ckeditor/ckeditor5-paste-from-office": "^44.0.0",
"@ckeditor/ckeditor5-remove-format": "^44.0.0",
"@ckeditor/ckeditor5-source-editing": "^44.0.0",
"@ckeditor/ckeditor5-special-characters": "^44.0.0",
"@ckeditor/ckeditor5-table": "^44.0.0",
"@ckeditor/ckeditor5-theme-lark": "^44.0.0",
"@ckeditor/ckeditor5-upload": "^44.0.0",
"@ckeditor/ckeditor5-watchdog": "^44.0.0",
"@ckeditor/ckeditor5-word-count": "^44.0.0",
"@jbtronics/bs-treeview": "^1.0.1",
"@part-db/html5-qrcode": "^3.1.0",
"@zxcvbn-ts/core": "^3.0.2",
"@zxcvbn-ts/language-common": "^3.0.3",
"@zxcvbn-ts/language-de": "^3.0.1",
"@zxcvbn-ts/language-en": "^3.0.1",
"@zxcvbn-ts/language-fr": "^3.0.1",
"@zxcvbn-ts/language-ja": "^3.0.1",
"barcode-detector": "^2.3.1",
"bootbox": "^6.0.0",
"bootswatch": "^5.1.3",
"bs-custom-file-input": "^1.3.4",
"clipboard": "^2.0.4",
"compression-webpack-plugin": "^10.0.0",
"compression-webpack-plugin": "^11.1.0",
"datatables.net": "^2.0.0",
"datatables.net-bs5": "^2.0.0",
"datatables.net-buttons-bs5": "^3.0.0",
@ -86,18 +88,17 @@
"datatables.net-select-bs5": "^2.0.0",
"dompurify": "^3.0.3",
"emoji.json": "^15.0.0",
"exports-loader": "^3.0.0",
"html5-qrcode": "^2.2.1",
"exports-loader": "^5.0.0",
"json-formatter-js": "^2.3.4",
"jszip": "^3.2.0",
"katex": "^0.16.0",
"marked": "^12.0.0",
"marked-gfm-heading-id": "^3.0.4",
"marked": "^15.0.4",
"marked-gfm-heading-id": "^4.1.1",
"marked-mangle": "^1.0.1",
"pdfmake": "^0.2.2",
"stimulus-use": "^0.52.0",
"tom-select": "^2.1.0",
"ts-loader": "^9.2.6",
"typescript": "^4.0.2"
"typescript": "^5.7.2"
}
}

View file

@ -11,6 +11,8 @@ parameters:
- src/Configuration/*
- src/Doctrine/Purger/*
- src/DataTables/Adapters/TwoStepORMAdapter.php
- src/Form/Fixes/*
- src/Translation/Fixes/*
@ -18,7 +20,7 @@ parameters:
treatPhpDocTypesAsCertain: false
symfony:
container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml'
containerXmlPath: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml'
doctrine:
objectManagerLoader: tests/object-manager.php
@ -28,11 +30,6 @@ parameters:
checkFunctionNameCase: false
checkAlwaysTrueInstanceof: false
checkAlwaysTrueCheckTypeFunctionCall: false
checkAlwaysTrueStrictComparison: false
reportAlwaysTrueInLastCondition: false
reportMaybesInPropertyPhpDocTypes: false
reportMaybesInMethodSignatures: false
@ -41,14 +38,14 @@ parameters:
booleansInConditions: false
uselessCast: false
requireParentConstructorCall: true
disallowedConstructs: false
overwriteVariablesWithLoop: false
closureUsesThis: false
matchingInheritedMethodNames: true
numericOperandsInArithmeticOperators: true
strictCalls: true
switchConditionsMatchingType: false
noVariableVariables: false
disallowedEmpty: false
disallowedShortTernary: false
ignoreErrors:
# Ignore errors caused by complex mapping with AbstractStructuralDBElement
@ -61,3 +58,6 @@ parameters:
# Ignore doctrine type mapping mismatch
- '#Property .* type mapping mismatch: property can contain .* but database expects .*#'
# Ignore error of unused WithPermPresetsTrait, as it is used in the migrations which are not analyzed by Phpstan
- '#Trait App\\Migration\\WithPermPresetsTrait is used zero times and is not analysed#'

View file

@ -118,3 +118,10 @@ DirectoryIndex index.php
# RedirectTemp cannot be used instead
</IfModule>
</IfModule>
# Set Content-Security-Policy for svg files (and compressed variants), to block embedded javascript in there
<IfModule mod_headers.c>
<FilesMatch "\.(svg|svg\.gz|svg\.br)$">
Header set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';"
</FilesMatch>
</IfModule>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,116 +0,0 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\ApiPlatform;
use ApiPlatform\JsonSchema\Schema;
use ApiPlatform\JsonSchema\SchemaFactoryInterface;
use ApiPlatform\Metadata\Operation;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
/**
* This decorator adds the properties given by DocumentedAPIProperty attributes on the classes to the schema.
*/
#[AsDecorator('api_platform.json_schema.schema_factory')]
class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterface
{
public function __construct(private readonly SchemaFactoryInterface $decorated)
{
}
public function buildSchema(
string $className,
string $format = 'json',
string $type = Schema::TYPE_OUTPUT,
Operation $operation = null,
Schema $schema = null,
array $serializerContext = null,
bool $forceCollection = false
): Schema {
$schema = $this->decorated->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
//Check if there is are DocumentedAPIProperty attributes on the class
$reflectionClass = new \ReflectionClass($className);
$attributes = $reflectionClass->getAttributes(DocumentedAPIProperty::class);
foreach ($attributes as $attribute) {
/** @var DocumentedAPIProperty $api_property */
$api_property = $attribute->newInstance();
$this->addPropertyToSchema($schema, $api_property->schemaName, $api_property->property,
$api_property, $serializerContext ?? [], $format);
}
return $schema;
}
private function addPropertyToSchema(Schema $schema, string $definitionName, string $normalizedPropertyName, DocumentedAPIProperty $propertyMetadata, array $serializerContext, string $format): void
{
$version = $schema->getVersion();
$swagger = Schema::VERSION_SWAGGER === $version;
$propertySchema = [];
if (false === $propertyMetadata->writeable) {
$propertySchema['readOnly'] = true;
}
if (!$swagger && false === $propertyMetadata->readable) {
$propertySchema['writeOnly'] = true;
}
if (null !== $description = $propertyMetadata->description) {
$propertySchema['description'] = $description;
}
$deprecationReason = $propertyMetadata->deprecationReason;
// see https://github.com/json-schema-org/json-schema-spec/pull/737
if (!$swagger && null !== $deprecationReason) {
$propertySchema['deprecated'] = true;
}
if (!empty($default = $propertyMetadata->default)) {
if ($default instanceof \BackedEnum) {
$default = $default->value;
}
$propertySchema['default'] = $default;
}
if (!empty($example = $propertyMetadata->example)) {
$propertySchema['example'] = $example;
}
if (!isset($propertySchema['example']) && isset($propertySchema['default'])) {
$propertySchema['example'] = $propertySchema['default'];
}
$propertySchema['type'] = $propertyMetadata->type;
$propertySchema['nullable'] = $propertyMetadata->nullable;
$propertySchema = new \ArrayObject($propertySchema);
$schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema;
}
}

View file

@ -21,7 +21,9 @@
declare(strict_types=1);
namespace App\ApiPlatform;
namespace App\ApiPlatform\DocumentedAPIProperties;
use ApiPlatform\Metadata\ApiProperty;
/**
* When this attribute is applied to a class, an property will be added to the API documentation using the given parameters.
@ -64,4 +66,55 @@ final class DocumentedAPIProperty
)
{
}
public function toAPIProperty(bool $use_swagger = false): ApiProperty
{
$openApiContext = [];
if (false === $this->writeable) {
$openApiContext['readOnly'] = true;
}
if (!$use_swagger && false === $this->readable) {
$openApiContext['writeOnly'] = true;
}
if (null !== $description = $this->description) {
$openApiContext['description'] = $description;
}
$deprecationReason = $this->deprecationReason;
// see https://github.com/json-schema-org/json-schema-spec/pull/737
if (!$use_swagger && null !== $deprecationReason) {
$openApiContext['deprecated'] = true;
}
if (!empty($default = $this->default)) {
if ($default instanceof \BackedEnum) {
$default = $default->value;
}
$openApiContext['default'] = $default;
}
if (!empty($example = $this->example)) {
$openApiContext['example'] = $example;
}
if (!isset($openApiContext['example']) && isset($openApiContext['default'])) {
$openApiContext['example'] = $openApiContext['default'];
}
$openApiContext['type'] = $this->type;
$openApiContext['nullable'] = $this->nullable;
return new ApiProperty(
description: $this->description,
readable: $this->readable,
writable: $this->writeable,
openapiContext: $openApiContext,
types: $this->type,
property: $this->property
);
}
}

View file

@ -0,0 +1,73 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\ApiPlatform\DocumentedAPIProperties;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ReflectionClass;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
/**
* This decorator adds the virtual properties defined by the DocumentedAPIProperty attribute to the property metadata
* which then get picked up by the openapi schema generator
*/
#[AsDecorator('api_platform.metadata.property.metadata_factory')]
class PropertyMetadataFactory implements PropertyMetadataFactoryInterface
{
public function __construct(private PropertyMetadataFactoryInterface $decorated)
{
}
public function create(string $resourceClass, string $property, array $options = []): ApiProperty
{
$metadata = $this->decorated->create($resourceClass, $property, $options);
//Only become active in the context of the openapi schema generation
if (!isset($options['schema_type'])) {
return $metadata;
}
if (!class_exists($resourceClass)) {
return $metadata;
}
$refClass = new ReflectionClass($resourceClass);
$attributes = $refClass->getAttributes(DocumentedAPIProperty::class);
//Look for the DocumentedAPIProperty attribute with the given property name
foreach ($attributes as $attribute) {
/** @var DocumentedAPIProperty $api_property */
$api_property = $attribute->newInstance();
//If attribute not matches the property name, skip it
if ($api_property->property !== $property) {
continue;
}
//Return the virtual property
return $api_property->toAPIProperty();
}
return $metadata;
}
}

View file

@ -0,0 +1,68 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\ApiPlatform\DocumentedAPIProperties;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Property\PropertyNameCollection;
use ReflectionClass;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
/**
* This decorator adds the virtual property names defined by the DocumentedAPIProperty attribute to the property name collection
* which then get picked up by the openapi schema generator
*/
#[AsDecorator('api_platform.metadata.property.name_collection_factory')]
class PropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
{
public function __construct(private readonly PropertyNameCollectionFactoryInterface $decorated)
{
}
public function create(string $resourceClass, array $options = []): PropertyNameCollection
{
// Get the default properties from the decorated service
$propertyNames = $this->decorated->create($resourceClass, $options);
//Only become active in the context of the openapi schema generation
if (!isset($options['schema_type'])) {
return $propertyNames;
}
if (!class_exists($resourceClass)) {
return $propertyNames;
}
$properties = iterator_to_array($propertyNames);
$refClass = new ReflectionClass($resourceClass);
foreach ($refClass->getAttributes(DocumentedAPIProperty::class) as $attribute) {
/** @var DocumentedAPIProperty $instance */
$instance = $attribute->newInstance();
$properties[] = $instance->property;
}
return new PropertyNameCollection($properties);
}
}

View file

@ -37,7 +37,7 @@ class EntityFilter extends AbstractFilter
public function __construct(
ManagerRegistry $managerRegistry,
private readonly EntityFilterHelper $filter_helper,
LoggerInterface $logger = null,
?LoggerInterface $logger = null,
?array $properties = null,
?NameConverterInterface $nameConverter = null
) {
@ -50,7 +50,7 @@ class EntityFilter extends AbstractFilter
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
Operation $operation = null,
?Operation $operation = null,
array $context = []
): void {
if (

View file

@ -92,12 +92,6 @@ class EntityFilterHelper
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.',
'openapi' => [
'example' => '',
'allowReserved' => false,// if true, query parameters will be not percent-encoded
'allowEmptyValue' => true,
'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
],
];
}
return $description;

View file

@ -38,7 +38,7 @@ final class LikeFilter extends AbstractFilter
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
Operation $operation = null,
?Operation $operation = null,
array $context = []
): void {
// Otherwise filter is applied to order and page as well
@ -50,7 +50,7 @@ final class LikeFilter extends AbstractFilter
}
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
$queryBuilder
->andWhere(sprintf('o.%s LIKE :%s', $property, $parameterName))
->andWhere(sprintf('ILIKE(o.%s, :%s) = TRUE', $property, $parameterName))
->setParameter($parameterName, $value);
}
@ -67,12 +67,6 @@ final class LikeFilter extends AbstractFilter
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => 'Filter using a LIKE SQL expression. Use % as wildcard for multiple characters and _ for single characters. For example, to search for all items containing foo, use foo. To search for all items starting with foo, use foo%. To search for all items ending with foo, use %foo',
'openapi' => [
'example' => '',
'allowReserved' => false,// if true, query parameters will be not percent-encoded
'allowEmptyValue' => true,
'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
],
];
}
return $description;

View file

@ -38,7 +38,7 @@ class PartStoragelocationFilter extends AbstractFilter
public function __construct(
ManagerRegistry $managerRegistry,
private readonly EntityFilterHelper $filter_helper,
LoggerInterface $logger = null,
?LoggerInterface $logger = null,
?array $properties = null,
?NameConverterInterface $nameConverter = null
) {
@ -51,7 +51,7 @@ class PartStoragelocationFilter extends AbstractFilter
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
Operation $operation = null,
?Operation $operation = null,
array $context = []
): void {
//Do not check for mapping here, as we are using a virtual property

View file

@ -0,0 +1,96 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\ApiPlatform\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\PropertyInfo\Type;
/**
* Due to their nature, tags are stored in a single string, separated by commas, which requires some more complex search logic.
* This filter allows to easily search for tags in a part entity.
*/
final class TagFilter extends AbstractFilter
{
protected function filterProperty(
string $property,
$value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
// Ignore filter if property is not enabled or mapped
if (
!$this->isPropertyEnabled($property, $resourceClass) ||
!$this->isPropertyMapped($property, $resourceClass)
) {
return;
}
//Escape any %, _ or \ in the tag
$value = addcslashes($value, '%_\\');
$tag_identifier_prefix = $queryNameGenerator->generateParameterName($property);
$expr = $queryBuilder->expr();
$tmp = $expr->orX(
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_1) = TRUE',
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_2) = TRUE',
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_3) = TRUE',
'ILIKE(o.'.$property.', :' . $tag_identifier_prefix . '_4) = TRUE',
);
$queryBuilder->andWhere($tmp);
//Set the parameters for the LIKE expression, in each variation of the tag (so with a comma, at the end, at the beginning, and on both ends, and equaling the tag)
$queryBuilder->setParameter($tag_identifier_prefix . '_1', '%,' . $value . ',%');
$queryBuilder->setParameter($tag_identifier_prefix . '_2', '%,' . $value);
$queryBuilder->setParameter($tag_identifier_prefix . '_3', $value . ',%');
$queryBuilder->setParameter($tag_identifier_prefix . '_4', $value);
}
public function getDescription(string $resourceClass): array
{
if (!$this->properties) {
return [];
}
$description = [];
foreach (array_keys($this->properties) as $property) {
$description[(string)$property] = [
'property' => $property,
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => 'Filter for tags of a part',
];
}
return $description;
}
}

View file

@ -0,0 +1,77 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\ApiPlatform;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Property\PropertyNameCollection;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use function Symfony\Component\String\u;
/**
* This decorator removes all camelCase property names from the property name collection, if a snake_case version exists.
* This is a fix for https://github.com/Part-DB/Part-DB-server/issues/862, as the openapi schema generator wrongly collects
* both camelCase and snake_case property names, which leads to duplicate properties in the schema.
* This seems to come from the fact that the openapi schema generator uses no serializerContext, which seems then to collect
* the getters too...
*/
#[AsDecorator('api_platform.metadata.property.name_collection_factory')]
class NormalizePropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
{
public function __construct(private readonly PropertyNameCollectionFactoryInterface $decorated)
{
}
public function create(string $resourceClass, array $options = []): PropertyNameCollection
{
// Get the default properties from the decorated service
$propertyNames = $this->decorated->create($resourceClass, $options);
//Only become active in the context of the openapi schema generation
if (!isset($options['schema_type'])) {
return $propertyNames;
}
//If we are not in the jsonapi generator (which sets no serializer groups), return the property names as is
if (isset($options['serializer_groups'])) {
return $propertyNames;
}
//Remove all camelCase property names from the collection, if a snake_case version exists
$properties = iterator_to_array($propertyNames);
foreach ($properties as $property) {
if (str_contains($property, '_')) {
$camelized = u($property)->camel()->toString();
//If the camelized version exists, remove it from the collection
$index = array_search($camelized, $properties, true);
if ($index !== false) {
unset($properties[$index]);
}
}
}
return new PropertyNameCollection($properties);
}
}

View file

@ -73,6 +73,9 @@ class CleanAttachmentsCommand extends Command
//Ignore image cache folder
$finder->exclude('cache');
//Ignore automigration folder
$finder->exclude('.automigration-backup');
$fs = new Filesystem();
$file_list = [];

View file

@ -0,0 +1,136 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Command\Attachments;
use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\AttachmentUpload;
use App\Exceptions\AttachmentDownloadException;
use App\Services\Attachments\AttachmentManager;
use App\Services\Attachments\AttachmentSubmitHandler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:attachments:download', "Downloads all attachments which have only an external URL to the local filesystem.")]
class DownloadAttachmentsCommand extends Command
{
public function __construct(private readonly AttachmentSubmitHandler $attachmentSubmitHandler,
private EntityManagerInterface $entityManager)
{
parent::__construct();
}
public function configure(): void
{
$this->setHelp('This command downloads all attachments, which only have an external URL, to the local filesystem, so that you have an offline copy of the attachments.');
$this->addOption('--private', null, null, 'If set, the attachments will be downloaded to the private storage.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$qb = $this->entityManager->createQueryBuilder();
$qb->select('attachment')
->from(Attachment::class, 'attachment')
->where('attachment.external_path IS NOT NULL')
->andWhere('attachment.external_path != \'\'')
->andWhere('attachment.internal_path IS NULL');
$query = $qb->getQuery();
$attachments = $query->getResult();
if (count($attachments) === 0) {
$io->success('No attachments with external URL found.');
return Command::SUCCESS;
}
$io->note('Found ' . count($attachments) . ' attachments with external URL, that will be downloaded.');
//If the option --private is set, the attachments will be downloaded to the private storage.
$private = $input->getOption('private');
if ($private) {
if (!$io->confirm('Attachments will be downloaded to the private storage. Continue?')) {
return Command::SUCCESS;
}
} else {
if (!$io->confirm('Attachments will be downloaded to the public storage, where everybody knowing the correct URL can access it. Continue?')){
return Command::SUCCESS;
}
}
$progressBar = $io->createProgressBar(count($attachments));
$progressBar->setFormat("%current%/%max% [%bar%] %percent:3s%% %elapsed:16s%/%estimated:-16s% \n%message%");
$progressBar->setMessage('Starting download...');
$progressBar->start();
$errors = [];
foreach ($attachments as $attachment) {
/** @var Attachment $attachment */
$progressBar->setMessage(sprintf('%s (ID: %s) from %s', $attachment->getName(), $attachment->getID(), $attachment->getHost()));
$progressBar->advance();
try {
$attachmentUpload = new AttachmentUpload(file: null, downloadUrl: true, private: $private);
$this->attachmentSubmitHandler->handleUpload($attachment, $attachmentUpload);
//Write changes to the database
$this->entityManager->flush();
} catch (AttachmentDownloadException $e) {
$errors[] = [
'attachment' => $attachment,
'error' => $e->getMessage()
];
}
}
$progressBar->finish();
//Fix the line break after the progress bar
$io->newLine();
$io->newLine();
if (count($errors) > 0) {
$io->warning('Some attachments could not be downloaded:');
foreach ($errors as $error) {
$io->warning(sprintf("Attachment %s (ID %s) could not be downloaded from %s:\n%s",
$error['attachment']->getName(),
$error['attachment']->getID(),
$error['attachment']->getExternalPath(),
$error['error'])
);
}
} else {
$io->success('All attachments downloaded successfully.');
}
return Command::SUCCESS;
}
}

View file

@ -0,0 +1,90 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Command\Attachments;
use App\Entity\Attachments\Attachment;
use App\Services\Attachments\AttachmentSubmitHandler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:attachments:sanitize-svg', "Sanitize uploaded SVG files.")]
class SanitizeSVGAttachmentsCommand extends Command
{
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly AttachmentSubmitHandler $attachmentSubmitHandler, ?string $name = null)
{
parent::__construct($name);
}
public function configure(): void
{
$this->setHelp('This command allows to sanitize SVG files uploaded via attachments. This happens automatically since version 1.17.1, this command is intended to be used for older files.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->info('This command will sanitize all uploaded SVG files. This is only required if you have uploaded (untrusted) SVG files before version 1.17.1. If you are running a newer version, you don\'t need to run this command (again).');
if (!$io->confirm('Do you want to continue?', false)) {
$io->success('Command aborted.');
return Command::FAILURE;
}
$io->info('Sanitizing SVG files...');
//Finding all attachments with svg files
$qb = $this->entityManager->createQueryBuilder();
$qb->select('a')
->from(Attachment::class, 'a')
->where('a.internal_path LIKE :pattern ESCAPE \'#\'')
->orWhere('a.original_filename LIKE :pattern ESCAPE \'#\'')
->setParameter('pattern', '%.svg');
$attachments = $qb->getQuery()->getResult();
$io->note('Found '.count($attachments).' attachments with SVG files.');
if (count($attachments) === 0) {
$io->success('No SVG files found.');
return Command::FAILURE;
}
$io->info('Sanitizing SVG files...');
$io->progressStart(count($attachments));
foreach ($attachments as $attachment) {
/** @var Attachment $attachment */
$io->note('Sanitizing attachment '.$attachment->getId().' ('.($attachment->getFilename() ?? '???').')');
$this->attachmentSubmitHandler->sanitizeSVGAttachment($attachment);
$io->progressAdvance();
}
$io->progressFinish();
$io->success('Sanitization finished. All SVG files have been sanitized.');
return Command::SUCCESS;
}
}

View file

@ -79,7 +79,7 @@ class CheckRequirementsCommand extends Command
//Checking 32-bit system
if (PHP_INT_SIZE === 4) {
$io->warning('You are using a 32-bit system. You will have problems with working with dates after the year 2038, therefore a 64-bit system is recommended.');
} elseif (PHP_INT_SIZE === 8) {
} elseif (PHP_INT_SIZE === 8) { //@phpstan-ignore-line //PHP_INT_SIZE is always 4 or 8
if (!$only_issues) {
$io->success('You are using a 64-bit system.');
}

View file

@ -79,6 +79,7 @@ class ConvertBBCodeCommand extends Command
/**
* Returns a list which entities and which properties need to be checked.
* @return array<class-string<AbstractNamedDBElement>, string[]>
*/
protected function getTargetsLists(): array
{
@ -109,7 +110,6 @@ class ConvertBBCodeCommand extends Command
$class
));
//Determine which entities of this type we need to modify
/** @var EntityRepository $repo */
$repo = $this->em->getRepository($class);
$qb = $repo->createQueryBuilder('e')
->select('e');

View file

@ -83,6 +83,19 @@ class SetPasswordCommand extends Command
while (!$success) {
$pw1 = $io->askHidden('Please enter new password:');
if ($pw1 === null) {
$io->error('No password entered! Please try again.');
//If we are in non-interactive mode, we can not ask again
if (!$input->isInteractive()) {
$io->warning('Non-interactive mode detected. No password can be entered that way! If you are using docker exec, please use -it flag.');
return Command::FAILURE;
}
continue;
}
$pw2 = $io->askHidden('Please confirm:');
if ($pw1 !== $pw2) {
$io->error('The entered password did not match! Please try again.');

View file

@ -35,7 +35,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:users:enable|partdb:user:enable', 'Enables/Disable the login of one or more users')]
class UserEnableCommand extends Command
{
public function __construct(protected EntityManagerInterface $entityManager, string $name = null)
public function __construct(protected EntityManagerInterface $entityManager, ?string $name = null)
{
parent::__construct($name);
}

View file

@ -206,12 +206,15 @@ class UsersPermissionsCommand extends Command
return '<fg=green>Allow</>';
} elseif ($permission_value === false) {
return '<fg=red>Disallow</>';
} elseif ($permission_value === null && !$inherit) {
}
// Permission value is null by this point
elseif (!$inherit) {
return '<fg=blue>Inherit</>';
} elseif ($permission_value === null && $inherit) {
} elseif ($inherit) {
return '<fg=red>Disallow (Inherited)</>';
}
//@phpstan-ignore-next-line This line is never reached, but PHPstorm complains otherwise
return '???';
}
}

View file

@ -35,6 +35,7 @@ use App\Entity\LabelSystem\LabelProcessMode;
use App\Entity\LabelSystem\LabelProfile;
use App\Entity\Parameters\AbstractParameter;
use App\Exceptions\AttachmentDownloadException;
use App\Exceptions\TwigModeException;
use App\Form\AdminPages\ImportType;
use App\Form\AdminPages\MassCreationForm;
use App\Repository\AbstractPartsContainingRepository;
@ -53,6 +54,7 @@ use InvalidArgumentException;
use Omines\DataTablesBundle\DataTableFactory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\RedirectResponse;
@ -211,10 +213,14 @@ abstract class BaseAdminController extends AbstractController
//Show preview for LabelProfile if needed.
if ($entity instanceof LabelProfile) {
$example = $this->barcodeExampleGenerator->getElement($entity->getOptions()->getSupportedElement());
$pdf_data = null;
try {
$pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example);
} catch (TwigModeException $exception) {
$form->get('options')->get('lines')->addError(new FormError($exception->getSafeMessage()));
}
}
/** @var AbstractPartsContainingRepository $repo */
$repo = $this->entityManager->getRepository($this->entity_class);
return $this->render($this->twig_template, [
@ -390,7 +396,7 @@ abstract class BaseAdminController extends AbstractController
{
if ($entity instanceof AbstractPartsContainingDBElement) {
/** @var AbstractPartsContainingRepository $repo */
$repo = $this->entityManager->getRepository($this->entity_class);
$repo = $this->entityManager->getRepository($this->entity_class); //@phpstan-ignore-line
if ($repo->getPartsCount($entity) > 0) {
$this->addFlash('error', t('entity.delete.must_not_contain_parts', ['%PATH%' => $entity->getFullPath()]));
@ -461,6 +467,11 @@ abstract class BaseAdminController extends AbstractController
$this->denyAccessUnlessGranted('read', $entity);
$entities = $em->getRepository($this->entity_class)->findAll();
if (count($entities) === 0) {
$this->addFlash('error', 'entity.export.flash.error.no_entities');
return $this->redirectToRoute($this->route_base.'_new');
}
return $exporter->exportEntityFromRequest($entities, $request);
}

View file

@ -51,15 +51,15 @@ class AttachmentFileController extends AbstractController
$this->denyAccessUnlessGranted('show_private', $attachment);
}
if ($attachment->isExternal()) {
throw new RuntimeException('You can not download external attachments!');
if (!$attachment->hasInternal()) {
throw $this->createNotFoundException('The file for this attachment is external and not stored locally!');
}
if (!$helper->isFileExisting($attachment)) {
throw new RuntimeException('The file associated with the attachment is not existing!');
if (!$helper->isInternalFileExisting($attachment)) {
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
}
$file_path = $helper->toAbsoluteFilePath($attachment);
$file_path = $helper->toAbsoluteInternalFilePath($attachment);
$response = new BinaryFileResponse($file_path);
//Set header content disposition, so that the file will be downloaded
@ -80,15 +80,15 @@ class AttachmentFileController extends AbstractController
$this->denyAccessUnlessGranted('show_private', $attachment);
}
if ($attachment->isExternal()) {
throw new RuntimeException('You can not download external attachments!');
if (!$attachment->hasInternal()) {
throw $this->createNotFoundException('The file for this attachment is external and not stored locally!');
}
if (!$helper->isFileExisting($attachment)) {
throw new RuntimeException('The file associated with the attachment is not existing!');
if (!$helper->isInternalFileExisting($attachment)) {
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
}
$file_path = $helper->toAbsoluteFilePath($attachment);
$file_path = $helper->toAbsoluteInternalFilePath($attachment);
$response = new BinaryFileResponse($file_path);
//Set header content disposition, so that the file will be downloaded

View file

@ -23,10 +23,13 @@ declare(strict_types=1);
namespace App\Controller;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\Part;
use App\Form\InfoProviderSystem\PartSearchType;
use App\Services\InfoProviderSystem\ExistingPartFinder;
use App\Services\InfoProviderSystem\PartInfoRetriever;
use App\Services\InfoProviderSystem\ProviderRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -42,7 +45,9 @@ class InfoProviderController extends AbstractController
{
public function __construct(private readonly ProviderRegistry $providerRegistry,
private readonly PartInfoRetriever $infoRetriever)
private readonly PartInfoRetriever $infoRetriever,
private readonly ExistingPartFinder $existingPartFinder
)
{
}
@ -72,21 +77,49 @@ class InfoProviderController extends AbstractController
//When we are updating a part, use its name as keyword, to make searching easier
//However we can only do this, if the form was not submitted yet
if ($update_target !== null && !$form->isSubmitted()) {
$form->get('keyword')->setData($update_target->getName());
//Use the provider reference if available, otherwise use the manufacturer product number
$keyword = $update_target->getProviderReference()->getProviderId() ?? $update_target->getManufacturerProductNumber();
//Or the name if both are not available
if ($keyword === "") {
$keyword = $update_target->getName();
}
$form->get('keyword')->setData($keyword);
//If we are updating a part, which already has a provider, preselect that provider in the form
if ($update_target->getProviderReference()->getProviderKey() !== null) {
try {
$form->get('providers')->setData([$this->providerRegistry->getProviderByKey($update_target->getProviderReference()->getProviderKey())]);
} catch (\InvalidArgumentException $e) {
//If the provider is not found, just ignore it
}
}
}
if ($form->isSubmitted() && $form->isValid()) {
$keyword = $form->get('keyword')->getData();
$providers = $form->get('providers')->getData();
$dtos = [];
try {
$results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
$dtos = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
} catch (ClientException $e) {
$this->addFlash('error', t('info_providers.search.error.client_exception'));
$this->addFlash('error',$e->getMessage());
//Log the exception
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
}
// modify the array to an array of arrays that has a field for a matching local Part
// the advantage to use that format even when we don't look for local parts is that we
// always work with the same interface
$results = array_map(function ($result) {return ['dto' => $result, 'localPart' => null];}, $dtos);
if(!$update_target) {
foreach ($results as $index => $result) {
$results[$index]['localPart'] = $this->existingPartFinder->findFirstExisting($result['dto']);
}
}
}
return $this->render('info_providers/search/part_search.html.twig', [

View file

@ -108,8 +108,31 @@ class LabelController extends AbstractController
$pdf_data = null;
$filename = 'invalid.pdf';
//Generate PDF either when the form is submitted and valid, or the form was not submit yet, and generate is set
if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile instanceof LabelProfile)) {
//Check if the label should be saved as profile
if ($form->get('save_profile')->isClicked() && $this->isGranted('@labels.create_profiles')) { //@phpstan-ignore-line Phpstan does not recognize the isClicked method
//Retrieve the profile name from the form
$new_name = $form->get('save_profile_name')->getData();
//ensure that the name is not empty
if ($new_name === '' || $new_name === null) {
$form->get('save_profile_name')->addError(new FormError($this->translator->trans('label_generator.profile_name_empty')));
goto render;
}
$profile = new LabelProfile();
$profile->setName($form->get('save_profile_name')->getData());
$profile->setOptions($form_options);
$this->em->persist($profile);
$this->em->flush();
$this->addFlash('success', 'label_generator.profile_saved');
return $this->redirectToRoute('label_dialog_profile', [
'profile' => $profile->getID(),
'target_id' => (string) $form->get('target_id')->getData()
]);
}
$target_id = (string) $form->get('target_id')->getData();
$targets = $this->findObjects($form_options->getSupportedElement(), $target_id);
if ($targets !== []) {
@ -117,7 +140,7 @@ class LabelController extends AbstractController
$pdf_data = $this->labelGenerator->generateLabel($form_options, $targets);
$filename = $this->getLabelName($targets[0], $profile);
} catch (TwigModeException $exception) {
$form->get('options')->get('lines')->addError(new FormError($exception->getMessage()));
$form->get('options')->get('lines')->addError(new FormError($exception->getSafeMessage()));
}
} else {
//$this->addFlash('warning', 'label_generator.no_entities_found');
@ -132,6 +155,7 @@ class LabelController extends AbstractController
}
}
render:
return $this->render('label_system/dialog.html.twig', [
'form' => $form,
'pdf_data' => $pdf_data,
@ -152,7 +176,7 @@ class LabelController extends AbstractController
{
$id_array = $this->rangeParser->parse($ids);
/** @var DBElementRepository $repo */
/** @var DBElementRepository<AbstractDBElement> $repo */
$repo = $this->em->getRepository($type->getEntityClass());
return $repo->getElementsFromIDArray($id_array);

View file

@ -229,6 +229,10 @@ class PartController extends AbstractController
$dto = $infoRetriever->getDetails($providerKey, $providerId);
$new_part = $infoRetriever->dtoToPart($dto);
if ($new_part->getCategory() === null || $new_part->getCategory()->getID() === null) {
$this->addFlash('warning', t("part.create_from_info_provider.no_category_yet"));
}
return $this->renderPartForm('new', $request, $new_part, [
'info_provider_dto' => $dto,
]);

View file

@ -112,8 +112,9 @@ class PartImportExportController extends AbstractController
$ids = $request->query->get('ids', '');
$parts = $this->partsTableActionHandler->idStringToArray($ids);
if ($parts === []) {
throw new \RuntimeException('No parts found!');
if (count($parts) === 0) {
$this->addFlash('error', 'entity.export.flash.error.no_entities');
return $this->redirectToRoute('homepage');
}
//Ensure that we have access to the parts

View file

@ -29,6 +29,7 @@ use App\DataTables\PartsDataTable;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\Part;
use App\Entity\Parts\StorageLocation;
use App\Entity\Parts\Supplier;
use App\Exceptions\InvalidRegexException;
@ -43,8 +44,11 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Contracts\Translation\TranslatorInterface;
use function Symfony\Component\Translation\t;
class PartListsController extends AbstractController
{
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator)
@ -60,6 +64,7 @@ class PartListsController extends AbstractController
$ids = $request->request->get('ids');
$action = $request->request->get('action');
$target = $request->request->get('target');
$redirectResponse = null;
if (!$this->isCsrfTokenValid('table_action', $request->request->get('_token'))) {
$this->addFlash('error', 'csfr_invalid');
@ -70,17 +75,36 @@ class PartListsController extends AbstractController
if (null === $action || null === $ids) {
$this->addFlash('error', 'part.table.actions.no_params_given');
} else {
$errors = [];
$parts = $actionHandler->idStringToArray($ids);
$redirectResponse = $actionHandler->handleAction($action, $parts, $target ? (int) $target : null, $redirect);
$redirectResponse = $actionHandler->handleAction($action, $parts, $target ? (int) $target : null, $redirect, $errors);
//Save changes
$this->entityManager->flush();
if (count($errors) === 0) {
$this->addFlash('success', 'part.table.actions.success');
} else {
$this->addFlash('error', t('part.table.actions.error', ['%count%' => count($errors)]));
//Create a flash message for each error
foreach ($errors as $error) {
/** @var Part $part */
$part = $error['part'];
$this->addFlash('error',
t('part.table.actions.error_detail', [
'%part_name%' => $part->getName(),
'%part_id%' => $part->getID(),
'%message%' => $error['message']
])
);
}
}
}
//If the action handler returned a response, we use it, otherwise we redirect back to the previous page.
if (isset($redirectResponse) && $redirectResponse instanceof Response) {
if ($redirectResponse !== null) {
return $redirectResponse;
}
@ -131,7 +155,11 @@ class PartListsController extends AbstractController
$filterForm->handleRequest($formRequest);
$table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(['filter' => $filter], $additional_table_vars))
$table = $this->dataTableFactory->createFromType(
PartsDataTable::class,
array_merge(['filter' => $filter], $additional_table_vars),
['lengthMenu' => PartsDataTable::LENGTH_MENU]
)
->handleRequest($request);
if ($table->isCallback()) {

View file

@ -42,10 +42,10 @@ declare(strict_types=1);
namespace App\Controller;
use App\Form\LabelSystem\ScanDialogType;
use App\Services\LabelSystem\Barcodes\BarcodeScanHelper;
use App\Services\LabelSystem\Barcodes\BarcodeRedirector;
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector;
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper;
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
use Doctrine\ORM\EntityNotFoundException;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -77,14 +77,22 @@ class ScanController extends AbstractController
$mode = $form['mode']->getData();
}
$infoModeData = null;
if ($input !== null) {
try {
$scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null);
//Perform a redirect if the info mode is not enabled
if (!$form['info_mode']->getData()) {
try {
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
} catch (EntityNotFoundException) {
$this->addFlash('success', 'scan.qr_not_found');
}
} else { //Otherwise retrieve infoModeData
$infoModeData = $scan_result->getDecodedForInfoMode();
}
} catch (InvalidArgumentException) {
$this->addFlash('error', 'scan.format_unknown');
}
@ -92,6 +100,7 @@ class ScanController extends AbstractController
return $this->render('label_system/scanner/scanner.html.twig', [
'form' => $form,
'infoModeData' => $infoModeData,
]);
}
@ -109,7 +118,7 @@ class ScanController extends AbstractController
throw new InvalidArgumentException('Unknown type: '.$type);
}
//Construct the scan result manually, as we don't have a barcode here
$scan_result = new BarcodeScanResult(
$scan_result = new LocalBarcodeScanResult(
target_type: BarcodeScanHelper::QR_TYPE_MAP[$type],
target_id: $id,
//The routes are only used on the internal generated QR codes

View file

@ -29,6 +29,7 @@ use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\StorageLocation;
use App\Entity\ProjectSystem\Project;
use App\Form\Type\Helper\StructuralEntityChoiceHelper;
use App\Services\Trees\NodesListBuilder;
@ -78,6 +79,12 @@ class SelectAPIController extends AbstractController
return $this->getResponseForClass(Project::class, false);
}
#[Route(path: '/storage_location', name: 'select_storage_location')]
public function locations(): Response
{
return $this->getResponseForClass(StorageLocation::class, true);
}
#[Route(path: '/export_level', name: 'select_export_level')]
public function exportLevel(): Response
{

View file

@ -22,16 +22,17 @@ declare(strict_types=1);
*/
namespace App\Controller;
use Symfony\Component\Runtime\SymfonyRuntime;
use App\Services\Attachments\AttachmentSubmitHandler;
use App\Services\Attachments\AttachmentURLGenerator;
use App\Services\Attachments\BuiltinAttachmentsFinder;
use App\Services\Doctrine\DBInfoHelper;
use App\Services\Doctrine\NatsortDebugHelper;
use App\Services\Misc\GitVersionInfo;
use App\Services\Misc\DBInfoHelper;
use App\Services\System\UpdateAvailableManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Runtime\SymfonyRuntime;
#[Route(path: '/tools')]
class ToolsController extends AbstractController
@ -45,7 +46,7 @@ class ToolsController extends AbstractController
}
#[Route(path: '/server_infos', name: 'tools_server_infos')]
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper,
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper, NatsortDebugHelper $natsortDebugHelper,
AttachmentSubmitHandler $attachmentSubmitHandler, UpdateAvailableManager $updateAvailableManager): Response
{
$this->denyAccessUnlessGranted('@system.server_infos');
@ -60,10 +61,10 @@ class ToolsController extends AbstractController
'default_theme' => $this->getParameter('partdb.global_theme'),
'enabled_locales' => $this->getParameter('partdb.locale_menu'),
'demo_mode' => $this->getParameter('partdb.demo_mode'),
'gpdr_compliance' => $this->getParameter('partdb.gdpr_compliance'),
'gdpr_compliance' => $this->getParameter('partdb.gdpr_compliance'),
'use_gravatar' => $this->getParameter('partdb.users.use_gravatar'),
'email_password_reset' => $this->getParameter('partdb.users.email_pw_reset'),
'enviroment' => $this->getParameter('kernel.environment'),
'environment' => $this->getParameter('kernel.environment'),
'is_debug' => $this->getParameter('kernel.debug'),
'email_sender' => $this->getParameter('partdb.mail.sender_email'),
'email_sender_name' => $this->getParameter('partdb.mail.sender_name'),
@ -93,6 +94,8 @@ class ToolsController extends AbstractController
'db_size' => $DBInfoHelper->getDatabaseSize(),
'db_name' => $DBInfoHelper->getDatabaseName() ?? 'Unknown',
'db_user' => $DBInfoHelper->getDatabaseUsername() ?? 'Unknown',
'db_natsort_method' => $natsortDebugHelper->getNaturalSortMethod(),
'db_natsort_slow_allowed' => $natsortDebugHelper->isSlowNaturalSortAllowed(),
//New version section
'new_version_available' => $updateAvailableManager->isUpdateAvailable(),

View file

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Controller;
use App\Entity\Parameters\AbstractParameter;
use Symfony\Component\HttpFoundation\Response;
use App\Entity\Attachments\Attachment;
use App\Entity\Parts\Category;
@ -92,7 +93,7 @@ class TypeaheadController extends AbstractController
/**
* This function map the parameter type to the class, so we can access its repository
* @return class-string
* @return class-string<AbstractParameter>
*/
private function typeToParameterClass(string $type): string
{
@ -155,7 +156,7 @@ class TypeaheadController extends AbstractController
//Ensure user has the correct permissions
$this->denyAccessUnlessGranted('read', $test_obj);
/** @var ParameterRepository $repository */
/** @var ParameterRepository<AbstractParameter> $repository */
$repository = $entityManager->getRepository($class);
$data = $repository->autocompleteParamName($query);

View file

@ -240,7 +240,10 @@ class UserSettingsController extends AbstractController
$page_need_reload = true;
}
/** @var Form $form We need a form implementation for the next calls */
if (!$form instanceof Form) {
throw new RuntimeException('Form is not an instance of Form, so we cannot retrieve the clicked button!');
}
//Remove the avatar attachment from the user if requested
if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName() && $user->getMasterPictureAttachment() instanceof Attachment) {
$em->remove($user->getMasterPictureAttachment());

View file

@ -41,7 +41,7 @@ class APITokenFixtures extends Fixture implements DependentFixtureInterface
public function load(ObjectManager $manager): void
{
/** @var User $admin_user */
$admin_user = $this->getReference(UserFixtures::ADMIN);
$admin_user = $this->getReference(UserFixtures::ADMIN, User::class);
$read_only_token = new ApiToken();
$read_only_token->setUser($admin_user);

View file

@ -35,7 +35,7 @@ use Doctrine\Persistence\ObjectManager;
class LogEntryFixtures extends Fixture implements DependentFixtureInterface
{
public function load(ObjectManager $manager)
public function load(ObjectManager $manager): void
{
$this->createCategoryEntries($manager);
$this->createDeletedCategory($manager);

View file

@ -106,7 +106,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
$partLot2->setComment('Test');
$partLot2->setNeedsRefill(true);
$partLot2->setStorageLocation($manager->find(StorageLocation::class, 3));
$partLot2->setVendorBarcode('lot2_vendor_barcode');
$partLot2->setUserBarcode('lot2_vendor_barcode');
$part->addPartLot($partLot2);
$orderdetail = new Orderdetail();
@ -131,7 +131,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
$attachment = new PartAttachment();
$attachment->setName('Test2');
$attachment->setPath('invalid');
$attachment->setInternalPath('invalid');
$attachment->setShowInTable(true);
$attachment->setAttachmentType($manager->find(AttachmentType::class, 1));
$part->addAttachment($attachment);

View file

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\DataFixtures;
use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
@ -41,7 +42,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
{
$anonymous = new User();
$anonymous->setName('anonymous');
$anonymous->setGroup($this->getReference(GroupFixtures::READONLY));
$anonymous->setGroup($this->getReference(GroupFixtures::READONLY, Group::class));
$anonymous->setNeedPwChange(false);
$anonymous->setPassword($this->encoder->hashPassword($anonymous, 'test'));
$manager->persist($anonymous);
@ -50,7 +51,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
$admin->setName('admin');
$admin->setPassword($this->encoder->hashPassword($admin, 'test'));
$admin->setNeedPwChange(false);
$admin->setGroup($this->getReference(GroupFixtures::ADMINS));
$admin->setGroup($this->getReference(GroupFixtures::ADMINS, Group::class));
$manager->persist($admin);
$this->addReference(self::ADMIN, $admin);
@ -60,7 +61,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
$user->setEmail('user@invalid.invalid');
$user->setFirstName('Test')->setLastName('User');
$user->setPassword($this->encoder->hashPassword($user, 'test'));
$user->setGroup($this->getReference(GroupFixtures::USERS));
$user->setGroup($this->getReference(GroupFixtures::USERS, Group::class));
$manager->persist($user);
$noread = new User();

View file

@ -54,7 +54,7 @@ class TwoStepORMAdapter extends ORMAdapter
private \Closure|null $query_modifier = null;
public function __construct(ManagerRegistry $registry = null)
public function __construct(?ManagerRegistry $registry = null)
{
parent::__construct($registry);
$this->detailQueryCallable = static function (QueryBuilder $qb, array $ids): never {

View file

@ -34,6 +34,7 @@ use App\Services\EntityURLGenerator;
use Doctrine\ORM\QueryBuilder;
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
use Omines\DataTablesBundle\Column\NumberColumn;
use Omines\DataTablesBundle\Column\TextColumn;
use Omines\DataTablesBundle\DataTable;
use Omines\DataTablesBundle\DataTableTypeInterface;
@ -49,8 +50,8 @@ final class AttachmentDataTable implements DataTableTypeInterface
{
$dataTable->add('dont_matter', RowClassColumn::class, [
'render' => function ($value, Attachment $context): string {
//Mark attachments with missing files yellow
if(!$this->attachmentHelper->isFileExisting($context)){
//Mark attachments yellow which have an internal file linked that doesn't exist
if($context->hasInternal() && !$this->attachmentHelper->isInternalFileExisting($context)){
return 'table-warning';
}
@ -63,8 +64,8 @@ final class AttachmentDataTable implements DataTableTypeInterface
'className' => 'no-colvis',
'render' => function ($value, Attachment $context): string {
if ($context->isPicture()
&& !$context->isExternal()
&& $this->attachmentHelper->isFileExisting($context)) {
&& $this->attachmentHelper->isInternalFileExisting($context)) {
$title = htmlspecialchars($context->getName());
if ($context->getFilename()) {
$title .= ' ('.htmlspecialchars($context->getFilename()).')';
@ -84,29 +85,14 @@ final class AttachmentDataTable implements DataTableTypeInterface
},
]);
$dataTable->add('id', NumberColumn::class, [
'label' => $this->translator->trans('part.table.id'),
'visible' => false,
]);
$dataTable->add('name', TextColumn::class, [
'label' => 'attachment.edit.name',
'orderField' => 'NATSORT(attachment.name)',
'render' => function ($value, Attachment $context) {
//Link to external source
if ($context->isExternal()) {
return sprintf(
'<a href="%s" class="link-external">%s</a>',
htmlspecialchars((string) $context->getURL()),
htmlspecialchars($value)
);
}
if ($this->attachmentHelper->isFileExisting($context)) {
return sprintf(
'<a href="%s" target="_blank" data-no-ajax>%s</a>',
$this->entityURLGenerator->viewURL($context),
htmlspecialchars($value)
);
}
return $value;
},
]);
$dataTable->add('attachment_type', TextColumn::class, [
@ -130,25 +116,60 @@ final class AttachmentDataTable implements DataTableTypeInterface
),
]);
$dataTable->add('filename', TextColumn::class, [
'label' => $this->translator->trans('attachment.table.filename'),
$dataTable->add('internal_link', TextColumn::class, [
'label' => 'attachment.table.internal_file',
'propertyPath' => 'filename',
'orderField' => 'NATSORT(attachment.original_filename)',
'render' => function ($value, Attachment $context) {
if ($this->attachmentHelper->isInternalFileExisting($context)) {
return sprintf(
'<a href="%s" target="_blank" data-no-ajax>%s</a>',
$this->entityURLGenerator->viewURL($context),
htmlspecialchars($value)
);
}
return $value;
}
]);
$dataTable->add('external_link', TextColumn::class, [
'label' => 'attachment.table.external_link',
'propertyPath' => 'host',
'orderField' => 'attachment.external_path',
'render' => function ($value, Attachment $context) {
if ($context->hasExternal()) {
return sprintf(
'<a href="%s" class="link-external" title="%s" target="_blank" rel="noopener">%s</a>',
htmlspecialchars((string) $context->getExternalPath()),
htmlspecialchars((string) $context->getExternalPath()),
htmlspecialchars($value),
);
}
return $value;
}
]);
$dataTable->add('filesize', TextColumn::class, [
'label' => $this->translator->trans('attachment.table.filesize'),
'render' => function ($value, Attachment $context) {
if ($context->isExternal()) {
if (!$context->hasInternal()) {
return sprintf(
'<span class="badge bg-primary">
<i class="fas fa-globe fa-fw"></i>%s
</span>',
$this->translator->trans('attachment.external')
$this->translator->trans('attachment.external_only')
);
}
if ($this->attachmentHelper->isFileExisting($context)) {
return $this->attachmentHelper->getHumanFileSize($context);
if ($this->attachmentHelper->isInternalFileExisting($context)) {
return sprintf(
'<span class="badge bg-secondary">
<i class="fas fa-hdd fa-fw"></i> %s
</span>',
$this->attachmentHelper->getHumanFileSize($context)
);
}
return sprintf(

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