mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-03-02 13:35:25 +01:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78bd858ebb | ||
|
|
19819454fa | ||
|
|
26a4b57cfb | ||
|
|
f3729ef9db | ||
|
|
ab09d319e9 | ||
|
|
df23ba07ba | ||
|
|
d20b668e87 | ||
|
|
f0646597fe | ||
|
|
6d783fd581 | ||
|
|
14fbf18733 | ||
|
|
e35c7c496f | ||
|
|
a218b8fdd6 | ||
|
|
1491672cf8 | ||
|
|
f9894ffff7 | ||
|
|
7b565817d6 | ||
|
|
a03b2ecf73 | ||
|
|
dd2f74e19e | ||
|
|
c1dcaf926a | ||
|
|
c116db9593 | ||
|
|
1b92b9f171 | ||
|
|
17e79207f0 | ||
|
|
4d187741e0 | ||
|
|
85c3031fcd | ||
|
|
a3e012d754 | ||
|
|
60f8e754c2 | ||
|
|
3e13a0d9d9 | ||
|
|
cd91dc8b5a | ||
|
|
bcaf96ed59 | ||
|
|
e2437d4c33 | ||
|
|
3798217abc | ||
|
|
57423436ce | ||
|
|
e824f6376a | ||
|
|
1f4bedc9b0 | ||
|
|
aa66285909 | ||
|
|
c6229568c5 | ||
|
|
6110f5be40 | ||
|
|
ea9cc6723f | ||
|
|
b5721dcfd0 | ||
|
|
d7383539ba | ||
|
|
11cdc282d2 | ||
|
|
b23f59271b | ||
|
|
999fe48a31 | ||
|
|
963079afbf | ||
|
|
a6d508205b | ||
|
|
fdf52a59fe | ||
|
|
dd0f8ec97c | ||
|
|
641b47b189 | ||
|
|
e1120dbfa7 | ||
|
|
f9d47e0865 | ||
|
|
d991643b0e | ||
|
|
8b8079a6f1 | ||
|
|
5faeb5dd56 | ||
|
|
5b3156ccf4 | ||
|
|
dc355773c9 | ||
|
|
1daf556206 | ||
|
|
1911c62edd | ||
|
|
2fe2740b62 | ||
|
|
4d7d624033 | ||
|
|
0abe3f0e61 | ||
|
|
77a6204798 | ||
|
|
64af418be2 | ||
|
|
15411d6c81 | ||
|
|
fd645a0bce | ||
|
|
f888028823 | ||
|
|
abc554c7b8 | ||
|
|
07cc8a9534 | ||
|
|
60ecbc7c32 | ||
|
|
b7af538cbf | ||
|
|
49c8b8003b | ||
|
|
65d04d4afb | ||
|
|
a449e82a22 | ||
|
|
34fd611946 | ||
|
|
62cbc168fb | ||
|
|
74d1904df1 | ||
|
|
7d69d6ba30 | ||
|
|
bc37d11f13 | ||
|
|
1825080d9e | ||
|
|
6926f6b233 | ||
|
|
459ae163da | ||
|
|
fc7b1e6d31 | ||
|
|
3198e5d750 | ||
|
|
f31cac580a | ||
|
|
753a12765b | ||
|
|
cbffc485f3 | ||
|
|
c15ddcdf9f | ||
|
|
264ed3aaab | ||
|
|
61a5ebde6b | ||
|
|
f4b4f14a67 | ||
|
|
9994dbd9db | ||
|
|
d976865e7a | ||
|
|
0445b87567 | ||
|
|
64c86fa11d | ||
|
|
548339911f | ||
|
|
e914a32894 | ||
|
|
f28e369c01 | ||
|
|
30b2c8b841 | ||
|
|
b5c7a789a2 | ||
|
|
168b4f6c15 | ||
|
|
bf5ed030fe | ||
|
|
b76b2740a7 | ||
|
|
d5f002ac20 | ||
|
|
2ec1a10623 | ||
|
|
ee69f9e576 | ||
|
|
b7af08503c | ||
|
|
08a1ce5f64 | ||
|
|
22f8448c65 | ||
|
|
6b0f0d31b9 | ||
|
|
feca20ef77 | ||
|
|
9e04a3405f | ||
|
|
46adb6d8b8 | ||
|
|
66e184c6b1 | ||
|
|
5b812104af | ||
|
|
0346b339c4 | ||
|
|
c6bff42cf7 | ||
|
|
03712fcf96 | ||
|
|
dbff543fa8 | ||
|
|
08bd4d54e3 | ||
|
|
eb30fb6e83 | ||
|
|
05e9b63f89 | ||
|
|
da0845c11c | ||
|
|
584062c29a | ||
|
|
752cfb3698 | ||
|
|
18db20e511 | ||
|
|
0f0adfcf36 | ||
|
|
7e99746b1e | ||
|
|
30afcc02b9 | ||
|
|
8ff2fef855 |
@@ -27,8 +27,8 @@
|
||||
# Pass the configuration from the docker env to the PHP environment (here you should list all .env options)
|
||||
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 CHECK_FOR_UPDATES
|
||||
PassEnv DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR DATABASE_MYSQL_USE_SSL_CA DATABASE_MYSQL_SSL_VERIFY_CERT
|
||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI CHECK_FOR_UPDATES ATTACHMENT_DOWNLOAD_BY_DEFAULT
|
||||
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
|
||||
@@ -42,6 +42,7 @@
|
||||
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
|
||||
PassEnv PROVIDER_MOUSER_KEY PROVIDER_MOUSER_SEARCH_OPTION PROVIDER_MOUSER_SEARCH_LIMIT PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE
|
||||
PassEnv EDA_KICAD_CATEGORY_DEPTH
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
|
||||
23
.env
23
.env
@@ -14,6 +14,15 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
|
||||
# Uncomment this line (and comment the line above to use a MySQL database
|
||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db?serverVersion=5.7
|
||||
|
||||
# Set this value to 1, if you want to use SSL to connect to the MySQL server. It will be tried to use the CA certificate
|
||||
# otherwise a CA bundle shipped with PHP will be used.
|
||||
# Leave it at 0, if you do not want to use SSL or if your server does not support it
|
||||
DATABASE_MYSQL_USE_SSL_CA=0
|
||||
|
||||
# Set this value to 0, if you don't want to verify the CA certificate of the MySQL server
|
||||
# Only do this, if you know what you are doing!
|
||||
DATABASE_MYSQL_SSL_VERIFY_CERT=1
|
||||
|
||||
###################################################################################
|
||||
# General settings
|
||||
###################################################################################
|
||||
@@ -29,13 +38,15 @@ INSTANCE_NAME="Part-DB"
|
||||
# Allow users to download attachments to the server by providing an URL
|
||||
# This could be a potential security issue, as the user can retrieve any file the server has access to (via internet)
|
||||
ALLOW_ATTACHMENT_DOWNLOADS=0
|
||||
# Set this to 1, if the "download external files" checkbox should be checked by default for new attachments
|
||||
ATTACHMENT_DOWNLOAD_BY_DEFAULT=0
|
||||
# Use gravatars for user avatars, when user has no own avatar defined
|
||||
USE_GRAVATAR=0
|
||||
# The maximum allowed size for attachment files in bytes (you can use M for megabytes and G for gigabytes)
|
||||
# Please note that the php.ini setting upload_max_filesize also limits the maximum size of uploaded files
|
||||
MAX_ATTACHMENT_FILE_SIZE="100M"
|
||||
|
||||
# The public reachable URL of this Part-DB installation. This is used for generating links to the website in emails and so on
|
||||
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates
|
||||
# This must end with a slash!
|
||||
DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||
|
||||
@@ -157,10 +168,20 @@ PROVIDER_MOUSER_SEARCH_LIMIT=50
|
||||
# Used when searching for keywords in the language specified when you signed up for Search API.
|
||||
PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE='true'
|
||||
|
||||
##################################################################################
|
||||
# EDA integration related settings
|
||||
##################################################################################
|
||||
|
||||
# This value determines the depth of the category tree, that is visible inside KiCad
|
||||
# 0 means that only the top level categories are visible. Set to a value > 0 to show more levels.
|
||||
# Set to -1, to show all parts of Part-DB inside a single category in KiCad
|
||||
EDA_KICAD_CATEGORY_DEPTH=0
|
||||
|
||||
###################################################################################
|
||||
# SAML Single sign on-settings
|
||||
###################################################################################
|
||||
# Set this to 1 to enable SAML single sign on
|
||||
# Be also sure to set the correct values for DEFAULT_URI
|
||||
SAML_ENABLED=0
|
||||
|
||||
# Set to 1, if your Part-DB installation is behind a reverse proxy and you want to use SAML
|
||||
|
||||
@@ -5,5 +5,9 @@ SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||
|
||||
DATABASE_URL="sqlite:///%kernel.project_dir%/var/app_test.db"
|
||||
# Doctrine automatically adds an _test suffix to database name in test env
|
||||
DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||
#DATABASE_URL=mysql://root:@127.0.0.1:3306/part-db
|
||||
|
||||
# Disable update checks, as tests would fail, when github is not reachable
|
||||
CHECK_FOR_UPDATES=0
|
||||
6
.github/workflows/assets_artifact_build.yml
vendored
6
.github/workflows/assets_artifact_build.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
@@ -77,13 +77,13 @@ jobs:
|
||||
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
|
||||
|
||||
- name: Upload assets artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Only dependencies and built assets
|
||||
path: /tmp/partdb_assets.zip
|
||||
|
||||
- name: Upload full artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Full Part-DB including dependencies and built assets
|
||||
path: /tmp/partdb_with_assets.zip
|
||||
|
||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
|
||||
8
.github/workflows/tests.yml
vendored
8
.github/workflows/tests.yml
vendored
@@ -26,6 +26,7 @@ jobs:
|
||||
SYMFONY_DEPRECATIONS_HELPER: disabled
|
||||
PHP_VERSION: ${{ matrix.php-versions }}
|
||||
DB_TYPE: ${{ matrix.db-type }}
|
||||
CHECK_FOR_UPDATES: false # Disable update checks for tests
|
||||
|
||||
steps:
|
||||
- name: Set Database env for MySQL
|
||||
@@ -45,7 +46,7 @@ jobs:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: pcov
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath, :php-psr
|
||||
|
||||
- name: Start MySQL
|
||||
run: sudo systemctl start mysql.service
|
||||
@@ -107,9 +108,10 @@ jobs:
|
||||
|
||||
- name: Do migrations
|
||||
run: php bin/console --env test doctrine:migrations:migrate -n
|
||||
|
||||
|
||||
# Use our own custom fixtures loading command to circumvent some problems with reset the autoincrement values
|
||||
- name: Load fixtures
|
||||
run: php bin/console --env test doctrine:fixtures:load -n
|
||||
run: php bin/console --env test partdb:fixtures:load -n
|
||||
|
||||
- name: Run PHPunit and generate coverage
|
||||
run: ./bin/phpunit --coverage-clover=coverage.xml
|
||||
|
||||
@@ -63,6 +63,8 @@ for the first time.
|
||||
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and
|
||||
prices for parts
|
||||
* API to access Part-DB from other applications/scripts
|
||||
* [Integration with KiCad](https://docs.part-db.de/usage/eda_integration.html): Use Part-DB as central datasource for your
|
||||
KiCad and see available parts from Part-DB directly inside KiCad.
|
||||
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 "tom-select/dist/css/tom-select.bootstrap5.css";
|
||||
import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
|
||||
/**
|
||||
* This is the frontend controller for StaticFileAutocompleteType form element.
|
||||
* Basically it loads a text file from the given url (via data-url) and uses it as a source for the autocomplete.
|
||||
* The file is just a list of strings, one per line, which will be used as the autocomplete options.
|
||||
* Lines starting with # will be ignored.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
_tomSelect;
|
||||
|
||||
connect() {
|
||||
|
||||
let settings = {
|
||||
persistent: false,
|
||||
create: true,
|
||||
maxItems: 1,
|
||||
maxOptions: 100,
|
||||
createOnBlur: true,
|
||||
selectOnTab: true,
|
||||
valueField: 'text',
|
||||
searchField: 'text',
|
||||
orderField: 'text',
|
||||
|
||||
//This a an ugly solution to disable the delimiter parsing of the TomSelect plugin
|
||||
delimiter: 'VERY_L0NG_D€LIMITER_WHICH_WILL_NEVER_BE_ENCOUNTERED_IN_A_STRING'
|
||||
};
|
||||
|
||||
if (this.element.dataset.url) {
|
||||
const url = this.element.dataset.url;
|
||||
settings.load = (query, callback) => {
|
||||
const self = this;
|
||||
if (self.loading > 1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
// Convert the text file to array
|
||||
let lines = text.split("\n");
|
||||
//Remove all lines beginning with #
|
||||
lines = lines.filter(x => !x.startsWith("#"));
|
||||
|
||||
//Convert the array to an object, where each line is in the text field
|
||||
lines = lines.map(x => {
|
||||
return {text: x};
|
||||
});
|
||||
|
||||
|
||||
//Unset the load function to prevent endless recursion
|
||||
self._tomSelect.settings.load = null;
|
||||
|
||||
callback(lines);
|
||||
}).catch(() => {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
//Destroy the TomSelect instance
|
||||
this._tomSelect.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
10
bin/phpunit
10
bin/phpunit
@@ -6,9 +6,13 @@ if (!ini_get('date.timezone')) {
|
||||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
} else {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
}
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"ext-mbstring": "*",
|
||||
"api-platform/core": "^3.1",
|
||||
"beberlei/doctrineextensions": "^1.2",
|
||||
"brick/math": "^0.11.0",
|
||||
"brick/math": "0.12.1 as 0.11.0",
|
||||
"composer/ca-bundle": "^1.3",
|
||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||
"doctrine/annotations": "1.14.3",
|
||||
"doctrine/data-fixtures": "^1.6.6",
|
||||
@@ -20,12 +21,12 @@
|
||||
"doctrine/doctrine-bundle": "^2.0",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.16",
|
||||
"dompdf/dompdf": "dev-master#87bea32efe0b0db309e1d31537201f64d5508280 as v2.0.3",
|
||||
"dompdf/dompdf": "dev-master#c9cf4be933e2406a51990bd4eb9e70612e790cc0 as v2.0.4",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"florianv/swap": "^4.0",
|
||||
"florianv/swap-bundle": "dev-master",
|
||||
"gregwar/captcha-bundle": "^2.1.0",
|
||||
"jbtronics/2fa-webauthn": "^v2.0.0",
|
||||
"jbtronics/2fa-webauthn": "^v2.2.0",
|
||||
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
||||
"jfcherng/php-diff": "^6.14",
|
||||
"knpuniversity/oauth2-client-bundle": "^2.15",
|
||||
@@ -38,7 +39,7 @@
|
||||
"nelmio/security-bundle": "^3.0",
|
||||
"nyholm/psr7": "^1.1",
|
||||
"ocramius/proxy-manager": "2.2.*",
|
||||
"omines/datatables-bundle": "^0.7.2",
|
||||
"omines/datatables-bundle": "^0.8.0",
|
||||
"part-db/label-fonts": "^1.0",
|
||||
"php-translation/symfony-bundle": "^0.14.0",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
@@ -51,35 +52,36 @@
|
||||
"shivas/versioning-bundle": "^4.0",
|
||||
"spatie/db-dumper": "^3.3.1",
|
||||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "6.3.*",
|
||||
"symfony/console": "6.3.*",
|
||||
"symfony/dotenv": "6.3.*",
|
||||
"symfony/expression-language": "6.3.*",
|
||||
"symfony/asset": "6.4.*",
|
||||
"symfony/console": "6.4.*",
|
||||
"symfony/dotenv": "6.4.*",
|
||||
"symfony/expression-language": "6.4.*",
|
||||
"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/form": "6.4.*",
|
||||
"symfony/framework-bundle": "6.4.*",
|
||||
"symfony/http-client": "6.4.*",
|
||||
"symfony/http-kernel": "6.4.*",
|
||||
"symfony/mailer": "6.4.*",
|
||||
"symfony/monolog-bundle": "^3.1",
|
||||
"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/string": "6.3.*",
|
||||
"symfony/translation": "6.3.*",
|
||||
"symfony/twig-bundle": "6.3.*",
|
||||
"symfony/polyfill-php82": "^1.28",
|
||||
"symfony/process": "6.4.*",
|
||||
"symfony/property-access": "6.4.*",
|
||||
"symfony/property-info": "6.4.*",
|
||||
"symfony/proxy-manager-bridge": "6.4.*",
|
||||
"symfony/rate-limiter": "6.4.*",
|
||||
"symfony/runtime": "6.4.*",
|
||||
"symfony/security-bundle": "6.4.*",
|
||||
"symfony/serializer": "6.4.*",
|
||||
"symfony/string": "6.4.*",
|
||||
"symfony/translation": "6.4.*",
|
||||
"symfony/twig-bundle": "6.4.*",
|
||||
"symfony/ux-translator": "^2.10",
|
||||
"symfony/ux-turbo": "^2.0",
|
||||
"symfony/validator": "6.3.*",
|
||||
"symfony/web-link": "6.3.*",
|
||||
"symfony/validator": "6.4.*",
|
||||
"symfony/web-link": "6.4.*",
|
||||
"symfony/webpack-encore-bundle": "^v2.0.1",
|
||||
"symfony/yaml": "6.3.*",
|
||||
"tecnickcom/tc-lib-barcode": "^1.15",
|
||||
"symfony/yaml": "6.4.*",
|
||||
"tecnickcom/tc-lib-barcode": "^2.1.4",
|
||||
"twig/cssinliner-extra": "^3.0",
|
||||
"twig/extra-bundle": "^3.0",
|
||||
"twig/html-extra": "^3.0",
|
||||
@@ -90,7 +92,7 @@
|
||||
"webmozart/assert": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"dama/doctrine-test-bundle": "^7.0",
|
||||
"dama/doctrine-test-bundle": "^v8.0.0",
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.2",
|
||||
"ekino/phpstan-banned-code": "^v1.0.0",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
@@ -102,13 +104,13 @@
|
||||
"psalm/plugin-symfony": "^v5.0.1",
|
||||
"rector/rector": "^0.18.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/browser-kit": "6.3.*",
|
||||
"symfony/css-selector": "6.3.*",
|
||||
"symfony/debug-bundle": "6.3.*",
|
||||
"symfony/browser-kit": "6.4.*",
|
||||
"symfony/css-selector": "6.4.*",
|
||||
"symfony/debug-bundle": "6.4.*",
|
||||
"symfony/maker-bundle": "^1.13",
|
||||
"symfony/phpunit-bridge": "6.3.*",
|
||||
"symfony/stopwatch": "6.3.*",
|
||||
"symfony/web-profiler-bundle": "6.3.*",
|
||||
"symfony/phpunit-bridge": "6.4.*",
|
||||
"symfony/stopwatch": "6.4.*",
|
||||
"symfony/web-profiler-bundle": "6.4.*",
|
||||
"symplify/easy-coding-standard": "^12.0",
|
||||
"vimeo/psalm": "^5.6.0"
|
||||
},
|
||||
@@ -161,7 +163,7 @@
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "6.3.*"
|
||||
"require": "6.4.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2552
composer.lock
generated
2552
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,18 @@
|
||||
api_platform:
|
||||
|
||||
title: 'Part-DB API'
|
||||
description: 'API of Part-DB'
|
||||
|
||||
version: '0.1.0'
|
||||
|
||||
# eager_loading:
|
||||
# max_joins: 100
|
||||
formats:
|
||||
jsonld: ['application/ld+json']
|
||||
json: ['application/json']
|
||||
jsonapi: ['application/vnd.api+json']
|
||||
|
||||
keep_legacy_inflector: false
|
||||
docs_formats:
|
||||
jsonld: ['application/ld+json']
|
||||
jsonopenapi: ['application/vnd.openapi+json']
|
||||
html: ['text/html']
|
||||
json: ['application/vnd.openapi+json']
|
||||
|
||||
swagger:
|
||||
api_keys:
|
||||
@@ -24,5 +28,9 @@ api_platform:
|
||||
vary: ['Content-Type', 'Authorization', 'Origin']
|
||||
extra_properties:
|
||||
standard_put: true
|
||||
rfc_7807_compliant_errors: true
|
||||
|
||||
pagination_client_items_per_page: true # Allow clients to override the default items per page
|
||||
pagination_client_items_per_page: true # Allow clients to override the default items per page
|
||||
|
||||
keep_legacy_inflector: false
|
||||
event_listeners_backward_compatibility_layer: false
|
||||
@@ -2,6 +2,9 @@ doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# Required for DAMA doctrine test bundle
|
||||
use_savepoints: true
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
|
||||
@@ -28,8 +31,8 @@ doctrine:
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App:
|
||||
is_bundle: false
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
csrf_protection: true
|
||||
annotations: false
|
||||
handle_all_throwables: true
|
||||
|
||||
# We set this header by ourself, so we can disable it here
|
||||
# We set this header by ourselves, 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
|
||||
@@ -26,7 +27,6 @@ framework:
|
||||
handler_id: null
|
||||
cookie_secure: auto
|
||||
cookie_samesite: lax
|
||||
storage_factory_id: session.storage.factory.native
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
@@ -32,7 +32,7 @@ nbgrp_onelogin_saml:
|
||||
privateKey: '%env(string:default:saml.sp.privateKey:string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
|
||||
# Optional settings
|
||||
#baseurl: 'http://myapp.com'
|
||||
baseurl: '%partdb.default_uri%saml/'
|
||||
strict: true
|
||||
debug: false
|
||||
security:
|
||||
|
||||
@@ -6,7 +6,7 @@ scheb_two_factor:
|
||||
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
|
||||
leeway: 5 # Acceptable time drift in seconds
|
||||
template: security/2fa_form.html.twig
|
||||
|
||||
backup_codes:
|
||||
|
||||
@@ -71,3 +71,5 @@ security:
|
||||
- { path: "^/\\w{2}/tree", role: PUBLIC_ACCESS }
|
||||
# Restrict access to API to users, which has the API access permission
|
||||
- { path: "^/api", allow_if: 'is_granted("@api.access_api") and is_authenticated()' }
|
||||
# Restrict access to KICAD to users, which has API access permission
|
||||
- { path: "^/kicad-api", allow_if: 'is_granted("@api.access_api") and is_authenticated()' }
|
||||
|
||||
@@ -35,6 +35,7 @@ parameters:
|
||||
# Attachments and files
|
||||
######################################################################################################################
|
||||
partdb.attachments.allow_downloads: '%env(bool:ALLOW_ATTACHMENT_DOWNLOADS)%' # Allow users to download attachments to server. Warning: This can be dangerous, because via that feature attackers maybe can access ressources on your intranet!
|
||||
partdb.attachments.download_by_default: '%env(bool:ATTACHMENT_DOWNLOAD_BY_DEFAULT)%' # If this is set the 'download external files' checkbox is set by default for new attachments (only if allow_downloads is set to true)
|
||||
partdb.attachments.dir.media: 'public/media/' # The folder where uploaded attachment files are saved (must be in public folder)
|
||||
partdb.attachments.dir.secure: 'uploads/' # The folder where secured attachment files are saved (must not be in public/)
|
||||
partdb.attachments.max_file_size: '%env(string:MAX_ATTACHMENT_FILE_SIZE)%' # The maximum size of an attachment file (in bytes, you can use M for megabytes and G for gigabytes)
|
||||
@@ -141,3 +142,4 @@ parameters:
|
||||
env(HISTORY_SAVE_REMOVED_DATA): 1
|
||||
env(HISTORY_SAVE_NEW_DATA): 1
|
||||
|
||||
env(EDA_KICAD_CATEGORY_DEPTH): 0
|
||||
|
||||
3
config/routes/security.yaml
Normal file
3
config/routes/security.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
_security_logout:
|
||||
resource: security.route_loader.logout
|
||||
type: service
|
||||
@@ -93,6 +93,7 @@ services:
|
||||
arguments:
|
||||
$allow_attachments_download: '%partdb.attachments.allow_downloads%'
|
||||
$max_file_size: '%partdb.attachments.max_file_size%'
|
||||
$download_by_default: '%partdb.attachments.download_by_default%'
|
||||
|
||||
App\Services\Attachments\AttachmentSubmitHandler:
|
||||
arguments:
|
||||
@@ -140,6 +141,19 @@ services:
|
||||
$saml_role_mapping: '%env(json:SAML_ROLE_MAPPING)%'
|
||||
$update_group_on_login: '%env(bool:SAML_UPDATE_GROUP_ON_LOGIN)%'
|
||||
|
||||
|
||||
security.access_token_extractor.header.token:
|
||||
class: Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor
|
||||
arguments:
|
||||
$tokenType: 'Token'
|
||||
|
||||
security.access_token_extractor.main:
|
||||
class: Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor
|
||||
arguments:
|
||||
$accessTokenExtractors:
|
||||
- '@security.access_token_extractor.header'
|
||||
- '@security.access_token_extractor.header.token'
|
||||
|
||||
####################################################################################################################
|
||||
# Cache
|
||||
####################################################################################################################
|
||||
@@ -302,6 +316,13 @@ services:
|
||||
$global_locale: '%partdb.locale%'
|
||||
$global_timezone: '%partdb.timezone%'
|
||||
|
||||
####################################################################################################################
|
||||
# EDA system
|
||||
####################################################################################################################
|
||||
App\Services\EDA\KiCadHelper:
|
||||
arguments:
|
||||
$category_depth: '%env(int:EDA_KICAD_CATEGORY_DEPTH)%'
|
||||
|
||||
####################################################################################################################
|
||||
# Symfony overrides
|
||||
####################################################################################################################
|
||||
@@ -349,6 +370,10 @@ services:
|
||||
$partdb_banner: '%partdb.banner%'
|
||||
$project_dir: '%kernel.project_dir%'
|
||||
|
||||
App\Doctrine\Middleware\MySQLSSLConnectionMiddlewareWrapper:
|
||||
arguments:
|
||||
$enabled: '%env(bool:DATABASE_MYSQL_USE_SSL_CA)%'
|
||||
$verify: '%env(bool:DATABASE_MYSQL_SSL_VERIFY_CERT)%'
|
||||
|
||||
####################################################################################################################
|
||||
# Monolog
|
||||
@@ -376,4 +401,4 @@ when@test:
|
||||
arguments:
|
||||
- '@doctrine.fixtures.loader'
|
||||
- '@doctrine'
|
||||
- { default: '@App\Doctrine\Purger\ResetAutoIncrementPurgerFactory' }
|
||||
- { default: '@App\Doctrine\Purger\DoNotUsePurgerFactory' }
|
||||
@@ -37,6 +37,9 @@ options listed, see `.env` file for full list of possible env variables.
|
||||
(e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). For sqlite use the following format to specify the
|
||||
absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as
|
||||
placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`)
|
||||
* `DATABASE_MYSQL_USE_SSL_CA`: If this value is set to `1` or `true` and a MySQL connection is used, then the connection
|
||||
is encrypted by SSL/TLS and the server certificate is verified against the system CA certificates or the CA certificate
|
||||
bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept all certificates.
|
||||
* `DEFAULT_LANG`: The default language to use server wide (when no language is explicitly specified by a user or via
|
||||
language chooser). Must be something like `en`, `de`, `fr`, etc.
|
||||
* `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something
|
||||
@@ -53,6 +56,9 @@ options listed, see `.env` file for full list of possible env variables.
|
||||
download a file specified as a URL and create it as local file. Please note that this allows users access to all
|
||||
resources publicly available to the server (so full access to other servers in the same local network), which could
|
||||
be a security risk.
|
||||
* `ATTACHMENT_DOWNLOAD_BY_DEFAULT`: When this is set to 1, the "download external file" checkbox is checked by default
|
||||
when adding a new attachment. Otherwise, it is unchecked by default. Use this if you wanna download all attachments
|
||||
locally by default. Attachment download is only possible, when `ALLOW_ATTACHMENT_DOWNLOADS` is set to 1.
|
||||
* `USE_GRAVATAR`: Set to `1` to use [gravatar.com](https://gravatar.com/) images for user avatars (as long as they have
|
||||
not set their own picture). The users browsers have to download the pictures from a third-party (gravatar) server, so
|
||||
this might be a privacy risk.
|
||||
@@ -125,6 +131,14 @@ then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAV
|
||||
* `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not
|
||||
contain sensitive information, but could confuse end-users.
|
||||
|
||||
### EDA related settings
|
||||
|
||||
* `EDA_KICAD_CATEGORY_DEPTH`: A number, which determines how many levels of Part-DB categories should be shown inside KiCad.
|
||||
All parts in the selected category and all subcategories are shown in KiCad.
|
||||
For performance reason this value should not be too high. The default is 0, which means that only the top level categories are shown in KiCad.
|
||||
All parts in the selected category and all subcategories are shown in KiCad. Set this to a higher value, if you want to show more categories in KiCad.
|
||||
When you set this value to -1, all parts are shown inside a single category in KiCad.
|
||||
|
||||
### SAML SSO settings
|
||||
|
||||
The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to log in to
|
||||
|
||||
@@ -49,6 +49,8 @@ It is installed on a web server and so can be accessed with any browser without
|
||||
* 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 %}))
|
||||
* API to access Part-DB from other applications/scripts
|
||||
* [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as central datasource for your
|
||||
KiCad and see available parts from Part-DB directly inside KiCad.
|
||||
|
||||
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.
|
||||
|
||||
@@ -47,8 +47,9 @@ services:
|
||||
|
||||
# You can configure Part-DB using environment variables
|
||||
# Below you can find the most essential ones predefined
|
||||
# However you can add add any other environment configuration you want here
|
||||
# However you can add any other environment configuration you want here
|
||||
# See .env file for all available options or https://docs.part-db.de/configuration.html
|
||||
# !!! Do not use quotes around the values, as they will be interpreted as part of the value and this will lead to errors !!!
|
||||
|
||||
# The language to use serverwide as default (en, de, ru, etc.)
|
||||
- DEFAULT_LANG=en
|
||||
@@ -65,9 +66,12 @@ services:
|
||||
# Use gravatars for user avatars, when user has no own avatar defined
|
||||
- USE_GRAVATAR=0
|
||||
|
||||
# Override value if you want to show to show a given text on homepage.
|
||||
# Override value if you want to show a given text on homepage.
|
||||
# When this is empty the content of config/banner.md is used as banner
|
||||
#- BANNER=This is a test banner<br>with a line break
|
||||
|
||||
# If you use a reverse proxy in front of Part-DB, you must configure the trusted proxies IP addresses here (see reverse proxy documentation for more information):
|
||||
# - TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
```
|
||||
|
||||
4. Customize the settings by changing the environment variables (or add new ones). See [Configuration]({% link
|
||||
|
||||
79
docs/usage/eda_integration.md
Normal file
79
docs/usage/eda_integration.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
layout: default
|
||||
title: EDA / KiCad integration
|
||||
parent: Usage
|
||||
---
|
||||
|
||||
# EDA / KiCad integration
|
||||
|
||||
Part-DB can function as central database for [EDA](https://en.wikipedia.org/wiki/Electronic_design_automation) or ECAD software used to design electronic schematics and PCBs.
|
||||
You can connect your EDA software and can view your available parts, with the data saved from Part-DB directly in your EDA software.
|
||||
Part-DB allows to configure additional metadata for the EDA, to associate symbols and footprints for use inside the EDA software, so the part becomes
|
||||
directly usable inside the EDA software.
|
||||
This also allows to configure available and usable parts and their properties in a central place, which is especially useful in teams, where multiple persons design PCBs.
|
||||
|
||||
**Currently only KiCad is supported!**
|
||||
|
||||
## KiCad Setup
|
||||
|
||||
{: .important }
|
||||
> Part-DB uses the HTTP library feature of KiCad, which is experimental and not part of the stable KiCad 7 releases. If you want to use this feature, you need to install a KiCad nightly build (7.99 version). This feature will most likely also be part of KiCad 8.
|
||||
|
||||
Part-DB should be accessible from the PCs with KiCAD. The URL should be stable (so no dynamically changing IP).
|
||||
You require a user account in Part-DB, which has the permission to access Part-DB API and create API tokens. Every user can has its own account, or you setup a shared read-only account.
|
||||
|
||||
To connect KiCad with Part-DB do following steps:
|
||||
|
||||
1. Create an API token on the user settings page for the KiCAD application and copy/save it, when it is shown. Currently KiCAD can only read Part-DB database, so a token with read only scope is enough.
|
||||
2. Add some EDA metadata to parts, categories or footprints. Only parts with useable info will show up in KiCad. See below for more info.
|
||||
3. Create a file `partd.kicad_httplib` (or similar, only the extension is important) with the following content:
|
||||
```
|
||||
{
|
||||
"meta": {
|
||||
"version": 1.0
|
||||
},
|
||||
"name": "Part-DB library",
|
||||
"description": "This KiCAD library fetches information externally from ",
|
||||
"source": {
|
||||
"type": "REST_API",
|
||||
"api_version": "v1",
|
||||
"root_url": "http://kicad-instance.invalid/en/kicad-api/",
|
||||
"token": "THE_GENERATED_API_TOKEN"
|
||||
}
|
||||
}
|
||||
```
|
||||
4. Replace the `root_url` with the URL of your Part-DB instance plus `/en/kicad-api/`. You can find the right value for this in the Part-DB user settings page under "API endpoints" in the "API tokens" panel.
|
||||
5. Replace the `token` field value with the token you have generated in step 1.
|
||||
6. Open KiCad and add this created file as library in the KiCad symbol table under (Preferences --> Manage Symbol Libraries)
|
||||
|
||||
If you then place a new part, the library dialog opens, and you should be able to see the categories and parts from Part-DB.
|
||||
|
||||
### How to associate footprints and symbols with parts
|
||||
|
||||
Part-DB dont save any concrete footprints or symbols for the part. Instead Part-DB just contains a reference string in the part metadata, which points to a symbol/footprint in KiCads local library.
|
||||
|
||||
You can define this on a per-part basis using the KiCad symbol and KiCad footprint field in the EDA tab of the part editor. Or you can define it at a category (symbol) or footprint level, to assign this value to all parts with this category and footprint.
|
||||
|
||||
For example to configure the values for an BC547 transistor you would put `Transistor_BJT:BC547` on the parts Kicad symbol to give it the right schematic symbol in EEschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in PcbNew.
|
||||
|
||||
If you type in a character, you will get an autocomplete list of all symbols and footprints available in the kicad standard library. You can also input your own value.
|
||||
|
||||
### Parts and category visibility
|
||||
|
||||
Only parts and their categories, on which there is any kind of EDA metadata are defined show up in KiCad. So if you want to see parts in KiCad,
|
||||
you need to define at least a symbol, footprint, reference prefix or value on a part, category or footprint.
|
||||
|
||||
You can use the "Force visibility" checkbox on a part or category to override this behavior and force parts to be visible or hidden in KiCad.
|
||||
|
||||
*Please note that KiCad caches the library categories. So if you change something, which would change the visibile categories in KiCad, you have to reload EEschema to see the changes.*
|
||||
|
||||
### Category depth in KiCad
|
||||
|
||||
For performance reasons, only the most top level categories of Part-DB are shown as categories in KiCad. All parts in the subcategories are shown in the top level category.
|
||||
|
||||
You can configure the depth of the categories shown in KiCad, via the `EDA_KICAD_CATEGORY_DEPTH` env option. The default value is 0, which meabs only the top level categories are shown.
|
||||
To show more levels of categories, you can set this value to a higher number.
|
||||
|
||||
If you set this value to -1, all parts are shown inside a single category in KiCad, without any subcategories.
|
||||
|
||||
You can view the "real" category path of a part in the part details dialog in KiCad.
|
||||
@@ -37,6 +37,10 @@ filled in.
|
||||
|
||||

|
||||
|
||||
If you want to update an existing part, go to the parts info page and click on the "Update from info provider" button in
|
||||
the tools tab. You will be redirected to a search page, where you can search the info providers to automatically update this
|
||||
part.
|
||||
|
||||
## Alternative names
|
||||
|
||||
Part-DB tries to automatically find existing elements from your database for the information it got from the providers
|
||||
|
||||
88
migrations/Version20231130180903.php
Normal file
88
migrations/Version20231130180903.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20231130180903 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Added EDA fields';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE categories ADD eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, ADD eda_info_invisible TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_bom TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_board TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_sim TINYINT(1) DEFAULT NULL, ADD eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE footprints ADD eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, ADD eda_info_value VARCHAR(255) DEFAULT NULL, ADD eda_info_invisible TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_bom TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_board TINYINT(1) DEFAULT NULL, ADD eda_info_exclude_from_sim TINYINT(1) DEFAULT NULL, ADD eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, ADD eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE `categories` DROP eda_info_reference_prefix, DROP eda_info_invisible, DROP eda_info_exclude_from_bom, DROP eda_info_exclude_from_board, DROP eda_info_exclude_from_sim, DROP eda_info_kicad_symbol');
|
||||
$this->addSql('ALTER TABLE `footprints` DROP eda_info_kicad_footprint');
|
||||
$this->addSql('ALTER TABLE `parts` DROP eda_info_reference_prefix, DROP eda_info_value, DROP eda_info_invisible, DROP eda_info_exclude_from_bom, DROP eda_info_exclude_from_board, DROP eda_info_exclude_from_sim, DROP eda_info_kicad_symbol, DROP eda_info_kicad_footprint');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_reference_prefix VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_invisible BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_bom BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_board BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_exclude_from_sim BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD COLUMN eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE footprints ADD COLUMN eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_reference_prefix VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_value VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_invisible BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_bom BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_board BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_exclude_from_sim BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD COLUMN eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__categories AS SELECT id, parent_id, id_preview_attachment, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment 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, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB 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, 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, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment) SELECT id, parent_id, id_preview_attachment, name, last_modified, datetime_added, comment, not_selectable, alternative_names, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment 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__footprints AS SELECT id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names 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_preview_attachment INTEGER DEFAULT NULL, id_footprint_3d INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "footprints" (id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names) SELECT id, parent_id, id_preview_attachment, id_footprint_3d, name, last_modified, datetime_added, comment, not_selectable, alternative_names FROM __temp__footprints');
|
||||
$this->addSql('DROP TABLE __temp__footprints');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON "footprints" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)');
|
||||
$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__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 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, 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)');
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
1
public/img/default_avatar.svg
Normal file
1
public/img/default_avatar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Ebene1"><rect x="0.021" y="-0.023" width="19.977" height="20" style="fill:#95bbdf;"/></g><path d="M10,10c2.194,0 4,-1.806 4,-4c0,-2.194 -1.806,-4 -4,-4c-2.194,0 -4,1.806 -4,4c0,2.194 1.806,4 4,4Zm-1.428,1.5c-3.078,0 -5.572,2.494 -5.572,5.572c0,0.512 0.416,0.928 0.928,0.928l12.144,0c0.512,0 0.928,-0.416 0.928,-0.928c0,-3.078 -2.494,-5.572 -5.572,-5.572l-2.856,0Z" style="fill:#fff;fill-rule:nonzero;"/></svg>
|
||||
|
After Width: | Height: | Size: 856 B |
13272
public/kicad/footprints.txt
Normal file
13272
public/kicad/footprints.txt
Normal file
File diff suppressed because it is too large
Load Diff
19594
public/kicad/symbols.txt
Normal file
19594
public/kicad/symbols.txt
Normal file
File diff suppressed because it is too large
Load Diff
75
src/ApiPlatform/ErrorHandler.php
Normal file
75
src/ApiPlatform/ErrorHandler.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
use ApiPlatform\State\ApiResource\Error;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
/**
|
||||
* This class adds a custom error if the user tries to create a new entity through a relation, and suggests to do reference it through an IRI instead.
|
||||
* This class decorates the default error handler of API Platform.
|
||||
*/
|
||||
#[AsDecorator('api_platform.state.error_provider')]
|
||||
final class ErrorHandler implements ProviderInterface
|
||||
{
|
||||
public function __construct(private readonly ProviderInterface $decorated, #[Autowire('%kernel.debug%')] private readonly bool $debug)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
{
|
||||
$request = $context['request'];
|
||||
$format = $request->getRequestFormat();
|
||||
$exception = $request->attributes->get('exception');
|
||||
|
||||
//Check if the exception is a ORM InvalidArgument exception and complains about a not-persisted entity through relation
|
||||
if ($exception instanceof ORMInvalidArgumentException && str_contains($exception->getMessage(), 'A new entity was found through the relationship')) {
|
||||
//Extract the entity class and property name from the exception message
|
||||
$matches = [];
|
||||
preg_match('/A new entity was found through the relationship \'(?<property>.*)\'/i', $exception->getMessage(), $matches);
|
||||
|
||||
$property = $matches['property'] ?? "unknown";
|
||||
|
||||
//Create a new error response
|
||||
$error = Error::createFromException($exception, 400);
|
||||
|
||||
//Return the error response
|
||||
$detail = "You tried to create a new entity through the relation '$property', but this is not allowed. Please create the entity first and then reference it through an IRI!";
|
||||
//If we are in debug mode, add the exception message to the error response
|
||||
if ($this->debug) {
|
||||
$detail .= " Original exception message: " . $exception->getMessage();
|
||||
}
|
||||
$error->setDetail($detail);
|
||||
return $error;
|
||||
}
|
||||
|
||||
|
||||
return $this->decorated->provide($operation, $uriVariables, $context);
|
||||
}
|
||||
}
|
||||
76
src/Command/LoadFixturesCommand.php
Normal file
76
src/Command/LoadFixturesCommand.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Doctrine\Purger\ResetAutoIncrementORMPurger;
|
||||
use App\Doctrine\Purger\DoNotUsePurgerFactory;
|
||||
use App\Doctrine\Purger\ResetAutoIncrementPurgerFactory;
|
||||
use Doctrine\Bundle\FixturesBundle\Purger\ORMPurgerFactory;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* This command does basically the same as doctrine:fixtures:load, but it purges the database before loading the fixtures.
|
||||
* It does so in another transaction, so we can modify the purger to reset the autoincrement, which would not be possible
|
||||
* because the implicit commit otherwise.
|
||||
*/
|
||||
#[AsCommand(name: 'partdb:fixtures:load', description: 'Load test fixtures into the database and allows to reset the autoincrement before loading the fixtures.', hidden: true)]
|
||||
class LoadFixturesCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$ui = new SymfonyStyle($input, $output);
|
||||
|
||||
$ui->warning('This command is for development and testing purposes only. It will purge the database and load fixtures afterwards. Do not use in production!');
|
||||
|
||||
if (! $ui->confirm(sprintf('Careful, database "%s" will be purged. Do you want to continue?', $this->entityManager->getConnection()->getDatabase()), ! $input->isInteractive())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$factory = new ResetAutoIncrementPurgerFactory();
|
||||
$purger = $factory->createForEntityManager(null, $this->entityManager);
|
||||
|
||||
$purger->purge();
|
||||
|
||||
//Afterwards run the load fixtures command as normal, but with the --append option
|
||||
$new_input = new ArrayInput([
|
||||
'command' => 'doctrine:fixtures:load',
|
||||
'--append' => true,
|
||||
]);
|
||||
|
||||
$returnCode = $this->getApplication()?->doRun($new_input, $output);
|
||||
|
||||
return $returnCode ?? Command::FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,18 @@ use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use App\Services\LogSystem\EventCommentHelper;
|
||||
use App\Services\Parts\PartFormHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
#[Route('/tools/info_providers')]
|
||||
class InfoProviderController extends AbstractController
|
||||
{
|
||||
@@ -64,7 +68,7 @@ class InfoProviderController extends AbstractController
|
||||
|
||||
#[Route('/search', name: 'info_providers_search')]
|
||||
#[Route('/update/{target}', name: 'info_providers_update_part_search')]
|
||||
public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target): Response
|
||||
public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
@@ -83,7 +87,14 @@ class InfoProviderController extends AbstractController
|
||||
$keyword = $form->get('keyword')->getData();
|
||||
$providers = $form->get('providers')->getData();
|
||||
|
||||
$results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
||||
try {
|
||||
$results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
||||
} catch (ClientException $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.client_exception'));
|
||||
$this->addFlash('error',$e->getMessage());
|
||||
//Log the exception
|
||||
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('info_providers/search/part_search.html.twig', [
|
||||
|
||||
84
src/Controller/KiCadApiController.php
Normal file
84
src/Controller/KiCadApiController.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\EDA\KiCadHelper;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
#[Route('/kicad-api/v1')]
|
||||
class KiCadApiController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly KiCadHelper $kiCADHelper,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route('/', name: 'kicad_api_root')]
|
||||
public function root(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('HAS_ACCESS_PERMISSIONS');
|
||||
|
||||
//The API documentation says this can be either blank or the URL to the endpoints
|
||||
return $this->json([
|
||||
'categories' => '',
|
||||
'parts' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/categories.json', name: 'kicad_api_categories')]
|
||||
public function categories(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@categories.read');
|
||||
|
||||
return $this->json($this->kiCADHelper->getCategories());
|
||||
}
|
||||
|
||||
#[Route('/parts/category/{category}.json', name: 'kicad_api_category')]
|
||||
public function categoryParts(?Category $category): Response
|
||||
{
|
||||
if ($category) {
|
||||
$this->denyAccessUnlessGranted('read', $category);
|
||||
} else {
|
||||
$this->denyAccessUnlessGranted('@categories.read');
|
||||
}
|
||||
$this->denyAccessUnlessGranted('@parts.read');
|
||||
|
||||
return $this->json($this->kiCADHelper->getCategoryParts($category));
|
||||
}
|
||||
|
||||
#[Route('/parts/{part}.json', name: 'kicad_api_part')]
|
||||
public function partDetails(Part $part): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('read', $part);
|
||||
|
||||
return $this->json($this->kiCADHelper->getKiCADPart($part));
|
||||
}
|
||||
}
|
||||
@@ -414,6 +414,12 @@ class PartController extends AbstractController
|
||||
throw new \LogicException("The timestamp must not be in the future!");
|
||||
}
|
||||
|
||||
//Ensure that the amount is not null or negative
|
||||
if ($amount <= 0) {
|
||||
$this->addFlash('warning', 'part.withdraw.zero_amount');
|
||||
goto err;
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
case "withdraw":
|
||||
|
||||
71
src/DataFixtures/EDADataFixtures.php
Normal file
71
src/DataFixtures/EDADataFixtures.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Part;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class EDADataFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [PartFixtures::class];
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
//Load elements from DB
|
||||
$category1 = $manager->find(Category::class, 1);
|
||||
$footprint1 = $manager->find(Footprint::class, 1);
|
||||
|
||||
$part1 = $manager->find(Part::class, 1);
|
||||
|
||||
//Put some data into category1 and foorprint1
|
||||
$category1?->getEdaInfo()
|
||||
->setExcludeFromBoard(true)
|
||||
->setKicadSymbol('Category:1')
|
||||
->setReferencePrefix('C')
|
||||
;
|
||||
|
||||
$footprint1?->getEdaInfo()
|
||||
->setKicadFootprint('Footprint:1')
|
||||
;
|
||||
|
||||
//Put some data into part1 (which overrides the data from category1 and footprint1 on part1)
|
||||
$part1?->getEdaInfo()
|
||||
->setExcludeFromSim(false)
|
||||
->setKicadSymbol('Part:1')
|
||||
->setKicadFootprint('Part:1')
|
||||
->setReferencePrefix('P')
|
||||
;
|
||||
|
||||
//Flush the changes
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
@@ -79,10 +79,7 @@ class LocaleDateTimeColumn extends AbstractColumn
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
protected function configureOptions(OptionsResolver $resolver): self
|
||||
protected function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
||||
@@ -57,10 +57,7 @@ class LogEntryTargetColumn extends AbstractColumn
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
$resolver->setDefault('show_associated', true);
|
||||
|
||||
@@ -79,10 +79,7 @@ class PartAttachmentsColumn extends AbstractColumn
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
||||
@@ -28,11 +28,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class RowClassColumn extends AbstractColumn
|
||||
{
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
@@ -56,7 +52,7 @@ class RowClassColumn extends AbstractColumn
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function normalize($value)
|
||||
public function normalize($value): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -32,10 +32,7 @@ class SIUnitNumberColumn extends AbstractColumn
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
||||
@@ -30,10 +30,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*/
|
||||
class SelectColumn extends AbstractColumn
|
||||
{
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver): self
|
||||
public function configureOptions(OptionsResolver $resolver): static
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
||||
@@ -67,8 +67,10 @@ class ErrorDataTable implements DataTableTypeInterface
|
||||
|
||||
//Build the array containing data
|
||||
$data = [];
|
||||
$n = 0;
|
||||
foreach ($options['errors'] as $error) {
|
||||
$data[] = ['error' => $error];
|
||||
$data['error_' . $n] = ['error' => $error];
|
||||
$n++;
|
||||
}
|
||||
|
||||
$dataTable->createAdapter(ArrayAdapter::class, $data);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use Composer\CaBundle\CaBundle;
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
|
||||
/**
|
||||
* This middleware sets SSL options for MySQL connections
|
||||
*/
|
||||
class MySQLSSLConnectionMiddlewareDriver extends AbstractDriverMiddleware
|
||||
{
|
||||
public function __construct(Driver $wrappedDriver, private readonly bool $enabled, private readonly bool $verify = true)
|
||||
{
|
||||
parent::__construct($wrappedDriver);
|
||||
}
|
||||
|
||||
public function connect(array $params): Connection
|
||||
{
|
||||
//Only set this on MySQL connections, as other databases don't support this parameter
|
||||
if($this->enabled && $this->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
|
||||
$params['driverOptions'][\PDO::MYSQL_ATTR_SSL_CA] = CaBundle::getSystemCaRootBundlePath();
|
||||
$params['driverOptions'][\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = $this->verify;
|
||||
}
|
||||
|
||||
return parent::connect($params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
|
||||
class MySQLSSLConnectionMiddlewareWrapper implements Middleware
|
||||
{
|
||||
public function __construct(private readonly bool $enabled, private readonly bool $verify = true)
|
||||
{
|
||||
}
|
||||
|
||||
public function wrap(Driver $driver): Driver
|
||||
{
|
||||
return new MySQLSSLConnectionMiddlewareDriver($driver, $this->enabled, $this->verify);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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)
|
||||
* 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
|
||||
@@ -20,29 +17,31 @@ declare(strict_types=1);
|
||||
* 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\Doctrine;
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use App\Exceptions\InvalidRegexException;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface;
|
||||
use Doctrine\DBAL\Event\ConnectionEventArgs;
|
||||
use Doctrine\DBAL\Events;
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
|
||||
/**
|
||||
* This subscriber is used to add the regexp operator to the SQLite platform.
|
||||
* This middleware is used to add the regexp operator to the SQLite platform.
|
||||
* As a PHP callback is called for every entry to compare it is most likely much slower than using regex on MySQL.
|
||||
* But as regex is not often used, this should be fine for most use cases, also it is almost impossible to implement a better solution.
|
||||
*/
|
||||
#[AsDoctrineListener(Events::postConnect)]
|
||||
class SQLiteRegexExtension
|
||||
class SQLiteRegexExtensionMiddlewareDriver extends AbstractDriverMiddleware
|
||||
{
|
||||
public function postConnect(ConnectionEventArgs $eventArgs): void
|
||||
public function connect(#[\SensitiveParameter] array $params): Connection
|
||||
{
|
||||
$connection = $eventArgs->getConnection();
|
||||
//Do connect process first
|
||||
$connection = parent::connect($params); // TODO: Change the autogenerated stub
|
||||
|
||||
//We only execute this on SQLite databases
|
||||
if ($connection->getDatabasePlatform() instanceof SqlitePlatform) {
|
||||
//Then add the functions if we are on SQLite
|
||||
if ($this->getDatabasePlatform() instanceof SqlitePlatform) {
|
||||
$native_connection = $connection->getNativeConnection();
|
||||
|
||||
//Ensure that the function really exists on the connection, as it is marked as experimental according to PHP documentation
|
||||
@@ -52,6 +51,9 @@ class SQLiteRegexExtension
|
||||
$native_connection->sqliteCreateFunction('FIELD2', self::field2(...), 2, \PDO::SQLITE_DETERMINISTIC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,8 +62,12 @@ class SQLiteRegexExtension
|
||||
* @param string $value
|
||||
* @return int
|
||||
*/
|
||||
final public static function regexp(string $pattern, string $value): int
|
||||
final public static function regexp(string $pattern, ?string $value): int
|
||||
{
|
||||
if ($value === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return (mb_ereg($pattern, $value)) ? 1 : 0;
|
||||
|
||||
@@ -107,4 +113,4 @@ class SQLiteRegexExtension
|
||||
|
||||
return $index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
|
||||
class SQLiteRegexExtensionMiddlewareWrapper implements Middleware
|
||||
{
|
||||
public function wrap(Driver $driver): Driver
|
||||
{
|
||||
return new SQLiteRegexExtensionMiddlewareDriver($driver);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ declare(strict_types=1);
|
||||
* 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\Doctrine\SetSQLMode;
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
@@ -37,7 +37,7 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware
|
||||
//Only set this on MySQL connections, as other databases don't support this parameter
|
||||
if($this->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
|
||||
//1002 is \PDO::MYSQL_ATTR_INIT_COMMAND constant value
|
||||
$params['driverOptions'][1002] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
|
||||
$params['driverOptions'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode, \'ONLY_FULL_GROUP_BY\', \'\'))';
|
||||
}
|
||||
|
||||
return parent::connect($params);
|
||||
@@ -20,7 +20,7 @@ declare(strict_types=1);
|
||||
* 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\Doctrine\SetSQLMode;
|
||||
namespace App\Doctrine\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
@@ -30,7 +30,6 @@ use Doctrine\DBAL\Driver\Middleware;
|
||||
*/
|
||||
class SetSQLModeMiddlewareWrapper implements Middleware
|
||||
{
|
||||
|
||||
public function wrap(Driver $driver): Driver
|
||||
{
|
||||
return new SetSQLModeMiddlewareDriver($driver);
|
||||
53
src/Doctrine/Purger/DoNotUsePurgerFactory.php
Normal file
53
src/Doctrine/Purger/DoNotUsePurgerFactory.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Doctrine\Purger;
|
||||
|
||||
use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory;
|
||||
use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface;
|
||||
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class DoNotUsePurgerFactory implements PurgerFactory
|
||||
{
|
||||
|
||||
public function createForEntityManager(
|
||||
?string $emName,
|
||||
EntityManagerInterface $em,
|
||||
array $excluded = [],
|
||||
bool $purgeWithTruncate = false
|
||||
): PurgerInterface {
|
||||
return new class() implements ORMPurgerInterface {
|
||||
|
||||
public function purge(): void
|
||||
{
|
||||
throw new \LogicException('Do not use doctrine:fixtures:load directly. Use partdb:fixtures:load instead!');
|
||||
}
|
||||
|
||||
public function setEntityManager(EntityManagerInterface $em)
|
||||
{
|
||||
// TODO: Implement setEntityManager() method.
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,6 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface
|
||||
|
||||
//Reseting autoincrement is only supported on MySQL platforms
|
||||
if ($platform instanceof AbstractMySQLPlatform ) { //|| $platform instanceof SqlitePlatform) {
|
||||
$connection->beginTransaction();
|
||||
$connection->executeQuery($this->getResetAutoIncrementSQL($tbl, $platform));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@ class TinyIntType extends Type
|
||||
* @param T $value
|
||||
*
|
||||
* @return (T is null ? null : int)
|
||||
*
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform): ?int
|
||||
{
|
||||
return $value === null ? null : (int) $value;
|
||||
}
|
||||
|
||||
132
src/Entity/EDA/EDACategoryInfo.php
Normal file
132
src/Entity/EDA/EDACategoryInfo.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Entity\EDA;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
#[Embeddable]
|
||||
class EDACategoryInfo
|
||||
{
|
||||
/**
|
||||
* @var string|null The reference prefix of the Part in the schematic. E.g. "R" for resistors, or "C" for capacitors.
|
||||
*/
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?string $reference_prefix = null;
|
||||
|
||||
/** @var bool|null Visibility of this part to EDA software in trinary logic. True=Visible, False=Invisible, Null=Auto */
|
||||
#[Column(name: 'invisible', type: Types::BOOLEAN, nullable: true)] //TODO: Rename column to visibility
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?bool $visibility = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded from the BOM */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?bool $exclude_from_bom = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded from the board/the PCB */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?bool $exclude_from_board = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded in the simulation */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?bool $exclude_from_sim = true;
|
||||
|
||||
/** @var string|null The KiCAD schematic symbol, which should be used (the path to the library) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
private ?string $kicad_symbol = null;
|
||||
|
||||
public function getReferencePrefix(): ?string
|
||||
{
|
||||
return $this->reference_prefix;
|
||||
}
|
||||
|
||||
public function setReferencePrefix(?string $reference_prefix): EDACategoryInfo
|
||||
{
|
||||
$this->reference_prefix = $reference_prefix;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVisibility(): ?bool
|
||||
{
|
||||
return $this->visibility;
|
||||
}
|
||||
|
||||
public function setVisibility(?bool $visibility): EDACategoryInfo
|
||||
{
|
||||
$this->visibility = $visibility;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromBom(): ?bool
|
||||
{
|
||||
return $this->exclude_from_bom;
|
||||
}
|
||||
|
||||
public function setExcludeFromBom(?bool $exclude_from_bom): EDACategoryInfo
|
||||
{
|
||||
$this->exclude_from_bom = $exclude_from_bom;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromBoard(): ?bool
|
||||
{
|
||||
return $this->exclude_from_board;
|
||||
}
|
||||
|
||||
public function setExcludeFromBoard(?bool $exclude_from_board): EDACategoryInfo
|
||||
{
|
||||
$this->exclude_from_board = $exclude_from_board;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromSim(): ?bool
|
||||
{
|
||||
return $this->exclude_from_sim;
|
||||
}
|
||||
|
||||
public function setExcludeFromSim(?bool $exclude_from_sim): EDACategoryInfo
|
||||
{
|
||||
$this->exclude_from_sim = $exclude_from_sim;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKicadSymbol(): ?string
|
||||
{
|
||||
return $this->kicad_symbol;
|
||||
}
|
||||
|
||||
public function setKicadSymbol(?string $kicad_symbol): EDACategoryInfo
|
||||
{
|
||||
$this->kicad_symbol = $kicad_symbol;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
49
src/Entity/EDA/EDAFootprintInfo.php
Normal file
49
src/Entity/EDA/EDAFootprintInfo.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Entity\EDA;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
#[Embeddable]
|
||||
class EDAFootprintInfo
|
||||
{
|
||||
/** @var string|null The KiCAD footprint, which should be used (the path to the library) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'footprint:read', 'footprint:write'])]
|
||||
private ?string $kicad_footprint = null;
|
||||
|
||||
public function getKicadFootprint(): ?string
|
||||
{
|
||||
return $this->kicad_footprint;
|
||||
}
|
||||
|
||||
public function setKicadFootprint(?string $kicad_footprint): EDAFootprintInfo
|
||||
{
|
||||
$this->kicad_footprint = $kicad_footprint;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
170
src/Entity/EDA/EDAPartInfo.php
Normal file
170
src/Entity/EDA/EDAPartInfo.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Entity\EDA;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embeddable;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
#[Embeddable]
|
||||
class EDAPartInfo
|
||||
{
|
||||
/**
|
||||
* @var string|null The reference prefix of the Part in the schematic. E.g. "R" for resistors, or "C" for capacitors.
|
||||
*/
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?string $reference_prefix = null;
|
||||
|
||||
/** @var string|null The value, which should be shown together with the part (e.g. 470 for a 470 Ohm resistor) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?string $value = null;
|
||||
|
||||
/** @var bool|null Visibility of this part to EDA software in trinary logic. True=Visible, False=Invisible, Null=Auto */
|
||||
#[Column(name: 'invisible', type: Types::BOOLEAN, nullable: true)] //TODO: Rename column to visibility
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?bool $visibility = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded from the BOM */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?bool $exclude_from_bom = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded from the board/the PCB */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?bool $exclude_from_board = null;
|
||||
|
||||
/** @var bool|null If this is set to true, then this part will be excluded in the simulation */
|
||||
#[Column(type: Types::BOOLEAN, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?bool $exclude_from_sim = null;
|
||||
|
||||
/** @var string|null The KiCAD schematic symbol, which should be used (the path to the library) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?string $kicad_symbol = null;
|
||||
|
||||
/** @var string|null The KiCAD footprint, which should be used (the path to the library) */
|
||||
#[Column(type: Types::STRING, nullable: true)]
|
||||
#[Groups(['full', 'eda_info:read', 'eda_info:write'])]
|
||||
private ?string $kicad_footprint = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getReferencePrefix(): ?string
|
||||
{
|
||||
return $this->reference_prefix;
|
||||
}
|
||||
|
||||
public function setReferencePrefix(?string $reference_prefix): EDAPartInfo
|
||||
{
|
||||
$this->reference_prefix = $reference_prefix;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue(): ?string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(?string $value): EDAPartInfo
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVisibility(): ?bool
|
||||
{
|
||||
return $this->visibility;
|
||||
}
|
||||
|
||||
public function setVisibility(?bool $visibility): EDAPartInfo
|
||||
{
|
||||
$this->visibility = $visibility;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromBom(): ?bool
|
||||
{
|
||||
return $this->exclude_from_bom;
|
||||
}
|
||||
|
||||
public function setExcludeFromBom(?bool $exclude_from_bom): EDAPartInfo
|
||||
{
|
||||
$this->exclude_from_bom = $exclude_from_bom;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromBoard(): ?bool
|
||||
{
|
||||
return $this->exclude_from_board;
|
||||
}
|
||||
|
||||
public function setExcludeFromBoard(?bool $exclude_from_board): EDAPartInfo
|
||||
{
|
||||
$this->exclude_from_board = $exclude_from_board;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeFromSim(): ?bool
|
||||
{
|
||||
return $this->exclude_from_sim;
|
||||
}
|
||||
|
||||
public function setExcludeFromSim(?bool $exclude_from_sim): EDAPartInfo
|
||||
{
|
||||
$this->exclude_from_sim = $exclude_from_sim;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKicadSymbol(): ?string
|
||||
{
|
||||
return $this->kicad_symbol;
|
||||
}
|
||||
|
||||
public function setKicadSymbol(?string $kicad_symbol): EDAPartInfo
|
||||
{
|
||||
$this->kicad_symbol = $kicad_symbol;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getKicadFootprint(): ?string
|
||||
{
|
||||
return $this->kicad_footprint;
|
||||
}
|
||||
|
||||
public function setKicadFootprint(?string $kicad_footprint): EDAPartInfo
|
||||
{
|
||||
$this->kicad_footprint = $kicad_footprint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -38,6 +38,8 @@ use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\EDA\EDACategoryInfo;
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use App\Repository\Parts\CategoryRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
@@ -47,6 +49,7 @@ use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parameters\CategoryParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -185,6 +188,19 @@ class Category extends AbstractPartsContainingDBElement
|
||||
#[Groups(['category:read'])]
|
||||
protected ?\DateTimeInterface $lastModified = null;
|
||||
|
||||
#[Assert\Valid]
|
||||
#[ORM\Embedded(class: EDACategoryInfo::class)]
|
||||
#[Groups(['full', 'category:read', 'category:write'])]
|
||||
protected EDACategoryInfo $eda_info;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->eda_info = new EDACategoryInfo();
|
||||
}
|
||||
|
||||
public function getPartnameHint(): string
|
||||
{
|
||||
@@ -278,14 +294,17 @@ class Category extends AbstractPartsContainingDBElement
|
||||
public function setDefaultComment(string $default_comment): self
|
||||
{
|
||||
$this->default_comment = $default_comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
public function __construct()
|
||||
|
||||
public function getEdaInfo(): EDACategoryInfo
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
return $this->eda_info;
|
||||
}
|
||||
|
||||
public function setEdaInfo(EDACategoryInfo $eda_info): Category
|
||||
{
|
||||
$this->eda_info = $eda_info;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Attachments\AttachmentTypeAttachment;
|
||||
use App\Entity\EDA\EDACategoryInfo;
|
||||
use App\Entity\EDA\EDAFootprintInfo;
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use App\Repository\Parts\FootprintRepository;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
@@ -47,6 +50,7 @@ use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Parameters\FootprintParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -137,6 +141,19 @@ class Footprint extends AbstractPartsContainingDBElement
|
||||
#[Groups(['footprint:read'])]
|
||||
protected ?\DateTimeInterface $lastModified = null;
|
||||
|
||||
#[Assert\Valid]
|
||||
#[ORM\Embedded(class: EDAFootprintInfo::class)]
|
||||
#[Groups(['full', 'footprint:read', 'footprint:write'])]
|
||||
protected EDAFootprintInfo $eda_info;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->eda_info = new EDAFootprintInfo();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* Getters
|
||||
@@ -166,11 +183,15 @@ class Footprint extends AbstractPartsContainingDBElement
|
||||
|
||||
return $this;
|
||||
}
|
||||
public function __construct()
|
||||
|
||||
public function getEdaInfo(): EDAFootprintInfo
|
||||
{
|
||||
parent::__construct();
|
||||
$this->children = new ArrayCollection();
|
||||
$this->attachments = new ArrayCollection();
|
||||
$this->parameters = new ArrayCollection();
|
||||
return $this->eda_info;
|
||||
}
|
||||
|
||||
public function setEdaInfo(EDAFootprintInfo $eda_info): Footprint
|
||||
{
|
||||
$this->eda_info = $eda_info;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ use App\ApiPlatform\Filter\EntityFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\ApiPlatform\Filter\PartStoragelocationFilter;
|
||||
use App\Entity\Attachments\AttachmentTypeAttachment;
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use App\Entity\Parts\PartTraits\AssociationTrait;
|
||||
use App\Entity\Parts\PartTraits\EDATrait;
|
||||
use App\Repository\PartRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
@@ -83,7 +85,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read',
|
||||
'orderdetail:read', 'pricedetail:read', 'parameter:read', 'attachment:read'],
|
||||
'orderdetail:read', 'pricedetail:read', 'parameter:read', 'attachment:read', 'eda_info:read'],
|
||||
'openapi_definition_name' => 'Read',
|
||||
], security: 'is_granted("read", object)'),
|
||||
new GetCollection(security: 'is_granted("@parts.read")'),
|
||||
@@ -92,7 +94,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
new Delete(security: 'is_granted("delete", object)'),
|
||||
],
|
||||
normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read', 'part_lot:read'], 'openapi_definition_name' => 'Read'],
|
||||
denormalizationContext: ['groups' => ['part:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||
denormalizationContext: ['groups' => ['part:write', 'api:basic:write', 'eda_info:write'], 'openapi_definition_name' => 'Write'],
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
#[ApiFilter(EntityFilter::class, properties: ["category", "footprint", "manufacturer", "partUnit"])]
|
||||
@@ -102,8 +104,6 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
#[ApiFilter(RangeFilter::class, properties: ["mass", "minamount"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
|
||||
#[DocumentedAPIProperty(schemaName: 'Part-Read', property: 'total_instock', type: 'number', nullable: false,
|
||||
description: 'The total amount of this part in stock (sum of all part lots).')]
|
||||
class Part extends AttachmentContainingDBElement
|
||||
{
|
||||
use AdvancedPropertyTrait;
|
||||
@@ -115,6 +115,7 @@ class Part extends AttachmentContainingDBElement
|
||||
use ParametersTrait;
|
||||
use ProjectTrait;
|
||||
use AssociationTrait;
|
||||
use EDATrait;
|
||||
|
||||
/** @var Collection<int, PartParameter>
|
||||
*/
|
||||
@@ -173,6 +174,7 @@ class Part extends AttachmentContainingDBElement
|
||||
|
||||
//By default, the part has no provider
|
||||
$this->providerReference = InfoProviderReference::noProvider();
|
||||
$this->eda_info = new EDAPartInfo();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
@@ -208,6 +210,7 @@ class Part extends AttachmentContainingDBElement
|
||||
|
||||
//Deep clone info provider
|
||||
$this->providerReference = clone $this->providerReference;
|
||||
$this->eda_info = clone $this->eda_info;
|
||||
}
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Repository\DBElementRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -68,7 +69,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["other_type", "comment"])]
|
||||
#[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['comment', 'addedDate', 'lastModified'])]
|
||||
class PartAssociation extends AbstractDBElement
|
||||
class PartAssociation extends AbstractDBElement implements TimeStampableInterface
|
||||
{
|
||||
use TimestampTrait;
|
||||
|
||||
|
||||
54
src/Entity/Parts/PartTraits/EDATrait.php
Normal file
54
src/Entity/Parts/PartTraits/EDATrait.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Entity\Parts\PartTraits;
|
||||
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Embedded;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
|
||||
trait EDATrait
|
||||
{
|
||||
#[Valid]
|
||||
#[Embedded(class: EDAPartInfo::class)]
|
||||
#[Groups(['full', 'part:read', 'part:write'])]
|
||||
protected EDAPartInfo $eda_info;
|
||||
|
||||
public function getEdaInfo(): EDAPartInfo
|
||||
{
|
||||
return $this->eda_info;
|
||||
}
|
||||
|
||||
public function setEdaInfo(?EDAPartInfo $eda_info): self
|
||||
{
|
||||
if ($eda_info !== null) {
|
||||
//Do a clone, to ensure that the property is updated in the database
|
||||
$eda_info = clone $eda_info;
|
||||
}
|
||||
|
||||
$this->eda_info = $eda_info;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ use App\Entity\Parts\PartLot;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -181,6 +182,8 @@ trait InstockTrait
|
||||
*
|
||||
* @return float The amount of parts given in partUnit
|
||||
*/
|
||||
#[Groups(['simple', 'extended', 'full', 'part:read'])]
|
||||
#[SerializedName('total_instock')]
|
||||
public function getAmountSum(): float
|
||||
{
|
||||
//TODO: Find a method to do this natively in SQL, the current method could be a bit slow
|
||||
|
||||
@@ -35,6 +35,7 @@ use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\ApiPlatform\Filter\LikeFilter;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Validator\UniqueValidatableInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
@@ -84,7 +85,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
#[ApiFilter(LikeFilter::class, properties: ["name", "comment", 'mountnames'])]
|
||||
#[ApiFilter(RangeFilter::class, properties: ['quantity'])]
|
||||
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified', 'quantity'])]
|
||||
class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInterface
|
||||
class ProjectBOMEntry extends AbstractDBElement implements UniqueValidatableInterface, TimeStampableInterface
|
||||
{
|
||||
use TimestampTrait;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Repository\UserSystem\ApiTokenRepository;
|
||||
use App\State\CurrentApiTokenProvider;
|
||||
use App\State\PartDBInfoProvider;
|
||||
@@ -54,7 +55,7 @@ use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
provider: CurrentApiTokenProvider::class,
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
class ApiToken
|
||||
class ApiToken implements TimeStampableInterface
|
||||
{
|
||||
|
||||
use TimestampTrait;
|
||||
|
||||
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\UserSystem;
|
||||
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -31,7 +32,7 @@ use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface;
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\Table(name: 'u2f_keys')]
|
||||
#[ORM\UniqueConstraint(name: 'user_unique', columns: ['user_id', 'key_handle'])]
|
||||
class U2FKey implements LegacyU2FKeyInterface
|
||||
class U2FKey implements LegacyU2FKeyInterface, TimeStampableInterface
|
||||
{
|
||||
use TimestampTrait;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace App\Entity\UserSystem;
|
||||
|
||||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Base\TimestampTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -31,7 +32,7 @@ use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource;
|
||||
#[ORM\Entity]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[ORM\Table(name: 'webauthn_keys')]
|
||||
class WebauthnKey extends BasePublicKeyCredentialSource
|
||||
class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampableInterface
|
||||
{
|
||||
use TimestampTrait;
|
||||
|
||||
|
||||
@@ -24,49 +24,52 @@ namespace App\EntityListeners;
|
||||
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\LabelSystem\LabelProfile;
|
||||
use App\Entity\UserSystem\Group;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Services\UserSystem\UserCacheKeyGenerator;
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
use App\Services\Cache\ElementCacheTagGenerator;
|
||||
use App\Services\Cache\UserCacheKeyGenerator;
|
||||
use Doctrine\ORM\Event\PostPersistEventArgs;
|
||||
use Doctrine\ORM\Event\PostRemoveEventArgs;
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use function get_class;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
class TreeCacheInvalidationListener
|
||||
{
|
||||
public function __construct(protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator)
|
||||
public function __construct(
|
||||
protected TagAwareCacheInterface $cache,
|
||||
protected UserCacheKeyGenerator $keyGenerator,
|
||||
protected ElementCacheTagGenerator $tagGenerator
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\PostUpdate]
|
||||
#[ORM\PostPersist]
|
||||
#[ORM\PostRemove]
|
||||
public function invalidate(AbstractDBElement $element, LifecycleEventArgs $event): void
|
||||
public function invalidate(AbstractDBElement $element, PostUpdateEventArgs|PostPersistEventArgs|PostRemoveEventArgs $event): void
|
||||
{
|
||||
//If an element was changed, then invalidate all cached trees with this element class
|
||||
if ($element instanceof AbstractStructuralDBElement || $element instanceof LabelProfile) {
|
||||
$secure_class_name = str_replace('\\', '_', $element::class);
|
||||
$this->cache->invalidateTags([$secure_class_name]);
|
||||
//For all changes, we invalidate the cache for all elements of this class
|
||||
$tags = [$this->tagGenerator->getElementTypeCacheTag($element)];
|
||||
|
||||
//Trigger a sidebar reload for all users (see SidebarTreeUpdater service)
|
||||
if(!$element instanceof LabelProfile) {
|
||||
$this->cache->invalidateTags(['sidebar_tree_update']);
|
||||
}
|
||||
|
||||
//For changes on structural elements, we also invalidate the sidebar tree
|
||||
if ($element instanceof AbstractStructuralDBElement) {
|
||||
$tags[] = 'sidebar_tree_update';
|
||||
}
|
||||
|
||||
//If a user change, then invalidate all cached trees for him
|
||||
//For user changes, we invalidate the cache for this user
|
||||
if ($element instanceof User) {
|
||||
$secure_class_name = str_replace('\\', '_', $element::class);
|
||||
$tag = $this->keyGenerator->generateKey($element);
|
||||
$this->cache->invalidateTags([$tag, $secure_class_name]);
|
||||
$tags[] = $this->keyGenerator->generateKey($element);
|
||||
}
|
||||
|
||||
/* If any group change, then invalidate all cached trees. Users Permissions can be inherited from groups,
|
||||
so a change in any group can cause big permisssion changes for users. So to be sure, invalidate all trees */
|
||||
if ($element instanceof Group) {
|
||||
$tag = 'groups';
|
||||
$this->cache->invalidateTags([$tag]);
|
||||
$tags[] = 'groups';
|
||||
}
|
||||
|
||||
//Invalidate the cache for the given tags
|
||||
$this->cache->invalidateTags($tags);
|
||||
}
|
||||
}
|
||||
|
||||
151
src/EventListener/ConsoleEnsureWebserverUserListener.php
Normal file
151
src/EventListener/ConsoleEnsureWebserverUserListener.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Symfony\Component\Console\ConsoleEvents;
|
||||
use Symfony\Component\Console\Event\ConsoleCommandEvent;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
|
||||
/**
|
||||
* This event listener is called before any console command is executed and should ensure that the webserver
|
||||
* user is used for all operations (and show a warning if not). This ensures that all files are created with the
|
||||
* correct permissions.
|
||||
* If the console is in non-interactive mode, a warning is shown, but the command is still executed.
|
||||
*/
|
||||
#[AsEventListener(ConsoleEvents::COMMAND)]
|
||||
class ConsoleEnsureWebserverUserListener
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire('%kernel.project_dir%')]
|
||||
private readonly string $project_root)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(ConsoleCommandEvent $event): void
|
||||
{
|
||||
$input = $event->getInput();
|
||||
$io = new SymfonyStyle($event->getInput(), $event->getOutput());
|
||||
|
||||
//Check if we are (not) running as the webserver user
|
||||
$webserver_user = $this->getWebserverUser();
|
||||
$running_user = $this->getRunningUser();
|
||||
|
||||
//Check if we are trying to run as root
|
||||
if ($this->isRunningAsRoot()) {
|
||||
$io->warning('You are running this command as root. This is not recommended, as it can cause permission problems. Please run this command as the webserver user "'. ($webserver_user ?? '??') . '" instead.');
|
||||
$io->info('You might have already caused permission problems by running this command as wrong user. If you encounter issues with Part-DB, delete the var/cache directory completely and let it be recreated by Part-DB.');
|
||||
if ($input->isInteractive() && !$io->confirm('Do you want to continue?', false)) {
|
||||
$event->disableCommand();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($webserver_user !== null && $running_user !== null && $webserver_user !== $running_user) {
|
||||
$io->warning('You are running this command as the user "' . $running_user . '". This is not recommended, as it can cause permission problems. Please run this command as the webserver user "' . $webserver_user . '" instead.');
|
||||
$io->info('You might have already caused permission problems by running this command as wrong user. If you encounter issues with Part-DB, delete the var/cache directory completely and let it be recreated by Part-DB.');
|
||||
if ($input->isInteractive() && !$io->confirm('Do you want to continue?', false)) {
|
||||
$event->disableCommand();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function isRunningAsRoot(): bool
|
||||
{
|
||||
//If we are on windows, we can't run as root
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Try to use the posix extension if available (Linux)
|
||||
if (function_exists('posix_geteuid')) {
|
||||
//Check if the current user is root
|
||||
return posix_geteuid() === 0;
|
||||
}
|
||||
|
||||
//Otherwise we can't determine the username
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the username of the user who started the current script if possible.
|
||||
* Returns null if the username could not be determined.
|
||||
* @return string|null
|
||||
*/
|
||||
private function getRunningUser(): ?string
|
||||
{
|
||||
//Try to use the posix extension if available (Linux)
|
||||
if (function_exists('posix_geteuid') && function_exists('posix_getpwuid')) {
|
||||
$id = posix_geteuid();
|
||||
|
||||
$user = posix_getpwuid($id);
|
||||
//Try to get the username from the posix extension or return the id
|
||||
return $user['name'] ?? ("ID: " . $id);
|
||||
}
|
||||
|
||||
//Otherwise we can't determine the username
|
||||
return $_SERVER['USERNAME'] ?? $_SERVER['USER'] ?? null;
|
||||
}
|
||||
|
||||
private function getWebserverUser(): ?string
|
||||
{
|
||||
//Determine the webserver user, by checking who owns the uploads/ directory
|
||||
$path_to_check = $this->project_root . '/uploads/';
|
||||
|
||||
//Determine the owner of this directory
|
||||
if (!is_dir($path_to_check)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//If we are on windows we need some special logic
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
//If we have the COM extension available, we can use it to determine the owner
|
||||
if (extension_loaded('com_dotnet')) {
|
||||
$su = new \COM("ADsSecurityUtility"); // Call interface
|
||||
//@phpstan-ignore-next-line
|
||||
$securityInfo = $su->GetSecurityDescriptor($path_to_check, 1, 1); // Call method
|
||||
return $securityInfo->owner; // Get file owner
|
||||
}
|
||||
|
||||
//Otherwise we can't determine the owner
|
||||
return null;
|
||||
}
|
||||
|
||||
//When we are on a POSIX system, we can use the fileowner function
|
||||
$owner = fileowner($path_to_check);
|
||||
|
||||
if (function_exists('posix_getpwuid')) {
|
||||
$user = posix_getpwuid($owner);
|
||||
//Try to get the username from the posix extension or return the id
|
||||
return $user['name'] ?? ("ID: " . $owner);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Form\Part\EDA\EDACategoryInfoType;
|
||||
use App\Form\Type\RichTextEditorType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
@@ -104,5 +105,11 @@ class CategoryAdminForm extends BaseEntityAdminForm
|
||||
],
|
||||
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity),
|
||||
]);
|
||||
|
||||
//EDA info
|
||||
$builder->add('eda_info', EDACategoryInfoType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\EDA\EDAFootprintInfo;
|
||||
use App\Form\Part\EDA\EDAFootprintInfoType;
|
||||
use App\Form\Type\MasterPictureAttachmentType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
@@ -37,5 +39,11 @@ class FootprintAdminForm extends BaseEntityAdminForm
|
||||
'filter' => '3d_model',
|
||||
'entity' => $entity,
|
||||
]);
|
||||
|
||||
//EDA info
|
||||
$builder->add('eda_info', EDAFootprintInfoType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,16 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class AttachmentFormType extends AbstractType
|
||||
{
|
||||
public function __construct(protected AttachmentManager $attachment_helper, protected UrlGeneratorInterface $urlGenerator, protected Security $security, protected AttachmentSubmitHandler $submitHandler, protected TranslatorInterface $translator, protected bool $allow_attachments_download, protected string $max_file_size)
|
||||
{
|
||||
public function __construct(
|
||||
protected AttachmentManager $attachment_helper,
|
||||
protected UrlGeneratorInterface $urlGenerator,
|
||||
protected Security $security,
|
||||
protected AttachmentSubmitHandler $submitHandler,
|
||||
protected TranslatorInterface $translator,
|
||||
protected bool $allow_attachments_download,
|
||||
protected bool $download_by_default,
|
||||
protected string $max_file_size
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
@@ -85,7 +93,8 @@ class AttachmentFormType extends AbstractType
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'data-controller' => 'elements--attachment-autocomplete',
|
||||
'data-autocomplete' => $this->urlGenerator->generate('typeahead_builtInRessources', ['query' => '__QUERY__']),
|
||||
'data-autocomplete' => $this->urlGenerator->generate('typeahead_builtInRessources',
|
||||
['query' => '__QUERY__']),
|
||||
//Disable browser autocomplete
|
||||
'autocomplete' => 'off',
|
||||
],
|
||||
@@ -132,6 +141,12 @@ class AttachmentFormType extends AbstractType
|
||||
}
|
||||
|
||||
if (!$file instanceof UploadedFile) {
|
||||
//When no file was uploaded, but a URL was entered, try to determine the attachment name from the URL
|
||||
if (empty($attachment->getName()) && !empty($attachment->getURL())) {
|
||||
$name = basename(parse_url($attachment->getURL(), PHP_URL_PATH));
|
||||
$attachment->setName($name);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -159,6 +174,30 @@ class AttachmentFormType extends AbstractType
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//If the attachment should be downloaded by default (and is download allowed at all), register a listener,
|
||||
// which sets the downloadURL checkbox to true for new attachments
|
||||
if ($this->download_by_default && $this->allow_attachments_download) {
|
||||
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event): void {
|
||||
$form = $event->getForm();
|
||||
$attachment = $form->getData();
|
||||
|
||||
if (!$attachment instanceof Attachment && $attachment !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//If the attachment was not created yet, set the downloadURL checkbox to true
|
||||
if ($attachment === null || $attachment->getId() === null) {
|
||||
$checkbox = $form->get('downloadURL');
|
||||
//Ensure that the checkbox is not disabled
|
||||
if ($checkbox->isDisabled()) {
|
||||
return;
|
||||
}
|
||||
//Set the checkbox
|
||||
$checkbox->setData(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
|
||||
88
src/Form/Part/EDA/EDACategoryInfoType.php
Normal file
88
src/Form/Part/EDA/EDACategoryInfoType.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\Part\EDA;
|
||||
|
||||
use App\Entity\EDA\EDACategoryInfo;
|
||||
use App\Entity\EDA\EDAFootprintInfo;
|
||||
use App\Form\Type\TriStateCheckboxType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
class EDACategoryInfoType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('reference_prefix', TextType::class, [
|
||||
'label' => 'eda_info.reference_prefix',
|
||||
'attr' => [
|
||||
'placeholder' => t('eda_info.reference_prefix.placeholder'),
|
||||
]
|
||||
]
|
||||
)
|
||||
->add('visibility', TriStateCheckboxType::class, [
|
||||
'help' => 'eda_info.visibility.help',
|
||||
'label' => 'eda_info.visibility',
|
||||
])
|
||||
->add('exclude_from_bom', TriStateCheckboxType::class, [
|
||||
'label' => 'eda_info.exclude_from_bom',
|
||||
'label_attr' => [
|
||||
'class' => 'checkbox-inline'
|
||||
]
|
||||
])
|
||||
->add('exclude_from_board', TriStateCheckboxType::class, [
|
||||
'label' => 'eda_info.exclude_from_board',
|
||||
'label_attr' => [
|
||||
'class' => 'checkbox-inline'
|
||||
]
|
||||
])
|
||||
->add('exclude_from_sim', TriStateCheckboxType::class, [
|
||||
'label' => 'eda_info.exclude_from_sim',
|
||||
'label_attr' => [
|
||||
'class' => 'checkbox-inline'
|
||||
]
|
||||
])
|
||||
->add('kicad_symbol', KicadFieldAutocompleteType::class, [
|
||||
'label' => 'eda_info.kicad_symbol',
|
||||
'type' => KicadFieldAutocompleteType::TYPE_SYMBOL,
|
||||
'attr' => [
|
||||
'placeholder' => t('eda_info.kicad_symbol.placeholder'),
|
||||
]
|
||||
])
|
||||
;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => EDACategoryInfo::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
56
src/Form/Part/EDA/EDAFootprintInfoType.php
Normal file
56
src/Form/Part/EDA/EDAFootprintInfoType.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\Part\EDA;
|
||||
|
||||
use App\Entity\EDA\EDAFootprintInfo;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
class EDAFootprintInfoType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('kicad_footprint', KicadFieldAutocompleteType::class, [
|
||||
'type' => KicadFieldAutocompleteType::TYPE_FOOTPRINT,
|
||||
'label' => 'eda_info.kicad_footprint',
|
||||
'attr' => [
|
||||
'placeholder' => t('eda_info.kicad_footprint.placeholder'),
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => EDAFootprintInfo::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
97
src/Form/Part/EDA/EDAPartInfoType.php
Normal file
97
src/Form/Part/EDA/EDAPartInfoType.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\Part\EDA;
|
||||
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use App\Form\Type\TriStateCheckboxType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
class EDAPartInfoType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('reference_prefix', TextType::class, [
|
||||
'label' => 'eda_info.reference_prefix',
|
||||
'attr' => [
|
||||
'placeholder' => t('eda_info.reference_prefix.placeholder'),
|
||||
]
|
||||
]
|
||||
)
|
||||
->add('value', TextType::class, [
|
||||
'label' => 'eda_info.value',
|
||||
'attr' => [
|
||||
'placeholder' => t('eda_info.value.placeholder'),
|
||||
]
|
||||
])
|
||||
->add('visibility', TriStateCheckboxType::class, [
|
||||
'help' => 'eda_info.visibility.help',
|
||||
'label' => 'eda_info.visibility',
|
||||
])
|
||||
->add('exclude_from_bom', TriStateCheckboxType::class, [
|
||||
'label' => 'eda_info.exclude_from_bom',
|
||||
'label_attr' => [
|
||||
'class' => 'checkbox-inline'
|
||||
]
|
||||
])
|
||||
->add('exclude_from_board', TriStateCheckboxType::class, [
|
||||
'label' => 'eda_info.exclude_from_board',
|
||||
'label_attr' => [
|
||||
'class' => 'checkbox-inline'
|
||||
]
|
||||
])
|
||||
->add('exclude_from_sim', TriStateCheckboxType::class, [
|
||||
'label' => 'eda_info.exclude_from_sim',
|
||||
'label_attr' => [
|
||||
'class' => 'checkbox-inline'
|
||||
]
|
||||
])
|
||||
->add('kicad_symbol', KicadFieldAutocompleteType::class, [
|
||||
'label' => 'eda_info.kicad_symbol',
|
||||
'type' => KicadFieldAutocompleteType::TYPE_SYMBOL,
|
||||
'attr' => [
|
||||
'placeholder' => t('eda_info.kicad_symbol.placeholder'),
|
||||
]
|
||||
])
|
||||
->add('kicad_footprint', KicadFieldAutocompleteType::class, [
|
||||
'label' => 'eda_info.kicad_footprint',
|
||||
'type' => KicadFieldAutocompleteType::TYPE_FOOTPRINT,
|
||||
'attr' => [
|
||||
'placeholder' => t('eda_info.kicad_footprint.placeholder'),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => EDAPartInfo::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
60
src/Form/Part/EDA/KicadFieldAutocompleteType.php
Normal file
60
src/Form/Part/EDA/KicadFieldAutocompleteType.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\Part\EDA;
|
||||
|
||||
use App\Form\Type\StaticFileAutocompleteType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* This is a specialized version of the StaticFileAutocompleteType, which loads the different types of Kicad lists.
|
||||
*/
|
||||
class KicadFieldAutocompleteType extends AbstractType
|
||||
{
|
||||
public const TYPE_FOOTPRINT = 'footprint';
|
||||
public const TYPE_SYMBOL = 'symbol';
|
||||
|
||||
public const FOOTPRINT_PATH = '/kicad/footprints.txt';
|
||||
public const SYMBOL_PATH = '/kicad/symbols.txt';
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired('type');
|
||||
$resolver->setAllowedValues('type', [self::TYPE_SYMBOL, self::TYPE_FOOTPRINT]);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'file' => fn(Options $options) => match ($options['type']) {
|
||||
self::TYPE_FOOTPRINT => self::FOOTPRINT_PATH,
|
||||
self::TYPE_SYMBOL => self::SYMBOL_PATH,
|
||||
default => throw new \InvalidArgumentException('Invalid type'),
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return StaticFileAutocompleteType::class;
|
||||
}
|
||||
}
|
||||
@@ -22,27 +22,28 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Part;
|
||||
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Attachments\PartAttachment;
|
||||
use App\Entity\EDA\EDAPartInfo;
|
||||
use App\Entity\Parameters\PartParameter;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Form\AttachmentFormType;
|
||||
use App\Form\ParameterType;
|
||||
use App\Form\Part\EDA\EDAPartInfoType;
|
||||
use App\Form\Type\MasterPictureAttachmentType;
|
||||
use App\Form\Type\RichTextEditorType;
|
||||
use App\Form\Type\SIUnitType;
|
||||
use App\Form\Type\StructuralEntityType;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\LogSystem\EventCommentNeededHelper;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ResetType;
|
||||
@@ -52,7 +53,6 @@ use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class PartBaseType extends AbstractType
|
||||
{
|
||||
@@ -255,6 +255,12 @@ class PartBaseType extends AbstractType
|
||||
'by_reference' => false,
|
||||
]);
|
||||
|
||||
//EDA info
|
||||
$builder->add('eda_info', EDAPartInfoType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$builder->add('log_comment', TextType::class, [
|
||||
'label' => 'edit.log_comment',
|
||||
'mapped' => false,
|
||||
|
||||
@@ -75,7 +75,8 @@ class StructuralEntityChoiceHelper
|
||||
}
|
||||
|
||||
if ($choice instanceof HasMasterAttachmentInterface) {
|
||||
$tmp['data-image'] = $choice->getMasterPictureAttachment() instanceof Attachment ?
|
||||
$tmp['data-image'] = ($choice->getMasterPictureAttachment() instanceof Attachment
|
||||
&& $choice->getMasterPictureAttachment()->isPicture()) ?
|
||||
$this->attachmentURLGenerator->getThumbnailURL($choice->getMasterPictureAttachment(),
|
||||
'thumbnail_xs')
|
||||
: null
|
||||
|
||||
63
src/Form/Type/StaticFileAutocompleteType.php
Normal file
63
src/Form/Type/StaticFileAutocompleteType.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use Symfony\Component\Asset\Packages;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Implements a text type with autocomplete functionality based on a static file, containing a list of autocomplete
|
||||
* suggestions.
|
||||
* Other values are allowed, but the user can select from the list of suggestions.
|
||||
* The file must be located in the public directory!
|
||||
*/
|
||||
class StaticFileAutocompleteType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Packages $assets
|
||||
) {
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setRequired('file');
|
||||
$resolver->setAllowedTypes('file', 'string');
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
|
||||
public function finishView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
//Add the data-controller and data-url attributes to the form field
|
||||
$view->vars['attr']['data-controller'] = 'elements--static-file-autocomplete';
|
||||
$view->vars['attr']['data-url'] = $this->assets->getUrl($options['file']);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,11 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo
|
||||
return $this->getPartsCountRecursiveWithDepthN($element, self::RECURSION_LIMIT);
|
||||
}
|
||||
|
||||
public function getPartsRecursive(AbstractPartsContainingDBElement $element): array
|
||||
{
|
||||
return $this->getPartsRecursiveWithDepthN($element, self::RECURSION_LIMIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of the recursive function to get the parts count.
|
||||
* This function is used to limit the recursion depth (remaining_depth is decreased on each call).
|
||||
@@ -91,6 +96,23 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo
|
||||
return $count;
|
||||
}
|
||||
|
||||
protected function getPartsRecursiveWithDepthN(AbstractPartsContainingDBElement $element, int $remaining_depth): array
|
||||
{
|
||||
if ($remaining_depth <= 0) {
|
||||
throw new \RuntimeException('Recursion limit reached!');
|
||||
}
|
||||
|
||||
//Add direct parts
|
||||
$parts = $this->getParts($element);
|
||||
|
||||
//Then iterate over all children and add their parts
|
||||
foreach ($element->getChildren() as $child) {
|
||||
$parts = array_merge($parts, $this->getPartsRecursiveWithDepthN($child, $remaining_depth - 1));
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
protected function getPartsByField(object $element, array $order_by, string $field_name): array
|
||||
{
|
||||
if (!$element instanceof AbstractPartsContainingDBElement) {
|
||||
|
||||
@@ -46,7 +46,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
class ApiTokenAuthenticator implements AuthenticatorInterface
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(service: 'security.access_token_extractor.header')]
|
||||
#[Autowire(service: 'security.access_token_extractor.main')]
|
||||
private readonly AccessTokenExtractorInterface $accessTokenExtractor,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
|
||||
@@ -39,6 +39,7 @@ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
|
||||
/**
|
||||
* @see \App\Tests\Serializer\PartNormalizerTest
|
||||
* TODO: Properly rewrite this class to use the SerializerAware interface and dont use the ObjectNormalizer directly
|
||||
*/
|
||||
class PartNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||
{
|
||||
@@ -65,7 +66,8 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||
|
||||
public function supportsNormalization($data, string $format = null, array $context = []): bool
|
||||
{
|
||||
return $data instanceof Part;
|
||||
//We only remove the type field for CSV export
|
||||
return $format === 'csv' && $data instanceof Part ;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,8 +88,6 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||
unset($data['type']);
|
||||
}
|
||||
|
||||
$data['total_instock'] = $object->getAmountSum();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class AttachmentSubmitHandler
|
||||
'htpasswd', ''];
|
||||
|
||||
public function __construct(protected AttachmentPathResolver $pathResolver, protected bool $allow_attachments_downloads,
|
||||
protected HttpClientInterface $httpClient, protected MimeTypesInterface $mimeTypes,
|
||||
protected HttpClientInterface $httpClient, protected MimeTypesInterface $mimeTypes,
|
||||
protected FileTypeFilterTools $filterTools, /**
|
||||
* @var string The user configured maximum upload size. This is a string like "10M" or "1G" and will be converted to
|
||||
*/
|
||||
@@ -143,19 +143,35 @@ class AttachmentSubmitHandler
|
||||
{
|
||||
$base_path = $secure_upload ? $this->pathResolver->getSecurePath() : $this->pathResolver->getMediaPath();
|
||||
|
||||
//Ensure the given attachment class is known to mapping
|
||||
if (!isset($this->folder_mapping[$attachment::class])) {
|
||||
throw new InvalidArgumentException('The given attachment class is not known! The passed class was: '.$attachment::class);
|
||||
}
|
||||
//Ensure the attachment has an assigned element
|
||||
if (!$attachment->getElement() instanceof AttachmentContainingDBElement) {
|
||||
throw new InvalidArgumentException('The given attachment is not assigned to an element! An element is needed to generate a path!');
|
||||
}
|
||||
|
||||
//Determine the folder prefix for the given attachment class:
|
||||
$prefix = null;
|
||||
//Check if we can use the class name dire
|
||||
if (isset($this->folder_mapping[$attachment::class])) {
|
||||
$prefix = $this->folder_mapping[$attachment::class];
|
||||
} else {
|
||||
//If not, check for instance of:
|
||||
foreach ($this->folder_mapping as $class => $folder) {
|
||||
if ($attachment instanceof $class) {
|
||||
$prefix = $folder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Ensure the given attachment class is known to mapping
|
||||
if (!$prefix) {
|
||||
throw new InvalidArgumentException('The given attachment class is not known! The passed class was: '.$attachment::class);
|
||||
}
|
||||
|
||||
//Build path
|
||||
return
|
||||
$base_path.DIRECTORY_SEPARATOR //Base path
|
||||
.$this->folder_mapping[$attachment::class].DIRECTORY_SEPARATOR.$attachment->getElement()->getID();
|
||||
.$prefix.DIRECTORY_SEPARATOR.$attachment->getElement()->getID();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,13 +204,22 @@ class AttachmentSubmitHandler
|
||||
//Rename blacklisted (unsecure) files to a better extension
|
||||
$this->renameBlacklistedExtensions($attachment);
|
||||
|
||||
//Check if we should assign this attachment to master picture
|
||||
//this is only possible if the attachment is new (not yet persisted to DB)
|
||||
if ($options['become_preview_if_empty'] && null === $attachment->getID() && $attachment->isPicture()) {
|
||||
$element = $attachment->getElement();
|
||||
if ($element instanceof AttachmentContainingDBElement && !$element->getMasterPictureAttachment() instanceof Attachment) {
|
||||
//Set / Unset the master picture attachment / preview image
|
||||
$element = $attachment->getElement();
|
||||
if ($element instanceof AttachmentContainingDBElement) {
|
||||
//Make this attachment the master picture if needed and this was requested
|
||||
if ($options['become_preview_if_empty']
|
||||
&& $element->getMasterPictureAttachment() === null //Element must not have an preview image yet
|
||||
&& null === $attachment->getID() //Attachment must be null
|
||||
&& $attachment->isPicture() //Attachment must be a picture
|
||||
) {
|
||||
$element->setMasterPictureAttachment($attachment);
|
||||
}
|
||||
|
||||
//If this attachment is the master picture, but is not a picture anymore, dont use it as master picture anymore
|
||||
if ($element->getMasterPictureAttachment() === $attachment && !$attachment->isPicture()) {
|
||||
$element->setMasterPictureAttachment(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $attachment;
|
||||
@@ -222,7 +247,7 @@ class AttachmentSubmitHandler
|
||||
//Check if the extension is blacklisted and replace the file extension with txt if needed
|
||||
if(in_array($ext, self::BLACKLISTED_EXTENSIONS, true)) {
|
||||
$new_path = $this->generateAttachmentPath($attachment, $attachment->isSecure())
|
||||
.DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'txt');
|
||||
.DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'txt');
|
||||
|
||||
//Move file to new directory
|
||||
$fs = new Filesystem();
|
||||
@@ -357,7 +382,7 @@ class AttachmentSubmitHandler
|
||||
|
||||
//Check if we have an extension given
|
||||
$pathinfo = pathinfo($filename);
|
||||
if ($pathinfo['extension'] !== '') {
|
||||
if (isset($pathinfo['extension']) && $pathinfo['extension'] !== '') {
|
||||
$new_ext = $pathinfo['extension'];
|
||||
} else { //Otherwise we have to guess the extension for the new file, based on its content
|
||||
$new_ext = $this->mimeTypes->getExtensions($this->mimeTypes->guessMimeType($tmp_path))[0] ?? 'tmp';
|
||||
|
||||
@@ -135,7 +135,10 @@ class AttachmentURLGenerator
|
||||
}
|
||||
|
||||
//GD can not work with SVG, so serve it directly...
|
||||
if ('svg' === $attachment->getExtension()) {
|
||||
//We can not use getExtension here, because it uses the original filename and not the real extension
|
||||
//Instead we use the logic, which is also used to determine if the attachment is a picture
|
||||
$extension = pathinfo(parse_url($attachment->getPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
||||
if ('svg' === $extension) {
|
||||
return $this->assets->getUrl($asset_path);
|
||||
}
|
||||
|
||||
|
||||
70
src/Services/Cache/ElementCacheTagGenerator.php
Normal file
70
src/Services/Cache/ElementCacheTagGenerator.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\Cache;
|
||||
|
||||
use Doctrine\Persistence\Proxy;
|
||||
|
||||
/**
|
||||
* The purpose of this class is to generate cache tags for elements.
|
||||
* E.g. to easily invalidate all caches for a given element type.
|
||||
*/
|
||||
class ElementCacheTagGenerator
|
||||
{
|
||||
private array $cache = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cache tag for the given element type, which can be used to invalidate all caches for this element type.
|
||||
* @param string|object $element
|
||||
* @return string
|
||||
*/
|
||||
public function getElementTypeCacheTag(string|object $element): string
|
||||
{
|
||||
//Ensure that the given element is a class name
|
||||
if (is_object($element)) {
|
||||
$element = get_class($element);
|
||||
} else { //And that the class exists
|
||||
if (!class_exists($element)) {
|
||||
throw new \InvalidArgumentException("The given class '$element' does not exist!");
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the tag is already cached
|
||||
if (isset($this->cache[$element])) {
|
||||
return $this->cache[$element];
|
||||
}
|
||||
|
||||
//If the element is a proxy, then get the real class name of the underlying object
|
||||
if (is_a($element, Proxy::class, true) || str_starts_with($element, 'Proxies\\')) {
|
||||
$element = get_parent_class($element);
|
||||
}
|
||||
|
||||
//Replace all backslashes with underscores to prevent problems with the cache and save the result
|
||||
$this->cache[$element] = str_replace('\\', '_', $element);
|
||||
return $this->cache[$element];
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\UserSystem;
|
||||
namespace App\Services\Cache;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use App\Entity\UserSystem\User;
|
||||
use Locale;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
344
src/Services/EDA/KiCadHelper.php
Normal file
344
src/Services/EDA/KiCadHelper.php
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\EDA;
|
||||
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\Cache\ElementCacheTagGenerator;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class KiCadHelper
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private readonly NodesListBuilder $nodesListBuilder,
|
||||
private readonly TagAwareCacheInterface $kicadCache,
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly ElementCacheTagGenerator $tagGenerator,
|
||||
private readonly UrlGeneratorInterface $urlGenerator,
|
||||
private readonly TranslatorInterface $translator,
|
||||
/** The maximum level of the shown categories. 0 Means only the top level categories are shown. -1 means only a single one containing */
|
||||
private readonly int $category_depth,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects containing all categories in the database in the format required by KiCAD.
|
||||
* The categories are flattened and sorted by their full path.
|
||||
* Categories, which contain no parts, are filtered out.
|
||||
* The result is cached for performance and invalidated on category changes.
|
||||
* @return array
|
||||
*/
|
||||
public function getCategories(): array
|
||||
{
|
||||
return $this->kicadCache->get('kicad_categories_' . $this->category_depth, function (ItemInterface $item) {
|
||||
//Invalidate the cache on category changes
|
||||
$secure_class_name = $this->tagGenerator->getElementTypeCacheTag(Category::class);
|
||||
$item->tag($secure_class_name);
|
||||
|
||||
//If the category depth is smaller than 0, create only one dummy category
|
||||
if ($this->category_depth < 0) {
|
||||
return [
|
||||
[
|
||||
'id' => '0',
|
||||
'name' => 'Part-DB',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
//Otherwise just get the categories and filter them
|
||||
|
||||
$categories = $this->nodesListBuilder->typeToNodesList(Category::class);
|
||||
$repo = $this->em->getRepository(Category::class);
|
||||
$result = [];
|
||||
foreach ($categories as $category) {
|
||||
//Skip invisible categories
|
||||
if ($category->getEdaInfo()->getVisibility() === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Skip categories with a depth greater than the configured one
|
||||
if ($category->getLevel() > $this->category_depth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Ensure that the category contains parts
|
||||
//For the last level, we need to use a recursive query, otherwise we can use a simple query
|
||||
/** @var Category $category */
|
||||
$parts_count = $category->getLevel() >= $this->category_depth ? $repo->getPartsCountRecursive($category) : $repo->getPartsCount($category);
|
||||
|
||||
if ($parts_count < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Check if the category should be visible
|
||||
if (!$this->shouldCategoryBeVisible($category)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Format the category for KiCAD
|
||||
$result[] = [
|
||||
'id' => (string)$category->getId(),
|
||||
'name' => $category->getFullPath('/'),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects containing all parts for the given category in the format required by KiCAD.
|
||||
* The result is cached for performance and invalidated on category or part changes.
|
||||
* @param Category|null $category
|
||||
* @return array
|
||||
*/
|
||||
public function getCategoryParts(?Category $category): array
|
||||
{
|
||||
return $this->kicadCache->get('kicad_category_parts_'.($category?->getID() ?? 0) . '_' . $this->category_depth,
|
||||
function (ItemInterface $item) use ($category) {
|
||||
$item->tag([
|
||||
$this->tagGenerator->getElementTypeCacheTag(Category::class),
|
||||
$this->tagGenerator->getElementTypeCacheTag(Part::class),
|
||||
//Visibility can change based on the footprint
|
||||
$this->tagGenerator->getElementTypeCacheTag(Footprint::class)
|
||||
]);
|
||||
|
||||
if ($this->category_depth >= 0) {
|
||||
//Ensure that the category is set
|
||||
if (!$category) {
|
||||
throw new NotFoundHttpException('Category must be set, if category_depth is greater than 1!');
|
||||
}
|
||||
|
||||
$category_repo = $this->em->getRepository(Category::class);
|
||||
if ($category->getLevel() >= $this->category_depth) {
|
||||
//Get all parts for the category and its children
|
||||
$parts = $category_repo->getPartsRecursive($category);
|
||||
} else {
|
||||
//Get only direct parts for the category (without children), as the category is not collapsed
|
||||
$parts = $category_repo->getParts($category);
|
||||
}
|
||||
} else {
|
||||
//Get all parts
|
||||
$parts = $this->em->getRepository(Part::class)->findAll();
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($parts as $part) {
|
||||
//If the part is invisible, then skip it
|
||||
if (!$this->shouldPartBeVisible($part)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'id' => (string)$part->getId(),
|
||||
'name' => $part->getName(),
|
||||
'description' => $part->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
});
|
||||
}
|
||||
|
||||
public function getKiCADPart(Part $part): array
|
||||
{
|
||||
$result = [
|
||||
'id' => (string)$part->getId(),
|
||||
'name' => $part->getName(),
|
||||
"symbolIdStr" => $part->getEdaInfo()->getKicadSymbol() ?? $part->getCategory()?->getEdaInfo()->getKicadSymbol() ?? "",
|
||||
"exclude_from_bom" => $this->boolToKicadBool($part->getEdaInfo()->getExcludeFromBom() ?? $part->getCategory()?->getEdaInfo()->getExcludeFromBom() ?? false),
|
||||
"exclude_from_board" => $this->boolToKicadBool($part->getEdaInfo()->getExcludeFromBoard() ?? $part->getCategory()?->getEdaInfo()->getExcludeFromBoard() ?? false),
|
||||
"exclude_from_sim" => $this->boolToKicadBool($part->getEdaInfo()->getExcludeFromSim() ?? $part->getCategory()?->getEdaInfo()->getExcludeFromSim() ?? true),
|
||||
"fields" => []
|
||||
];
|
||||
|
||||
$result["fields"]["footprint"] = $this->createField($part->getEdaInfo()->getKicadFootprint() ?? $part->getFootprint()?->getEdaInfo()->getKicadFootprint() ?? "");
|
||||
$result["fields"]["reference"] = $this->createField($part->getEdaInfo()->getReferencePrefix() ?? 'U', true);
|
||||
$result["fields"]["value"] = $this->createField($part->getEdaInfo()->getValue() ?? $part->getName(), true);
|
||||
$result["fields"]["keywords"] = $this->createField($part->getTags());
|
||||
|
||||
//Use the part info page as datasheet link. It must be an absolute URL.
|
||||
$result["fields"]["datasheet"] = $this->createField(
|
||||
$this->urlGenerator->generate(
|
||||
'part_info',
|
||||
['id' => $part->getId()],
|
||||
UrlGeneratorInterface::ABSOLUTE_URL)
|
||||
);
|
||||
|
||||
//Add basic fields
|
||||
$result["fields"]["description"] = $this->createField($part->getDescription());
|
||||
if ($part->getCategory()) {
|
||||
$result["fields"]["Category"] = $this->createField($part->getCategory()->getFullPath('/'));
|
||||
}
|
||||
if ($part->getManufacturer()) {
|
||||
$result["fields"]["Manufacturer"] = $this->createField($part->getManufacturer()->getName());
|
||||
}
|
||||
if ($part->getManufacturerProductNumber() !== "") {
|
||||
$result['fields']["MPN"] = $this->createField($part->getManufacturerProductNumber());
|
||||
}
|
||||
if ($part->getManufacturingStatus()) {
|
||||
$result["fields"]["Manufacturing Status"] = $this->createField(
|
||||
//Always use the english translation
|
||||
$this->translator->trans($part->getManufacturingStatus()->toTranslationKey(), locale: 'en')
|
||||
);
|
||||
}
|
||||
if ($part->getFootprint()) {
|
||||
$result["fields"]["Part-DB Footprint"] = $this->createField($part->getFootprint()->getName());
|
||||
}
|
||||
if ($part->getPartUnit()) {
|
||||
$unit = $part->getPartUnit()->getName();
|
||||
if ($part->getPartUnit()->getUnit() !== "") {
|
||||
$unit .= ' ('.$part->getPartUnit()->getUnit().')';
|
||||
}
|
||||
$result["fields"]["Part-DB Unit"] = $this->createField($unit);
|
||||
}
|
||||
if ($part->getMass()) {
|
||||
$result["fields"]["Mass"] = $this->createField($part->getMass() . ' g');
|
||||
}
|
||||
$result["fields"]["Part-DB ID"] = $this->createField($part->getId());
|
||||
if (!empty($part->getIpn())) {
|
||||
$result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn());
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given part should be visible for the EDA.
|
||||
* @param Category $category
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldCategoryBeVisible(Category $category): bool
|
||||
{
|
||||
$eda_info = $category->getEdaInfo();
|
||||
|
||||
//If the category visibility is explicitly set, then use it
|
||||
if ($eda_info->getVisibility() !== null) {
|
||||
return $eda_info->getVisibility();
|
||||
}
|
||||
|
||||
//try to check if the fields were set
|
||||
if ($eda_info->getKicadSymbol() !== null
|
||||
|| $eda_info->getReferencePrefix() !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check if there is any part in this category, which should be visible
|
||||
$category_repo = $this->em->getRepository(Category::class);
|
||||
if ($category->getLevel() >= $this->category_depth) {
|
||||
//Get all parts for the category and its children
|
||||
$parts = $category_repo->getPartsRecursive($category);
|
||||
} else {
|
||||
//Get only direct parts for the category (without children), as the category is not collapsed
|
||||
$parts = $category_repo->getParts($category);
|
||||
}
|
||||
|
||||
foreach ($parts as $part) {
|
||||
if ($this->shouldPartBeVisible($part)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Otherwise the category should be not visible
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given part should be visible for the EDA.
|
||||
* @param Part $part
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldPartBeVisible(Part $part): bool
|
||||
{
|
||||
$eda_info = $part->getEdaInfo();
|
||||
$category = $part->getCategory();
|
||||
|
||||
//If the user set a visibility, then use it
|
||||
if ($eda_info->getVisibility() !== null) {
|
||||
return $part->getEdaInfo()->getVisibility();
|
||||
}
|
||||
|
||||
//If the part has a category, then use the category visibility if possible
|
||||
if ($category && $category->getEdaInfo()->getVisibility() !== null) {
|
||||
return $category->getEdaInfo()->getVisibility();
|
||||
}
|
||||
|
||||
//If both are null, then we try to determine the visibility based on if fields are set
|
||||
if ($eda_info->getKicadSymbol() !== null
|
||||
|| $eda_info->getKicadFootprint() !== null
|
||||
|| $eda_info->getReferencePrefix() !== null
|
||||
|| $eda_info->getValue() !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check also if the fields are set for the category (if it exists)
|
||||
if ($category && (
|
||||
$category->getEdaInfo()->getKicadSymbol() !== null
|
||||
|| $category->getEdaInfo()->getReferencePrefix() !== null
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//And on the footprint
|
||||
if ($part->getFootprint() && $part->getFootprint()->getEdaInfo()->getKicadFootprint() !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Otherwise the part should be not visible
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a boolean value to the format required by KiCAD.
|
||||
* @param bool $value
|
||||
* @return string
|
||||
*/
|
||||
private function boolToKicadBool(bool $value): string
|
||||
{
|
||||
return $value ? 'True' : 'False';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a field array for KiCAD
|
||||
* @param string|int|float $value
|
||||
* @param bool $visible
|
||||
* @return array
|
||||
*/
|
||||
private function createField(string|int|float $value, bool $visible = false): array
|
||||
{
|
||||
return [
|
||||
'value' => (string)$value,
|
||||
'visible' => $this->boolToKicadBool($visible),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -186,7 +186,8 @@ final class DTOtoEntityConverter
|
||||
}
|
||||
|
||||
//Add other images
|
||||
foreach ($dto->images ?? [] as $image) {
|
||||
$images = $this->files_unique($dto->images ?? []);
|
||||
foreach ($images as $image) {
|
||||
//Ensure that the image is not the same as the preview image
|
||||
if ($image->url === $dto->preview_image_url) {
|
||||
continue;
|
||||
@@ -195,10 +196,10 @@ final class DTOtoEntityConverter
|
||||
$entity->addAttachment($this->convertFile($image, $image_type));
|
||||
}
|
||||
|
||||
|
||||
//Add datasheets
|
||||
$datasheet_type = $this->getDatasheetType();
|
||||
foreach ($dto->datasheets ?? [] as $datasheet) {
|
||||
$datasheets = $this->files_unique($dto->datasheets ?? []);
|
||||
foreach ($datasheets as $datasheet) {
|
||||
$entity->addAttachment($this->convertFile($datasheet, $datasheet_type));
|
||||
}
|
||||
|
||||
@@ -210,6 +211,27 @@ final class DTOtoEntityConverter
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given array of files with all duplicates removed.
|
||||
* @param FileDTO[] $files
|
||||
* @return FileDTO[]
|
||||
*/
|
||||
private function files_unique(array $files): array
|
||||
{
|
||||
$unique = [];
|
||||
//We use the URL and name as unique identifier. If two file DTO have the same URL and name, they are considered equal
|
||||
//and get filtered out, if it already exists in the array
|
||||
foreach ($files as $file) {
|
||||
//Skip already existing files, to preserve the order. The second condition ensure that we keep the version with a name over the one without a name
|
||||
if (isset($unique[$file->url]) && $unique[$file->url]->name !== null) {
|
||||
continue;
|
||||
}
|
||||
$unique[$file->url] = $file;
|
||||
}
|
||||
|
||||
return array_values($unique);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the existing entity of the given class with the given name or create it if it does not exist.
|
||||
* If the name is null, null is returned.
|
||||
|
||||
@@ -44,15 +44,20 @@ namespace App\Services\LabelSystem;
|
||||
use App\Entity\LabelSystem\LabelProfile;
|
||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||
use App\Repository\LabelProfileRepository;
|
||||
use App\Services\UserSystem\UserCacheKeyGenerator;
|
||||
use App\Services\Cache\ElementCacheTagGenerator;
|
||||
use App\Services\Cache\UserCacheKeyGenerator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
final class LabelProfileDropdownHelper
|
||||
{
|
||||
public function __construct(private readonly TagAwareCacheInterface $cache, private readonly EntityManagerInterface $entityManager, private readonly UserCacheKeyGenerator $keyGenerator)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly TagAwareCacheInterface $cache,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly UserCacheKeyGenerator $keyGenerator,
|
||||
private readonly ElementCacheTagGenerator $tagGenerator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +72,7 @@ final class LabelProfileDropdownHelper
|
||||
$type = LabelSupportedElement::from($type);
|
||||
}
|
||||
|
||||
$secure_class_name = str_replace('\\', '_', LabelProfile::class);
|
||||
$secure_class_name = $this->tagGenerator->getElementTypeCacheTag(LabelProfile::class);
|
||||
$key = 'profile_dropdown_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.'_'.$type->value;
|
||||
|
||||
/** @var LabelProfileRepository $repo */
|
||||
|
||||
@@ -88,7 +88,7 @@ class ParameterExtractor
|
||||
protected function stringToParam(string $input, string $class): ?AbstractParameter
|
||||
{
|
||||
$input = trim($input);
|
||||
$regex = '/^(.*) *(?:=|:) *(.+)/u';
|
||||
$regex = '/^(.*) *(?:=|:)(?!\/) *(.+)/u';
|
||||
|
||||
$matches = [];
|
||||
preg_match($regex, $input, $matches);
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
|
||||
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||
|
||||
/**
|
||||
* Workaround for using snake_case properties with ReflectionExtractor until this PR is merged:
|
||||
* https://github.com/symfony/symfony/pull/51697
|
||||
*/
|
||||
#[AsTaggedItem('property_info.access_extractor', priority: 0)]
|
||||
class SnakeCasePropertyAccessExtractor implements PropertyAccessExtractorInterface
|
||||
{
|
||||
|
||||
public function __construct(#[Autowire(service: 'property_info.reflection_extractor')]
|
||||
private readonly PropertyAccessExtractorInterface $reflectionExtractor)
|
||||
{
|
||||
//$this->reflectionExtractor = new ReflectionExtractor();
|
||||
}
|
||||
|
||||
public function isReadable(string $class, string $property, array $context = []): ?bool
|
||||
{
|
||||
//Null means skip this extractor
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Camelizes a given string.
|
||||
*/
|
||||
private function camelize(string $string): string
|
||||
{
|
||||
return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
|
||||
}
|
||||
|
||||
|
||||
public function isWritable(string $class, string $property, array $context = []): ?bool
|
||||
{
|
||||
//Check writeablity using a camelized property name
|
||||
$isWriteable = $this->reflectionExtractor->isWritable($class, $this->camelize($property), $context);
|
||||
//If we found a writeable property that way, return true
|
||||
if ($isWriteable === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Otherwise skip this extractor
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Trees;
|
||||
|
||||
use App\Entity\Attachments\AttachmentContainingDBElement;
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Repository\AttachmentContainingDBElementRepository;
|
||||
use App\Repository\DBElementRepository;
|
||||
use App\Repository\StructuralDBElementRepository;
|
||||
use App\Services\UserSystem\UserCacheKeyGenerator;
|
||||
use App\Services\Cache\ElementCacheTagGenerator;
|
||||
use App\Services\Cache\UserCacheKeyGenerator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
@@ -40,8 +39,12 @@ use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
*/
|
||||
class NodesListBuilder
|
||||
{
|
||||
public function __construct(protected EntityManagerInterface $em, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator)
|
||||
{
|
||||
public function __construct(
|
||||
protected EntityManagerInterface $em,
|
||||
protected TagAwareCacheInterface $cache,
|
||||
protected UserCacheKeyGenerator $keyGenerator,
|
||||
protected ElementCacheTagGenerator $tagGenerator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,9 +53,9 @@ class NodesListBuilder
|
||||
*
|
||||
* @template T of AbstractDBElement
|
||||
*
|
||||
* @param string $class_name the class name of the entity you want to retrieve
|
||||
* @param string $class_name the class name of the entity you want to retrieve
|
||||
* @phpstan-param class-string<T> $class_name
|
||||
* @param AbstractStructuralDBElement|null $parent This entity will be used as root element. Set to null, to use global root
|
||||
* @param AbstractStructuralDBElement|null $parent This entity will be used as root element. Set to null, to use global root
|
||||
*
|
||||
* @return AbstractDBElement[] a flattened list containing the tree elements
|
||||
* @phpstan-return list<T>
|
||||
@@ -86,7 +89,7 @@ class NodesListBuilder
|
||||
{
|
||||
$parent_id = $parent instanceof AbstractStructuralDBElement ? $parent->getID() : '0';
|
||||
// Backslashes are not allowed in cache keys
|
||||
$secure_class_name = str_replace('\\', '_', $class_name);
|
||||
$secure_class_name = $this->tagGenerator->getElementTypeCacheTag($class_name);
|
||||
$key = 'list_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.$parent_id;
|
||||
|
||||
return $this->cache->get($key, function (ItemInterface $item) use ($class_name, $parent, $secure_class_name) {
|
||||
@@ -105,7 +108,7 @@ class NodesListBuilder
|
||||
* The value is cached for performance reasons.
|
||||
*
|
||||
* @template T of AbstractStructuralDBElement
|
||||
* @param T $element
|
||||
* @param T $element
|
||||
* @return AbstractStructuralDBElement[]
|
||||
*
|
||||
* @phpstan-return list<T>
|
||||
|
||||
@@ -22,9 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Trees;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\LabelSystem\LabelProfile;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
@@ -34,10 +32,12 @@ use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\UserSystem\Group;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Helpers\Trees\TreeViewNode;
|
||||
use App\Services\UserSystem\UserCacheKeyGenerator;
|
||||
use App\Services\Cache\UserCacheKeyGenerator;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||
|
||||
@@ -25,17 +25,18 @@ namespace App\Services\Trees;
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Helpers\Trees\TreeViewNode;
|
||||
use App\Helpers\Trees\TreeViewNodeIterator;
|
||||
use App\Repository\StructuralDBElementRepository;
|
||||
use App\Services\Cache\ElementCacheTagGenerator;
|
||||
use App\Services\Cache\UserCacheKeyGenerator;
|
||||
use App\Services\EntityURLGenerator;
|
||||
use App\Services\UserSystem\UserCacheKeyGenerator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
use RecursiveIteratorIterator;
|
||||
@@ -51,25 +52,37 @@ use function count;
|
||||
*/
|
||||
class TreeViewGenerator
|
||||
{
|
||||
public function __construct(protected EntityURLGenerator $urlGenerator, protected EntityManagerInterface $em, protected TagAwareCacheInterface $cache,
|
||||
protected UserCacheKeyGenerator $keyGenerator, protected TranslatorInterface $translator, private UrlGeneratorInterface $router,
|
||||
protected bool $rootNodeExpandedByDefault, protected bool $rootNodeEnabled)
|
||||
{
|
||||
public function __construct(
|
||||
protected EntityURLGenerator $urlGenerator,
|
||||
protected EntityManagerInterface $em,
|
||||
protected TagAwareCacheInterface $cache,
|
||||
protected ElementCacheTagGenerator $tagGenerator,
|
||||
protected UserCacheKeyGenerator $keyGenerator,
|
||||
protected TranslatorInterface $translator,
|
||||
private UrlGeneratorInterface $router,
|
||||
protected bool $rootNodeExpandedByDefault,
|
||||
protected bool $rootNodeEnabled,
|
||||
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a TreeView list for the entities of the given class.
|
||||
*
|
||||
* @param string $class The class for which the treeView should be generated
|
||||
* @param AbstractStructuralDBElement|null $parent The root nodes in the tree should have this element as parent (use null, if you want to get all entities)
|
||||
* @param string $mode The link type that will be generated for the hyperlink section of each node (see EntityURLGenerator for possible values).
|
||||
* @param string $class The class for which the treeView should be generated
|
||||
* @param AbstractStructuralDBElement|null $parent The root nodes in the tree should have this element as parent (use null, if you want to get all entities)
|
||||
* @param string $mode The link type that will be generated for the hyperlink section of each node (see EntityURLGenerator for possible values).
|
||||
* Set to empty string, to disable href field.
|
||||
* @param AbstractDBElement|null $selectedElement The element that should be selected. If set to null, no element will be selected.
|
||||
* @param AbstractDBElement|null $selectedElement The element that should be selected. If set to null, no element will be selected.
|
||||
*
|
||||
* @return TreeViewNode[] an array of TreeViewNode[] elements of the root elements
|
||||
*/
|
||||
public function getTreeView(string $class, ?AbstractStructuralDBElement $parent = null, string $mode = 'list_parts', ?AbstractDBElement $selectedElement = null): array
|
||||
{
|
||||
public function getTreeView(
|
||||
string $class,
|
||||
?AbstractStructuralDBElement $parent = null,
|
||||
string $mode = 'list_parts',
|
||||
?AbstractDBElement $selectedElement = null
|
||||
): array {
|
||||
$head = [];
|
||||
|
||||
$href_type = $mode;
|
||||
@@ -110,11 +123,11 @@ class TreeViewGenerator
|
||||
}
|
||||
|
||||
if ($item->getNodes() !== null && $item->getNodes() !== []) {
|
||||
$item->addTag((string) count($item->getNodes()));
|
||||
$item->addTag((string)count($item->getNodes()));
|
||||
}
|
||||
|
||||
if ($href_type !== '' && null !== $item->getId()) {
|
||||
$entity = $this->em->getPartialReference($class, $item->getId());
|
||||
$entity = $this->em->find($class, $item->getId());
|
||||
$item->setHref($this->urlGenerator->getURL($entity, $href_type));
|
||||
}
|
||||
|
||||
@@ -155,12 +168,12 @@ class TreeViewGenerator
|
||||
{
|
||||
$icon = "fa-fw fa-treeview fa-solid ";
|
||||
return match ($class) {
|
||||
Category::class => $icon . 'fa-tags',
|
||||
StorageLocation::class => $icon . 'fa-cube',
|
||||
Footprint::class => $icon . 'fa-microchip',
|
||||
Manufacturer::class => $icon . 'fa-industry',
|
||||
Supplier::class => $icon . 'fa-truck',
|
||||
Project::class => $icon . 'fa-archive',
|
||||
Category::class => $icon.'fa-tags',
|
||||
StorageLocation::class => $icon.'fa-cube',
|
||||
Footprint::class => $icon.'fa-microchip',
|
||||
Manufacturer::class => $icon.'fa-industry',
|
||||
Supplier::class => $icon.'fa-truck',
|
||||
Project::class => $icon.'fa-archive',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
@@ -170,8 +183,8 @@ class TreeViewGenerator
|
||||
* Gets a tree of TreeViewNode elements. The root elements has $parent as parent.
|
||||
* The treeview is generic, that means the href are null and ID values are set.
|
||||
*
|
||||
* @param string $class The class for which the tree should be generated
|
||||
* @param AbstractStructuralDBElement|null $parent the parent the root elements should have
|
||||
* @param string $class The class for which the tree should be generated
|
||||
* @param AbstractStructuralDBElement|null $parent the parent the root elements should have
|
||||
*
|
||||
* @return TreeViewNode[]
|
||||
*/
|
||||
@@ -192,13 +205,12 @@ class TreeViewGenerator
|
||||
return $repo->getGenericNodeTree($parent);
|
||||
}
|
||||
|
||||
$secure_class_name = str_replace('\\', '_', $class);
|
||||
$secure_class_name = $this->tagGenerator->getElementTypeCacheTag($class);
|
||||
$key = 'treeview_'.$this->keyGenerator->generateKey().'_'.$secure_class_name;
|
||||
|
||||
return $this->cache->get($key, function (ItemInterface $item) use ($repo, $parent, $secure_class_name) {
|
||||
// Invalidate when groups, an element with the class or the user changes
|
||||
$item->tag(['groups', 'tree_treeview', $this->keyGenerator->generateKey(), $secure_class_name]);
|
||||
|
||||
return $repo->getGenericNodeTree($parent);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ declare(strict_types=1);
|
||||
* 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\Services\UserSystem;
|
||||
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Attachments\UserAttachment;
|
||||
@@ -30,16 +30,23 @@ use App\Entity\UserSystem\User;
|
||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||
use App\Services\Attachments\AttachmentURLGenerator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Liip\ImagineBundle\Service\FilterService;
|
||||
use Symfony\Component\Asset\Packages;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class UserAvatarHelper
|
||||
{
|
||||
public const IMG_DEFAULT_AVATAR_PATH = '/img/default_avatar.png';
|
||||
/**
|
||||
* Path to the default avatar image (must not start with a slash, or the asset package will not work)
|
||||
*/
|
||||
public const IMG_DEFAULT_AVATAR_PATH = 'img/default_avatar.svg';
|
||||
|
||||
public function __construct(private readonly bool $use_gravatar, private readonly Packages $packages, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly FilterService $filterService, private readonly EntityManagerInterface $entityManager, private readonly AttachmentSubmitHandler $submitHandler)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly bool $use_gravatar,
|
||||
private readonly Packages $packages,
|
||||
private readonly AttachmentURLGenerator $attachmentURLGenerator,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly AttachmentSubmitHandler $submitHandler
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -78,13 +85,8 @@ class UserAvatarHelper
|
||||
return $this->getGravatar($user, 50); //50px wide picture
|
||||
}
|
||||
|
||||
try {
|
||||
//Otherwise we can serve the relative path via Asset component
|
||||
return $this->filterService->getUrlOfFilteredImage(self::IMG_DEFAULT_AVATAR_PATH, 'thumbnail_xs');
|
||||
} catch (RuntimeException) {
|
||||
//If the filter fails, we can not serve the thumbnail and fall back to the original image and log an warning
|
||||
return $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH);
|
||||
}
|
||||
//Otherwise serve the default image (its an SVG, so we dont need to thumbnail it)
|
||||
return $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH);
|
||||
}
|
||||
|
||||
public function getAvatarMdURL(User $user): string
|
||||
@@ -100,20 +102,15 @@ class UserAvatarHelper
|
||||
return $this->getGravatar($user, 150);
|
||||
}
|
||||
|
||||
try {
|
||||
//Otherwise we can serve the relative path via Asset component
|
||||
return $this->filterService->getUrlOfFilteredImage(self::IMG_DEFAULT_AVATAR_PATH, 'thumbnail_xs');
|
||||
} catch (RuntimeException) {
|
||||
//If the filter fails, we can not serve the thumbnail and fall back to the original image and log an warning
|
||||
return $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH);
|
||||
}
|
||||
//Otherwise serve the default image (its an SVG, so we dont need to thumbnail it)
|
||||
return $this->packages->getUrl(self::IMG_DEFAULT_AVATAR_PATH);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get either a Gravatar URL or complete image tag for a specified email address.
|
||||
*
|
||||
* @param User $user The user for which the gravator should be generated
|
||||
* @param User $user The user for which the gravator should be generated
|
||||
* @param int $s Size in pixels, defaults to 80px [ 1 - 2048 ]
|
||||
* @param string $d Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
|
||||
* @param string $r Maximum rating (inclusive) [ g | pg | r | x ]
|
||||
@@ -131,7 +128,7 @@ class UserAvatarHelper
|
||||
$url = 'https://www.gravatar.com/avatar/';
|
||||
$url .= md5(strtolower(trim($email)));
|
||||
|
||||
return $url . "?s=$s&d=$d&r=$r";
|
||||
return $url."?s=$s&d=$d&r=$r";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
64
symfony.lock
64
symfony.lock
@@ -6,12 +6,12 @@
|
||||
"version": "v1.6.1"
|
||||
},
|
||||
"api-platform/core": {
|
||||
"version": "3.1",
|
||||
"version": "3.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.1",
|
||||
"ref": "adf0c75f4bed8b0043a6680376323404953578c5"
|
||||
"version": "3.2",
|
||||
"ref": "696d44adc3c0d4f5d25a2f1c4f3700dd8a5c6db9"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/api_platform.yaml",
|
||||
@@ -87,12 +87,12 @@
|
||||
"version": "v0.5.3"
|
||||
},
|
||||
"doctrine/doctrine-bundle": {
|
||||
"version": "2.10",
|
||||
"version": "2.11",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.10",
|
||||
"ref": "e025a6cb69b195970543820b2f18ad21724473fa"
|
||||
"ref": "0db4b12b5df45f5122213b4ecd18733ab7fa7d53"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/doctrine.yaml",
|
||||
@@ -363,10 +363,10 @@
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "f10b8054de1a94a3b9e8806a6453fd5c98491c44"
|
||||
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
||||
},
|
||||
"files": [
|
||||
"./phpstan.dist.neon"
|
||||
"phpstan.dist.neon"
|
||||
]
|
||||
},
|
||||
"phpstan/phpstan-doctrine": {
|
||||
@@ -559,12 +559,12 @@
|
||||
"version": "v4.2.3"
|
||||
},
|
||||
"symfony/framework-bundle": {
|
||||
"version": "6.2",
|
||||
"version": "6.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.2",
|
||||
"ref": "af47254c5e4cd543e6af3e4508298ffebbdaddd3"
|
||||
"version": "6.4",
|
||||
"ref": "a91c965766ad3ff2ae15981801643330eb42b6a5"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/cache.yaml",
|
||||
@@ -638,12 +638,12 @@
|
||||
"version": "v5.3.8"
|
||||
},
|
||||
"symfony/phpunit-bridge": {
|
||||
"version": "6.3",
|
||||
"version": "6.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "01dfaa98c58f7a7b5a9b30e6edb7074af7ed9819"
|
||||
"ref": "1f5830c331065b6e4c9d5fa2105e322d29fcd573"
|
||||
},
|
||||
"files": [
|
||||
".env.test",
|
||||
@@ -705,15 +705,16 @@
|
||||
"version": "v5.3.4"
|
||||
},
|
||||
"symfony/security-bundle": {
|
||||
"version": "6.2",
|
||||
"version": "6.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.0",
|
||||
"ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48"
|
||||
"version": "6.4",
|
||||
"ref": "2ae08430db28c8eb4476605894296c82a642028f"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/security.yaml"
|
||||
"config/packages/security.yaml",
|
||||
"config/routes/security.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/security-core": {
|
||||
@@ -752,16 +753,16 @@
|
||||
"version": "v5.1.0"
|
||||
},
|
||||
"symfony/translation": {
|
||||
"version": "5.3",
|
||||
"version": "6.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "master",
|
||||
"version": "5.3",
|
||||
"ref": "da64f5a2b6d96f5dc24914517c0350a5f91dee43"
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "64fe617084223633e1dedf9112935d8c95410d3e"
|
||||
},
|
||||
"files": [
|
||||
"./config/packages/translation.yaml",
|
||||
"./translations/.gitignore"
|
||||
"config/packages/translation.yaml",
|
||||
"translations/.gitignore"
|
||||
]
|
||||
},
|
||||
"symfony/translation-contracts": {
|
||||
@@ -848,18 +849,15 @@
|
||||
]
|
||||
},
|
||||
"symfony/webpack-encore-bundle": {
|
||||
"version": "1.17",
|
||||
"version": "2.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.10",
|
||||
"ref": "eff2e505d4557c967b6710fe06bd947ba555cae5"
|
||||
"version": "2.0",
|
||||
"ref": "082d754b3bd54b3fc669f278f1eea955cfd23cf5"
|
||||
},
|
||||
"files": [
|
||||
"assets/app.js",
|
||||
"assets/bootstrap.js",
|
||||
"assets/controllers.json",
|
||||
"assets/controllers/hello_controller.js",
|
||||
"assets/styles/app.css",
|
||||
"config/packages/webpack_encore.yaml",
|
||||
"package.json",
|
||||
@@ -909,13 +907,17 @@
|
||||
"version": "3.5.1"
|
||||
},
|
||||
"web-auth/webauthn-symfony-bundle": {
|
||||
"version": "3.3",
|
||||
"version": "4.7",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "3.0",
|
||||
"ref": "9926090a80c2cceeffe96e6c3312b397ea55d4a7"
|
||||
}
|
||||
"ref": "a5dff33bd46575bea263af94069650af7742dcb6"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/webauthn.yaml",
|
||||
"config/routes/webauthn_routes.yaml"
|
||||
]
|
||||
},
|
||||
"webmozart/assert": {
|
||||
"version": "1.4.0"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
{% block additional_controls %}
|
||||
{{ form_row(form.filetype_filter) }}
|
||||
{{ form_row(form.alternative_names) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block edit_title %}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
{% block additional_pills %}
|
||||
<li class="nav-item"><a data-bs-toggle="tab" class="nav-link link-anchor" href="#home_options">{% trans %}admin.options{% endtrans %}</a></li>
|
||||
<li class="nav-item"><a data-bs-toggle="tab" class="nav-link link-anchor" href="#home_advanced">{% trans %}admin.advanced{% endtrans %}</a></li>
|
||||
<li class="nav-item"><a data-bs-toggle="tab" class="nav-link link-anchor" href="#eda">{% trans %}part.edit.tab.eda{% endtrans %}</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block edit_title %}
|
||||
@@ -34,4 +35,29 @@
|
||||
{{ form_row(form.default_description) }}
|
||||
{{ form_row(form.default_comment) }}
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="eda">
|
||||
{{ form_row(form.eda_info.reference_prefix) }}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
{{ form_row(form.eda_info.visibility) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
{{ form_widget(form.eda_info.exclude_from_bom) }}
|
||||
{{ form_widget(form.eda_info.exclude_from_board) }}
|
||||
{{ form_widget(form.eda_info.exclude_from_sim) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
<h6>{% trans %}eda_info.kicad_section.title{% endtrans %}:</h6>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_row(form.eda_info.kicad_symbol) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -19,4 +19,19 @@
|
||||
|
||||
{% block additional_controls %}
|
||||
{{ form_row(form.alternative_names) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block additional_pills %}
|
||||
<li class="nav-item"><a data-bs-toggle="tab" class="nav-link link-anchor" href="#eda">{% trans %}part.edit.tab.eda{% endtrans %}</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block additional_panes %}
|
||||
<div class="tab-pane" id="eda">
|
||||
<div class="row">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
<h6>{% trans %}eda_info.kicad_section.title{% endtrans %}:</h6>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_row(form.eda_info.kicad_footprint) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,5 @@
|
||||
{% macro datatable(datatable, controller = 'elements/datatables/datatables', state_save_tag = null) %}
|
||||
<div {{ stimulus_controller(controller, {"stateSaveTag": state_save_tag}) }} data-dt-settings='{{ datatable_settings(datatable)|escape('html_attr') }}' data-dt-url="{{ app.request.requestUri }}">
|
||||
<div {{ stimulus_controller(controller, {"stateSaveTag": state_save_tag}) }} data-dt-settings='{{ datatable_settings(datatable)|escape('html_attr') }}' data-dt-url="{{ app.request.baseUrl ~ app.request.requestUri }}">
|
||||
<div {{ stimulus_target(controller, 'dt') }}>
|
||||
<div class="card-body">
|
||||
<div class="card">
|
||||
@@ -25,7 +25,7 @@
|
||||
data-delete-message="{% trans %}part_list.action.delete-message{% endtrans %}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('table_action') }}">
|
||||
|
||||
<input type="hidden" name="redirect_back" value="{{ app.request.requestUri }}">
|
||||
<input type="hidden" name="redirect_back" value="{{ app.request.baseUrl ~ app.request.requestUri }}">
|
||||
|
||||
<input type="hidden" name="ids" {{ stimulus_target('elements/datatables/parts', 'selectIDs') }} value="">
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user