Compare commits

...

374 Commits

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

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

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations validators.en.xlf (Italian)

* New translations security.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

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

* New translations messages.en.xlf (German)

* New translations validators.en.xlf (German)

* New translations messages.en.xlf (Italian)

* New translations validators.en.xlf (Italian)

* New translations security.en.xlf (Italian)

* New translations messages.en.xlf (Japanese)

* New translations validators.en.xlf (Japanese)

* New translations messages.en.xlf (Russian)

* New translations validators.en.xlf (Russian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (English)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

* New translations messages.en.xlf (Italian)

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

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

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

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

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

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

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

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

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

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

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

Fixes issue #339
2023-08-01 15:31:40 +02:00
Jan Böhmer
9bf814d4cd Fixed error when StructuralEntityChoice type was used for non structural entities. 2023-08-01 15:14:32 +02:00
Jan Böhmer
b5c0f37f88 Fixed exception on visiting certain group edit pages.
This fixes issue #340
2023-08-01 15:06:57 +02:00
Jan Böhmer
05d73d2f68 RELEASE v1.6.0 2023-08-01 00:22:29 +02:00
Jan Böhmer
ff284d056c Updated dependencies. 2023-08-01 00:22:03 +02:00
Jan Böhmer
2393c759f3 Merge branch 'octopart-ip' 2023-07-31 22:43:19 +02:00
Jan Böhmer
0a983513ea Added documentation for the octopart provider. 2023-07-31 22:43:14 +02:00
Jan Böhmer
c737348cea Dont skip the Sqlite migration. Do nothing instead
Before this migration was never finished and always re executed on subsequent migration attempts.
2023-07-31 22:30:02 +02:00
Jan Böhmer
e8e2258357 Added migration for SQLite 2023-07-31 22:28:19 +02:00
Jan Böhmer
d847b74522 Reenable caching for part provider system. 2023-07-31 22:21:20 +02:00
Jan Böhmer
5750e7dbdf Fixed exception if octopart did not returned an category for a part 2023-07-31 22:21:08 +02:00
Jan Böhmer
ffe76f9d2e Use full path for categories returned by Octopart 2023-07-31 22:17:24 +02:00
Jan Böhmer
5b1ad541a8 Added an caching layer to octopart provider to reduce API queries. 2023-07-31 22:11:46 +02:00
Jan Böhmer
4824a82c3f Fixed error, when octopart search got no results 2023-07-31 01:00:34 +02:00
Jan Böhmer
f7cea1100c Allow to configure various options of the octopart provider 2023-07-31 00:57:33 +02:00
Jan Böhmer
827dd01e28 Get shopping informations from octopart 2023-07-31 00:13:59 +02:00
Jan Böhmer
d969f49ecc Get specifications, mass, footprint and manufacturer status from Octopart 2023-07-31 00:01:19 +02:00
Jan Böhmer
0f336b6f89 Allow for longer manufacturer and supplier URLs 2023-07-31 00:00:57 +02:00
Jan Böhmer
43cc37d10f Added very basic octopart info provider 2023-07-30 23:04:18 +02:00
Jan Böhmer
6a00b8e168 Added possibility to manage client_credentials OAuth tokens 2023-07-30 15:24:46 +02:00
Jan Böhmer
1b3fc2c23c Fixed PHPUnit exception 2023-07-29 19:03:46 +02:00
Jan Böhmer
8e96971b9c Merge remote-tracking branch 'origin/l10n_master' 2023-07-29 18:55:48 +02:00
Jan Böhmer
8e39e330fe Updated dependencies. 2023-07-29 18:55:24 +02:00
Jan Böhmer
15ba6572df Try to detect if an external attachment is an image or not.
Before we assumed that it is a picture in all cases.
2023-07-29 18:50:18 +02:00
Jan Böhmer
1c222ff293 Improved attachment name generation from DTO url
We dont include the query paramaters in the name anymore
2023-07-29 18:38:18 +02:00
Jan Böhmer
2fb1ec7f8a New translations messages.en.xlf (German) 2023-07-29 18:10:26 +02:00
Jan Böhmer
70f1db9619 New translations messages.en.xlf (German) 2023-07-29 17:10:30 +02:00
Jan Böhmer
11be65678e Fixed order by FIELD emulation on sqlite via INSTR
We need to search for the comma token separators at both ends of the search term, otherwise the ordering will be wrong.
2023-07-29 17:06:35 +02:00
Jan Böhmer
80ed064cd6 Emulate the field function in SQLIte by using an string search, instead of our PHP function callback 2023-07-29 16:50:47 +02:00
Jan Böhmer
62b1e33616 Fixed field ordering on SQLite 2023-07-29 16:42:27 +02:00
Jan Böhmer
52f2ef6d30 New translations messages.en.xlf (German) 2023-07-28 23:30:26 +02:00
Jan Böhmer
8ace78a873 New translations messages.en.xlf (German) 2023-07-28 11:50:42 +02:00
Jan Böhmer
5c30210534 Fixed some stuff on the emulated Field2 function and added tests 2023-07-27 00:38:17 +02:00
Jan Böhmer
1d03b6c38d Added tests for sqlite emulated functions 2023-07-26 23:39:53 +02:00
Jan Böhmer
d3ead8742e Implement a special field2 function, to migitiate the argument count limit in sqlite
This fixes issue #332 on SQLite DBs
2023-07-26 23:23:25 +02:00
Jan Böhmer
ed6b0057b7 Make the manufacturer field optional on part manufacturer data during Partkeepr import
This should fix issue #333
2023-07-26 22:55:18 +02:00
Jan Böhmer
90fbcb88d8 Updated dependencies 2023-07-23 23:22:00 +02:00
Jan Böhmer
be2ed52d93 Fixed phpstan issues 2023-07-23 23:04:45 +02:00
Jan Böhmer
f1af19a52a Fixed wrong background color for structural links 2023-07-23 23:02:04 +02:00
Jan Böhmer
4c4501073c Use a two step approach to retrieve part tables
This allows us to combine the fast filtering with the fetch joins, which should speed up the datatables a lot.
2023-07-23 21:48:38 +02:00
Jan Böhmer
b8b9a416ac Fixed wrong type of the supplier filter 2023-07-23 21:33:50 +02:00
Jan Böhmer
fc7e436ca9 Improved image and attachment viewing on part info page 2023-07-23 01:32:28 +02:00
Jan Böhmer
1de1eebc59 Fixed PHPstan issues 2023-07-23 01:20:38 +02:00
Jan Böhmer
a4d411656b Added a field extension for SQLite and let the database sort the elements by the given ID order directly 2023-07-23 01:19:48 +02:00
Jan Böhmer
1ec4266f96 Fixed sorting for element choice type and added tests 2023-07-23 01:01:29 +02:00
Jan Böhmer
61f02d693f Added info about alternative names to info provider documentation 2023-07-23 00:29:16 +02:00
Jan Böhmer
cd80552ce7 Fixed PHPstan issues 2023-07-23 00:14:29 +02:00
Jan Böhmer
d59b8817c3 Do not use fetch join, as even with the N+1 problem the queries are faster than with the very complex and slow expressions needed for the fetch Join pagination 2023-07-22 23:51:06 +02:00
Jan Böhmer
8ce5f4a796 Do not cache entities directly in NodesListBuilder but cache only the IDs instead
Otherwise the doctrine proxies break, and we get issues with loading the preview_images in structural Elements.
2023-07-20 23:20:46 +02:00
Jan Böhmer
2e8cb35acc Use ManyToOne instead of OneToOne for master_picture_attachment like before 2023-07-20 21:54:13 +02:00
Jan Böhmer
c0f626e9bd Fixed phpstan issue 2023-07-19 23:01:29 +02:00
Jan Böhmer
2ddfe48aba Do the doctrine definitions of the master preview attachment in the sub classes
This makes lazy fetching works and saves some db queries.
2023-07-19 22:49:40 +02:00
Jan Böhmer
2c6de84c9a Only check every 10 minutes if the user needs to setup a 2FA method enforced by its group
That saves us 3 database queries on many requests.
2023-07-19 00:56:28 +02:00
Jan Böhmer
87cf4c2d08 Do a extra lazy fetch on webauthn and u2f keys property of a user
In the most cases we just need the count of them and not the exact details. This just gives as a bit better performance on most requests
2023-07-19 00:20:38 +02:00
Jan Böhmer
3a8c835880 Lazily sort info providers list, to prevent unneccesarry work on most container calls 2023-07-19 00:11:19 +02:00
Jan Böhmer
e2dbd3d873 Fixed bug, that prevented all related history entries for an element
As the query param had the same name for each iteration, it got overwritten every time
2023-07-19 00:01:42 +02:00
Jan Böhmer
55aabddd41 Allow to sort by user in log data table 2023-07-18 23:45:52 +02:00
Jan Böhmer
eb07820523 Fixed issue that prevented undo changes from being logged 2023-07-18 23:42:01 +02:00
Jan Böhmer
8116217019 Fixed exception when undo/revert to a change which contained a change to a enum property 2023-07-18 23:34:45 +02:00
Jan Böhmer
6fb1845ff7 Use fixed version of jbtronics/2fa-webauthn-bundle 2023-07-17 23:46:12 +02:00
Jan Böhmer
75325f0ed8 Added some trademark disclaimer to Part-DB documentation 2023-07-17 23:28:24 +02:00
Jan Böhmer
0577f9e166 Added some documentation about caching 2023-07-17 23:25:22 +02:00
Jan Böhmer
52c6884e28 Allow slashes in digikey product ids 2023-07-17 23:21:30 +02:00
Jan Böhmer
2be76a488f Added documentation for part provider system 2023-07-17 23:14:35 +02:00
Jan Böhmer
686535fe42 New translations messages.en.xlf (English) 2023-07-17 00:53:09 +02:00
Jan Böhmer
5c17aee1e4 Properly escape group table name
Otherwise MySQL 8 will not be able to use this schema
2023-07-17 00:49:25 +02:00
Jan Böhmer
9b35ac3a99 Updated yarn dependencies. 2023-07-17 00:45:46 +02:00
Jan Böhmer
59b78e850f Merge branch 'part_info_provider_integration' 2023-07-17 00:43:51 +02:00
Jan Böhmer
3a8c5a788f Fixed phpstan issues 2023-07-17 00:43:35 +02:00
Jan Böhmer
afcbbe0f43 Fixed phpunit tests 2023-07-17 00:34:00 +02:00
Jan Böhmer
d10d29e590 Do not enable the create part from provider permission automatically
This allows users to create new datastructures, which is maybe not wanted. Besides it has to be configured first.
2023-07-17 00:20:38 +02:00
Jan Böhmer
7b61cb3163 Added more env variables to configure providers 2023-07-17 00:19:02 +02:00
Jan Böhmer
4c1c6701b3 Test availability of the info provider pages 2023-07-16 23:56:30 +02:00
Jan Böhmer
f423fdf7f8 Fixed bug in DB schema, which prevented the creation of parts without info provider reference 2023-07-16 23:48:55 +02:00
Jan Böhmer
a5995a2ce8 Centralized logic for part creation form 2023-07-16 23:46:20 +02:00
Jan Böhmer
c810b6772c Show the value returned by the provider on part creation page.
This makes it easier to check or assign a element manually
2023-07-16 23:19:02 +02:00
Jan Böhmer
b74ab18a6d Added possibility to define alternative names on data structures
This can be used to find elements, based on the data returned by info providers
2023-07-16 22:59:46 +02:00
Jan Böhmer
edc54aaf91 Added migrations for sqlite and new additional_names field 2023-07-16 20:47:25 +02:00
Jan Böhmer
b3b205cd6e Added permissions to control access to info providers and oauth tokens 2023-07-16 20:33:24 +02:00
Jan Böhmer
2fe4def775 New translations messages.en.xlf (Chinese Simplified) 2023-07-16 20:30:24 +02:00
Jan Böhmer
7bbf612394 Fixed title of info providers list 2023-07-16 20:09:20 +02:00
Jan Böhmer
97ab1f0492 Improved search page 2023-07-16 20:05:11 +02:00
Jan Böhmer
81bfcdd158 New translations messages.en.xlf (Chinese Simplified) 2023-07-16 19:30:23 +02:00
Jan Böhmer
6862d318f0 Cache the DTO objects returned by the info providers
This saves API requests
2023-07-16 19:05:26 +02:00
Jan Böhmer
412fa3f0bf Get datasheets and category from digikey 2023-07-16 18:35:44 +02:00
Jan Böhmer
01d9109c45 Improved digikey provider 2023-07-16 17:10:48 +02:00
Jan Böhmer
dd914d1d64 New translations messages.en.xlf (Chinese Simplified) 2023-07-16 16:56:03 +02:00
Jan Böhmer
5cf3624a3a New translations messages.en.xlf (Chinese Simplified) 2023-07-16 13:20:24 +02:00
Jan Böhmer
c7ff8c2dd1 New translations messages.en.xlf (Chinese Simplified) 2023-07-16 12:20:29 +02:00
Jan Böhmer
83c202d675 New translations messages.en.xlf (Chinese Simplified) 2023-07-16 11:20:25 +02:00
Jan Böhmer
f7648e3311 Added an button to connect the oauth providers from WebUI 2023-07-16 03:18:33 +02:00
Jan Böhmer
c203de082e Added proper OAuth authentication for digikey and other providers 2023-07-16 03:07:53 +02:00
Jan Böhmer
a95ba1acc4 Add a reference to the used info provider to a part 2023-07-16 01:24:49 +02:00
Jan Böhmer
db325525e4 New translations messages.en.xlf (Chinese Simplified) 2023-07-15 22:30:25 +02:00
Jan Böhmer
db97114fb4 Use preview image and other additional images provided by the info provider 2023-07-15 21:41:35 +02:00
Jan Böhmer
b18a300f10 New translations validators.en.xlf (Chinese Simplified) 2023-07-15 21:30:27 +02:00
Jan Böhmer
61ffb857ee New translations messages.en.xlf (Chinese Simplified) 2023-07-15 21:30:26 +02:00
Jan Böhmer
701212239d Use an experimental doctrine/orm version to fix some issues persisting attachments while simutanously creating a new attachment type
The circular reference between attachmentTypeAttachment and attachmentType seems to confuse doctrine. This is fixed in the experimental version
2023-07-15 21:17:10 +02:00
Jan Böhmer
422fa01c6f Use the initial element for database if the value was not changed. 2023-07-15 21:00:45 +02:00
Jan Böhmer
62820b4dd9 New translations validators.en.xlf (Chinese Simplified) 2023-07-15 20:30:54 +02:00
Jan Böhmer
8ea92ef330 Added tests for DTOConverter 2023-07-15 18:18:35 +02:00
Jan Böhmer
de82249d8d Provide footprint information on TMEProvider 2023-07-15 01:52:46 +02:00
Jan Böhmer
94a26ae75a Allow to extract ranges from paramaters 2023-07-15 01:41:29 +02:00
Jan Böhmer
f9fdae9de9 Added an TME data provider 2023-07-15 01:01:20 +02:00
Jan Böhmer
0cb46039dd Allow to retrieve price and shopping informations from info providers 2023-07-14 00:09:22 +02:00
Jan Böhmer
c4439cc9db Mark newly created entities better in structural entity selector 2023-07-12 23:58:40 +02:00
Jan Böhmer
6cd9640b30 Allow to automatically find or create entities from database based on info providers 2023-07-12 23:43:16 +02:00
Jan Böhmer
f9bce3dfdb Merge branch 'master' into part_info_provider_integration 2023-07-11 22:04:06 +02:00
Jan Böhmer
8eb0c997ed Updated dependencies 2023-07-11 22:01:32 +02:00
Jan Böhmer
a8f96e06bd Automatically whitelist the SAML IIDP domain for external redirect
This fixes issue #318
2023-07-11 21:50:55 +02:00
Jan Böhmer
9a2945927f New translations security.en.xlf (Chinese Simplified) 2023-07-10 23:36:28 +02:00
Jan Böhmer
89f9249ec6 New translations messages.en.xlf (Chinese Simplified) 2023-07-10 23:36:27 +02:00
Jan Böhmer
24f572253f New translations messages.en.xlf (Chinese Simplified) 2023-07-10 22:36:25 +02:00
Jan Böhmer
4fc9c19893 Removed useless tabs in code blocks in installation guide 2023-07-10 13:26:07 +02:00
Jan Böhmer
f6fcd730a8 Added how you checkout a stable release in the installation guide
Related to discussion #317
2023-07-10 13:20:44 +02:00
Jan Böhmer
716a56979d Added basic possibilty to create parts based on infoProviders 2023-07-09 23:31:40 +02:00
Jan Böhmer
538476be99 Added a info provider for element14/Farnell 2023-07-09 18:51:54 +02:00
Jan Böhmer
93a170a893 Added basic search system in info providers 2023-07-09 17:55:41 +02:00
Jan Böhmer
e0301f096f Added an very basic system to configure info providers 2023-07-09 14:27:41 +02:00
Jan Böhmer
9e3cb4d694 Use enum for manufacturing status 2023-07-08 23:49:47 +02:00
Jan Böhmer
49b76c3e43 New translations messages.en.xlf (English) 2023-07-08 23:16:14 +02:00
Jan Böhmer
9962784991 New translations messages.en.xlf (Chinese Simplified) 2023-07-08 23:16:12 +02:00
Jan Böhmer
6336b38cfc Bumped version to 1.6.0-dev 2023-07-08 23:07:33 +02:00
Jan Böhmer
2362835275 Merge branch 'user_impersonator' 2023-07-08 23:07:12 +02:00
Jan Böhmer
8a4ede9d43 Write to event log, when a user gets impersonated 2023-07-08 23:07:06 +02:00
Jan Böhmer
cc1595e048 Added button to user admin to impersonate a certain user 2023-07-08 22:43:41 +02:00
Jan Böhmer
ca16763423 Bumped version to 1.5.1 2023-07-08 21:11:55 +02:00
Jan Böhmer
b6dd5bb881 Fixed ordering columns of tables when columns were reordered 2023-07-08 20:16:52 +02:00
Jan Böhmer
f8e299ec56 Added new env option to show all parts on a page by default
Related to discussion #312
2023-07-08 19:33:23 +02:00
Jan Böhmer
91e9c6e048 Use bootstrap popover for title attribute in datatables 2023-07-08 19:08:00 +02:00
Jan Böhmer
b941b97eee Show full paths of elements on hover in part tables
Related to discussion #312
2023-07-08 19:02:43 +02:00
Jan Böhmer
d38ac652fc Do not cut QR code on small label pages
Fixes issue #314
2023-07-08 18:46:29 +02:00
Jan Böhmer
bdcf3b71ce Fixed exception when parameter constraint unit field is empty 2023-07-08 18:39:44 +02:00
Jan Böhmer
ddbf8b7725 Fixed phpstan issue 2023-07-04 00:35:57 +02:00
Jan Böhmer
a6fd4547a7 Bumped version to 1.5.1 2023-07-04 00:31:37 +02:00
Jan Böhmer
d20153c569 Added basic logic for impersonation 2023-07-04 00:31:13 +02:00
Jan Böhmer
15e072a2ff Fixed exception when the calculated minimum_order_price is null
This fixes issue #311
2023-07-03 23:41:39 +02:00
Jan Böhmer
f98e20aa84 Fixed errors importing partkeepr databases 2023-07-03 23:33:45 +02:00
Jan Böhmer
e7a1b33ae6 Allow to set the exchange rate of a currency to null (not existing) after it was set once 2023-07-03 22:15:58 +02:00
Jan Böhmer
2d5f23271f Force that an currency has an iso currency code
Otherwise it will crash a lot of formatter code (and a currency which is not existing is not really useful)
2023-07-03 22:11:12 +02:00
Jan Böhmer
059110ae7a Improved styling of a info level flash toast in darkmode 2023-07-03 22:01:39 +02:00
Jan Böhmer
ac84c175af Bumped version to 1.5.0 2023-07-03 00:59:12 +02:00
Jan Böhmer
3b6014c229 Updated dependencies 2023-07-03 00:58:43 +02:00
Jan Böhmer
9cb265c6f5 Improved margin between darkmode label and darkmode selector 2023-07-03 00:55:11 +02:00
Jan Böhmer
a47f0ccc12 Fixed phpstan issue 2023-07-03 00:38:14 +02:00
Jan Böhmer
e032f6b33d Use root node of sidebar trees as link to link to all parts list 2023-07-03 00:34:37 +02:00
Jan Böhmer
98e179ba06 Validate bom when adding additional bom entries via addPart controller to prevent invalid BOMs
This fixes issue #302
2023-07-03 00:28:37 +02:00
Jan Böhmer
2ebb4fef4c Added some tests to constraint validators 2023-07-02 23:59:06 +02:00
Jan Böhmer
e72b120c12 Use new UniqueObjectCollection constraint to ensure that BOM entries does not contain duplicate items 2023-07-02 20:49:10 +02:00
Jan Böhmer
7b87b00b44 Properly reset the page length when reloading a datatable
Fixes issue #309
2023-07-02 19:44:26 +02:00
Jan Böhmer
2b793bf242 Fixed tests 2023-07-02 17:46:09 +02:00
Jan Böhmer
49ae906029 Allow to directly specify the scanned string via an input query parameter 2023-07-02 14:16:32 +02:00
Jan Böhmer
4f82a0f026 Fixed qr code scan URL paths 2023-07-02 14:03:29 +02:00
Jan Böhmer
ae8edffdc8 Put the dompdf fonts and temp files in a folder inside var/dompdf, which should always be writable by the server process 2023-07-02 13:57:15 +02:00
Jan Böhmer
2b67c1c631 Use development version of dompdf, so we can use character level fallback fonts for dompdf
This is useful, so mixed character text is always shown and you not need to explicitly select unifont as font
2023-07-02 03:36:42 +02:00
Jan Böhmer
d395cf66a0 Fixed problem with label additional styles for labels 2023-07-02 03:28:17 +02:00
Jan Böhmer
956ed9e8ae Added GNU unifont as fallback font for labels with CJK characters 2023-07-02 03:26:56 +02:00
Jan Böhmer
63726b09d6 New translations security.en.xlf (English) 2023-06-30 00:26:50 +02:00
Jan Böhmer
f78d42cc30 New translations validators.en.xlf (English) 2023-06-30 00:26:49 +02:00
Jan Böhmer
5d1c807a86 New translations messages.en.xlf (English) 2023-06-30 00:26:48 +02:00
Jan Böhmer
fad077aad9 New translations security.en.xlf (Chinese Simplified) 2023-06-30 00:26:47 +02:00
Jan Böhmer
24f47bee73 New translations validators.en.xlf (Chinese Simplified) 2023-06-30 00:26:46 +02:00
Jan Böhmer
d93b7b2cb2 New translations messages.en.xlf (Chinese Simplified) 2023-06-30 00:26:45 +02:00
Jan Böhmer
ab03111a84 New translations security.en.xlf (German) 2023-06-30 00:26:38 +02:00
Jan Böhmer
1ba03b69f6 New translations validators.en.xlf (German) 2023-06-30 00:26:37 +02:00
Jan Böhmer
9957f64628 New translations messages.en.xlf (German) 2023-06-30 00:26:36 +02:00
Jan Böhmer
6505af2a8b Disable translation file linting as it causes problems with empty translation files 2023-06-29 23:37:42 +02:00
Jan Böhmer
54c74bac6e Show in README that we now require PHP 8.1 2023-06-29 23:35:06 +02:00
Jan Böhmer
5cf4c879dd Merge remote-tracking branch 'origin/l10n_master' 2023-06-29 23:32:12 +02:00
Jan Böhmer
aa00db48ce Updated dependencies 2023-06-29 23:31:17 +02:00
Jan Böhmer
4a158db632 New translations messages.en.xlf (German) 2023-06-29 23:26:32 +02:00
Jan Böhmer
f57a0ecba2 Use correct permission name 'show_private' instead of 'show_secure' in templates
This fixes issue #307
2023-06-28 16:30:16 +02:00
Jan Böhmer
3614c82632 Use light background for image on part page 2023-06-28 16:21:03 +02:00
Jan Böhmer
9207d41f17 New translations messages.en.xlf (English) 2023-06-28 16:17:50 +02:00
Jan Böhmer
c5abd0ff3f New translations security.en.xlf (Chinese Traditional) 2023-06-28 16:17:49 +02:00
Jan Böhmer
2c2bde1e05 New translations validators.en.xlf (Chinese Traditional) 2023-06-28 16:17:48 +02:00
Jan Böhmer
dcff8c0d9a New translations messages.en.xlf (Chinese Traditional) 2023-06-28 16:17:47 +02:00
Jan Böhmer
44555e5289 New translations security.en.xlf (Chinese Simplified) 2023-06-28 16:17:45 +02:00
Jan Böhmer
9c4eff68a3 New translations validators.en.xlf (Chinese Simplified) 2023-06-28 16:17:44 +02:00
Jan Böhmer
8f9122c706 New translations messages.en.xlf (Chinese Simplified) 2023-06-28 16:17:43 +02:00
Jan Böhmer
3eb1b476dd New translations messages.en.xlf (Russian) 2023-06-28 16:17:40 +02:00
Jan Böhmer
0de9f48be4 New translations messages.en.xlf (Japanese) 2023-06-28 16:17:37 +02:00
Jan Böhmer
162b482a8b New translations messages.en.xlf (Greek) 2023-06-28 16:17:34 +02:00
Jan Böhmer
2a46358ccf New translations messages.en.xlf (German) 2023-06-28 16:17:31 +02:00
Jan Böhmer
8146d6c293 New translations messages.en.xlf (French) 2023-06-28 16:17:29 +02:00
Jan Böhmer
5ab067cf86 Use password strenght estimator when setting new password after password reset. 2023-06-28 16:12:38 +02:00
Jan Böhmer
720859197c Removed unused controller, which was used for U2F registration before 2023-06-28 16:10:18 +02:00
Jan Böhmer
ce064a0b37 Use symfony/ux-translator to translate password_strength_estimator badges 2023-06-28 16:00:11 +02:00
Jan Böhmer
bfd82fb415 Use zxcvbn-core for password strength estimator and lazy load the controller
As we can now configure the used wordbooks, we can reduce the size of the file extremly
2023-06-28 14:44:29 +02:00
Jan Böhmer
655f656781 New translations messages.en.xlf (English) 2023-06-27 01:20:33 +02:00
Jan Böhmer
3f32841f49 New translations security.en.xlf (Chinese Traditional) 2023-06-27 01:20:32 +02:00
Jan Böhmer
4d7c021925 New translations validators.en.xlf (Chinese Traditional) 2023-06-27 01:20:31 +02:00
Jan Böhmer
ac923fe669 New translations messages.en.xlf (Chinese Traditional) 2023-06-27 01:20:30 +02:00
Jan Böhmer
d35b62995e New translations security.en.xlf (Chinese Simplified) 2023-06-27 01:20:29 +02:00
Jan Böhmer
ae7d2745db New translations validators.en.xlf (Chinese Simplified) 2023-06-27 01:20:28 +02:00
Jan Böhmer
5ff47e63bc New translations messages.en.xlf (Chinese Simplified) 2023-06-27 01:20:27 +02:00
Jan Böhmer
389341f613 New translations validators.en.xlf (Russian) 2023-06-27 01:20:25 +02:00
Jan Böhmer
00b51ad40d New translations messages.en.xlf (Russian) 2023-06-27 01:20:24 +02:00
Jan Böhmer
34c39597f5 New translations validators.en.xlf (Japanese) 2023-06-27 01:20:22 +02:00
Jan Böhmer
4516e75b6f New translations messages.en.xlf (Japanese) 2023-06-27 01:20:21 +02:00
Jan Böhmer
040518cca8 New translations validators.en.xlf (German) 2023-06-27 01:20:17 +02:00
Jan Böhmer
5d336e5fb9 New translations messages.en.xlf (German) 2023-06-27 01:20:16 +02:00
Jan Böhmer
e7d0103869 New translations validators.en.xlf (French) 2023-06-27 01:20:14 +02:00
Jan Böhmer
55cb10910f New translations messages.en.xlf (French) 2023-06-27 01:20:13 +02:00
Jan Böhmer
ecded8af93 Added password meter based on zxcvbn
Maybe we will use a different package later, as this one is very big...
2023-06-27 01:07:26 +02:00
Jan Böhmer
20826daa18 Show a notice flash if the content of labels is empty
This implements the suggestion of issue #297
2023-06-27 00:18:47 +02:00
Jan Böhmer
742f1f4622 Moved add bom entries button to top of BOM table 2023-06-27 00:11:16 +02:00
Jan Böhmer
49cf20545f Fixed exception occuring when deleting an element 2023-06-27 00:10:55 +02:00
Jan Böhmer
289e6f3d1c Added translations for darkmode buttons 2023-06-27 00:02:56 +02:00
Jan Böhmer
b246d17a33 Fixed darkmode in tomselect group headers. 2023-06-26 23:59:44 +02:00
Jan Böhmer
c6b6616ee3 Added internal part number (IPN) as label placeholders
This fixes issue #306
2023-06-26 23:56:23 +02:00
Jan Böhmer
d6500c45aa Use the domain name in server_name field of Google Authenticator QR code
We achieve that by decorating the GoogleAuthenticator service
2023-06-26 23:47:54 +02:00
Jan Böhmer
6fd79688b0 Merge branch 'darkmode-migration' 2023-06-26 23:02:12 +02:00
Jan Böhmer
c6478857bc Improved dark mode 2023-06-26 23:01:32 +02:00
Jan Böhmer
8a711ffecb Added darkmode styles for CKEDITOR 2023-06-26 22:57:36 +02:00
Jan Böhmer
139ea879df Fixed deprecation notices on marked js 2023-06-26 21:48:39 +02:00
Jan Böhmer
6a0968cc02 Updated dependencies. 2023-06-26 21:37:17 +02:00
Jan Böhmer
5a1fa409d8 Do not try to reset autoincrement of sqlite test DB as this somehow cause trouble with loading fixtures... 2023-06-20 02:02:23 +02:00
Jan Böhmer
225da163bb Fixed exception on user settings submission 2023-06-20 01:43:02 +02:00
Jan Böhmer
801ed0fbaf Made tom select dark mode compatible 2023-06-20 01:30:08 +02:00
Jan Böhmer
ea44fe0f16 Fixed indention of structual element 2023-06-20 01:11:41 +02:00
Jan Böhmer
6081fe3295 Fixed darkmode for some elements 2023-06-20 01:01:40 +02:00
Jan Böhmer
6df65a0b9d Implemented a new darkmode selector using bootstrap 5.3 color mode 2023-06-19 01:08:11 +02:00
Jan Böhmer
0aec9419ec Configure fixtures load decorator only for test env, as the base command is not available in production environments (missing dev dependencies) 2023-06-18 23:49:45 +02:00
Jan Böhmer
bb510a9240 Fixed test failure, caused by validation on user element 2023-06-18 22:11:58 +02:00
Jan Böhmer
cce3e1cfb8 Specify order in which the data fixtures should be loaded 2023-06-18 22:06:42 +02:00
Jan Böhmer
4977f6c270 Reset autoincrements on SQLite with our ResetAutoIncrementPurger too and make it default for fixtures load 2023-06-18 22:06:06 +02:00
Jan Böhmer
fe1715259a Updated dependencies 2023-06-18 21:38:02 +02:00
Jan Böhmer
f4c0d84380 Bumped version to 1.5.0-dev 2023-06-18 21:33:42 +02:00
Jan Böhmer
8a20584e27 Use enum for undo mode 2023-06-18 21:26:28 +02:00
Jan Böhmer
218b0adb8f Only enable Choice and InstanceOfConstraints if a value is provided 2023-06-18 21:20:07 +02:00
Jan Böhmer
7d99607919 Use an enum for the part stock change type 2023-06-18 20:42:05 +02:00
Jan Böhmer
9adfcc7aec Use an enum for target_type in log entries 2023-06-18 18:31:39 +02:00
Jan Böhmer
2da7463edf Use a enum for level in LogEntries 2023-06-18 17:25:55 +02:00
Jan Böhmer
4a644d8712 Replaced filter classes getters with public readonly properties to improve DX 2023-06-18 16:41:00 +02:00
Jan Böhmer
afa17ca429 Explicitly convert the database size to an int to prevent type error 2023-06-18 16:12:39 +02:00
Jan Böhmer
50708c6942 Use level 5 of phpstan analysis for CI static analysis 2023-06-18 16:02:50 +02:00
Jan Böhmer
cbdf0a9392 Fixed some errors introduced by earlier typings 2023-06-18 16:01:28 +02:00
Jan Böhmer
b7c8ca2a48 Improved typing and phpdoc type annotations 2023-06-18 15:37:42 +02:00
Jan Böhmer
3817ba774d Ignore the remaining issues 2023-06-18 00:28:21 +02:00
Jan Böhmer
e8771ea118 Fixed some more phpstan issues 2023-06-18 00:00:58 +02:00
Jan Böhmer
2f46fbfc7a Added stricter phpstan checks 2023-06-14 23:14:49 +02:00
Jan Böhmer
78b0e1bf7e Fixed errors when setting setParent on a proxied AbstractStructuralDBElement 2023-06-13 21:00:25 +02:00
Jan Böhmer
19530a9102 Fixed some PHPStan level 5 issues 2023-06-13 20:24:54 +02:00
Jan Böhmer
74051c5649 Exclude tests from phpstan analysis 2023-06-13 19:06:50 +02:00
Jan Böhmer
f3f391ab43 Updated phpstan config 2023-06-13 19:01:19 +02:00
Jan Böhmer
fc75621f1a Fixed DataFixtures 2023-06-13 18:54:18 +02:00
Jan Böhmer
fc3290271c Started to increase the phpstan level 2023-06-13 10:36:34 +02:00
Jan Böhmer
71cd4057a7 Use enums for LabelOptions 2023-06-12 23:39:30 +02:00
Jan Böhmer
485b35fbd4 Fixed static analysis issues 2023-06-11 23:16:07 +02:00
Jan Böhmer
172884ace8 Updated webprofiler bundle 2023-06-11 20:00:25 +02:00
Jan Böhmer
b788c3745c Updated recipe of twig-bundle 2023-06-11 19:59:44 +02:00
Jan Böhmer
7658cfcdbd Updated symfony/stimulus-bridge recipe 2023-06-11 19:58:31 +02:00
Jan Böhmer
52c8ea13af Updated doctrine/annotations recipe 2023-06-11 19:54:50 +02:00
Jan Böhmer
8f424f3273 Removed sensio/framework-extra-bundle as it was abandoned and is not needed anymore 2023-06-11 19:52:43 +02:00
Jan Böhmer
3d7cf8f7f3 Removed remaining annotations 2023-06-11 19:42:02 +02:00
Jan Böhmer
930adaf439 Moved custom validators from annotations to attributes 2023-06-11 19:32:15 +02:00
Jan Böhmer
e5a14557a2 Fixed strict typing errors 2023-06-11 19:05:27 +02:00
Jan Böhmer
6a2ff9d153 Added declare strict types to all files 2023-06-11 18:59:07 +02:00
Jan Böhmer
bea90a7d94 Updated dependencies 2023-06-11 18:27:49 +02:00
Jan Böhmer
e57d6e508a Fixed some serializer deprecations 2023-06-11 18:12:22 +02:00
Jan Böhmer
219b57a362 Fixed some deprecations 2023-06-11 17:38:08 +02:00
Jan Böhmer
df8f54f5a4 Fixed tests for stricter typing 2023-06-11 15:32:29 +02:00
Jan Böhmer
fcbb1849ec Applied rectors phpunit 9 migrations to tests 2023-06-11 15:15:55 +02:00
Jan Böhmer
684334ba22 Improved code style of tests 2023-06-11 15:02:59 +02:00
Jan Böhmer
5629215ce4 Use imports instead of FQNs 2023-06-11 15:00:28 +02:00
Jan Böhmer
f63b6d7207 Fixed service wiring configuration 2023-06-11 14:50:47 +02:00
Jan Böhmer
98dc553938 Applied rector to test files 2023-06-11 14:18:53 +02:00
Jan Böhmer
7ee01d9a05 Applied rector with PHP8.1 migration rules 2023-06-11 14:17:19 +02:00
Jan Böhmer
dc6a67c2f0 Made the ALLOWED_ELEMENT_CLASS protected so we can apply rector
Its bad style to override a public const in a child class
2023-06-11 14:02:59 +02:00
Jan Böhmer
affed459df Updated recipe of doctrine/doctrine-bundle 2023-06-11 13:49:21 +02:00
Jan Böhmer
8d4b8b02b8 Upgraded to symfony 6.3 2023-06-11 13:46:24 +02:00
Jan Böhmer
8c430a3af0 Fixed tests 2023-06-11 13:42:45 +02:00
Jan Böhmer
b7573a40d7 Fixed webauthn two factor authentication 2023-06-11 13:14:45 +02:00
Jan Böhmer
624696711d Merge branch 'master' into php81-migration 2023-06-11 12:20:02 +02:00
Jan Böhmer
bf4725a768 New translations security.en.xlf (Chinese Traditional) 2023-06-09 11:46:55 +02:00
Jan Böhmer
ed65abf786 New translations validators.en.xlf (Chinese Traditional) 2023-06-09 11:46:54 +02:00
Jan Böhmer
8d29fe8679 New translations messages.en.xlf (Chinese Traditional) 2023-06-09 11:46:53 +02:00
Jan Böhmer
f4f77c62c8 New translations security.en.xlf (Chinese Simplified) 2023-06-09 11:46:52 +02:00
Jan Böhmer
2cc08cdea1 New translations validators.en.xlf (Chinese Simplified) 2023-06-09 11:46:52 +02:00
Jan Böhmer
03dc6d63ed New translations messages.en.xlf (Chinese Simplified) 2023-06-09 11:46:51 +02:00
Jan Böhmer
bcb3ccec9a Added translation for security key registration error message 2023-06-07 00:52:13 +02:00
Jan Böhmer
4bec8efea1 Make LogoutLoggerEventSubscriber an event listener 2023-06-07 00:46:03 +02:00
Jan Böhmer
58b2c2bd69 Merge branch 'master' into php81-migration 2023-06-06 23:46:37 +02:00
Jan Böhmer
79ab1a2277 Fixed various issues inside the tests 2023-05-28 02:16:40 +02:00
Jan Böhmer
7c03630e24 Added DB migration to fix compatibility with latest webauthn bundle 2023-05-28 02:06:48 +02:00
Jan Böhmer
34a65419c7 Use attributes as route provider (instead of annotations) 2023-05-28 01:57:49 +02:00
Jan Böhmer
7191ece7a5 Configure doctrine to use attributes instead of annotations 2023-05-28 01:55:30 +02:00
Jan Böhmer
0837f84a43 Migrated doctrine annotations to attributes 2023-05-28 01:51:13 +02:00
Jan Böhmer
0bc4699cdc Started to move doctrine annotations to attributes (rector automated) 2023-05-28 01:33:45 +02:00
Jan Böhmer
bb1285c35c Remove defaultDescription from commands, as this is now part of the annotation 2023-05-28 01:32:04 +02:00
Jan Böhmer
21fc554589 Fixed error with LogoutLoggerEventSubscriber 2023-05-28 01:25:00 +02:00
Jan Böhmer
a43af180a7 Applied rector rules up to symfony 6.2 2023-05-28 01:21:05 +02:00
Jan Böhmer
88ea920dfb Add rector for automated refactoring 2023-05-28 01:16:12 +02:00
Jan Böhmer
132aac3951 Removed config/bootstrap.php which was left over from symfony 4.4 and which caused problems with phpunit 2023-05-28 01:01:19 +02:00
Jan Böhmer
1dbf36b86b Use str_contains and similar instead of strpos 2023-05-27 23:58:28 +02:00
Jan Böhmer
508de10191 Modernized phpunit.xml.dist 2023-05-27 21:20:21 +02:00
Jan Böhmer
ccfe259c69 Updated recipe of symfony/webpack-encore 2023-05-27 21:05:03 +02:00
Jan Böhmer
0573f80525 Updated symfony/security recipe 2023-05-27 21:04:28 +02:00
Jan Böhmer
8fb4e6c4ee Updated recipe of symfony/recipe 2023-05-27 21:02:59 +02:00
Jan Böhmer
05b2515b3b Updated recipe of symfony/framework-bundle 2023-05-27 21:02:02 +02:00
Jan Böhmer
0ecb339fdf Updated recipe of scheb/2fa bundle 2023-05-27 20:53:34 +02:00
Jan Böhmer
92ddebc289 Updated recipe of php-http/discovery 2023-05-27 20:52:04 +02:00
Jan Böhmer
1a3f0675bf Updated doctrine bundle recipe 2023-05-27 20:51:05 +02:00
Jan Böhmer
c24019fd57 Fixed error preventing the service container from compiling 2023-05-27 20:46:02 +02:00
Jan Böhmer
55641a234c Require PHP 8.1 in composer.json 2023-05-27 20:40:30 +02:00
Jan Böhmer
9f52d364c9 Use newer nbgrp/onelogin-saml-bundle bundle for handling SAML 2023-05-27 20:38:32 +02:00
Jan Böhmer
edce70bc12 Updated symfony to 6.2, updated scheb/2fa bundle and removed obsolete hslavich/oneloginsaml-bundle 2023-05-27 20:35:36 +02:00
Jan Böhmer
b8a7f81f55 Bumped dependencies (dont work yet) 2023-05-27 20:25:51 +02:00
Jan Böhmer
ef9b2aefe5 Set platform in composer.json to PHP 8.1.0 and updated dependencies 2023-05-27 20:10:18 +02:00
Jan Böhmer
cd1413a74e CheckRequirementsCommand now recommends PHP 8.2 2023-05-27 20:07:03 +02:00
Jan Böhmer
4e9d93957e Removed tests for PHP 7.4 and PHP 8.0 2023-05-27 20:06:44 +02:00
688 changed files with 36990 additions and 12629 deletions

View File

@@ -28,13 +28,19 @@
PassEnv APP_ENV APP_DEBUG APP_SECRET
PassEnv TRUSTED_PROXIES TRUSTED_HOSTS LOCK_DSN
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES
PassEnv MAILER_DSN ALLOW_EMAIL_PW_RESET EMAIL_SENDER_EMAIL EMAIL_SENDER_NAME
PassEnv HISTORY_SAVE_CHANGED_FIELDS HISTORY_SAVE_CHANGED_DATA HISTORY_SAVE_REMOVED_DATA HISTORY_SAVE_NEW_DATA
PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP
PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAMLP_SP_PRIVATE_KEY
# In old version the SAML sp private key env, was wrongly named SAMLP_SP_PRIVATE_KEY, keep it for backward compatibility
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAML_SP_PRIVATE_KEY SAMLP_SP_PRIVATE_KEY
PassEnv TABLE_DEFAULT_PAGE_SIZE
PassEnv PROVIDER_DIGIKEY_CLIENT_ID PROVIDER_DIGIKEY_SECRET PROVIDER_DIGIKEY_CURRENCY PROVIDER_DIGIKEY_LANGUAGE PROVIDER_DIGIKEY_COUNTRY
PassEnv PROVIDER_ELEMENT14_KEY PROVIDER_ELEMENT14_STORE_ID
PassEnv PROVIDER_TME_KEY PROVIDER_TME_SECRET PROVIDER_TME_CURRENCY PROVIDER_TME_LANGUAGE PROVIDER_TME_COUNTRY PROVIDER_TME_GET_GROSS_PRICES
PassEnv PROVIDER_OCTOPART_CLIENT_ID PROVIDER_OCTOPART_SECRET PROVIDER_OCTOPART_CURRENCY PROVIDER_OCTOPART_COUNTRY PROVIDER_OCTOPART_SEARCH_LIMIT PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to

61
.env
View File

@@ -44,6 +44,9 @@ DEFAULT_URI="https://partdb.changeme.invalid/"
# Leave this empty, to make all change reasons optional
ENFORCE_CHANGE_COMMENTS_FOR=""
# Disable that if you do not want that Part-DB connects to GitHub to check for available updates, or if your server can not connect to the internet
CHECK_FOR_UPDATES=1
###################################################################################
# Email settings
###################################################################################
@@ -84,6 +87,62 @@ ERROR_PAGE_ADMIN_EMAIL=''
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
ERROR_PAGE_SHOW_HELP=1
##################################################################################
# Part table settings
##################################################################################
# The default page size for the part table (set to -1 to show all parts on one page)
TABLE_DEFAULT_PAGE_SIZE=50
##################################################################################
# Info provider settings
##################################################################################
# Digikey Provider:
# You can get your client id and secret from https://developer.digikey.com/
PROVIDER_DIGIKEY_CLIENT_ID=
PROVIDER_DIGIKEY_SECRET=
# The currency to get prices in
PROVIDER_DIGIKEY_CURRENCY=EUR
# The language to get results in (en, de, fr, it, es, zh, ja, ko)
PROVIDER_DIGIKEY_LANGUAGE=en
# The country to get results for
PROVIDER_DIGIKEY_COUNTRY=DE
# Farnell Provider:
# You can get your API key from https://partner.element14.com/
PROVIDER_ELEMENT14_KEY=
# Configure the store domain you want to use. This decides the language and currency of results. You can get a list of available stores from https://partner.element14.com/docs/Product_Search_API_REST__Description
PROVIDER_ELEMENT14_STORE_ID=de.farnell.com
# TME Provider:
# You can get your API key from https://developers.tme.eu/en/
PROVIDER_TME_KEY=
PROVIDER_TME_SECRET=
# The currency to get prices in
PROVIDER_TME_CURRENCY=EUR
# The language to get results in (en, de, pl)
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
PROVIDER_TME_GET_GROSS_PRICES=1
# Octopart / Nexar Provider:
# You can get your API key from https://nexar.com/api
PROVIDER_OCTOPART_CLIENT_ID=
PROVIDER_OCTOPART_SECRET=
# The currency and country to get prices for (you have to set both to get meaningful results)
# 3 letter ISO currency code (e.g. EUR, USD, GBP)
PROVIDER_OCTOPART_CURRENCY=EUR
# 2 letter ISO country code (e.g. DE, US, GB)
PROVIDER_OCTOPART_COUNTRY=DE
# The number of results to get from Octopart while searching (please note that this counts towards your API limits)
PROVIDER_OCTOPART_SEARCH_LIMIT=10
# Set to false to include non authorized offers in the results
PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS=1
###################################################################################
# SAML Single sign on-settings
###################################################################################
@@ -116,7 +175,7 @@ SAML_SP_ENTITY_ID="https://partdb.changeme.invalid/sp"
# The public certificate of the SAML SP
SAML_SP_X509_CERT="MIIC..."
# The private key of the SAML SP
SAMLP_SP_PRIVATE_KEY="MIIE..."
SAML_SP_PRIVATE_KEY="MIIE..."
######################################################################################

View File

@@ -19,7 +19,7 @@ jobs:
APP_ENV: prod
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get Composer Cache Directory
id: composer-cache

View File

@@ -17,11 +17,11 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
-
name: Docker meta
id: docker_meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
@@ -49,12 +49,12 @@ jobs:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
with:
platforms: 'arm64,arm'
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
@@ -65,7 +65,7 @@ jobs:
-
name: Build and push
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get Composer Cache Directory
id: composer-cache
@@ -38,9 +38,10 @@ jobs:
- name: Lint twig templates
run: ./bin/console lint:twig templates --env=prod
- name: Lint translations
run: ./bin/console lint:xliff translations
# This causes problems with emtpy language files
#- name: Lint translations
# run: ./bin/console lint:xliff translations
- name: Check dependencies for security
uses: symfonycorp/security-checker-action@v5

View File

@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
php-versions: [ '7.4', '8.0', '8.1', '8.2' ]
php-versions: [ '8.1', '8.2' ]
db-type: [ 'mysql', 'sqlite' ]
env:
@@ -38,7 +38,7 @@ jobs:
if: matrix.db-type == 'sqlite'
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -109,7 +109,7 @@ jobs:
run: php bin/console --env test doctrine:migrations:migrate -n
- name: Load fixtures
run: php bin/console --env test doctrine:fixtures:load -n --purger reset_autoincrement_purger
run: php bin/console --env test doctrine:fixtures:load -n
- name: Run PHPunit and generate coverage
run: ./bin/phpunit --coverage-clover=coverage.xml

View File

@@ -3,7 +3,7 @@
![Static analysis](https://github.com/Part-DB/Part-DB-symfony/workflows/Static%20analysis/badge.svg)
[![codecov](https://codecov.io/gh/Part-DB/Part-DB-symfony/branch/master/graph/badge.svg)](https://codecov.io/gh/Part-DB/Part-DB-server)
![GitHub License](https://img.shields.io/github/license/Part-DB/Part-DB-symfony)
![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%207.4-green)
![PHP Version](https://img.shields.io/badge/PHP-%3E%3D%208.1-green)
![Docker Pulls](https://img.shields.io/docker/pulls/jbtronics/part-db1)
![Docker Build Status](https://github.com/Part-DB/Part-DB-symfony/workflows/Docker%20Image%20Build/badge.svg)
@@ -51,6 +51,7 @@ Two-factor authentication is supported (Google Authenticator and Webauthn/U2F ke
* Support for multiple currencies and automatic update of exchange rates supported
* Powerful search and filter function, including parametric search (search for parts according to some specifications)
* Automatic thumbnail generation for pictures
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and prices for parts
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
@@ -60,10 +61,10 @@ Part-DB is also used by small companies and universities for managing their inve
## Requirements
* A **web server** (like Apache2 or nginx) that is capable of running [Symfony 5](https://symfony.com/doc/current/reference/requirements.html),
this includes a minimum PHP version of **PHP 7.4**
this includes a minimum PHP version of **PHP 8.1**
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite.
* Shell access to your server is highly suggested!
* For building the client side assets **yarn** and **nodejs** is needed.
* For building the client side assets **yarn** and **nodejs** (>= 18.0) is needed.
## Installation
If you want to upgrade your legacy (< 1.0.0) version of Part-DB to this version, please read [this](https://docs.part-db.de/upgrade_legacy.html) first.

View File

@@ -1 +1 @@
1.4.2
1.7.2

3
assets/bootstrap.js vendored
View File

@@ -4,8 +4,7 @@ import { startStimulusApp } from '@symfony/stimulus-bridge';
export const app = startStimulusApp(require.context(
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
true,
/\.(j|t)sx?$/
/\.[jt]sx?$/
));
// register any custom, 3rd party controllers here
// app.register('some_controller_name', SomeImportedController);

View File

@@ -181,7 +181,8 @@ Editor.defaultConfig = {
'DejaVu Serif, serif',
'Helvetica, Arial, sans-serif',
'Times New Roman, Times, serif',
'Courier New, Courier, monospace'
'Courier New, Courier, monospace',
'Unifont, monospace',
],
supportAllValues: true
},

View File

@@ -76,6 +76,7 @@ const PLACEHOLDERS = [
['[[FOOTPRINT_FULL]]', 'Footprint (Full path)'],
['[[MASS]]', 'Mass'],
['[[MPN]]', 'Manufacturer Product Number (MPN)'],
['[[IPN]]', 'Internal Part Number (IPN)'],
['[[TAGS]]', 'Tags'],
['[[M_STATUS]]', 'Manufacturing status'],
['[[DESCRIPTION]]', 'Description'],

View File

@@ -39,6 +39,7 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
'Footprint (Full path)': 'Footprint (Vollständiger Pfad)',
'Mass': 'Gewicht',
'Manufacturer Product Number (MPN)': 'Hersteller Produktnummer (MPN)',
'Internal Part Number (IPN)': 'Internal Part Number (IPN)',
'Tags': 'Tags',
'Manufacturing status': 'Herstellungsstatus',
'Description': 'Beschreibung',

View File

@@ -18,43 +18,118 @@
*/
import {Controller} from "@hotwired/stimulus";
import Darkmode from "darkmode-js/src";
import "darkmode-js"
export default class extends Controller {
_darkmode;
connect() {
if (typeof window.getComputedStyle(document.body).mixBlendMode == 'undefined') {
console.warn("The browser does not support mix blend mode. Darkmode will not work.");
this.setMode(this.getMode());
document.querySelectorAll('input[name="darkmode"]').forEach((radio) => {
radio.addEventListener('change', this._radioChanged.bind(this));
});
}
/**
* Event listener for the change of radio buttons
* @private
*/
_radioChanged(event) {
const new_mode = this.getSelectedMode();
this.setMode(new_mode);
}
/**
* Get the current mode from the local storage
* @return {'dark', 'light', 'auto'}
*/
getMode() {
return localStorage.getItem('darkmode') ?? 'auto';
}
/**
* Set the mode in the local storage and apply it and change the state of the radio buttons
* @param mode
*/
setMode(mode) {
if (mode !== 'dark' && mode !== 'light' && mode !== 'auto') {
console.warn('Invalid darkmode mode: ' + mode);
mode = 'auto';
}
localStorage.setItem('darkmode', mode);
this.setButtonMode(mode);
if (mode === 'auto') {
this._setDarkmodeAuto();
} else if (mode === 'dark') {
this._enableDarkmode();
} else if (mode === 'light') {
this._disableDarkmode();
}
}
/**
* Get the selected mode via the radio buttons
* @return {'dark', 'light', 'auto'}
*/
getSelectedMode() {
return document.querySelector('input[name="darkmode"]:checked').value;
}
/**
* Set the state of the radio buttons
* @param mode
*/
setButtonMode(mode) {
document.querySelector('input[name="darkmode"][value="' + mode + '"]').checked = true;
}
/**
* Enable darkmode by adding the data-bs-theme="dark" to the html tag
* @private
*/
_enableDarkmode() {
//Add data-bs-theme="dark" to the html tag
document.documentElement.setAttribute('data-bs-theme', 'dark');
}
/**
* Disable darkmode by adding the data-bs-theme="light" to the html tag
* @private
*/
_disableDarkmode() {
//Set data-bs-theme to light
document.documentElement.setAttribute('data-bs-theme', 'light');
}
/**
* Set the darkmode to auto and enable/disable it depending on the system settings, also add
* an event listener to change the darkmode if the system settings change
* @private
*/
_setDarkmodeAuto() {
if (this.getMode() !== 'auto') {
return;
}
try {
const darkmode = new Darkmode();
this._darkmode = darkmode;
//Unhide darkmode button
this._showWidget();
//Set the switch according to our current darkmode state
const toggler = document.getElementById("toggleDarkmode");
toggler.checked = darkmode.isActivated();
}
catch (e)
{
console.error(e);
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
this._enableDarkmode();
} else {
this._disableDarkmode();
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
console.log('Prefered color scheme changed to ' + event.matches ? 'dark' : 'light');
this._setDarkmodeAuto();
});
}
_showWidget() {
this.element.classList.remove('hidden');
}
toggleDarkmode() {
this._darkmode.toggle();
/**
* Check if darkmode is activated
* @return {boolean}
*/
isDarkmodeActivated() {
return document.documentElement.getAttribute('data-bs-theme') === 'dark';
}
}

View File

@@ -21,6 +21,8 @@
import { Controller } from '@hotwired/stimulus';
import { marked } from "marked";
import { mangle } from "marked-mangle";
import { gfmHeadingId } from "marked-gfm-heading-id";
import DOMPurify from 'dompurify';
import "../../css/app/markdown.css";
@@ -81,6 +83,10 @@ export default class extends Controller {
*/
configureMarked()
{
marked.use(mangle());
marked.use(gfmHeadingId({
}));
marked.setOptions({
gfm: true,
});

View File

@@ -61,7 +61,7 @@ export default class extends Controller {
if(!prototype) {
console.warn("Prototype is not set, we cannot create a new element. This is most likely due to missing permissions.");
bootbox.alert("You do not have the permsissions to create a new element. (No protoype element is set)");
bootbox.alert("You do not have the permissions to create a new element. (No protoype element is set)");
return;
}

View File

@@ -65,12 +65,19 @@ export default class extends Controller {
localStorage.setItem( this.getStateSaveKey(), JSON.stringify(data) );
}
stateLoadCallback(settings) {
const data = JSON.parse( localStorage.getItem(this.getStateSaveKey()) );
stateLoadCallback() {
const json = localStorage.getItem(this.getStateSaveKey());
if(json === null || json === undefined) {
return null;
}
const data = JSON.parse(json);
if (data) {
//Do not save the start value (current page), as we want to always start at the first page on a page reload
data.start = 0;
delete data.start;
//Reset the data length to the default value by deleting the length property
delete data.length;
}
return data;
@@ -88,6 +95,19 @@ export default class extends Controller {
//Add url info, as the one available in the history is not enough, as Turbo may have not changed it yet
settings.url = this.element.dataset.dtUrl;
//Add initial_order info to the settings, so that the order on the initial page load is the one saved in the state
const saved_state = this.stateLoadCallback();
if (saved_state !== null) {
const raw_order = saved_state.order;
settings.initial_order = raw_order.map((order) => {
return {
column: order[0],
dir: order[1]
}
});
}
let options = {
colReorder: true,
responsive: true,
@@ -97,7 +117,7 @@ export default class extends Controller {
},
buttons: [{
"extend": 'colvis',
'className': 'mr-2 btn-light',
'className': 'mr-2 btn-outline-secondary',
'columns': ':not(.no-colvis)',
"text": "<i class='fa fa-cog'></i>"
}],
@@ -123,6 +143,22 @@ export default class extends Controller {
console.error("Error initializing datatables: " + err);
});
//Fix height of the length selector
promise.then((dt) => {
//Find all length selectors (select with name dt_length), which are inside a label
const lengthSelectors = document.querySelectorAll('label select[name="dt_length"]');
//And remove the surrounding label, while keeping the select with all event handlers
lengthSelectors.forEach((selector) => {
selector.parentElement.replaceWith(selector);
});
//Find all column visibility buttons (button with buttons-colvis class) and remove the btn-secondary class
const colVisButtons = document.querySelectorAll('button.buttons-colvis');
colVisButtons.forEach((button) => {
button.classList.remove('btn-secondary');
});
});
//Dispatch an event to let others know that the datatables has been loaded
promise.then((dt) => {
const event = new CustomEvent(EVENT_DT_LOADED, {bubbles: true});
@@ -203,4 +239,16 @@ export default class extends Controller {
return this.element.dataset.select ?? false;
}
invertSelection() {
//Do nothing if the datatable is not selectable
if(!this.isSelectable()) {
return;
}
//Invert the selected rows on the datatable
const selected_rows = this._dt.rows({selected: true});
this._dt.rows().select();
selected_rows.deselect();
}
}

View File

@@ -0,0 +1,72 @@
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Controller} from "@hotwired/stimulus";
import * as bootbox from "bootbox";
import "../../css/components/bootbox_extensions.css";
export default class extends Controller
{
static values = {
message: String,
title: String
}
connect()
{
this._confirmed = false;
this.element.addEventListener('click', this._onClick.bind(this));
}
_onClick(event)
{
//If a user has not already confirmed the deletion, just let turbo do its work
if (this._confirmed) {
this._confirmed = false;
return;
}
event.preventDefault();
event.stopPropagation();
const that = this;
bootbox.confirm({
title: this.titleValue,
message: this.messageValue,
callback: (result) => {
if (result) {
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
that._confirmed = true;
//Click the link
that.element.click();
} else {
that._confirmed = false;
}
}
});
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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/>.
*/
import {Controller} from "@hotwired/stimulus";
import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core';
import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common';
import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en';
import * as zxcvbnDePackage from '@zxcvbn-ts/language-de';
import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr';
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja';
import {trans, USER_PASSWORD_STRENGTH_VERY_WEAK, USER_PASSWORD_STRENGTH_WEAK, USER_PASSWORD_STRENGTH_MEDIUM,
USER_PASSWORD_STRENGTH_STRONG, USER_PASSWORD_STRENGTH_VERY_STRONG} from '../../translator.js';
/* stimulusFetch: 'lazy' */
export default class extends Controller {
_passwordInput;
static targets = ["badge", "warning"]
_getTranslations() {
//Get the current locale
const locale = document.documentElement.lang;
if (locale.includes('de')) {
return zxcvbnDePackage.translations;
} else if (locale.includes('fr')) {
return zxcvbnFrPackage.translations;
} else if (locale.includes('ja')) {
return zxcvbnJaPackage.translations;
}
//Fallback to english
return zxcvbnEnPackage.translations;
}
connect() {
//Find the password input field
this._passwordInput = this.element.querySelector('input[type="password"]');
//Configure zxcvbn
const options = {
graphs: zxcvbnCommonPackage.adjacencyGraphs,
dictionary: {
...zxcvbnCommonPackage.dictionary,
// We could use the english dictionary here too, but it is very big. So we just use the common words
//...zxcvbnEnPackage.dictionary,
},
translations: this._getTranslations(),
};
zxcvbnOptions.setOptions(options);
//Add event listener to the password input field
this._passwordInput.addEventListener('input', this._onPasswordInput.bind(this));
}
_onPasswordInput() {
//Retrieve the password
const password = this._passwordInput.value;
//Estimate the password strength
const result = zxcvbn(password);
//Update the badge
this.badgeTarget.parentElement.classList.remove("d-none");
this._setBadgeToLevel(result.score);
this.warningTarget.innerHTML = result.feedback.warning;
}
_setBadgeToLevel(level) {
let text, classes;
switch (level) {
case 0:
text = trans(USER_PASSWORD_STRENGTH_VERY_WEAK);
classes = "bg-danger badge-danger";
break;
case 1:
text = trans(USER_PASSWORD_STRENGTH_WEAK);
classes = "bg-warning badge-warning";
break;
case 2:
text = trans(USER_PASSWORD_STRENGTH_MEDIUM)
classes = "bg-info badge-info";
break;
case 3:
text = trans(USER_PASSWORD_STRENGTH_STRONG);
classes = "bg-primary badge-primary";
break;
case 4:
text = trans(USER_PASSWORD_STRENGTH_VERY_STRONG);
classes = "bg-success badge-success";
break;
default:
text = "???";
classes = "bg-secondary badge-secondary";
}
this.badgeTarget.innerHTML = text;
//Remove all classes
this.badgeTarget.className = '';
//Re-add the classes
this.badgeTarget.classList.add("badge");
this.badgeTarget.classList.add(...classes.split(" "));
}
}

View File

@@ -22,6 +22,8 @@ import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select";
import {Controller} from "@hotwired/stimulus";
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
export default class extends Controller {
_tomSelect;
@@ -40,8 +42,7 @@ export default class extends Controller {
allowEmptyOption: true,
selectOnTab: true,
maxOptions: null,
create: allowAdd,
createFilter: /\D/, //Must contain a non-digit character, otherwise they would be recognized as DB ID
create: allowAdd ? this.createItem.bind(this) : false,
searchField: [
{field: "text", weight : 2},
@@ -68,6 +69,15 @@ export default class extends Controller {
this._tomSelect.sync();
}
createItem(input, callback) {
callback({
//$%$ is a special value prefix, that is used to identify items, that are not yet in the DB
value: '$%$' + input,
text: input,
not_in_db_yet: true,
});
}
updateValidity() {
//Mark this input as invalid, if the selected option is disabled
@@ -97,14 +107,27 @@ export default class extends Controller {
}
if (data.short) {
return '<div><b>' + escape(data.short) + '</b></div>';
let short = escape(data.short)
//Make text italic, if the item is not yet in the DB
if (data.not_in_db_yet) {
short = '<i>' + short + '</i>';
}
return '<div><b>' + short + '</b></div>';
}
let name = "";
if (data.parent) {
name += escape(data.parent) + "&nbsp;→&nbsp;";
}
name += "<b>" + escape(data.text) + "</b>";
if (data.not_in_db_yet) {
//Not yet added items are shown italic and with a badge
name += "<i><b>" + escape(data.text) + "</b></i>" + "<span class='ms-3 badge bg-info badge-info'>" + trans(ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB) + "</span>";
} else {
name += "<b>" + escape(data.text) + "</b>";
}
return '<div>' + (data.image ? "<img class='structural-entity-select-image' style='margin-right: 5px;' ' src='" + data.image + "'/>" : "") + name + '</div>';
}

View File

@@ -81,6 +81,14 @@ export default class extends Controller {
this._tree.remove();
}
const BS53Theme = {
getOptions() {
return {
onhoverColor: 'var(--bs-secondary-bg)',
};
}
}
this._tree = new BSTreeView(this.treeTarget, {
levels: 1,
showTags: this._showTags,
@@ -93,7 +101,7 @@ export default class extends Controller {
}
},
//onNodeContextmenu: contextmenu_handler,
}, [BS5Theme, FAIconTheme]);
}, [BS5Theme, BS53Theme, FAIconTheme]);
this.treeTarget.addEventListener(EVENT_INITIALIZED, (event) => {
/** @type {BSTreeView} */

View File

@@ -0,0 +1,65 @@
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Controller} from "@hotwired/stimulus";
/**
* This controller is used on a checkbox, which toggles the max value of all number input fields
*/
export default class extends Controller {
_checkbox;
getCheckbox() {
if (this._checkbox) {
return this._checkbox;
}
//Find the checkbox inside the controller element
this._checkbox = this.element.querySelector('input[type="checkbox"]');
return this._checkbox;
}
connect() {
//Add event listener to the checkbox
this.getCheckbox().addEventListener('change', this.toggleInputLimits.bind(this));
}
toggleInputLimits() {
//Find all input fields with the data-toggle-input-limits-target="max"
const inputFields = document.querySelectorAll("input[type='number']");
inputFields.forEach((inputField) => {
//Ensure that the input field has either a max or a data-max attribute
if (!inputField.hasAttribute('max') && !inputField.hasAttribute('data-max')) {
return;
}
//If the checkbox is checked, rename the max attribute to data-max
if (this.getCheckbox().checked) {
inputField.setAttribute('data-max', inputField.getAttribute('max'));
inputField.removeAttribute('max');
} else {
//If the checkbox is not checked, rename the data-max attribute back to max
inputField.setAttribute('max', inputField.getAttribute('data-max'));
inputField.removeAttribute('data-max');
}
});
}
}

View File

@@ -99,7 +99,7 @@ label:not(.form-check-label, .custom-control-label) {
form .col-form-label.required:after, form label.required:after {
bottom: 4px;
color: var(--bs-dark);
color: var(--bs-secondary-color);
content: "\2022";
filter: opacity(75%);
position: relative;

View File

@@ -67,7 +67,6 @@ ul.structural_link {
padding-bottom: 7px;
padding-left: 0;
list-style: none;
background-color: inherit;
}
/* Display list items side by side */
@@ -79,7 +78,7 @@ ul.structural_link li {
/* Add a slash symbol (/) before/behind each list item */
ul.structural_link li+li:before {
padding: 2px;
color: grey;
color: var(--bs-tertiary-color);
/*content: "/\00a0";*/
font-family: "Font Awesome 5 Free";
font-weight: 900;
@@ -89,13 +88,13 @@ ul.structural_link li+li:before {
/* Add a color to all links inside the list */
ul.structural_link li a {
color: #0275d8;
color: var(--bs-link-color);
text-decoration: none;
}
/* Add a color on mouse-over */
ul.structural_link li a:hover {
color: #01447e;
color: var(--bs-link-hover-color);
text-decoration: underline;
}

View File

@@ -78,8 +78,6 @@ body {
overflow: -moz-scrollbars-none;
/* Use standard version for hiding the scrollbar */
scrollbar-width: none;
background-color: var(--light);
}
#sidebar-container {

View File

@@ -91,7 +91,7 @@ th.select-checkbox {
/** Fix datatables select-checkbox position */
table.dataTable tr.selected td.select-checkbox:after
{
margin-top: -28px !important;
margin-top: -20px !important;
}
@@ -116,23 +116,33 @@ table.dataTable > thead > tr > th.select-checkbox:before,
table.dataTable > thead > tr > th.select-checkbox:after {
display: block;
position: absolute;
top: 1.2em;
top: 0.9em;
left: 50%;
width: 12px;
height: 12px;
width: 1em !important;
height: 1em !important;
box-sizing: border-box;
}
table.dataTable > thead > tr > th.select-checkbox:before {
content: " ";
margin-top: -5px;
margin-left: -6px;
border: 1px solid black;
border: 2px solid var(--bs-tertiary-color);
border-radius: 3px;
}
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before {
border: 2px solid var(--bs-tertiary-color) !important;
}
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after, table.dataTable > tbody > tr > th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:after {
width: 1em !important;
height: 1em !important;
}
table.dataTable > thead > tr.selected > th.select-checkbox:after {
content: "✓";
font-size: 20px;
margin-top: -23px;
margin-top: -20px;
margin-left: -6px;
text-align: center;
/*text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9; */

View File

@@ -36,3 +36,42 @@
.ck-html-label .ck-content hr {
margin: 2px;
}
/***********************************************
* Hide CKEditor powered by message
***********************************************/
.ck-powered-by {
display: none;
}
/***********************************************
* Use Bootstrap color vars for CKEditor
***********************************************/
:root {
--ck-color-base-foreground: var(--bs-secondary-bg);
--ck-color-base-background: var(--bs-body-bg);
--ck-color-base-border: var(--bs-border-color);
--ck-color-base-action: var(--bs-success);
--ck-color-base-focus: var(--bs-primary-border-subtle);
--ck-color-base-text: var(--bs-body-color);
--ck-color-base-active: var(--bs-primary-bg-subtle);
--ck-color-base-active-focus: var(--bs-primary);
--ck-color-base-error: var(--bs-danger);
/* Improve contrast between text and toolbar */
--ck-color-toolbar-background: var(--bs-tertiary-bg);
/* Buttons */
--ck-color-button-default-hover-background: var(--bs-secondary-bg);
--ck-color-button-default-active-background: var(--bs-secondary-bg);
--ck-color-button-on-background: var(--bs-body-bg);
--ck-color-button-on-hover-background: var(--bs-secondary-bg);
--ck-color-button-on-active-background: var(--bs-secondary-bg);
--ck-color-button-on-disabled-background: var(--bs-secondary-bg);
--ck-color-button-on-color: var(--bs-primary)
}

View File

@@ -18,6 +18,29 @@
*/
.tagsinput.ts-wrapper.multi .ts-control > div {
background: var(--bs-secondary);
color: var(--bs-white);
}
background: var(--bs-secondary-bg);
color: var(--bs-body-color);
}
/*********
* BS 5.3 compatible dark mode
***************/
.ts-dropdown .active {
background-color: var(--bs-secondary-bg) !important;
color: var(--bs-body-color) !important;
}
.ts-dropdown, .ts-control, .ts-control input {
color: var(--bs-body-color) !important;
}
.ts-dropdown, .ts-dropdown.form-control, .ts-dropdown.form-select {
background: var(--bs-body-bg);
}
.ts-dropdown .optgroup-header {
color: var(--bs-tertiary-color);
background: var(--bs-body-bg);
cursor: default;
}

3
assets/fonts/dompdf/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Ignore font files
*.otf
*.ttf

View File

@@ -0,0 +1 @@
Put your font ttf files in this folder to make them available to the label generator.

View File

@@ -22,7 +22,6 @@
import '../css/app/layout.css';
import '../css/app/helpers.css';
import '../css/app/darkmode.css';
import '../css/app/tables.css';
import '../css/app/bs-overrides.css';
import '../css/app/treeview.css';

View File

@@ -47,7 +47,8 @@
method: config.method,
data: {
_dt: config.name,
_init: true
_init: true,
order: config.initial_order ?? undefined,
}
}).done(function(data) {
var baseState;
@@ -72,6 +73,17 @@
}
} else {
request._dt = config.name;
//Try to resolve the original column index when the column was reordered (using the ColReorder plugin)
//Only do this when _ColReorder_iOrigCol is available
if (settings.aoColumns && settings.aoColumns.length && settings.aoColumns[0]._ColReorder_iOrigCol !== undefined) {
if (request.order && request.order.length) {
request.order.forEach(function (order) {
order.column = settings.aoColumns[order.column]._ColReorder_iOrigCol;
});
}
}
$.ajax(typeof config.url === 'function' ? config.url(dt) : config.url, {
method: config.method,
data: request

View File

@@ -59,13 +59,16 @@ class RegisterEventHelper {
}
registerTooltips() {
this.registerLoadHandler(() => {
const handler = () => {
$(".tooltip").remove();
//Exclude dropdown buttons from tooltips, otherwise we run into endless errors from bootstrap (bootstrap.esm.js:614 Bootstrap doesn't allow more than one instance per element. Bound instance: bs.dropdown.)
$('a[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i.fas[title]')
$('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i[title], small[title]')
//@ts-ignore
.tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'});
});
};
this.registerLoadHandler(handler);
document.addEventListener('dt:loaded', handler);
}
registerSpecialCharInput() {

View File

@@ -21,8 +21,13 @@
class WebauthnTFA {
// Decodes a Base64Url string
_base64UrlDecode = (input) => {
_b64UrlSafeEncode = (str) => {
const b64 = btoa(str);
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
// Decodes a Base64Url string
_b64UrlSafeDecode = (input) => {
input = input
.replace(/-/g, '+')
.replace(/_/g, '/');
@@ -39,13 +44,16 @@ class WebauthnTFA {
};
// Converts an array of bytes into a Base64Url string
_arrayToBase64String = (a) => btoa(String.fromCharCode(...a));
_arrayToBase64String = (a) => {
const str = String.fromCharCode(...a);
return this._b64UrlSafeEncode(str);
}
// Prepares the public key options object returned by the Webauthn Framework
_preparePublicKeyOptions = publicKey => {
//Convert challenge from Base64Url string to Uint8Array
publicKey.challenge = Uint8Array.from(
this._base64UrlDecode(publicKey.challenge),
this._b64UrlSafeDecode(publicKey.challenge),
c => c.charCodeAt(0)
);
@@ -67,7 +75,7 @@ class WebauthnTFA {
return {
...data,
id: Uint8Array.from(
this._base64UrlDecode(data.id),
this._b64UrlSafeDecode(data.id),
c => c.charCodeAt(0)
),
};
@@ -81,7 +89,7 @@ class WebauthnTFA {
return {
...data,
id: Uint8Array.from(
this._base64UrlDecode(data.id),
this._b64UrlSafeDecode(data.id),
c => c.charCodeAt(0)
),
};

16
assets/translator.js Normal file
View File

@@ -0,0 +1,16 @@
import { localeFallbacks } from '../var/translations/configuration';
import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
/*
* This file is part of the Symfony UX Translator package.
*
* If folder "../var/translations" does not exist, or some translations are missing,
* you must warmup your Symfony cache to refresh JavaScript translations.
*
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
*/
setLocaleFallbacks(localeFallbacks);
export { trans };
export * from '../var/translations';

View File

@@ -2,7 +2,7 @@
"type": "project",
"license": "AGPL-3.0-or-later",
"require": {
"php": "^7.4 || ^8.0",
"php": "^8.1",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -11,67 +11,70 @@
"ext-json": "*",
"ext-mbstring": "*",
"beberlei/doctrineextensions": "^1.2",
"brick/math": "^0.8.15",
"composer/package-versions-deprecated": "1.11.99.4",
"doctrine/annotations": "^1.6",
"brick/math": "^0.11.0",
"composer/package-versions-deprecated": "^1.11.99.5",
"doctrine/annotations": "1.14.3",
"doctrine/data-fixtures": "^1.6.6",
"doctrine/dbal": "^3.4.6",
"doctrine/doctrine-bundle": "^2.0",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.9",
"dompdf/dompdf": "^2.0.0",
"doctrine/orm": "^2.16",
"dompdf/dompdf": "dev-master#87bea32efe0b0db309e1d31537201f64d5508280 as v2.0.3",
"erusev/parsedown": "^1.7",
"florianv/swap": "^4.0",
"florianv/swap-bundle": "dev-master",
"gregwar/captcha-bundle": "^2.1.0",
"hslavich/oneloginsaml-bundle": "^2.10",
"jbtronics/2fa-webauthn": "^1.0.0",
"jbtronics/2fa-webauthn": "^v2.0.0",
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
"jfcherng/php-diff": "^6.14",
"knpuniversity/oauth2-client-bundle": "^2.15",
"league/csv": "^9.8.0",
"league/html-to-markdown": "^5.0.1",
"liip/imagine-bundle": "^2.2",
"nbgrp/onelogin-saml-bundle": "^1.3",
"nelexa/zip": "^4.0",
"nelmio/security-bundle": "^3.0",
"nyholm/psr7": "^1.1",
"ocramius/proxy-manager": "2.2.*",
"omines/datatables-bundle": "^0.5.0",
"php-translation/symfony-bundle": "^0.13.0",
"omines/datatables-bundle": "^0.7.2",
"part-db/label-fonts": "^1.0",
"php-translation/symfony-bundle": "^0.14.0",
"phpdocumentor/reflection-docblock": "^5.2",
"s9e/text-formatter": "^2.1",
"scheb/2fa-backup-code": "^5.13",
"scheb/2fa-bundle": "^5.13",
"scheb/2fa-google-authenticator": "^5.13",
"scheb/2fa-trusted-device": "^5.13",
"sensio/framework-extra-bundle": "^6.1.1",
"scheb/2fa-backup-code": "^6.8.0",
"scheb/2fa-bundle": "^6.8.0",
"scheb/2fa-google-authenticator": "^6.8.0",
"scheb/2fa-trusted-device": "^6.8.0",
"shivas/versioning-bundle": "^4.0",
"spatie/db-dumper": "^2.21",
"spatie/db-dumper": "^3.3.1",
"symfony/apache-pack": "^1.0",
"symfony/asset": "5.4.*",
"symfony/console": "5.4.*",
"symfony/dotenv": "5.4.*",
"symfony/expression-language": "5.4.*",
"symfony/flex": "^1.1",
"symfony/form": "5.4.*",
"symfony/framework-bundle": "5.4.*",
"symfony/http-client": "5.4.*",
"symfony/http-kernel": "5.4.*",
"symfony/mailer": "5.4.*",
"symfony/asset": "6.3.*",
"symfony/console": "6.3.*",
"symfony/dotenv": "6.3.*",
"symfony/expression-language": "6.3.*",
"symfony/flex": "^v2.3.1",
"symfony/form": "6.3.*",
"symfony/framework-bundle": "6.3.*",
"symfony/http-client": "6.3.*",
"symfony/http-kernel": "6.3.*",
"symfony/mailer": "6.3.*",
"symfony/monolog-bundle": "^3.1",
"symfony/process": "5.4.*",
"symfony/property-access": "5.4.*",
"symfony/property-info": "5.4.*",
"symfony/proxy-manager-bridge": "5.4.*",
"symfony/rate-limiter": "5.4.*",
"symfony/runtime": "5.4.*",
"symfony/security-bundle": "5.4.*",
"symfony/serializer": "5.4.*",
"symfony/translation": "5.4.*",
"symfony/twig-bundle": "5.4.*",
"symfony/process": "6.3.*",
"symfony/property-access": "6.3.*",
"symfony/property-info": "6.3.*",
"symfony/proxy-manager-bridge": "6.3.*",
"symfony/rate-limiter": "6.3.*",
"symfony/runtime": "6.3.*",
"symfony/security-bundle": "6.3.*",
"symfony/serializer": "6.3.*",
"symfony/translation": "6.3.*",
"symfony/twig-bundle": "6.3.*",
"symfony/ux-translator": "^2.10",
"symfony/ux-turbo": "^2.0",
"symfony/validator": "5.4.*",
"symfony/web-link": "5.4.*",
"symfony/webpack-encore-bundle": "^1.1",
"symfony/yaml": "5.4.*",
"symfony/validator": "6.3.*",
"symfony/web-link": "6.3.*",
"symfony/webpack-encore-bundle": "^v2.0.1",
"symfony/yaml": "6.3.*",
"tecnickcom/tc-lib-barcode": "^1.15",
"twig/cssinliner-extra": "^3.0",
"twig/extra-bundle": "^3.0",
@@ -79,28 +82,30 @@
"twig/inky-extra": "^3.0",
"twig/intl-extra": "^3.0",
"twig/markdown-extra": "^3.0",
"web-auth/webauthn-symfony-bundle": "^3.3",
"web-auth/webauthn-symfony-bundle": "^4.0.0",
"webmozart/assert": "^1.4"
},
"require-dev": {
"dama/doctrine-test-bundle": "^7.0",
"doctrine/doctrine-fixtures-bundle": "^3.2",
"ekino/phpstan-banned-code": "^v1.0.0",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.4.7",
"phpstan/phpstan-doctrine": "^1.2.11",
"phpstan/phpstan-strict-rules": "^1.5",
"phpstan/phpstan-symfony": "^1.1.7",
"psalm/plugin-symfony": "^v5.0.1",
"rector/rector": "^0.18.0",
"roave/security-advisories": "dev-latest",
"symfony/browser-kit": "^5.2",
"symfony/css-selector": "^5.2",
"symfony/debug-bundle": "^5.2",
"symfony/browser-kit": "6.3.*",
"symfony/css-selector": "6.3.*",
"symfony/debug-bundle": "6.3.*",
"symfony/maker-bundle": "^1.13",
"symfony/phpunit-bridge": "5.4.*",
"symfony/stopwatch": "^5.2",
"symfony/web-profiler-bundle": "^5.2",
"symplify/easy-coding-standard": "^11.0",
"vimeo/psalm": "^5.6.0",
"doctrine/doctrine-fixtures-bundle": "^3.2"
"symfony/phpunit-bridge": "6.3.*",
"symfony/stopwatch": "6.3.*",
"symfony/web-profiler-bundle": "6.3.*",
"symplify/easy-coding-standard": "^12.0",
"vimeo/psalm": "^5.6.0"
},
"suggest": {
"ext-bcmath": "Used to improve price calculation performance",
@@ -111,7 +116,7 @@
"*": "dist"
},
"platform": {
"php": "7.4.0"
"php": "8.1.0"
},
"sort-packages": true,
"allow-plugins": {
@@ -143,7 +148,7 @@
"post-update-cmd": [
"@auto-scripts"
],
"phpstan": "vendor/bin/phpstan analyse src --level 2 --memory-limit 1G"
"phpstan": "vendor/bin/phpstan analyse src --level 5 --memory-limit 1G"
},
"conflict": {
"symfony/symfony": "*"
@@ -151,9 +156,7 @@
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.4.*"
"require": "6.3.*"
}
},
"repositories": [
]
}
}

6572
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +0,0 @@
<?php
use Symfony\Component\Dotenv\Dotenv;
require dirname(__DIR__).'/vendor/autoload.php';
if (!class_exists(Dotenv::class)) {
throw new LogicException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
}
// Load cached env vars if the .env.local.php file exists
// Run "composer dump-env prod" to create it (requires symfony/flex >=1.2)
if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) {
(new Dotenv(false))->populate($env);
} else {
// load all the .env files
(new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env');
}
$_SERVER += $_ENV;
$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev';
$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV'];
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0';

View File

@@ -2,7 +2,6 @@
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
@@ -27,5 +26,9 @@ return [
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
SpomkyLabs\CborBundle\SpomkyLabsCborBundle::class => ['all' => true],
Webauthn\Bundle\WebauthnBundle::class => ['all' => true],
Hslavich\OneloginSamlBundle\HslavichOneloginSamlBundle::class => ['all' => true],
Nbgrp\OneloginSamlBundle\NbgrpOneloginSamlBundle::class => ['all' => true],
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true],
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
];

View File

@@ -20,3 +20,6 @@ framework:
tree.cache:
adapter: cache.app
tags: true
info_provider.cache:
adapter: cache.app

View File

@@ -9,7 +9,7 @@ datatables:
# Set options, as documented at https://datatables.net/reference/option/
options:
lengthMenu : [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]
pageLength: 50
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>>>
<'card'

View File

@@ -21,12 +21,15 @@ doctrine:
orm:
auto_generate_proxy_classes: true
enable_lazy_ghost_objects: true
report_fields_where_declared: true
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
type: attribute
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
@@ -35,6 +38,8 @@ doctrine:
string_functions:
regexp: DoctrineExtensions\Query\Mysql\Regexp
ifnull: DoctrineExtensions\Query\Mysql\IfNull
field: DoctrineExtensions\Query\Mysql\Field
field2: App\Doctrine\Functions\Field2
when@test:
doctrine:

View File

@@ -0,0 +1,11 @@
dompdf_font_loader:
auto_install: true
fonts:
unifont:
normal: "%kernel.project_dir%/vendor/part-db/label-fonts/fonts/unifont.ttf"
# Enable autodiscovery of fonts, so that font installation is much easier
autodiscovery:
paths:
- "%kernel.project_dir%/assets/fonts/dompdf"

View File

@@ -2,8 +2,12 @@
framework:
secret: '%env(APP_SECRET)%'
csrf_protection: true
handle_all_throwables: true
# Must be set to true, to enable the change of HTTP methhod via _method parameter, otherwise our delete routines does not work anymore
# We set this header by ourself, so we can disable it here
disallow_search_engine_index: false
# Must be set to true, to enable the change of HTTP method via _method parameter, otherwise our delete routines does not work anymore
# TODO: Rework delete routines to work without _method parameter as it is not recommended anymore (see https://github.com/symfony/symfony/issues/45278)
http_method_override: true
@@ -29,9 +33,6 @@ framework:
php_errors:
log: true
form:
legacy_error_messages: false # Enable to use the new Form component validation messages
when@test:
framework:
test: true

View File

@@ -1,60 +0,0 @@
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
hslavich_onelogin_saml:
# Basic settings
idp:
entityId: '%env(string:SAML_IDP_ENTITY_ID)%'
singleSignOnService:
url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
singleLogoutService:
url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
x509cert: '%env(string:SAML_IDP_X509_CERT)%'
sp:
entityId: '%env(string:SAML_SP_ENTITY_ID)%'
assertionConsumerService:
url: '%partdb.default_uri%saml/acs'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
singleLogoutService:
url: '%partdb.default_uri%logout'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
x509cert: '%env(string:SAML_SP_X509_CERT)%'
privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%'
# Optional settings
#baseurl: 'http://myapp.com'
strict: true
debug: false
security:
allowRepeatAttributeName: true
# nameIdEncrypted: false
authnRequestsSigned: true
logoutRequestSigned: true
logoutResponseSigned: true
# wantMessagesSigned: false
# wantAssertionsSigned: true
# wantNameIdEncrypted: false
# requestedAuthnContext: true
# signMetadata: false
# wantXMLValidation: true
# relaxDestinationValidation: false
# destinationStrictlyMatches: true
# rejectUnsolicitedResponsesWithInResponseTo: false
# signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
# digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
#contactPerson:
# technical:
# givenName: 'Tech User'
# emailAddress: 'techuser@example.com'
# support:
# givenName: 'Support User'
# emailAddress: 'supportuser@example.com'
# administrative:
# givenName: 'Administrative User'
# emailAddress: 'administrativeuser@example.com'
#organization:
# en:
# name: 'Part-DB-name'
# displayname: 'Displayname'
# url: 'http://example.com'

View File

@@ -0,0 +1,5 @@
framework:
http_client:
default_options:
headers:
'User-Agent': 'Part-DB'

View File

@@ -0,0 +1,10 @@
services:
Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory'
Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory'
http_discovery.psr17_factory:
class: Http\Discovery\Psr17Factory

View File

@@ -0,0 +1,38 @@
knpu_oauth2_client:
clients:
# configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration
ip_digikey_oauth:
type: generic
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
client_id: '%env(PROVIDER_DIGIKEY_CLIENT_ID)%'
client_secret: '%env(PROVIDER_DIGIKEY_SECRET)%'
redirect_route: 'oauth_client_check'
redirect_params: {name: 'ip_digikey_oauth'}
provider_options:
urlAuthorize: 'https://api.digikey.com/v1/oauth2/authorize'
urlAccessToken: 'https://api.digikey.com/v1/oauth2/token'
urlResourceOwnerDetails: ''
# Sandbox
#urlAuthorize: 'https://sandbox-api.digikey.com/v1/oauth2/authorize'
#urlAccessToken: 'https://sandbox-api.digikey.com/v1/oauth2/token'
#urlResourceOwnerDetails: ''
ip_octopart_oauth:
type: generic
provider_class: '\League\OAuth2\Client\Provider\GenericProvider'
client_id: '%env(PROVIDER_OCTOPART_CLIENT_ID)%'
client_secret: '%env(PROVIDER_OCTOPART_SECRET)%'
redirect_route: 'oauth_client_check'
redirect_params: { name: 'ip_octopart_oauth' }
provider_options:
urlAuthorize: 'https://identity.nexar.com/connect/authorize'
urlAccessToken: 'https://identity.nexar.com/connect/token'
urlResourceOwnerDetails: ''

View File

@@ -1,2 +0,0 @@
framework:
lock: '%env(LOCK_DSN)%'

View File

@@ -0,0 +1,68 @@
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
# Define a parameter here, so we can access it later in the default fallback
parameters:
saml.sp.privateKey: '%env(string:SAML_SP_PRIVATE_KEY)%'
nbgrp_onelogin_saml:
onelogin_settings:
default:
# Basic settings
idp:
entityId: '%env(string:SAML_IDP_ENTITY_ID)%'
singleSignOnService:
url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
singleLogoutService:
url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
x509cert: '%env(string:SAML_IDP_X509_CERT)%'
sp:
entityId: '%env(string:SAML_SP_ENTITY_ID)%'
assertionConsumerService:
url: '%partdb.default_uri%saml/acs'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
singleLogoutService:
url: '%partdb.default_uri%logout'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
x509cert: '%env(string:SAML_SP_X509_CERT)%'
# Before the env variable was wrongly named "SAMLP_SP_PRIVATE_KEY".
# For compatibility reasons we keep it and only fallback to the new name if the old one is not set. This may be removed in the future.
privateKey: '%env(string:default:saml.sp.privateKey:string:SAMLP_SP_PRIVATE_KEY)%'
# Optional settings
#baseurl: 'http://myapp.com'
strict: true
debug: false
security:
allowRepeatAttributeName: true
# nameIdEncrypted: false
authnRequestsSigned: true
logoutRequestSigned: true
logoutResponseSigned: true
# wantMessagesSigned: false
# wantAssertionsSigned: true
# wantNameIdEncrypted: false
# requestedAuthnContext: true
# signMetadata: false
# wantXMLValidation: true
# relaxDestinationValidation: false
# destinationStrictlyMatches: true
# rejectUnsolicitedResponsesWithInResponseTo: false
# signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
# digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
#contactPerson:
# technical:
# givenName: 'Tech User'
# emailAddress: 'techuser@example.com'
# support:
# givenName: 'Support User'
# emailAddress: 'supportuser@example.com'
# administrative:
# givenName: 'Administrative User'
# emailAddress: 'administrativeuser@example.com'
#organization:
# en:
# name: 'Part-DB-name'
# displayname: 'Displayname'
# url: 'http://example.com'

View File

@@ -12,6 +12,13 @@ nelmio_security:
external_redirects:
abort: true
log: true
allow_list:
# Whitelist the domain of the SAML IDP, so we can redirect to it during the SAML login process
- '%env(string:key:host:url:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
# Whitelist the info provider APIs (OAuth redirects)
- 'digikey.com'
- 'nexar.com'
# forces Microsoft's XSS-Protection with
# its block mode

View File

@@ -1,10 +1,10 @@
# See the configuration reference at https://symfony.com/bundles/SchebTwoFactorBundle/5.x/configuration.html
# See the configuration reference at https://symfony.com/bundles/SchebTwoFactorBundle/6.x/configuration.html
scheb_two_factor:
google:
enabled: true # If Google Authenticator should be enabled, default false
server_name: '%partdb.title%' # Server name used in QR code
issuer: 'Part-DB' # Issuer name used in QR code
server_name: '$$DOMAIN$$' # This field is replaced by the domain name of the server in DecoratedGoogleAuthenticator
issuer: '%partdb.title%' # Issuer name used in QR code
digits: 6 # Number of digits in authentication code
window: 1 # How many codes before/after the current one would be accepted as valid
template: security/2fa_form.html.twig
@@ -23,6 +23,6 @@ scheb_two_factor:
security_tokens:
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
# If you're using guard-based authentication, you have to use this one:
# - Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken
# - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
# If you're using authenticator-based security (introduced in Symfony 5.1), you have to use this one:
# - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
- Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken

View File

@@ -1,6 +1,5 @@
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
@@ -22,6 +21,9 @@ security:
user_checker: App\Security\UserChecker
entry_point: form_login
# Enable user impersonation
switch_user: { role: CAN_SWITCH_USER }
two_factor:
auth_form_path: 2fa_login
check_path: 2fa_login_check

View File

@@ -1,3 +0,0 @@
sensio_framework_extra:
router:
annotations: false

View File

@@ -1,4 +1,2 @@
framework:
test: true
session:
storage_id: session.storage.mock_file

4
config/packages/uid.yaml Normal file
View File

@@ -0,0 +1,4 @@
framework:
uid:
default_uuid_version: 7
time_based_uuid_version: 7

View File

@@ -0,0 +1,3 @@
ux_translator:
# The directory where the JavaScript translations are dumped
dump_directory: '%kernel.project_dir%/var/translations'

View File

@@ -4,7 +4,9 @@ when@dev:
intercept_redirects: false
framework:
profiler: { only_exceptions: false }
profiler:
only_exceptions: false
collect_serializer_data: true
when@test:
web_profiler:

View File

@@ -11,7 +11,7 @@ parameters:
partdb.banner: '%env(trim:string:BANNER)%' # The info text shown in the homepage, if empty config/banner.md is used
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
partdb.locale_menu: ['en', 'de', 'fr', 'ru', 'ja'] # The languages that are shown in user drop down menu
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja'] # The languages that are shown in user drop down menu
partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all.
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
@@ -19,10 +19,12 @@ parameters:
######################################################################################################################
# Users and Privacy
######################################################################################################################
partdb.gpdr_compliance: true # If this option is activated, IP addresses are anonymized to be GPDR compliant
partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant
partdb.users.use_gravatar: '%env(bool:USE_GRAVATAR)%' # Set to false, if no Gravatar images should be used for user profiles.
partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured.
partdb.check_for_updates: '%env(bool:CHECK_FOR_UPDATES)' # Set to false, if Part-DB should not contact the GitHub API to check for updates
######################################################################################################################
# Mail settings
######################################################################################################################
@@ -48,6 +50,11 @@ parameters:
######################################################################################################################
partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled
######################################################################################################################
# Table settings
######################################################################################################################
partdb.table.default_page_size: '%env(int:TABLE_DEFAULT_PAGE_SIZE)%' # The default number of entries shown per page in tables
######################################################################################################################
# Sidebar
######################################################################################################################
@@ -119,6 +126,8 @@ parameters:
env(EMAIL_SENDER_NAME): 'Part-DB Mailer'
env(ALLOW_EMAIL_PW_RESET): 0
env(TABLE_DEFAULT_PAGE_SIZE): 50
env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server
env(TRUSTED_HOSTS): '' # Trust all host names by default

View File

@@ -139,6 +139,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
ic_logos:
label: "perm.tools.ic_logos"
info_providers:
label: "perm.part.info_providers"
operations:
create_parts:
label: "perm.part.info_providers.create_parts"
alsoSet: ['parts.create']
groups:
label: "perm.groups"
group: "system"
@@ -190,6 +197,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
set_password:
label: "perm.users.set_password"
alsoSet: 'read'
impersonate:
label: "perm.users.impersonate"
alsoSet: ['set_password']
change_user_settings:
label: "perm.users.change_user_settings"
show_history:
@@ -239,6 +249,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
alsoSet: 'show_logs'
server_infos:
label: "perm.server_infos"
manage_oauth_tokens:
label: "Manage OAuth tokens"
show_updates:
label: "perm.system.show_available_updates"
attachments:
label: "perm.part.attachments"

View File

@@ -1,12 +1,8 @@
#index:
# path: /
# controller: App\Controller\DefaultController::index
# Redirect every url without an locale to the locale of the user/the global base locale
scan_qr:
path: /scan/{type}/{id}
controller: App\Controller\ScanController:scanQRCode
controller: App\Controller\ScanController::scanQRCode
csp_report:
path: /csp/report

View File

@@ -1,6 +1,8 @@
controllers:
resource: ../../src/Controller/
type: annotation
resource:
path: ../../src/Controller/
namespace: App\Controller
type: attribute
prefix: '{_locale}'
defaults:
@@ -11,4 +13,4 @@ controllers:
kernel:
resource: ../../src/Kernel.php
type: annotation
type: attribute

View File

@@ -1,4 +1,4 @@
hslavich_saml_sp:
resource: "@HslavichOneloginSamlBundle/Resources/config/routing.yml"
nbgrp_saml:
resource: "@NbgrpOneloginSamlBundle/Resources/config/routes.php"
# Only load the SAML routes if SAML is enabled
condition: "env('SAML_ENABLED') == '1' or env('SAML_ENABLED') == 'true'"

View File

@@ -14,16 +14,19 @@ services:
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
bool $demo_mode: '%partdb.demo_mode%'
bool $gpdr_compliance : '%partdb.gpdr_compliance%'
bool $kernel_debug: '%kernel.debug%'
bool $gdpr_compliance: '%partdb.gdpr_compliance%'
bool $kernel_debug_enabled: '%kernel.debug%'
string $kernel_cache_dir: '%kernel.cache_dir%'
string $partdb_title: '%partdb.title%'
string $default_currency: '%partdb.default_currency%'
string $base_currency: '%partdb.default_currency%'
_instanceof:
App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface:
tags: ['app.label_placeholder_provider']
App\Services\InfoProviderSystem\Providers\InfoProviderInterface:
tags: ['app.info_provider']
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
@@ -88,7 +91,7 @@ services:
App\Form\AttachmentFormType:
arguments:
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
$allow_attachments_download: '%partdb.attachments.allow_downloads%'
$max_file_size: '%partdb.attachments.max_file_size%'
App\Services\Attachments\AttachmentSubmitHandler:
@@ -97,12 +100,6 @@ services:
$mimeTypes: '@mime_types'
$max_upload_size: '%partdb.attachments.max_file_size%'
App\EventSubscriber\LogSystem\LogoutLoggerListener:
tags:
- name: 'kernel.event_listener'
event: 'Symfony\Component\Security\Http\Event\LogoutEvent'
dispatcher: security.event_dispatcher.main
App\Services\LogSystem\EventCommentNeededHelper:
arguments:
$enforce_change_comments_for: '%partdb.enforce_change_comments_for%'
@@ -183,7 +180,7 @@ services:
App\EventSubscriber\UserSystem\SetUserTimezoneSubscriber:
arguments:
$timezone: '%partdb.timezone%'
$default_timezone: '%partdb.timezone%'
App\Controller\SecurityController:
arguments:
@@ -227,6 +224,11 @@ services:
tags:
- { name: 'app.label_placeholder_provider', priority: 10}
App\Services\LabelSystem\DompdfFactory:
arguments:
$fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/'
$tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/'
####################################################################################################################
# Trees
####################################################################################################################
@@ -235,6 +237,46 @@ services:
$rootNodeExpandedByDefault: '%partdb.sidebar.root_expanded%'
$rootNodeEnabled: '%partdb.sidebar.root_node_enable%'
####################################################################################################################
# Part info provider system
####################################################################################################################
App\Services\InfoProviderSystem\ProviderRegistry:
arguments:
$providers: !tagged_iterator 'app.info_provider'
App\Services\InfoProviderSystem\Providers\Element14Provider:
arguments:
$api_key: '%env(string:PROVIDER_ELEMENT14_KEY)%'
$store_id: '%env(string:PROVIDER_ELEMENT14_STORE_ID)%'
App\Services\InfoProviderSystem\Providers\DigikeyProvider:
arguments:
$clientId: '%env(string:PROVIDER_DIGIKEY_CLIENT_ID)%'
$currency: '%env(string:PROVIDER_DIGIKEY_CURRENCY)%'
$language: '%env(string:PROVIDER_DIGIKEY_LANGUAGE)%'
$country: '%env(string:PROVIDER_DIGIKEY_COUNTRY)%'
App\Services\InfoProviderSystem\Providers\TMEClient:
arguments:
$secret: '%env(string:PROVIDER_TME_SECRET)%'
$token: '%env(string:PROVIDER_TME_KEY)%'
App\Services\InfoProviderSystem\Providers\TMEProvider:
arguments:
$currency: '%env(string:PROVIDER_TME_CURRENCY)%'
$country: '%env(string:PROVIDER_TME_COUNTRY)%'
$language: '%env(string:PROVIDER_TME_LANGUAGE)%'
$get_gross_prices: '%env(bool:PROVIDER_TME_GET_GROSS_PRICES)%'
App\Services\InfoProviderSystem\Providers\OctopartProvider:
arguments:
$clientId: '&env(string:PROVIDER_OCTOPART_CLIENT_ID)%'
$secret: '%env(string:PROVIDER_OCTOPART_SECRET)%'
$country: '%env(string:PROVIDER_OCTOPART_COUNTRY)%'
$currency: '%env(string:PROVIDER_OCTOPART_CURRENCY)%'
$search_limit: '%env(int:PROVIDER_OCTOPART_SEARCH_LIMIT)%'
$onlyAuthorizedSellers: '%env(bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS)%'
####################################################################################################################
# Symfony overrides
####################################################################################################################
@@ -265,7 +307,7 @@ services:
tags:
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
# We are needing this service inside of a migration, where only the container is injected. So we need to define it as public, to access it from the container.
# We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container.
App\Services\UserSystem\PermissionPresetsHelper:
public: true
@@ -273,6 +315,10 @@ services:
arguments:
$project_dir: '%kernel.project_dir%'
App\Services\System\UpdateAvailableManager:
arguments:
$check_for_updates: '%partdb.check_for_updates%'
####################################################################################################################
# Monolog
####################################################################################################################
@@ -289,3 +335,14 @@ services:
autowire: true
tags:
- { name: monolog.processor }
when@test:
services:
# Decorate the doctrine fixtures load command to use our custom purger by default
doctrine.fixtures_load_command.custom:
decorates: doctrine.fixtures_load_command
class: Doctrine\Bundle\FixturesBundle\Command\LoadDataFixturesDoctrineCommand
arguments:
- '@doctrine.fixtures.loader'
- '@doctrine'
- { default: '@App\Doctrine\Purger\ResetAutoIncrementPurgerFactory' }

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

View File

@@ -36,6 +36,7 @@ The following configuration options can only be changed by the server administra
* `datastructure_edit`: Edit operation of a existing datastructure (e.g. category, manufacturer, ...)
* `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...)
* `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...)
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new versions, or if your server can not connect to the internet.
### E-Mail settings
* `MAILER_DSN`: You can configure the mail provider which should be used for email delivery (see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587`
@@ -43,6 +44,9 @@ The following configuration options can only be changed by the server administra
* `EMAIL_SENDER_NAME`: Similar to `EMAIL_SENDER_EMAIL` but this allows you to specify the name from which the mails are sent from.
* `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you wan to allow users to reset their password via an email notification. You have to configure the mailprovider first before via the MAILER_DSN setting.
### Table related settings
* `TABLE_DEFAULT_PAGE_SIZE`: The default page size for tables. This is the number of rows which are shown per page. Set to `-1` to disable pagination and show all rows at once.
### History/Eventlog related settings
The following options are used to configure, which (and how much) data is written to the system log:
* `HISTORY_SAVE_CHANGED_FIELDS`: When this option is set to true, the name of the fields which are changed, are saved to the DB (so for example it is logged that a user has changed, that the user has changed the name and description of the field, but not the data/content of these changes)
@@ -71,6 +75,9 @@ You can find more advanced settings in the `config/packages/hslavich_onelogin_sa
* `SAML_SP_X509_CERT`: The public X.509 certificate of your SAML SP (here Part-DB). This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIC` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_PRIVATE_KEY` setting.
* `SAML_SP_PRIVATE_KEY`: The private key of your SAML SP (here Part-DB), corresponding the public key specified in `SAML_SP_X509_CERT`. This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIE` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_X509_CERT` setting.
### Information provider settings
The settings prefixes with `PROVIDER_*` are used to configure the information providers.
See the [information providers]({% link usage/information_provider_system.md %}) page for more information.
### Other / less used options
* `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct IP informations (see [here](https://symfony.com/doc/current/deployment/proxies.html) for more info).
@@ -98,7 +105,7 @@ The following options are available:
* `partdb.global_theme`: The default theme to use, when no user specific theme is set. Should be one of the themes from the `partdb.available_themes` config option.
* `partdb.locale_menu`: The codes of the languages, which should be shown in the language chooser menu (the one with the user icon in the navbar). The first language in the list will be the default language.
* `partdb.gpdr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU.
* `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU.
* `partdb.sidebar.items`: The panel contents which should be shown in the sidebar by default. You can also change the number of sidebar panels by changing the number of items in this list.
* `partdb.sidebar.root_node_enable`: Show a root node in the sidebar trees, of which all nodes are children of
* `partdb.sidebar.root_expanded`: Expand the root node in the sidebar trees by default

View File

@@ -37,6 +37,7 @@ It is installed on a web server and so can be accessed with any browser without
* Support for multiple currencies and automatic update of exchange rates supported
* Powerful search and filter function, including parametric search (search for parts according to some specifications)
* Easy migration from an existing PartKeepr instance (see [here]({%link partkeepr_migration.md %}))
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and prices for parts (see [here]({% link usage/information_provider_system.md %}))
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
or makerspaces, where many users have should have (controlled) access to the shared inventory.

View File

@@ -22,9 +22,9 @@ sudo apt install git curl zip ca-certificates software-properties-common apt-tra
```
### Install PHP and apache2
Part-DB is written in [PHP](https://php.net) and therefore needs an PHP interpreter to run. Part-DB needs PHP 7.3 or higher, however it is recommended to use the most recent version of PHP for performance reasons and future compatibility.
Part-DB is written in [PHP](https://php.net) and therefore needs an PHP interpreter to run. Part-DB needs PHP 8.1 or higher, however it is recommended to use the most recent version of PHP for performance reasons and future compatibility.
As Debian 11 does not ship PHP 8.1 in it's default repositories, we have to add a repository for it. You can skip this step if your distribution is shipping a recent version of PHP or you want to use the built-in PHP version.
As Debian 11 does not ship PHP 8.1 in it's default repositories, we have to add a repository for it. You can skip this step if your distribution is shipping a recent version of PHP or you want to use the built-in PHP version. If you are using Debian 12, you can skip this step, as PHP 8.1 is already included in the default repositories.
```bash
# Add sury repository for PHP 8.1
sudo curl -sSL https://packages.sury.org/php/README.txt | sudo bash -x
@@ -74,6 +74,17 @@ We now have all prerequisites installed and can start to install Part-DB. We wil
git clone https://github.com/Part-DB/Part-DB-symfony.git /var/www/partdb
```
By default you are now on the latest development version. In most cases you want to use the latest stable version. You can switch to the latest stable version (tagged) by running the following command:
```bash
# This finds the latest release/tag and checks it out
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
```
Alternatively you can checkout a specific version by running (see [GitHub Relases page](https://github.com/Part-DB/Part-DB-server/releases) for a list of available versions):
```bash
# This checks out the version 1.5.2
git checkout v1.5.2
```
Change ownership of the files to the apache user:
```bash
chown -R www-data:www-data /var/www/partdb
@@ -90,7 +101,7 @@ The basic configuration of Part-DB is done by a `.env.local` file in the main di
cp .env .env.local
```
In your `.env.local` you can configure Part-DB according to your wishes. A full list of configuration options can be found [here]({% link configuration.md %}.
In your `.env.local` you can configure Part-DB according to your wishes. A full list of configuration options can be found [here](../configuration.md).
Other configuration options like the default language or default currency can be found in `config/parameters.yaml`.
Please check that the `partdb.default_currency` value in `config/parameters.yaml` matches your mainly used currency, as this can not be changed after creating price informations.
@@ -184,6 +195,10 @@ If you want to update your existing Part-DB installation, you just have to run t
cd /var/www/partdb
# Pull latest Part-DB version from GitHub
git pull
# Checkout the latest version (or use a specific version, like described above)
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
# Apply correct permission
chown -R www-data:www-data .
# Install new composer dependencies

View File

@@ -143,7 +143,7 @@ The reverse is also possible: If you have existing SAML users and want them to b
> It is recommended that you let the original admin user (ID: 2) be a local user, so you can still login to Part-DB if the SAML identity provider is not available.
## Advanced SAML configuration
You can find some more advanced SAML configuration options in the `config/packages/hslavich_onelogin_saml.yaml` file. Refer to the file for more information.
You can find some more advanced SAML configuration options in the `config/packages/nbgrp_onelogin_saml.yaml` file. Refer to the file for more information.
Normally you don't have to change anything here.
Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep your changes. On docker containers you have to configure a volume mapping for it.

View File

@@ -0,0 +1,142 @@
---
title: Information provider system
layout: default
parent: Usage
---
# Information provider system
Part-DB can create parts based on information from external sources: For example with the right setup you can just search for a part number
and Part-DB will query selected distributors and manufacturers for the part and create a part with the information it found.
This way your Part-DB parts automatically get datasheet links, prices, parameters and more, with just a few clicks.
## Usage
Before you can use the information provider system, you have to configure at least one information provider, which act as data source.
See below for a list of available information providers and available configuration options.
For many providers it is enough, to setup the API keys in the env configuration, some require an additional OAuth connection.
You can list all enabled information providers in the browser at `https://your-partdb-instance.tld/tools/info_providers/providers` (you need the right permission for it, see below).
To use the information provider system, your user need to have the right permissions. Go to the permission management page of
a user or a group and assign the permissions of the "Info providers" group in the "Miscellaneous" tab.
If you have the required permission you will find in the sidebar in the "Tools" section the entry "Create part from info provider".
Click this and you will land on a search page. Enter the part number you want to search for and select the information providers you want to use.
After you click Search, you will be presented with the results and can select the result that fits best.
With a click on the blue plus button, you will be redirected to the part creation page with the information already filled in.
![image]({% link assets/usage/information_provider_system/animation.gif %})
## Alternative names
Part-DB tries to automatically find existing elements from your database for the information it got from the providers for fields like manufacturer, footprint, etc.
For this it searches for a element with the same name (case-insensitive) as the information it got from the provider. So e.g. if the provider returns "EXAMPLE CORP" as manufacturer,
Part-DB will automatically select the element with the name "Example Corp" from your database.
As the names of these fields differ from provider to provider (and maybe not even normalized for the same provider), you
can define multiple alternative names for an element (on their editing page).
For example if define a manufacturer "Example Corp" with the alternative names "Example Corp.", "Example Corp", "Example Corp. Inc." and "Example Corporation",
then the provider can return any of these names and Part-DB will still automatically select the right element.
If Part-DB finds no matching element, it will automatically create a new one, when you do not change the value before saving.
## Attachment types
The information provider system uses attachment types to differentiate between datasheets and image attachments.
For this it will create a "Datasheet" and "Image" attachment type on the first run. You can change the names of these
types in the attachment type settings (as long as you keep the "Datasheet"/"Image" in the alternative names field).
If you already have attachment types for images and datasheets and want the information provider system to use them, you can
add the alternative names "Datasheet" and "Image" to the alternative names field of the attachment types.
## Data providers
The system tries to be as flexible as possible, so many different information sources can be used.
Each information source is called am "info provider" and handles the communication with the external source.
The providers are just a driver which handles the communication with the different external sources and converts them into a common format Part-DB understands.
That way it is pretty easy to create new providers as they just need to do very little work.
Normally the providers utilize an API of a service, and you need to create a account at the provider and get an API key.
Also there are limits on how many requests you can do per day or months, depending on the provider and your contract with them.
The following providers are currently available and shipped with Part-DB:
(All trademarks are property of their respective owners. Part-DB is not affiliated with any of the companies.)
### Ocotpart
The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and getting informations.
To use it you have to create an account at Nexar and create a new application on the [Nexar Portal](https://portal.nexar.com/).
The name does not matter, but it is important that the application has access to the "Supply" scope.
In the Authorization tab, you will find the client ID and client secret, which you have to enter in the Part-DB env configuration (see below).
Please note that the Nexar API in the free plan is limited to 1000 results per month.
That means if you search for a keyword and results in 10 parts, then 10 will be substracted from your monthly limit. You can see your current usage on the Nexar portal.
Part-DB caches the search results internally, so if you have searched for a part before, it will not count against your monthly limit again, when you create it from the search results.
Following env configuration options are available:
* `PROVIDER_OCTOPART_CLIENT_ID`: The client ID you got from Nexar (mandatory)
* `PROVIDER_OCTOPART_SECRET`: The client secret you got from Nexar (mandatory)
* `PROVIDER_OCTOPART_CURRENCY`: The currency you want to get prices in if available (optional, 3 letter ISO-code, default: `EUR`). If an offer is only available in a certain currency,
Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert it to your preferred currency.
* `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code, default: `DE`). To get correct prices, you have to set this and the currency setting to the correct value.
* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This affects how quickly your monthly limit is used up.
* `PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS`: If set to `true`, only offers from [authorized sellers](https://octopart.com/authorized) will be returned (optional, default: `false`).
**Attention**: If you change the octopart clientID after you have already used the provider, you have to remove the OAuth token in the Part-DB database. Remove the entry in the table `oauth_tokens` with the name `ip_octopart_oauth`.
### Digi-Key
The Digi-Key provider uses the [Digi-Key API](https://developer.digikey.com/) to search for parts and getting shopping information from [Digi-Key](https://www.digikey.com/).
To use it you have to create an account at Digi-Key and get an API key on the [Digi-Key API page](https://developer.digikey.com/).
You must create an organization there and create a "Production app". Most settings are not important, you just have to grant access to the "Product Information" API.
You will get an Client ID and a Client Secret, which you have to enter in the Part-DB env configuration (see below).
Following env configuration options are available:
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
* `PROVIDER_DIGIKEY_SECRET`: The client secret you got from Digi-Key (mandatory)
* `PROVIDER_DIGIKEY_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`)
* `PROVIDER_DIGIKEY_LANGUAGE`: The language you want to get the descriptions in (optional, default: `en`)
* `PROVIDER_DIGIKEY_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
The Digi-Key provider needs an additional OAuth connection. To do this, go to the information provider list (`https://your-partdb-instance.tld/tools/info_providers/providers`),
go the Digi-Key provider (in the disabled page) and click on the "Connect OAuth" button. You will be redirected to Digi-Key, where you have to login and grant access to the app.
To do this your user needs the "Manage OAuth tokens" permission from the "System" section in the "System" tab.
The OAuth connection should only be needed once, but if you have any problems with the provider, just click the button again, to establish a new connection.
### TME
The TME provider use the API of [TME](https://www.tme.eu/) to search for parts and getting shopping information from them.
To use it you have to create an account at TME and get an API key on the [TME API page](https://developers.tme.eu/en/).
You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see below).
Following env configuration options are available:
* `PROVIDER_TME_KEY`: The API key you got from TME (mandatory)
* `PROVIDER_TME_SECRET`: The API secret you got from TME (mandatory)
* `PROVIDER_TME_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`)
* `PROVIDER_TME_LANGUAGE`: The language you want to get the descriptions in (`en`, `de` and `pl`) (optional, default: `en`)
* `PROVIDER_TME_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
* `PROVIDER_TME_GET_GROSS_PRICES`: If this is set to `1` the prices will be gross prices (including tax), otherwise net prices (optional, default: `0`)
### Farnell / Element14 / Newark
The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and getting shopping information from [Farnell](https://www.farnell.com/).
You have to create an account at Farnell and get an API key on the [Farnell API page](https://partner.element14.com/).
Register a new application there (settings does not matter, as long as you select the "Product Search API") and you will get an API key.
Following env configuration options are available:
* `PROVIDER_ELEMENT14_KEY`: The API key you got from Farnell (mandatory)
* `PROVIDER_ELEMENT14_STORE_ID`: The store ID you want to use. This decides the language of results, currency and country of prices (optional, default: `de.farnell.com`, see [here](https://partner.element14.com/docs/Product_Search_API_REST__Description) for availailable values)
### Custom provider
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long as it is a valid Symfony service, it will be automatically loaded and can be used.
Besides some metadata functions, you have to implement the `searchByKeyword()` and `getDetails()` functions, which do the actual API requests and return the information to Part-DB.
See the existing providers for examples.
If you created a new provider, feel free to create a pull request to add it to the Part-DB core.
## Result caching
To reduce the number of API calls against the providers, the results are cached:
* The search results (exact search term) are cached for 7 days
* The product details are cached for 4 days
If you need a fresh result, you can clear the cache by running `php .\bin\console cache:pool:clear info_provider.cache` on the command line.
The default `php bin/console cache:clear` also clears the result cache, as it clears all caches.

View File

@@ -92,4 +92,27 @@ The following variables are in injected into Twig and can be accessed using `{%
| `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog |
| `{% raw %}{{ user }}{% endraw %}` | The current logged in user. Null if you are not logged in |
| `{% raw %}{{ install_title }}{% endraw %}` | The name of the current Part-DB instance (similar to [[INSTALL_NAME]] placeholder). |
| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated |
| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated |
## Use custom fonts for PDF labels
You can use your own fonts for label generation. To do this, put the TTF files of the fonts you want to use into the `assets/fonts/dompdf` folder.
The filename will be used as name for the font family and you can use a `_bold` (or `_b`), `_italic` (or `_i`) or `_bold_italic` (or `_bi`) suffix to define
different styles of the font. So for example, if you copy the file `myfont.ttf` and `myfont_bold.ttf` into the `assets/fonts/dompdf` folder, you can use the font family `myfont` with regular and bold style.
Afterwards regenerate cache with `php bin/console cache:clear`, so the new fonts will be available for label generation.
The fonts will not be availble from the UI directly, you have to use it in the HTML directly either by defining a `style="font-family: 'myfont';"` attribute on the HTML element or by using a CSS class.
You can define the font globally for the label, by adding following statement to the "Additional styles (CSS)" option in the label generator settings:
```css
* {
font-family: 'myfont';
}
```
## Non-latin characters in PDF labels
The default used font (DejaVu) does not support all characters. Especially characters from non-latin languages like Chinese, Japanese, Korean, Arabic, Hebrew, Cyrillic, etc. are not supported.
For this we use [Unifont](http://unifoundry.com/unifont.html) as fallback font. This font supports all (or most) unicode characters, but is not as beautiful as DejaVu.
If you want to use a different (more beautiful) font, you can use the [custom fonts](#use-custom-fonts-for-pdf-labels) feature.
There is the [Noto](https://www.google.com/get/noto/) font family from Google, which supports a lot of languages and is available in different styles (regular, bold, italic, bold-italic).
For example you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautful Chinese, Japanese and Korean characters.

View File

@@ -72,4 +72,11 @@ See the configuration reference for more information.
## Personal stocks and stock locations
For makerspaces and universities with a lot of users, where each user can have his own stock, which only he should be able to access, you can assign
the user as "owner" of a part lot. This way, only him is allowed to add or remove parts from this lot.
the user as "owner" of a part lot. This way, only him is allowed to add or remove parts from this lot.
## Update notfications
Part-DB can show you a notification that there is a newer version than currently installed available. The notification will be shown on the homepage and the server info page.
It is only be shown to users which has the `Show available Part-DB updates` permission.
For the notification to work, Part-DB queries the GitHub API every 2 days to check for new releases. No data is sent to GitHub besides the metadata required for the connection (so the public IP address of your computer running Part-DB).
If you don't want Part-DB to query the GitHub API, or if your server can not reach the internet, you can disable the update notifications by setting the `CHECK_FOR_UPDATES` option to `false`.

View File

@@ -69,6 +69,12 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
//For attachments
$this->addSql('DELETE FROM `attachements` WHERE attachements.class_name = "Part" AND (SELECT COUNT(parts.id) FROM parts WHERE parts.id = attachements.element_id) = 0;');
//Add perms_labels column to groups table if not existing (it was not created in certain legacy versions)
//This prevents the migration failing (see https://github.com/Part-DB/Part-DB-server/issues/366 and https://github.com/Part-DB/Part-DB-server/issues/67)
if (!$this->doesColumnExist('groups', 'perms_labels')) {
$this->addSql('ALTER TABLE `groups` ADD `perms_labels` SMALLINT NOT NULL AFTER `perms_tools`');
}
/**************************************************************************************************************
* Doctrine generated SQL
**************************************************************************************************************/

View File

@@ -8,9 +8,6 @@ use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230417211732 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
@@ -41,7 +38,7 @@ final class Version20230417211732 extends AbstractMultiPlatformMigration
public function sqLiteUp(Schema $schema): void
{
//As legacy database can only be migrated to MySQL, we don't need to implement this method.
$this->skipIf(true, 'Not needed for SQLite');
//Dont skip here, as this causes this migration always to be executed. Do nothing instead.
}
public function sqLiteDown(Schema $schema): void

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20230528000149 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Add other_ui column to webauthn_keys table, needed for future compatibility with more complex webauthn authenticators';
}
public function mySQLUp(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE webauthn_keys ADD other_ui LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\'');
}
public function mySQLDown(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE webauthn_keys DROP other_ui');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array)
, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
(DC2Type:trust_path)
, aaguid CLOB NOT NULL --
(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --
(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
}

View File

@@ -0,0 +1,351 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
final class Version20230716184033 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Create the structure needed for the information provider system';
}
public function mySQLUp(Schema $schema): void
{
$this->addSql('CREATE TABLE oauth_tokens (id INT AUTO_INCREMENT NOT NULL, token VARCHAR(255) DEFAULT NULL, expires_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', refresh_token VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE INDEX oauth_tokens_unique_name (name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE attachment_types ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE categories ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE currencies ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE footprints ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE `groups` ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE manufacturers ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE measurement_units ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE parts ADD provider_reference_provider_key VARCHAR(255) DEFAULT NULL, ADD provider_reference_provider_id VARCHAR(255) DEFAULT NULL, ADD provider_reference_provider_url VARCHAR(255) DEFAULT NULL, ADD provider_reference_last_updated DATETIME DEFAULT NULL');
$this->addSql('ALTER TABLE projects ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE storelocations ADD alternative_names LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE suppliers ADD alternative_names LONGTEXT DEFAULT NULL');
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('DROP TABLE oauth_tokens');
$this->addSql('ALTER TABLE `attachment_types` DROP alternative_names');
$this->addSql('ALTER TABLE `categories` DROP alternative_names');
$this->addSql('ALTER TABLE currencies DROP alternative_names');
$this->addSql('ALTER TABLE `footprints` DROP alternative_names');
$this->addSql('ALTER TABLE `groups` DROP alternative_names');
$this->addSql('ALTER TABLE `manufacturers` DROP alternative_names');
$this->addSql('ALTER TABLE `measurement_units` DROP alternative_names');
$this->addSql('ALTER TABLE `parts` DROP provider_reference_provider_key, DROP provider_reference_provider_id, DROP provider_reference_provider_url, DROP provider_reference_last_updated');
$this->addSql('ALTER TABLE projects DROP alternative_names');
$this->addSql('ALTER TABLE `storelocations` DROP alternative_names');
$this->addSql('ALTER TABLE `suppliers` DROP alternative_names');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, token VARCHAR(255) DEFAULT NULL, expires_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
, refresh_token VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)');
$this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)');
$this->addSql('ALTER TABLE attachment_types ADD COLUMN alternative_names CLOB DEFAULT NULL');
$this->addSql('ALTER TABLE categories ADD COLUMN alternative_names CLOB DEFAULT NULL');
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
$this->addSql('DROP TABLE currencies');
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
$this->addSql('DROP TABLE __temp__currencies');
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
$this->addSql('ALTER TABLE footprints ADD COLUMN alternative_names CLOB DEFAULT NULL');
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM groups');
$this->addSql('DROP TABLE groups');
$this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json)
, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)');
$this->addSql('CREATE INDEX group_idx_name ON groups (name)');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)');
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log');
$this->addSql('DROP TABLE log');
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level TINYINT NOT NULL --(DC2Type:tinyint)
, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json)
, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log');
$this->addSql('DROP TABLE __temp__log');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
$this->addSql('ALTER TABLE manufacturers ADD COLUMN alternative_names CLOB DEFAULT NULL');
$this->addSql('ALTER TABLE measurement_units ADD COLUMN alternative_names CLOB DEFAULT NULL');
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn FROM parts');
$this->addSql('DROP TABLE parts');
$this->addSql('CREATE TABLE parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url VARCHAR(255) NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, ipn VARCHAR(100) DEFAULT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO parts (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn FROM __temp__parts');
$this->addSql('DROP TABLE __temp__parts');
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)');
$this->addSql('CREATE INDEX parts_idx_ipn ON parts (ipn)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)');
$this->addSql('CREATE INDEX parts_idx_name ON parts (name)');
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)');
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)');
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)');
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)');
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails');
$this->addSql('DROP TABLE pricedetails');
$this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal)
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
$this->addSql('DROP TABLE __temp__pricedetails');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)');
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)');
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
$this->addSql('DROP TABLE project_bom_entries');
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
$this->addSql('DROP TABLE __temp__project_bom_entries');
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('ALTER TABLE projects ADD COLUMN alternative_names CLOB DEFAULT NULL');
$this->addSql('ALTER TABLE storelocations ADD COLUMN alternative_names CLOB DEFAULT NULL');
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM suppliers');
$this->addSql('DROP TABLE suppliers');
$this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
$this->addSql('DROP TABLE __temp__suppliers');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)');
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)');
$this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)');
$this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)');
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM users');
$this->addSql('DROP TABLE users');
$this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL --(DC2Type:json)
, saml_user BOOLEAN NOT NULL, about_me CLOB NOT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data, saml_user, about_me, show_email_on_profile FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE INDEX user_idx_username ON users (name)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array)
, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added, other_ui FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('DROP TABLE oauth_tokens');
$this->addSql('CREATE TEMPORARY TABLE __temp__attachment_types AS SELECT id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added FROM "attachment_types"');
$this->addSql('DROP TABLE "attachment_types"');
$this->addSql('CREATE TABLE "attachment_types" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, filetype_filter CLOB NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "attachment_types" (id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added FROM __temp__attachment_types');
$this->addSql('DROP TABLE __temp__attachment_types');
$this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON "attachment_types" (parent_id)');
$this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON "attachment_types" (id_preview_attachment)');
$this->addSql('CREATE INDEX attachment_types_idx_name ON "attachment_types" (name)');
$this->addSql('CREATE INDEX attachment_types_idx_parent_name ON "attachment_types" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__categories AS SELECT id, parent_id, id_preview_attachment, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added FROM "categories"');
$this->addSql('DROP TABLE "categories"');
$this->addSql('CREATE TABLE "categories" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, partname_hint CLOB NOT NULL, partname_regex CLOB NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description CLOB NOT NULL, default_comment CLOB NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "categories" (id, parent_id, id_preview_attachment, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added FROM __temp__categories');
$this->addSql('DROP TABLE __temp__categories');
$this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON "categories" (parent_id)');
$this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON "categories" (id_preview_attachment)');
$this->addSql('CREATE INDEX category_idx_name ON "categories" (name)');
$this->addSql('CREATE INDEX category_idx_parent_name ON "categories" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
$this->addSql('DROP TABLE currencies');
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
$this->addSql('DROP TABLE __temp__currencies');
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__footprints AS SELECT id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added FROM "footprints"');
$this->addSql('DROP TABLE "footprints"');
$this->addSql('CREATE TABLE "footprints" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_footprint_3d INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "footprints" (id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added FROM __temp__footprints');
$this->addSql('DROP TABLE __temp__footprints');
$this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)');
$this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)');
$this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)');
$this->addSql('CREATE INDEX footprint_idx_name ON "footprints" (name)');
$this->addSql('CREATE INDEX footprint_idx_parent_name ON "footprints" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM "groups"');
$this->addSql('DROP TABLE "groups"');
$this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL --
(DC2Type:json)
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "groups" (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
$this->addSql('DROP TABLE __temp__groups');
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)');
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON "groups" (id_preview_attachment)');
$this->addSql('CREATE INDEX group_idx_name ON "groups" (name)');
$this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM log');
$this->addSql('DROP TABLE log');
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level TINYINT NOT NULL --
(DC2Type:tinyint)
, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --
(DC2Type:json)
, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO log (id, id_user, username, datetime, level, target_id, target_type, extra, type) SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM __temp__log');
$this->addSql('DROP TABLE __temp__log');
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
$this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "manufacturers"');
$this->addSql('DROP TABLE "manufacturers"');
$this->addSql('CREATE TABLE "manufacturers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "manufacturers" (id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__manufacturers');
$this->addSql('DROP TABLE __temp__manufacturers');
$this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)');
$this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON "manufacturers" (id_preview_attachment)');
$this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)');
$this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__measurement_units AS SELECT id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added FROM "measurement_units"');
$this->addSql('DROP TABLE "measurement_units"');
$this->addSql('CREATE TABLE "measurement_units" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_F5AF83CF727ACA70 FOREIGN KEY (parent_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "measurement_units" (id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added FROM __temp__measurement_units');
$this->addSql('DROP TABLE __temp__measurement_units');
$this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON "measurement_units" (parent_id)');
$this->addSql('CREATE INDEX IDX_F5AF83CFEA7100A1 ON "measurement_units" (id_preview_attachment)');
$this->addSql('CREATE INDEX unit_idx_name ON "measurement_units" (name)');
$this->addSql('CREATE INDEX unit_idx_parent_name ON "measurement_units" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order FROM "parts"');
$this->addSql('DROP TABLE "parts"');
$this->addSql('CREATE TABLE "parts" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url VARCHAR(255) NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "parts" (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order FROM __temp__parts');
$this->addSql('DROP TABLE __temp__parts');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)');
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)');
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)');
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)');
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)');
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)');
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)');
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
$this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)');
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM "pricedetails"');
$this->addSql('DROP TABLE "pricedetails"');
$this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --
(DC2Type:big_decimal)
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "pricedetails" (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
$this->addSql('DROP TABLE __temp__pricedetails');
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)');
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)');
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)');
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
$this->addSql('DROP TABLE project_bom_entries');
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
$this->addSql('DROP TABLE __temp__project_bom_entries');
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM projects');
$this->addSql('DROP TABLE projects');
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM __temp__projects');
$this->addSql('DROP TABLE __temp__projects');
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
$this->addSql('CREATE TEMPORARY TABLE __temp__storelocations AS SELECT id, parent_id, storage_type_id, id_owner, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, part_owner_must_match, comment, not_selectable, name, last_modified, datetime_added FROM "storelocations"');
$this->addSql('DROP TABLE "storelocations"');
$this->addSql('CREATE TABLE "storelocations" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, storage_type_id INTEGER DEFAULT NULL, id_owner INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, part_owner_must_match BOOLEAN DEFAULT 0 NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_751702021E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "storelocations" (id, parent_id, storage_type_id, id_owner, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, part_owner_must_match, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, storage_type_id, id_owner, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, part_owner_must_match, comment, not_selectable, name, last_modified, datetime_added FROM __temp__storelocations');
$this->addSql('DROP TABLE __temp__storelocations');
$this->addSql('CREATE INDEX IDX_7517020727ACA70 ON "storelocations" (parent_id)');
$this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON "storelocations" (storage_type_id)');
$this->addSql('CREATE INDEX IDX_751702021E5A74C ON "storelocations" (id_owner)');
$this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON "storelocations" (id_preview_attachment)');
$this->addSql('CREATE INDEX location_idx_name ON "storelocations" (name)');
$this->addSql('CREATE INDEX location_idx_parent_name ON "storelocations" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "suppliers"');
$this->addSql('DROP TABLE "suppliers"');
$this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --
(DC2Type:big_decimal)
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "suppliers" (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
$this->addSql('DROP TABLE __temp__suppliers');
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)');
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)');
$this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)');
$this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM "users"');
$this->addSql('DROP TABLE "users"');
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --
(DC2Type:json)
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --
(DC2Type:json)
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, saml_user BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB NOT NULL --
(DC2Type:json)
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, show_email_on_profile, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)');
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added FROM webauthn_keys');
$this->addSql('DROP TABLE webauthn_keys');
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
(DC2Type:base64)
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
(DC2Type:array)
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
(DC2Type:trust_path)
, aaguid CLOB NOT NULL --
(DC2Type:aaguid)
, credential_public_key CLOB NOT NULL --
(DC2Type:base64)
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, other_ui CLOB DEFAULT NULL --
(DC2Type:array)
, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, other_ui, name, last_modified, datetime_added FROM __temp__webauthn_keys');
$this->addSql('DROP TABLE __temp__webauthn_keys');
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
}
}

View File

@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
final class Version20230730131708 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Allow longer fields for manufacturer and supplier product urls, allow client credentials grant to have null refresh token';
}
public function mySQLUp(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE oauth_tokens CHANGE token token LONGTEXT DEFAULT NULL, CHANGE refresh_token refresh_token LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE orderdetails CHANGE supplier_product_url supplier_product_url LONGTEXT NOT NULL');
$this->addSql('ALTER TABLE parts CHANGE manufacturer_product_url manufacturer_product_url LONGTEXT NOT NULL');
}
public function mySQLDown(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE oauth_tokens CHANGE token token VARCHAR(255) DEFAULT NULL, CHANGE refresh_token refresh_token VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE `orderdetails` CHANGE supplier_product_url supplier_product_url VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE `parts` CHANGE manufacturer_product_url manufacturer_product_url VARCHAR(255) NOT NULL');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__oauth_tokens AS SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM oauth_tokens');
$this->addSql('DROP TABLE oauth_tokens');
$this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, token CLOB DEFAULT NULL, expires_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
, refresh_token CLOB DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)');
$this->addSql('INSERT INTO oauth_tokens (id, token, expires_at, refresh_token, name, last_modified, datetime_added) SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM __temp__oauth_tokens');
$this->addSql('DROP TABLE __temp__oauth_tokens');
$this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__orderdetails AS SELECT id, part_id, id_supplier, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added FROM orderdetails');
$this->addSql('DROP TABLE orderdetails');
$this->addSql('CREATE TABLE orderdetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, part_id INTEGER NOT NULL, id_supplier INTEGER DEFAULT NULL, supplierpartnr VARCHAR(255) NOT NULL, obsolete BOOLEAN NOT NULL, supplier_product_url CLOB NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_489AFCDC4CE34BEC FOREIGN KEY (part_id) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_489AFCDCCBF180EB FOREIGN KEY (id_supplier) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO orderdetails (id, part_id, id_supplier, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added) SELECT id, part_id, id_supplier, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added FROM __temp__orderdetails');
$this->addSql('DROP TABLE __temp__orderdetails');
$this->addSql('CREATE INDEX orderdetails_supplier_part_nr ON orderdetails (supplierpartnr)');
$this->addSql('CREATE INDEX IDX_489AFCDC4CE34BEC ON orderdetails (part_id)');
$this->addSql('CREATE INDEX IDX_489AFCDCCBF180EB ON orderdetails (id_supplier)');
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated FROM parts');
$this->addSql('DROP TABLE parts');
$this->addSql('CREATE TABLE parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, ipn VARCHAR(100) DEFAULT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO parts (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated FROM __temp__parts');
$this->addSql('DROP TABLE __temp__parts');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)');
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)');
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)');
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)');
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)');
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)');
$this->addSql('CREATE INDEX parts_idx_name ON parts (name)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)');
$this->addSql('CREATE INDEX parts_idx_ipn ON parts (ipn)');
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__oauth_tokens AS SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM oauth_tokens');
$this->addSql('DROP TABLE oauth_tokens');
$this->addSql('CREATE TABLE oauth_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, token VARCHAR(255) DEFAULT NULL, expires_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
, refresh_token VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL)');
$this->addSql('INSERT INTO oauth_tokens (id, token, expires_at, refresh_token, name, last_modified, datetime_added) SELECT id, token, expires_at, refresh_token, name, last_modified, datetime_added FROM __temp__oauth_tokens');
$this->addSql('DROP TABLE __temp__oauth_tokens');
$this->addSql('CREATE UNIQUE INDEX oauth_tokens_unique_name ON oauth_tokens (name)');
$this->addSql('CREATE TEMPORARY TABLE __temp__orderdetails AS SELECT id, part_id, id_supplier, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added FROM "orderdetails"');
$this->addSql('DROP TABLE "orderdetails"');
$this->addSql('CREATE TABLE "orderdetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, part_id INTEGER NOT NULL, id_supplier INTEGER DEFAULT NULL, supplierpartnr VARCHAR(255) NOT NULL, obsolete BOOLEAN NOT NULL, supplier_product_url VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_489AFCDC4CE34BEC FOREIGN KEY (part_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_489AFCDCCBF180EB FOREIGN KEY (id_supplier) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "orderdetails" (id, part_id, id_supplier, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added) SELECT id, part_id, id_supplier, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added FROM __temp__orderdetails');
$this->addSql('DROP TABLE __temp__orderdetails');
$this->addSql('CREATE INDEX IDX_489AFCDC4CE34BEC ON "orderdetails" (part_id)');
$this->addSql('CREATE INDEX IDX_489AFCDCCBF180EB ON "orderdetails" (id_supplier)');
$this->addSql('CREATE INDEX orderdetails_supplier_part_nr ON "orderdetails" (supplierpartnr)');
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated FROM "parts"');
$this->addSql('DROP TABLE "parts"');
$this->addSql('CREATE TABLE "parts" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url VARCHAR(255) NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "parts" (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated FROM __temp__parts');
$this->addSql('DROP TABLE __temp__parts');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)');
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)');
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)');
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)');
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)');
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)');
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)');
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
$this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)');
}
}

View File

@@ -7,10 +7,12 @@
"@hotwired/turbo": "^7.0.1",
"@popperjs/core": "^2.10.2",
"@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",
"bootstrap": "^5.1.3",
"core-js": "^3.23.0",
"intl-messageformat": "^10.2.5",
"jquery": "^3.5.1",
"popper.js": "^1.14.7",
"regenerator-runtime": "^0.13.9",
@@ -28,45 +30,50 @@
"build": "encore production --progress"
},
"dependencies": {
"@ckeditor/ckeditor5-alignment": "^37.1.0",
"@ckeditor/ckeditor5-autoformat": "^37.1.0",
"@ckeditor/ckeditor5-basic-styles": "^37.1.0",
"@ckeditor/ckeditor5-block-quote": "^37.1.0",
"@ckeditor/ckeditor5-code-block": "^37.1.0",
"@ckeditor/ckeditor5-dev-utils": "^37.0.0",
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
"@ckeditor/ckeditor5-editor-classic": "^37.1.0",
"@ckeditor/ckeditor5-essentials": "^37.1.0",
"@ckeditor/ckeditor5-find-and-replace": "^37.1.0",
"@ckeditor/ckeditor5-font": "^37.1.0",
"@ckeditor/ckeditor5-heading": "^37.1.0",
"@ckeditor/ckeditor5-highlight": "^37.1.0",
"@ckeditor/ckeditor5-horizontal-line": "^37.1.0",
"@ckeditor/ckeditor5-html-embed": "^37.1.0",
"@ckeditor/ckeditor5-html-support": "^37.1.0",
"@ckeditor/ckeditor5-image": "^37.1.0",
"@ckeditor/ckeditor5-indent": "^37.1.0",
"@ckeditor/ckeditor5-link": "^37.1.0",
"@ckeditor/ckeditor5-list": "^37.1.0",
"@ckeditor/ckeditor5-markdown-gfm": "^37.1.0",
"@ckeditor/ckeditor5-media-embed": "^37.1.0",
"@ckeditor/ckeditor5-paragraph": "^37.1.0",
"@ckeditor/ckeditor5-paste-from-office": "^37.1.0",
"@ckeditor/ckeditor5-remove-format": "^37.1.0",
"@ckeditor/ckeditor5-source-editing": "^37.1.0",
"@ckeditor/ckeditor5-special-characters": "^37.1.0",
"@ckeditor/ckeditor5-table": "^37.1.0",
"@ckeditor/ckeditor5-theme-lark": "^37.1.0",
"@ckeditor/ckeditor5-upload": "^37.1.0",
"@ckeditor/ckeditor5-watchdog": "^37.1.0",
"@ckeditor/ckeditor5-word-count": "^37.1.0",
"@ckeditor/ckeditor5-alignment": "^39.0.1",
"@ckeditor/ckeditor5-autoformat": "^39.0.1",
"@ckeditor/ckeditor5-basic-styles": "^39.0.1",
"@ckeditor/ckeditor5-block-quote": "^39.0.1",
"@ckeditor/ckeditor5-code-block": "^39.0.1",
"@ckeditor/ckeditor5-dev-translations": "^38.4.0",
"@ckeditor/ckeditor5-dev-utils": "^38.4.0",
"@ckeditor/ckeditor5-editor-classic": "^39.0.1",
"@ckeditor/ckeditor5-essentials": "^39.0.1",
"@ckeditor/ckeditor5-find-and-replace": "^39.0.1",
"@ckeditor/ckeditor5-font": "^39.0.1",
"@ckeditor/ckeditor5-heading": "^39.0.1",
"@ckeditor/ckeditor5-highlight": "^39.0.1",
"@ckeditor/ckeditor5-horizontal-line": "^39.0.1",
"@ckeditor/ckeditor5-html-embed": "^39.0.1",
"@ckeditor/ckeditor5-html-support": "^39.0.1",
"@ckeditor/ckeditor5-image": "^39.0.1",
"@ckeditor/ckeditor5-indent": "^39.0.1",
"@ckeditor/ckeditor5-link": "^39.0.1",
"@ckeditor/ckeditor5-list": "^39.0.1",
"@ckeditor/ckeditor5-markdown-gfm": "^39.0.1",
"@ckeditor/ckeditor5-media-embed": "^39.0.1",
"@ckeditor/ckeditor5-paragraph": "^39.0.1",
"@ckeditor/ckeditor5-paste-from-office": "^39.0.1",
"@ckeditor/ckeditor5-remove-format": "^39.0.1",
"@ckeditor/ckeditor5-source-editing": "^39.0.1",
"@ckeditor/ckeditor5-special-characters": "^39.0.1",
"@ckeditor/ckeditor5-table": "^39.0.1",
"@ckeditor/ckeditor5-theme-lark": "^39.0.1",
"@ckeditor/ckeditor5-upload": "^39.0.1",
"@ckeditor/ckeditor5-watchdog": "^39.0.1",
"@ckeditor/ckeditor5-word-count": "^39.0.1",
"@jbtronics/bs-treeview": "^1.0.1",
"@zxcvbn-ts/core": "^3.0.2",
"@zxcvbn-ts/language-common": "^3.0.3",
"@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",
"bootbox": "^6.0.0",
"bootswatch": "^5.1.3",
"bs-custom-file-input": "^1.3.4",
"clipboard": "^2.0.4",
"compression-webpack-plugin": "^10.0.0",
"darkmode-js": "^1.5.0",
"datatables.net-bs5": "^1.10.20",
"datatables.net-buttons-bs5": "^2.2.2",
"datatables.net-colreorder-bs5": "^1.5.1",
@@ -74,13 +81,15 @@
"datatables.net-responsive-bs5": "^2.2.3",
"datatables.net-select-bs5": "^1.2.7",
"dompurify": "^3.0.3",
"emoji.json": "^14.0.0",
"emoji.json": "^15.0.0",
"exports-loader": "^3.0.0",
"html5-qrcode": "^2.2.1",
"json-formatter-js": "^2.3.4",
"jszip": "^3.2.0",
"katex": "^0.16.0",
"marked": "^4.3.0",
"marked": "^7.0.4",
"marked-gfm-heading-id": "^3.0.4",
"marked-mangle": "^1.0.1",
"pdfmake": "^0.2.2",
"stimulus-use": "^0.52.0",
"tom-select": "^2.1.0",

View File

@@ -1,11 +1,55 @@
parameters:
level: 5
paths:
- src
# - tests
excludePaths:
- src/DataTables/Adapter/*
- src/Configuration/*
- src/Doctrine/Purger/*
inferPrivatePropertyTypeFromConstructor: true
treatPhpDocTypesAsCertain: false
symfony:
container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml'
excludes_analyse:
- src/DataTables/Adapter/*
- src/Configuration/*
- src/Doctrine/Purger/*
checkUninitializedProperties: true
checkFunctionNameCase: true
checkAlwaysTrueInstanceof: false
checkAlwaysTrueCheckTypeFunctionCall: false
checkAlwaysTrueStrictComparison: false
reportAlwaysTrueInLastCondition: false
reportMaybesInPropertyPhpDocTypes: false
reportMaybesInMethodSignatures: false
strictRules:
disallowedLooseComparison: false
booleansInConditions: false
uselessCast: false
requireParentConstructorCall: true
disallowedConstructs: false
overwriteVariablesWithLoop: false
closureUsesThis: false
matchingInheritedMethodNames: true
numericOperandsInArithmeticOperators: true
strictCalls: true
switchConditionsMatchingType: false
noVariableVariables: false
ignoreErrors:
# Ignore errors caused by complex mapping with AbstractStructuralDBElement
- '#AbstractStructuralDBElement does not have a field named \$parent#'
- '#AbstractStructuralDBElement does not have a field named \$name#'
# Ignore errors related to the use of the ParametersTrait in Part entity
- '#expects .*PartParameter, .*AbstractParameter given.#'
- '#Part::getParameters\(\) should return .*AbstractParameter#'

View File

@@ -1,20 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="tests/bootstrap.php">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php"
convertDeprecationsToExceptions="false"
>
<php>
<ini name="error_reporting" value="-1"/>
<server name="APP_ENV" value="test" force="true"/>
<server name="SHELL_VERBOSITY" value="-1"/>
<server name="SYMFONY_PHPUNIT_REMOVE" value=""/>
<server name="SYMFONY_PHPUNIT_VERSION" value="9"/>
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5"/>
<ini name="memory_limit" value="512M"/>
<ini name="display_errors" value="1"/>
</php>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>

63
rector.php Normal file
View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector;
use Rector\Config\RectorConfig;
use Rector\Doctrine\Set\DoctrineSetList;
use Rector\PHPUnit\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector;
use Rector\PHPUnit\Set\PHPUnitLevelSetList;
use Rector\PHPUnit\Set\PHPUnitSetList;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
use Rector\Symfony\Set\SymfonyLevelSetList;
use Rector\Symfony\Set\SymfonySetList;
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml');
$rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php');
//Import class names instead of using fully qualified class names
$rectorConfig->importNames();
//But keep the fully qualified class names for classes in the global namespace
$rectorConfig->importShortClasses(false);
$rectorConfig->paths([
__DIR__ . '/config',
__DIR__ . '/public',
__DIR__ . '/src',
__DIR__ . '/tests',
]);
// register a single rule
//$rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
$rectorConfig->rules([
DeclareStrictTypesRector::class,
]);
// define sets of rules
$rectorConfig->sets([
//PHP rules
SetList::CODE_QUALITY,
LevelSetList::UP_TO_PHP_81,
//Symfony rules
SymfonyLevelSetList::UP_TO_SYMFONY_62,
SymfonySetList::SYMFONY_CODE_QUALITY,
//Doctrine rules
DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
DoctrineSetList::DOCTRINE_CODE_QUALITY,
//PHPUnit rules
PHPUnitLevelSetList::UP_TO_PHPUNIT_90,
PHPUnitSetList::PHPUNIT_CODE_QUALITY,
]);
$rectorConfig->skip([
AddDoesNotPerformAssertionToNonAssertingTestRector::class,
CountArrayToEmptyArrayComparisonRector::class,
]);
};

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Command\Attachments;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Services\Attachments\AttachmentManager;
use App\Services\Attachments\AttachmentPathResolver;
use App\Services\Attachments\AttachmentReverseSearch;
@@ -40,30 +41,21 @@ use function count;
use const DIRECTORY_SEPARATOR;
#[AsCommand('partdb:attachments:clean-unused|app:clean-attachments', 'Lists (and deletes if wanted) attachments files that are not used anymore (abandoned files).')]
class CleanAttachmentsCommand extends Command
{
protected static $defaultName = 'partdb:attachments:clean-unused|app:clean-attachments';
protected AttachmentManager $attachment_helper;
protected AttachmentReverseSearch $reverseSearch;
protected MimeTypes $mimeTypeGuesser;
protected AttachmentPathResolver $pathResolver;
public function __construct(AttachmentManager $attachmentHelper, AttachmentReverseSearch $reverseSearch, AttachmentPathResolver $pathResolver)
public function __construct(protected AttachmentManager $attachment_helper, protected AttachmentReverseSearch $reverseSearch, protected AttachmentPathResolver $pathResolver)
{
$this->attachment_helper = $attachmentHelper;
$this->pathResolver = $pathResolver;
$this->reverseSearch = $reverseSearch;
$this->mimeTypeGuesser = new MimeTypes();
parent::__construct();
}
protected function configure(): void
{
$this
->setDescription('Lists (and deletes if wanted) attachments files that are not used anymore (abandoned files).')
->setHelp('This command allows to find all files in the media folder which are not associated with an attachment anymore.'.
' These files are not needed and can eventually deleted.');
$this->setHelp('This command allows to find all files in the media folder which are not associated with an attachment anymore.'.
' These files are not needed and can eventually deleted.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -91,7 +83,7 @@ class CleanAttachmentsCommand extends Command
foreach ($finder as $file) {
//If not attachment object uses this file, print it
if (0 === count($this->reverseSearch->findAttachmentsByFile($file))) {
if ([] === $this->reverseSearch->findAttachmentsByFile($file)) {
$file_list[] = $file;
$table->addRow([
$fs->makePathRelative($file->getPathname(), $mediaPath),
@@ -101,14 +93,14 @@ class CleanAttachmentsCommand extends Command
}
}
if (count($file_list) > 0) {
if ($file_list !== []) {
$table->render();
$continue = $io->confirm(sprintf('Found %d abandoned files. Do you want to delete them? This can not be undone!', count($file_list)), false);
if (!$continue) {
//We are finished here, when no files should be deleted
return 0;
return Command::SUCCESS;
}
//Delete the files
@@ -121,7 +113,7 @@ class CleanAttachmentsCommand extends Command
$io->success('No abandoned files found.');
}
return 0;
return Command::SUCCESS;
}
/**

View File

@@ -1,7 +1,11 @@
<?php
declare(strict_types=1);
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\ORM\EntityManagerInterface;
use PhpZip\Constants\ZipCompressionMethod;
@@ -16,19 +20,11 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:backup', 'Backup the files and the database of Part-DB')]
class BackupCommand extends Command
{
protected static $defaultName = 'partdb:backup';
protected static $defaultDescription = 'Backup the files and the database of Part-DB';
private string $project_dir;
private EntityManagerInterface $entityManager;
public function __construct(string $project_dir, EntityManagerInterface $entityManager)
public function __construct(private readonly string $project_dir, private readonly EntityManagerInterface $entityManager)
{
$this->project_dir = $project_dir;
$this->entityManager = $entityManager;
parent::__construct();
}
@@ -71,13 +67,10 @@ class BackupCommand extends Command
$io->info('Backup Part-DB to '.$output_filepath);
//Check if the file already exists
if (file_exists($output_filepath)) {
//Then ask the user, if he wants to overwrite the file
if (!$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) {
$io->error('Backup aborted!');
return Command::FAILURE;
}
//Then ask the user, if he wants to overwrite the file
if (file_exists($output_filepath) && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) {
$io->error('Backup aborted!');
return Command::FAILURE;
}
$io->note('Starting backup...');
@@ -115,8 +108,6 @@ class BackupCommand extends Command
/**
* Constructs the MySQL PDO DSN.
* Taken from https://github.com/doctrine/dbal/blob/3.5.x/src/Driver/PDO/MySQL/Driver.php
*
* @param array $params
*/
private function configureDumper(array $params, DbDumper $dumper): void
{
@@ -166,7 +157,7 @@ class BackupCommand extends Command
$io->error('Could not dump database: '.$e->getMessage());
$io->error('This can maybe be fixed by installing the mysqldump binary and adding it to the PATH variable!');
}
} elseif ($connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform) {
} elseif ($connection->getDatabasePlatform() instanceof SqlitePlatform) {
$io->note('SQLite database detected. Copy DB file to ZIP...');
$params = $connection->getParams();
$zip->addFile($params['path'], 'var/app.db');

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
@@ -17,9 +20,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -27,23 +30,17 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
#[AsCommand('partdb:check-requirements', 'Checks if the requirements Part-DB needs or recommends are fulfilled.')]
class CheckRequirementsCommand extends Command
{
protected static $defaultName = 'partdb:check-requirements';
protected ContainerBagInterface $params;
public function __construct(ContainerBagInterface $params)
public function __construct(protected ContainerBagInterface $params)
{
$this->params = $params;
parent::__construct();
}
protected function configure(): void
{
$this
->setDescription('Checks if the requirements Part-DB needs or recommends are fulfilled.')
->addOption('only_issues', 'i', InputOption::VALUE_NONE, 'Only show issues, not success messages.')
$this->addOption('only_issues', 'i', InputOption::VALUE_NONE, 'Only show issues, not success messages.')
;
}
@@ -66,105 +63,124 @@ class CheckRequirementsCommand extends Command
}
protected function checkPHP(SymfonyStyle $io, $only_issues = false): void
protected function checkPHP(SymfonyStyle $io, bool $only_issues = false): void
{
//Check PHP versions
$io->isVerbose() && $io->comment('Checking PHP version...');
if (PHP_VERSION_ID < 80100) {
if ($io->isVerbose()) {
$io->comment('Checking PHP version...');
}
//We recommend PHP 8.2, but 8.1 is the minimum
if (PHP_VERSION_ID < 80200) {
$io->warning('You are using PHP '. PHP_VERSION .'. This will work, but a newer version is recommended.');
} else {
!$only_issues && $io->success('PHP version is sufficient.');
} elseif (!$only_issues) {
$io->success('PHP version is sufficient.');
}
//Check if opcache is enabled
$io->isVerbose() && $io->comment('Checking Opcache...');
if ($io->isVerbose()) {
$io->comment('Checking Opcache...');
}
$opcache_enabled = ini_get('opcache.enable') === '1';
if (!$opcache_enabled) {
$io->warning('Opcache is not enabled. This will work, but performance will be better with opcache enabled. Set opcache.enable=1 in your php.ini to enable it');
} else {
!$only_issues && $io->success('Opcache is enabled.');
} elseif (!$only_issues) {
$io->success('Opcache is enabled.');
}
//Check if opcache is configured correctly
$io->isVerbose() && $io->comment('Checking Opcache configuration...');
if ($io->isVerbose()) {
$io->comment('Checking Opcache configuration...');
}
if ($opcache_enabled && (ini_get('opcache.memory_consumption') < 256 || ini_get('opcache.max_accelerated_files') < 20000)) {
$io->warning('Opcache configuration can be improved. See https://symfony.com/doc/current/performance.html for more info.');
} else {
!$only_issues && $io->success('Opcache configuration is already performance optimized.');
} elseif (!$only_issues) {
$io->success('Opcache configuration is already performance optimized.');
}
}
protected function checkPartDBConfig(SymfonyStyle $io, $only_issues = false): void
protected function checkPartDBConfig(SymfonyStyle $io, bool $only_issues = false): void
{
//Check if APP_ENV is set to prod
$io->isVerbose() && $io->comment('Checking debug mode...');
if($this->params->get('kernel.debug')) {
if ($io->isVerbose()) {
$io->comment('Checking debug mode...');
}
if ($this->params->get('kernel.debug')) {
$io->warning('You have activated debug mode, this is will leak informations in a production environment.');
} else {
!$only_issues && $io->success('Debug mode disabled.');
} elseif (!$only_issues) {
$io->success('Debug mode disabled.');
}
}
protected function checkPHPExtensions(SymfonyStyle $io, $only_issues = false): void
protected function checkPHPExtensions(SymfonyStyle $io, bool $only_issues = false): void
{
//Get all installed PHP extensions
$extensions = get_loaded_extensions();
$io->isVerbose() && $io->comment('Your PHP installation has '. count($extensions) .' extensions installed: '. implode(', ', $extensions));
if ($io->isVerbose()) {
$io->comment('Your PHP installation has '. count($extensions) .' extensions installed: '. implode(', ', $extensions));
}
$db_drivers_count = 0;
if(!in_array('pdo_mysql', $extensions)) {
if(!in_array('pdo_mysql', $extensions, true)) {
$io->error('pdo_mysql is not installed. You will not be able to use MySQL databases.');
} else {
!$only_issues && $io->success('PHP extension pdo_mysql is installed.');
if (!$only_issues) {
$io->success('PHP extension pdo_mysql is installed.');
}
$db_drivers_count++;
}
if(!in_array('pdo_sqlite', $extensions)) {
if(!in_array('pdo_sqlite', $extensions, true)) {
$io->error('pdo_sqlite is not installed. You will not be able to use SQLite. databases');
} else {
!$only_issues && $io->success('PHP extension pdo_sqlite is installed.');
if (!$only_issues) {
$io->success('PHP extension pdo_sqlite is installed.');
}
$db_drivers_count++;
}
$io->isVerbose() && $io->comment('You have '. $db_drivers_count .' database drivers installed.');
if ($io->isVerbose()) {
$io->comment('You have '. $db_drivers_count .' database drivers installed.');
}
if ($db_drivers_count === 0) {
$io->error('You have no database drivers installed. You have to install at least one database driver!');
}
if(!in_array('curl', $extensions)) {
if (!in_array('curl', $extensions, true)) {
$io->warning('curl extension is not installed. Install curl extension for better performance');
} else {
!$only_issues && $io->success('PHP extension curl is installed.');
} elseif (!$only_issues) {
$io->success('PHP extension curl is installed.');
}
$gd_installed = in_array('gd', $extensions);
if(!$gd_installed) {
$gd_installed = in_array('gd', $extensions, true);
if (!$gd_installed) {
$io->error('GD is not installed. GD is required for image processing.');
} else {
!$only_issues && $io->success('PHP extension GD is installed.');
} elseif (!$only_issues) {
$io->success('PHP extension GD is installed.');
}
//Check if GD has jpeg support
$io->isVerbose() && $io->comment('Checking if GD has jpeg support...');
if ($io->isVerbose()) {
$io->comment('Checking if GD has jpeg support...');
}
if ($gd_installed) {
$gd_info = gd_info();
if($gd_info['JPEG Support'] === false) {
if ($gd_info['JPEG Support'] === false) {
$io->warning('Your GD does not have jpeg support. You will not be able to generate thumbnails of jpeg images.');
} else {
!$only_issues && $io->success('GD has jpeg support.');
} elseif (!$only_issues) {
$io->success('GD has jpeg support.');
}
if($gd_info['PNG Support'] === false) {
if ($gd_info['PNG Support'] === false) {
$io->warning('Your GD does not have png support. You will not be able to generate thumbnails of png images.');
} else {
!$only_issues && $io->success('GD has png support.');
} elseif (!$only_issues) {
$io->success('GD has png support.');
}
if($gd_info['WebP Support'] === false) {
if ($gd_info['WebP Support'] === false) {
$io->warning('Your GD does not have WebP support. You will not be able to generate thumbnails of WebP images.');
} else {
!$only_issues && $io->success('GD has WebP support.');
} elseif (!$only_issues) {
$io->success('GD has WebP support.');
}
}
@@ -173,4 +189,4 @@ class CheckRequirementsCommand extends Command
}
}
}

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Command\Currencies;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\PriceInformations\Currency;
use App\Services\Tools\ExchangeRateUpdater;
use Doctrine\ORM\EntityManagerInterface;
@@ -35,30 +36,17 @@ use Symfony\Component\Console\Style\SymfonyStyle;
use function count;
use function strlen;
#[AsCommand('partdb:currencies:update-exchange-rates|partdb:update-exchange-rates|app:update-exchange-rates', 'Updates the currency exchange rates.')]
class UpdateExchangeRatesCommand extends Command
{
protected static $defaultName = 'partdb:currencies:update-exchange-rates|partdb:update-exchange-rates|app:update-exchange-rates';
protected string $base_current;
protected EntityManagerInterface $em;
protected ExchangeRateUpdater $exchangeRateUpdater;
public function __construct(string $base_current, EntityManagerInterface $entityManager, ExchangeRateUpdater $exchangeRateUpdater)
public function __construct(protected string $base_current, protected EntityManagerInterface $em, protected ExchangeRateUpdater $exchangeRateUpdater)
{
//$this->swap = $swap;
$this->base_current = $base_current;
$this->em = $entityManager;
$this->exchangeRateUpdater = $exchangeRateUpdater;
parent::__construct();
}
protected function configure(): void
{
$this
->setDescription('Updates the currency exchange rates.')
->addArgument('iso_code', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The ISO Codes of the currencies that should be updated.');
$this->addArgument('iso_code', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The ISO Codes of the currencies that should be updated.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -69,7 +57,7 @@ class UpdateExchangeRatesCommand extends Command
if (3 !== strlen($this->base_current)) {
$io->error('Chosen Base current is not valid. Check your settings!');
return 1;
return Command::FAILURE;
}
$io->note('Update currency exchange rates with base currency: '.$this->base_current);
@@ -78,11 +66,7 @@ class UpdateExchangeRatesCommand extends Command
$iso_code = $input->getArgument('iso_code');
$repo = $this->em->getRepository(Currency::class);
if (!empty($iso_code)) {
$candidates = $repo->findBy(['iso_code' => $iso_code]);
} else {
$candidates = $repo->findAll();
}
$candidates = empty($iso_code) ? $repo->findAll() : $repo->findBy(['iso_code' => $iso_code]);
$success_counter = 0;
@@ -106,6 +90,6 @@ class UpdateExchangeRatesCommand extends Command
$io->success(sprintf('%d (of %d) currency exchange rates were updated.', $success_counter, count($candidates)));
return 0;
return Command::SUCCESS;
}
}

View File

@@ -22,6 +22,8 @@ declare(strict_types=1);
namespace App\Command\Logs;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\UserSystem\User;
use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\LogSystem\AbstractLogEntry;
use App\Repository\LogEntryRepository;
@@ -36,23 +38,14 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\Translation\TranslatorInterface;
#[AsCommand('partdb:logs:show|app:show-logs', 'List the last event log entries.')]
class ShowEventLogCommand extends Command
{
protected static $defaultName = 'partdb:logs:show|app:show-logs';
protected EntityManagerInterface $entityManager;
protected TranslatorInterface $translator;
protected ElementTypeNameGenerator $elementTypeNameGenerator;
protected LogEntryRepository $repo;
protected LogEntryExtraFormatter $formatter;
public function __construct(EntityManagerInterface $entityManager,
TranslatorInterface $translator, ElementTypeNameGenerator $elementTypeNameGenerator, LogEntryExtraFormatter $formatter)
public function __construct(protected EntityManagerInterface $entityManager,
protected TranslatorInterface $translator, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected LogEntryExtraFormatter $formatter)
{
$this->entityManager = $entityManager;
$this->translator = $translator;
$this->elementTypeNameGenerator = $elementTypeNameGenerator;
$this->formatter = $formatter;
$this->repo = $this->entityManager->getRepository(AbstractLogEntry::class);
parent::__construct();
}
@@ -74,7 +67,7 @@ class ShowEventLogCommand extends Command
if ($page > $max_page && $max_page > 0) {
$io->error("There is no page ${page}! The maximum page is ${max_page}.");
return 1;
return Command::FAILURE;
}
$io->note("There are a total of ${total_count} log entries in the DB.");
@@ -84,21 +77,19 @@ class ShowEventLogCommand extends Command
$this->showPage($output, $desc, $limit, $page, $max_page, $showExtra);
if ($onePage) {
return 0;
return Command::SUCCESS;
}
$continue = $io->confirm('Do you want to show the next page?');
++$page;
}
return 0;
return Command::SUCCESS;
}
protected function configure(): void
{
$this
->setDescription('List the last event log entries.')
->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many log entries should be shown per page.', 50)
$this->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many log entries should be shown per page.', 50)
->addOption('oldest_first', null, InputOption::VALUE_NONE, 'Show older entries first.')
->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'Which page should be shown?', 1)
->addOption('onePage', null, InputOption::VALUE_NONE, 'Show only one page (dont ask to go to next).')
@@ -147,14 +138,12 @@ class ShowEventLogCommand extends Command
$target_class = $this->elementTypeNameGenerator->getLocalizedTypeLabel($entry->getTargetClass());
}
if ($entry->getUser()) {
if ($entry->getUser() instanceof User) {
$user = $entry->getUser()->getFullName(true);
} elseif ($entry->isCLIEntry()) {
$user = $entry->getCLIUsername() . ' [CLI]';
} else {
if ($entry->isCLIEntry()) {
$user = $entry->getCLIUsername() . ' [CLI]';
} else {
$user = $entry->getUsername() . ' [deleted]';
}
$user = $entry->getUsername() . ' [deleted]';
}
$row = [

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Command\Migrations;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\Attachments\AttachmentType;
use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\ProjectSystem\Project;
@@ -47,6 +48,7 @@ use function count;
/**
* This command converts the BBCode used by old Part-DB versions (<1.0), to the current used Markdown format.
*/
#[AsCommand('partdb:migrations:convert-bbcode|app:convert-bbcode', 'Converts BBCode used in old Part-DB versions to newly used Markdown')]
class ConvertBBCodeCommand extends Command
{
/**
@@ -57,18 +59,10 @@ class ConvertBBCodeCommand extends Command
* @var string The regex (performed in PHP) used to check if a property really contains BBCODE
*/
protected const BBCODE_REGEX = '/\\[.+\\].*\\[\\/.+\\]/';
protected static $defaultName = 'partdb:migrations:convert-bbcode|app:convert-bbcode';
protected EntityManagerInterface $em;
protected PropertyAccessorInterface $propertyAccessor;
protected BBCodeToMarkdownConverter $converter;
public function __construct(EntityManagerInterface $entityManager, PropertyAccessorInterface $propertyAccessor)
public function __construct(protected EntityManagerInterface $em, protected PropertyAccessorInterface $propertyAccessor)
{
$this->em = $entityManager;
$this->propertyAccessor = $propertyAccessor;
$this->converter = new BBCodeToMarkdownConverter();
parent::__construct();
@@ -76,9 +70,7 @@ class ConvertBBCodeCommand extends Command
protected function configure(): void
{
$this
->setDescription('Converts BBCode used in old Part-DB versions to newly used Markdown')
->setHelp('Older versions of Part-DB (<1.0) used BBCode for rich text formatting.
$this->setHelp('Older versions of Part-DB (<1.0) used BBCode for rich text formatting.
Part-DB now uses Markdown which offers more features but is incompatible with BBCode.
When you upgrade from an pre 1.0 version you have to run this command to convert your comment fields');
@@ -129,25 +121,25 @@ class ConvertBBCodeCommand extends Command
//Fetch resulting classes
$results = $qb->getQuery()->getResult();
$io->note(sprintf('Found %d entities, that need to be converted!', count($results)));
$io->note(sprintf('Found %d entities, that need to be converted!', is_countable($results) ? count($results) : 0));
//In verbose mode print the names of the entities
foreach ($results as $result) {
/** @var AbstractNamedDBElement $result */
$io->writeln(
'Convert entity: '.$result->getName().' ('.get_class($result).': '.$result->getID().')',
'Convert entity: '.$result->getName().' ('.$result::class.': '.$result->getID().')',
OutputInterface::VERBOSITY_VERBOSE
);
foreach ($properties as $property) {
//Retrieve bbcode from entity
$bbcode = $this->propertyAccessor->getValue($result, $property);
//Check if the current property really contains BBCode
if (!preg_match(static::BBCODE_REGEX, $bbcode)) {
if (!preg_match(static::BBCODE_REGEX, (string) $bbcode)) {
continue;
}
$io->writeln(
'BBCode (old): '
.str_replace('\n', ' ', substr($bbcode, 0, 255)),
.str_replace('\n', ' ', substr((string) $bbcode, 0, 255)),
OutputInterface::VERBOSITY_VERY_VERBOSE
);
$markdown = $this->converter->convert($bbcode);
@@ -168,6 +160,6 @@ class ConvertBBCodeCommand extends Command
$io->success('Changes saved to DB successfully!');
}
return 0;
return Command::SUCCESS;
}
}

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
@@ -17,9 +20,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\Migrations;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Services\ImportExportSystem\PartKeeprImporter\PKDatastructureImporter;
use App\Services\ImportExportSystem\PartKeeprImporter\MySQLDumpXMLConverter;
use App\Services\ImportExportSystem\PartKeeprImporter\PKImportHelper;
@@ -33,34 +36,19 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:migrations:import-partkeepr', 'Import a PartKeepr database XML dump into Part-DB')]
class ImportPartKeeprCommand extends Command
{
protected static $defaultName = 'partdb:migrations:import-partkeepr';
protected EntityManagerInterface $em;
protected MySQLDumpXMLConverter $xml_converter;
protected PKDatastructureImporter $datastructureImporter;
protected PKImportHelper $importHelper;
protected PKPartImporter $partImporter;
protected PKOptionalImporter $optionalImporter;
public function __construct(EntityManagerInterface $em, MySQLDumpXMLConverter $xml_converter,
PKDatastructureImporter $datastructureImporter, PKPartImporter $partImporter, PKImportHelper $importHelper,
PKOptionalImporter $optionalImporter)
public function __construct(protected EntityManagerInterface $em, protected MySQLDumpXMLConverter $xml_converter,
protected PKDatastructureImporter $datastructureImporter, protected PKPartImporter $partImporter, protected PKImportHelper $importHelper,
protected PKOptionalImporter $optionalImporter)
{
parent::__construct(self::$defaultName);
$this->em = $em;
$this->datastructureImporter = $datastructureImporter;
$this->importHelper = $importHelper;
$this->partImporter = $partImporter;
$this->xml_converter = $xml_converter;
$this->optionalImporter = $optionalImporter;
}
protected function configure()
protected function configure(): void
{
$this->setDescription('Import a PartKeepr database XML dump into Part-DB');
$this->setHelp('This command allows you to import a PartKeepr database exported by mysqldump as XML file into Part-DB');
$this->addArgument('file', InputArgument::REQUIRED, 'The file to which should be imported.');
@@ -100,7 +88,7 @@ class ImportPartKeeprCommand extends Command
if (!$this->importHelper->checkVersion($data)) {
$db_version = $this->importHelper->getDatabaseSchemaVersion($data);
$io->error('The version of the imported database is not supported! (Version: '.$db_version.')');
return 1;
return Command::FAILURE;
}
//Import the mandatory data
@@ -118,7 +106,7 @@ class ImportPartKeeprCommand extends Command
$io->success('Imported '.$count.' users.');
}
return 0;
return Command::SUCCESS;
}
private function doImport(SymfonyStyle $io, array $data): void
@@ -155,4 +143,4 @@ class ImportPartKeeprCommand extends Command
$io->success('Imported '.$count.' parts.');
}
}
}

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
@@ -17,9 +20,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\User;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\UserSystem\User;
use App\Security\SamlUserFactory;
use Doctrine\ORM\EntityManagerInterface;
@@ -30,25 +33,17 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user', 'Converts a local user to a SAML user (and vice versa)')]
class ConvertToSAMLUserCommand extends Command
{
protected static $defaultName = 'partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user';
protected EntityManagerInterface $entityManager;
protected bool $saml_enabled;
public function __construct(EntityManagerInterface $entityManager, bool $saml_enabled)
public function __construct(protected EntityManagerInterface $entityManager, protected bool $saml_enabled)
{
parent::__construct();
$this->entityManager = $entityManager;
$this->saml_enabled = $saml_enabled;
}
protected function configure(): void
{
$this
->setDescription('Converts a local user to a SAML user (and vice versa)')
->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.')
$this->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.')
->addArgument('user', InputArgument::REQUIRED, 'The username (or email) of the user')
->addOption('to-local', null, InputOption::VALUE_NONE, 'Converts a SAML user to a local user')
;
@@ -70,7 +65,7 @@ class ConvertToSAMLUserCommand extends Command
if (!$user) {
$io->error('User not found!');
return 1;
return Command::FAILURE;
}
$io->info('User found: '.$user->getFullName(true) . ': '.$user->getEmail().' [ID: ' . $user->getID() . ']');
@@ -87,7 +82,7 @@ class ConvertToSAMLUserCommand extends Command
$io->confirm('You are going to convert a SAML user to a local user. This means, that the user can only login via the login form. '
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
$user->setSAMLUser(false);
$user->setSamlUser(false);
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
$this->entityManager->flush();
@@ -102,7 +97,7 @@ class ConvertToSAMLUserCommand extends Command
$io->confirm('You are going to convert a local user to a SAML user. This means, that the user can only login via SAML afterwards. The password in the DB will be removed. '
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
$user->setSAMLUser(true);
$user->setSamlUser(true);
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
$this->entityManager->flush();
@@ -112,4 +107,4 @@ class ConvertToSAMLUserCommand extends Command
return 0;
}
}
}

View File

@@ -22,6 +22,7 @@ declare(strict_types=1);
namespace App\Command\User;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\UserSystem\User;
use App\Events\SecurityEvent;
use App\Events\SecurityEvents;
@@ -34,28 +35,17 @@ use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
#[AsCommand('partdb:users:set-password|app:set-password|users:set-password|partdb:user:set-password', 'Sets the password of a user')]
class SetPasswordCommand extends Command
{
protected static $defaultName = 'partdb:users:set-password|app:set-password|users:set-password|partdb:user:set-password';
protected EntityManagerInterface $entityManager;
protected UserPasswordHasherInterface $encoder;
protected EventDispatcherInterface $eventDispatcher;
public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordEncoder, EventDispatcherInterface $eventDispatcher)
public function __construct(protected EntityManagerInterface $entityManager, protected UserPasswordHasherInterface $encoder, protected EventDispatcherInterface $eventDispatcher)
{
$this->entityManager = $entityManager;
$this->encoder = $passwordEncoder;
$this->eventDispatcher = $eventDispatcher;
parent::__construct();
}
protected function configure(): void
{
$this
->setDescription('Sets the password of a user')
->setHelp('This password allows you to set the password of a user, without knowing the old password.')
$this->setHelp('This password allows you to set the password of a user, without knowing the old password.')
->addArgument('user', InputArgument::REQUIRED, 'The username or email of the user')
;
}
@@ -67,17 +57,17 @@ class SetPasswordCommand extends Command
$user = $this->entityManager->getRepository(User::class)->findByEmailOrName($user_name);
if (!$user) {
if (!$user instanceof User) {
$io->error(sprintf('No user with the given username %s found in the database!', $user_name));
return 1;
return Command::FAILURE;
}
$io->note('User found!');
if ($user->isSamlUser()) {
$io->error('This user is a SAML user, so you can not change the password!');
return 1;
return Command::FAILURE;
}
$proceed = $io->confirm(
@@ -85,7 +75,7 @@ class SetPasswordCommand extends Command
$user->getFullName(true), $user->getID()));
if (!$proceed) {
return 1;
return Command::FAILURE;
}
$success = false;
@@ -116,6 +106,6 @@ class SetPasswordCommand extends Command
$security_event = new SecurityEvent($user);
$this->eventDispatcher->dispatch($security_event, SecurityEvents::PASSWORD_CHANGED);
return 0;
return Command::SUCCESS;
}
}

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
@@ -17,9 +20,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\User;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\PermissionData;
use App\Entity\UserSystem\User;
@@ -31,22 +34,12 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:users:upgrade-permissions-schema', '(Manually) upgrades the permissions schema of all users to the latest version.')]
final class UpgradePermissionsSchemaCommand extends Command
{
protected static $defaultName = 'partdb:users:upgrade-permissions-schema';
protected static $defaultDescription = '(Manually) upgrades the permissions schema of all users to the latest version.';
private PermissionSchemaUpdater $permissionSchemaUpdater;
private EntityManagerInterface $em;
private EventCommentHelper $eventCommentHelper;
public function __construct(PermissionSchemaUpdater $permissionSchemaUpdater, EntityManagerInterface $entityManager, EventCommentHelper $eventCommentHelper)
public function __construct(private readonly PermissionSchemaUpdater $permissionSchemaUpdater, private readonly EntityManagerInterface $em, private readonly EventCommentHelper $eventCommentHelper)
{
parent::__construct(self::$defaultName);
$this->permissionSchemaUpdater = $permissionSchemaUpdater;
$this->eventCommentHelper = $eventCommentHelper;
$this->em = $entityManager;
}
protected function configure(): void
@@ -81,26 +74,22 @@ final class UpgradePermissionsSchemaCommand extends Command
}
$io->info('Found '. count($groups_to_upgrade) .' groups and '. count($users_to_upgrade) .' users that need an update.');
if (empty($groups_to_upgrade) && empty($users_to_upgrade)) {
if ($groups_to_upgrade === [] && $users_to_upgrade === []) {
$io->success('All users and group permissions schemas are up-to-date. No update needed.');
return 0;
return Command::SUCCESS;
}
//List all users and groups that need an update
$io->section('Groups that need an update:');
$io->listing(array_map(static function (Group $group) {
return $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')';
}, $groups_to_upgrade));
$io->listing(array_map(static fn(Group $group): string => $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')', $groups_to_upgrade));
$io->section('Users that need an update:');
$io->listing(array_map(static function (User $user) {
return $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')';
}, $users_to_upgrade));
$io->listing(array_map(static fn(User $user): string => $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')', $users_to_upgrade));
if(!$io->confirm('Continue with the update?', false)) {
$io->warning('Update aborted.');
return 0;
return Command::SUCCESS;
}
//Update all users and groups

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
@@ -17,9 +20,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\User;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\UserSystem\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
@@ -29,24 +32,17 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:users:enable|partdb:user:enable', 'Enables/Disable the login of one or more users')]
class UserEnableCommand extends Command
{
protected static $defaultName = 'partdb:users:enable|partdb:user:enable';
protected EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager, string $name = null)
public function __construct(protected EntityManagerInterface $entityManager, string $name = null)
{
$this->entityManager = $entityManager;
parent::__construct($name);
}
protected function configure(): void
{
$this
->setDescription('Enables/Disable the login of one or more users')
->setHelp('This allows you to allow or prevent the login of certain user. Use the --disable option to disable the login for the given users')
$this->setHelp('This allows you to allow or prevent the login of certain user. Use the --disable option to disable the login for the given users')
->addArgument('users', InputArgument::IS_ARRAY, 'The usernames of the users to use')
->addOption('all', 'a', InputOption::VALUE_NONE, 'Enable/Disable all users')
->addOption('disable', 'd', InputOption::VALUE_NONE, 'Disable the login of the given users')
@@ -73,7 +69,7 @@ class UserEnableCommand extends Command
} else { //Otherwise, fetch the users from DB
foreach ($usernames as $username) {
$user = $repo->findByEmailOrName($username);
if ($user === null) {
if (!$user instanceof User) {
$io->error('No user found with username: '.$username);
return self::FAILURE;
}
@@ -87,9 +83,7 @@ class UserEnableCommand extends Command
$io->note('The following users will be enabled:');
}
$io->table(['Username', 'Enabled/Disabled'],
array_map(static function(User $user) {
return [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled'];
}, $users));
array_map(static fn(User $user) => [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled'], $users));
if(!$io->confirm('Do you want to continue?')) {
$io->warning('Aborting!');
@@ -107,4 +101,4 @@ class UserEnableCommand extends Command
return self::SUCCESS;
}
}
}

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
@@ -17,9 +20,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\User;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
@@ -28,24 +32,17 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:users:list|users:list', 'Lists all users')]
class UserListCommand extends Command
{
protected static $defaultName = 'partdb:users:list|users:list';
protected EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
public function __construct(protected EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
parent::__construct();
}
protected function configure(): void
{
$this
->setDescription('Lists all users')
->setHelp('This command lists all users in the database.')
$this->setHelp('This command lists all users in the database.')
->addOption('local', 'l', null, 'Only list local users')
->addOption('saml', 's', null, 'Only list SAML users')
;
@@ -82,13 +79,13 @@ class UserListCommand extends Command
foreach ($users as $user) {
$table->addRow([
$user->getId(),
$user->getID(),
$user->getUsername(),
$user->getFullName(),
$user->getEmail(),
$user->getGroup() !== null ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group',
$user->getGroup() instanceof Group ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group',
$user->isDisabled() ? 'Yes' : 'No',
$user->isSAMLUser() ? 'SAML' : 'Local',
$user->isSamlUser() ? 'SAML' : 'Local',
]);
}
@@ -98,4 +95,4 @@ class UserListCommand extends Command
return self::SUCCESS;
}
}
}

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
@@ -17,9 +20,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command\User;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Entity\UserSystem\User;
use App\Repository\UserRepository;
use App\Services\UserSystem\PermissionManager;
@@ -34,22 +37,14 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\Translation\TranslatorInterface;
#[AsCommand('partdb:users:permissions|partdb:user:permissions', 'View and edit the permissions of a given user')]
class UsersPermissionsCommand extends Command
{
protected static $defaultName = 'partdb:users:permissions|partdb:user:permissions';
protected static $defaultDescription = 'View and edit the permissions of a given user';
protected EntityManagerInterface $entityManager;
protected UserRepository $userRepository;
protected PermissionManager $permissionResolver;
protected TranslatorInterface $translator;
public function __construct(EntityManagerInterface $entityManager, PermissionManager $permissionResolver, TranslatorInterface $translator)
public function __construct(protected EntityManagerInterface $entityManager, protected PermissionManager $permissionResolver, protected TranslatorInterface $translator)
{
$this->entityManager = $entityManager;
$this->userRepository = $entityManager->getRepository(User::class);
$this->permissionResolver = $permissionResolver;
$this->translator = $translator;
parent::__construct(self::$defaultName);
}
@@ -73,12 +68,12 @@ class UsersPermissionsCommand extends Command
//Find user
$io->note('Finding user with username: ' . $username);
$user = $this->userRepository->findByEmailOrName($username);
if ($user === null) {
if (!$user instanceof User) {
$io->error('No user found with username: ' . $username);
return Command::FAILURE;
}
$io->note(sprintf('Found user %s with ID %d', $user->getFullName(true), $user->getId()));
$io->note(sprintf('Found user %s with ID %d', $user->getFullName(true), $user->getID()));
$edit_mapping = $this->renderPermissionTable($output, $user, $inherit);
@@ -102,7 +97,7 @@ class UsersPermissionsCommand extends Command
$new_value_str = $io->ask('Enter the new value for the permission (A = allow, D = disallow, I = inherit)');
switch (strtolower($new_value_str)) {
switch (strtolower((string) $new_value_str)) {
case 'a':
case 'allow':
$new_value = true;
@@ -209,11 +204,11 @@ class UsersPermissionsCommand extends Command
if ($permission_value === true) {
return '<fg=green>Allow</>';
} else if ($permission_value === false) {
} elseif ($permission_value === false) {
return '<fg=red>Disallow</>';
} else if ($permission_value === null && !$inherit) {
} elseif ($permission_value === null && !$inherit) {
return '<fg=blue>Inherit</>';
} else if ($permission_value === null && $inherit) {
} elseif ($permission_value === null && $inherit) {
return '<fg=red>Disallow (Inherited)</>';
}

View File

@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
@@ -17,9 +20,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use App\Services\Misc\GitVersionInfo;
use Shivas\VersioningBundle\Service\VersionManagerInterface;
use Symfony\Component\Console\Command\Command;
@@ -27,25 +30,16 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand('partdb:version|app:version', 'Shows the currently installed version of Part-DB.')]
class VersionCommand extends Command
{
protected static $defaultName = 'partdb:version|app:version';
protected VersionManagerInterface $versionManager;
protected GitVersionInfo $gitVersionInfo;
public function __construct(VersionManagerInterface $versionManager, GitVersionInfo $gitVersionInfo)
public function __construct(protected VersionManagerInterface $versionManager, protected GitVersionInfo $gitVersionInfo)
{
$this->versionManager = $versionManager;
$this->gitVersionInfo = $gitVersionInfo;
parent::__construct();
}
protected function configure(): void
{
$this
->setDescription('Shows the currently installed version of Part-DB.')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -66,6 +60,6 @@ class VersionCommand extends Command
$io->info('OS: '. php_uname());
$io->info('PHP extension: '. implode(', ', get_loaded_extensions()));
return 0;
return Command::SUCCESS;
}
}
}

View File

@@ -37,8 +37,9 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/attachment_type")
* @see \App\Tests\Controller\AdminPages\AttachmentTypeControllerTest
*/
#[Route(path: '/attachment_type')]
class AttachmentTypeController extends BaseAdminController
{
protected string $entity_class = AttachmentType::class;
@@ -48,44 +49,34 @@ class AttachmentTypeController extends BaseAdminController
protected string $attachment_class = AttachmentTypeAttachment::class;
protected ?string $parameter_class = AttachmentTypeParameter::class;
/**
* @Route("/{id}", name="attachment_type_delete", methods={"DELETE"})
*/
#[Route(path: '/{id}', name: 'attachment_type_delete', methods: ['DELETE'])]
public function delete(Request $request, AttachmentType $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
{
return $this->_delete($request, $entity, $recursionHelper);
}
/**
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="attachment_type_edit")
* @Route("/{id}", requirements={"id"="\d+"})
*/
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'attachment_type_edit')]
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
public function edit(AttachmentType $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
{
return $this->_edit($entity, $request, $em, $timestamp);
}
/**
* @Route("/new", name="attachment_type_new")
* @Route("/{id}/clone", name="attachment_type_clone")
* @Route("/")
*/
#[Route(path: '/new', name: 'attachment_type_new')]
#[Route(path: '/{id}/clone', name: 'attachment_type_clone')]
#[Route(path: '/')]
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AttachmentType $entity = null): Response
{
return $this->_new($request, $em, $importer, $entity);
}
/**
* @Route("/export", name="attachment_type_export_all")
*/
#[Route(path: '/export', name: 'attachment_type_export_all')]
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
{
return $this->_exportAll($em, $exporter, $request);
}
/**
* @Route("/{id}/export", name="attachment_type_export")
*/
#[Route(path: '/{id}/export', name: 'attachment_type_export')]
public function exportEntity(AttachmentType $entity, EntityExporter $exporter, Request $request): Response
{
return $this->_exportEntity($entity, $exporter, $request);

View File

@@ -29,6 +29,7 @@ use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Base\AbstractPartsContainingDBElement;
use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\Base\PartsContainingRepositoryInterface;
use App\Entity\LabelSystem\LabelProcessMode;
use App\Entity\LabelSystem\LabelProfile;
use App\Entity\Parameters\AbstractParameter;
use App\Entity\UserSystem\User;
@@ -72,29 +73,16 @@ abstract class BaseAdminController extends AbstractController
protected string $route_base = '';
protected string $attachment_class = '';
protected ?string $parameter_class = '';
protected UserPasswordHasherInterface $passwordEncoder;
protected TranslatorInterface $translator;
protected AttachmentSubmitHandler $attachmentSubmitHandler;
protected EventCommentHelper $commentHelper;
protected HistoryHelper $historyHelper;
protected TimeTravel $timeTravel;
protected DataTableFactory $dataTableFactory;
/**
* @var EventDispatcher|EventDispatcherInterface
*/
protected $eventDispatcher;
protected LabelGenerator $labelGenerator;
protected LabelExampleElementsGenerator $barcodeExampleGenerator;
protected EntityManagerInterface $entityManager;
public function __construct(TranslatorInterface $translator, UserPasswordHasherInterface $passwordEncoder,
AttachmentSubmitHandler $attachmentSubmitHandler,
EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel,
DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, LabelExampleElementsGenerator $barcodeExampleGenerator,
LabelGenerator $labelGenerator, EntityManagerInterface $entityManager)
public function __construct(protected TranslatorInterface $translator, protected UserPasswordHasherInterface $passwordEncoder,
protected AttachmentSubmitHandler $attachmentSubmitHandler,
protected EventCommentHelper $commentHelper, protected HistoryHelper $historyHelper, protected TimeTravel $timeTravel,
protected DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator,
protected LabelGenerator $labelGenerator, protected EntityManagerInterface $entityManager)
{
if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) {
throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!');
@@ -107,18 +95,7 @@ abstract class BaseAdminController extends AbstractController
if ('' === $this->parameter_class || ($this->parameter_class && !is_a($this->parameter_class, AbstractParameter::class, true))) {
throw new InvalidArgumentException('You have to override the $parameter_class value with a valid Parameter class in your subclass!');
}
$this->translator = $translator;
$this->passwordEncoder = $passwordEncoder;
$this->attachmentSubmitHandler = $attachmentSubmitHandler;
$this->commentHelper = $commentHelper;
$this->historyHelper = $historyHelper;
$this->timeTravel = $timeTravel;
$this->dataTableFactory = $dataTableFactory;
$this->eventDispatcher = $eventDispatcher;
$this->barcodeExampleGenerator = $barcodeExampleGenerator;
$this->labelGenerator = $labelGenerator;
$this->entityManager = $entityManager;
}
protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?DateTime
@@ -177,13 +154,13 @@ abstract class BaseAdminController extends AbstractController
$form_options = [
'attachment_class' => $this->attachment_class,
'parameter_class' => $this->parameter_class,
'disabled' => null !== $timeTravel_timestamp,
'disabled' => $timeTravel_timestamp instanceof \DateTime,
];
//Disable editing of options, if user is not allowed to use twig...
if (
$entity instanceof LabelProfile
&& 'twig' === $entity->getOptions()->getLinesMode()
&& LabelProcessMode::TWIG === $entity->getOptions()->getProcessMode()
&& !$this->isGranted('@labels.use_twig')
) {
$form_options['disable_options'] = true;
@@ -245,7 +222,7 @@ abstract class BaseAdminController extends AbstractController
/** @var AbstractPartsContainingRepository $repo */
$repo = $this->entityManager->getRepository($this->entity_class);
return $this->renderForm($this->twig_template, [
return $this->render($this->twig_template, [
'entity' => $entity,
'form' => $form,
'route_base' => $this->route_base,
@@ -267,15 +244,9 @@ abstract class BaseAdminController extends AbstractController
return true;
}
protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null)
protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null): Response
{
if (null === $entity) {
/** @var AbstractStructuralDBElement|User $new_entity */
$new_entity = new $this->entity_class();
} else {
/** @var AbstractStructuralDBElement|User $new_entity */
$new_entity = clone $entity;
}
$new_entity = $entity instanceof AbstractNamedDBElement ? clone $entity : new $this->entity_class();
$this->denyAccessUnlessGranted('read', $new_entity);
@@ -287,42 +258,37 @@ abstract class BaseAdminController extends AbstractController
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//Perform additional actions
if ($this->additionalActionNew($form, $new_entity)) {
//Upload passed files
$attachments = $form['attachments'];
foreach ($attachments as $attachment) {
/** @var FormInterface $attachment */
$options = [
'secure_attachment' => $attachment['secureFile']->getData(),
'download_url' => $attachment['downloadURL']->getData(),
];
//Perform additional actions
if ($form->isSubmitted() && $form->isValid() && $this->additionalActionNew($form, $new_entity)) {
//Upload passed files
$attachments = $form['attachments'];
foreach ($attachments as $attachment) {
/** @var FormInterface $attachment */
$options = [
'secure_attachment' => $attachment['secureFile']->getData(),
'download_url' => $attachment['downloadURL']->getData(),
];
try {
$this->attachmentSubmitHandler->handleFormSubmit(
$attachment->getData(),
$attachment['file']->getData(),
$options
);
} catch (AttachmentDownloadException $attachmentDownloadException) {
$this->addFlash(
'error',
$this->translator->trans(
'attachment.download_failed'
).' '.$attachmentDownloadException->getMessage()
);
}
try {
$this->attachmentSubmitHandler->handleFormSubmit(
$attachment->getData(),
$attachment['file']->getData(),
$options
);
} catch (AttachmentDownloadException $attachmentDownloadException) {
$this->addFlash(
'error',
$this->translator->trans(
'attachment.download_failed'
).' '.$attachmentDownloadException->getMessage()
);
}
$this->commentHelper->setMessage($form['log_comment']->getData());
$em->persist($new_entity);
$em->flush();
$this->addFlash('success', 'entity.created_flash');
return $this->redirectToRoute($this->route_base.'_edit', ['id' => $new_entity->getID()]);
}
$this->commentHelper->setMessage($form['log_comment']->getData());
$em->persist($new_entity);
$em->flush();
$this->addFlash('success', 'entity.created_flash');
return $this->redirectToRoute($this->route_base.'_edit', ['id' => $new_entity->getID()]);
}
if ($form->isSubmitted() && !$form->isValid()) {
@@ -362,14 +328,13 @@ abstract class BaseAdminController extends AbstractController
try {
$errors = $importer->importFileAndPersistToDB($file, $options);
/** @var ConstraintViolationList $error */
foreach ($errors as $name => $error) {
foreach ($error['violations'] as $violation) {
foreach ($error as $violation) {
$this->addFlash('error', $name.': '.$violation->getMessage());
}
}
}
catch (UnexpectedValueException $e) {
catch (UnexpectedValueException) {
$this->addFlash('error', 'parts.import.flash.error.invalid_file');
}
}
@@ -402,7 +367,7 @@ abstract class BaseAdminController extends AbstractController
}
ret:
return $this->renderForm($this->twig_template, [
return $this->render($this->twig_template, [
'entity' => $new_entity,
'form' => $form,
'import_form' => $import_form,
@@ -437,7 +402,7 @@ abstract class BaseAdminController extends AbstractController
{
$this->denyAccessUnlessGranted('delete', $entity);
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
if ($this->isCsrfTokenValid('delete'.$entity->getID(), $request->request->get('_token'))) {
$entityManager = $this->entityManager;

View File

@@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/category")
* @see \App\Tests\Controller\AdminPages\CategoryControllerTest
*/
#[Route(path: '/category')]
class CategoryController extends BaseAdminController
{
protected string $entity_class = Category::class;
@@ -47,44 +48,34 @@ class CategoryController extends BaseAdminController
protected string $attachment_class = CategoryAttachment::class;
protected ?string $parameter_class = CategoryParameter::class;
/**
* @Route("/{id}", name="category_delete", methods={"DELETE"})
*/
#[Route(path: '/{id}', name: 'category_delete', methods: ['DELETE'])]
public function delete(Request $request, Category $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
{
return $this->_delete($request, $entity, $recursionHelper);
}
/**
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="category_edit")
* @Route("/{id}", requirements={"id"="\d+"})
*/
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'category_edit')]
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
public function edit(Category $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
{
return $this->_edit($entity, $request, $em, $timestamp);
}
/**
* @Route("/new", name="category_new")
* @Route("/{id}/clone", name="category_clone")
* @Route("/")
*/
#[Route(path: '/new', name: 'category_new')]
#[Route(path: '/{id}/clone', name: 'category_clone')]
#[Route(path: '/')]
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Category $entity = null): Response
{
return $this->_new($request, $em, $importer, $entity);
}
/**
* @Route("/export", name="category_export_all")
*/
#[Route(path: '/export', name: 'category_export_all')]
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
{
return $this->_exportAll($em, $exporter, $request);
}
/**
* @Route("/{id}/export", name="category_export")
*/
#[Route(path: '/{id}/export', name: 'category_export')]
public function exportEntity(Category $entity, EntityExporter $exporter, Request $request): Response
{
return $this->_exportEntity($entity, $exporter, $request);

View File

@@ -52,10 +52,9 @@ use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @Route("/currency")
*
* Class CurrencyController
*/
#[Route(path: '/currency')]
class CurrencyController extends BaseAdminController
{
protected string $entity_class = Currency::class;
@@ -65,8 +64,6 @@ class CurrencyController extends BaseAdminController
protected string $attachment_class = CurrencyAttachment::class;
protected ?string $parameter_class = CurrencyParameter::class;
protected ExchangeRateUpdater $exchangeRateUpdater;
public function __construct(
TranslatorInterface $translator,
UserPasswordHasherInterface $passwordEncoder,
@@ -79,10 +76,8 @@ class CurrencyController extends BaseAdminController
LabelExampleElementsGenerator $barcodeExampleGenerator,
LabelGenerator $labelGenerator,
EntityManagerInterface $entityManager,
ExchangeRateUpdater $exchangeRateUpdater
protected ExchangeRateUpdater $exchangeRateUpdater
) {
$this->exchangeRateUpdater = $exchangeRateUpdater;
parent::__construct(
$translator,
$passwordEncoder,
@@ -98,9 +93,7 @@ class CurrencyController extends BaseAdminController
);
}
/**
* @Route("/{id}", name="currency_delete", methods={"DELETE"})
*/
#[Route(path: '/{id}', name: 'currency_delete', methods: ['DELETE'])]
public function delete(Request $request, Currency $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
{
return $this->_delete($request, $entity, $recursionHelper);
@@ -131,36 +124,28 @@ class CurrencyController extends BaseAdminController
return true;
}
/**
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="currency_edit")
* @Route("/{id}", requirements={"id"="\d+"})
*/
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'currency_edit')]
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
public function edit(Currency $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
{
return $this->_edit($entity, $request, $em, $timestamp);
}
/**
* @Route("/new", name="currency_new")
* @Route("/{id}/clone", name="currency_clone")
* @Route("/")
*/
#[Route(path: '/new', name: 'currency_new')]
#[Route(path: '/{id}/clone', name: 'currency_clone')]
#[Route(path: '/')]
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Currency $entity = null): Response
{
return $this->_new($request, $em, $importer, $entity);
}
/**
* @Route("/export", name="currency_export_all")
*/
#[Route(path: '/export', name: 'currency_export_all')]
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
{
return $this->_exportAll($em, $exporter, $request);
}
/**
* @Route("/{id}/export", name="currency_export")
*/
#[Route(path: '/{id}/export', name: 'currency_export')]
public function exportEntity(Currency $entity, EntityExporter $exporter, Request $request): Response
{
return $this->_exportEntity($entity, $exporter, $request);

View File

@@ -37,8 +37,9 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/footprint")
* @see \App\Tests\Controller\AdminPages\FootprintControllerTest
*/
#[Route(path: '/footprint')]
class FootprintController extends BaseAdminController
{
protected string $entity_class = Footprint::class;
@@ -48,44 +49,34 @@ class FootprintController extends BaseAdminController
protected string $attachment_class = FootprintAttachment::class;
protected ?string $parameter_class = FootprintParameter::class;
/**
* @Route("/{id}", name="footprint_delete", methods={"DELETE"})
*/
#[Route(path: '/{id}', name: 'footprint_delete', methods: ['DELETE'])]
public function delete(Request $request, Footprint $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
{
return $this->_delete($request, $entity, $recursionHelper);
}
/**
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="footprint_edit")
* @Route("/{id}", requirements={"id"="\d+"})
*/
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'footprint_edit')]
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
public function edit(Footprint $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
{
return $this->_edit($entity, $request, $em, $timestamp);
}
/**
* @Route("/new", name="footprint_new")
* @Route("/{id}/clone", name="footprint_clone")
* @Route("/")
*/
#[Route(path: '/new', name: 'footprint_new')]
#[Route(path: '/{id}/clone', name: 'footprint_clone')]
#[Route(path: '/')]
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Footprint $entity = null): Response
{
return $this->_new($request, $em, $importer, $entity);
}
/**
* @Route("/export", name="footprint_export_all")
*/
#[Route(path: '/export', name: 'footprint_export_all')]
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
{
return $this->_exportAll($em, $exporter, $request);
}
/**
* @Route("/{id}/export", name="footprint_export")
*/
#[Route(path: '/{id}/export', name: 'footprint_export')]
public function exportEntity(AttachmentType $entity, EntityExporter $exporter, Request $request): Response
{
return $this->_exportEntity($entity, $exporter, $request);

View File

@@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/label_profile")
* @see \App\Tests\Controller\AdminPages\LabelProfileControllerTest
*/
#[Route(path: '/label_profile')]
class LabelProfileController extends BaseAdminController
{
protected string $entity_class = LabelProfile::class;
@@ -48,44 +49,34 @@ class LabelProfileController extends BaseAdminController
//Just a placeholder
protected ?string $parameter_class = null;
/**
* @Route("/{id}", name="label_profile_delete", methods={"DELETE"})
*/
#[Route(path: '/{id}', name: 'label_profile_delete', methods: ['DELETE'])]
public function delete(Request $request, LabelProfile $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
{
return $this->_delete($request, $entity, $recursionHelper);
}
/**
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="label_profile_edit")
* @Route("/{id}", requirements={"id"="\d+"})
*/
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'label_profile_edit')]
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
public function edit(LabelProfile $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
{
return $this->_edit($entity, $request, $em, $timestamp);
}
/**
* @Route("/new", name="label_profile_new")
* @Route("/{id}/clone", name="label_profile_clone")
* @Route("/")
*/
#[Route(path: '/new', name: 'label_profile_new')]
#[Route(path: '/{id}/clone', name: 'label_profile_clone')]
#[Route(path: '/')]
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?LabelProfile $entity = null): Response
{
return $this->_new($request, $em, $importer, $entity);
}
/**
* @Route("/export", name="label_profile_export_all")
*/
#[Route(path: '/export', name: 'label_profile_export_all')]
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
{
return $this->_exportAll($em, $exporter, $request);
}
/**
* @Route("/{id}/export", name="label_profile_export")
*/
#[Route(path: '/{id}/export', name: 'label_profile_export')]
public function exportEntity(LabelProfile $entity, EntityExporter $exporter, Request $request): Response
{
return $this->_exportEntity($entity, $exporter, $request);

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