mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-21 17:22:28 +01:00
Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9313f870bc | ||
|
|
9e72e88930 | ||
|
|
dcb64bf0a6 | ||
|
|
5d07070558 | ||
|
|
8c6ba9175b | ||
|
|
ccaa2c48e2 | ||
|
|
5d38bf2e66 | ||
|
|
15331da389 | ||
|
|
477171abac | ||
|
|
dc85e4f4a4 | ||
|
|
ac402a6697 | ||
|
|
f86d35f8d1 | ||
|
|
7d6c04e3cf | ||
|
|
5c059ce9fe | ||
|
|
575bffe0bf | ||
|
|
d0b70253fa | ||
|
|
5f04b2649f | ||
|
|
f0099859bb | ||
|
|
906b654afa | ||
|
|
14740fad58 | ||
|
|
e97a149474 | ||
|
|
c1d1270d59 | ||
|
|
e550918d7c | ||
|
|
f3449babc1 | ||
|
|
e444388517 | ||
|
|
bd2559c37b | ||
|
|
7abf44e893 | ||
|
|
0b94a31d15 | ||
|
|
989e09b610 | ||
|
|
7e69e80290 | ||
|
|
a3177dcfaf | ||
|
|
10e54d7a2d | ||
|
|
ed514a01bb | ||
|
|
47fce4e914 | ||
|
|
54276e19e9 | ||
|
|
193650efd4 | ||
|
|
b7aae7d87b | ||
|
|
2c799d894b | ||
|
|
5745fc1046 | ||
|
|
80085abe16 | ||
|
|
fe5dd065ed | ||
|
|
945fd988b3 | ||
|
|
3bbff0aecf | ||
|
|
9188331c1e | ||
|
|
be5663c468 | ||
|
|
9ac8098f15 | ||
|
|
bd5ee837f4 | ||
|
|
4be6cb2459 | ||
|
|
c466cb68b9 | ||
|
|
820be46ed3 | ||
|
|
4437f206af | ||
|
|
a1f4b35749 | ||
|
|
b38f49a90e | ||
|
|
5d318b2693 | ||
|
|
c7b9f9e50a | ||
|
|
256d628543 | ||
|
|
508641d1e8 | ||
|
|
61e2dde400 | ||
|
|
85ae862381 | ||
|
|
7a9b7c87a4 | ||
|
|
8f033910ce | ||
|
|
38b5e95842 | ||
|
|
2c67586873 | ||
|
|
b99e6c9a21 | ||
|
|
49944cda87 | ||
|
|
3b36b2a4dc | ||
|
|
1dfcffe70d | ||
|
|
a9b3dcd2c2 | ||
|
|
31f9145d3f | ||
|
|
ba04b94964 | ||
|
|
4ecf99c17e | ||
|
|
80389ff236 | ||
|
|
9e80b23726 | ||
|
|
494a1c49f9 | ||
|
|
4a77064826 | ||
|
|
ce90f10243 | ||
|
|
426aa4e41d | ||
|
|
bdc953cab0 | ||
|
|
15725a9f38 | ||
|
|
cc7d290feb | ||
|
|
40a2a46a5e | ||
|
|
2e160b0b0b | ||
|
|
5aaba102a7 | ||
|
|
52e459ec60 | ||
|
|
4a30819ea5 | ||
|
|
27969a1f65 | ||
|
|
c68b13b075 | ||
|
|
1446aab451 | ||
|
|
86f77fde1a | ||
|
|
02134dc959 | ||
|
|
c27b02512f | ||
|
|
222e76ce47 | ||
|
|
0efb32c891 | ||
|
|
e808964913 | ||
|
|
9ed1e896cb | ||
|
|
49e521404a | ||
|
|
2ae34b856a | ||
|
|
6230ad971b | ||
|
|
20caad24ed | ||
|
|
eabdd3b11f | ||
|
|
8fad743e85 | ||
|
|
f9fd015ecb | ||
|
|
27de5ae387 | ||
|
|
4f43f10672 | ||
|
|
fb45ef432e | ||
|
|
d0a8e33bf2 | ||
|
|
5a19024bec | ||
|
|
e0635f7ead | ||
|
|
6fa5efc4ca | ||
|
|
7394a23a83 | ||
|
|
bbe4de996a | ||
|
|
7030e752fc | ||
|
|
d845f8b7e3 | ||
|
|
8a18951562 | ||
|
|
cb9433902c | ||
|
|
472e1ce0a3 | ||
|
|
5e85c52a57 | ||
|
|
6a06a24296 | ||
|
|
99f04d71af | ||
|
|
d1b8a36b93 | ||
|
|
f20da0f049 | ||
|
|
5d3ab01176 | ||
|
|
83cd91f1d1 | ||
|
|
5f39d8e594 | ||
|
|
6ff60e556e | ||
|
|
5b7f44f4ea | ||
|
|
dc906bfb0f | ||
|
|
b70c9d4f00 | ||
|
|
03e0584279 | ||
|
|
960ee342e4 | ||
|
|
f5a5114999 | ||
|
|
e6d9237bda | ||
|
|
c831d57614 | ||
|
|
c5904303e3 | ||
|
|
586a57c2c9 | ||
|
|
91fb861fd3 | ||
|
|
b13655e951 | ||
|
|
e064ee4263 | ||
|
|
60f926924b | ||
|
|
97c3b9002a | ||
|
|
78ec0f1ea3 | ||
|
|
c0b74d83a5 | ||
|
|
9dd172df98 | ||
|
|
d3659858eb | ||
|
|
b637f5c3dd | ||
|
|
05ab3c3b7b | ||
|
|
f9d5a9a3b5 | ||
|
|
82aec6f1ee | ||
|
|
c39a9a4da7 | ||
|
|
9d1cd0477a | ||
|
|
1e998fccbb | ||
|
|
2fcd48d4f2 | ||
|
|
4e79bb120a | ||
|
|
2d85734703 | ||
|
|
ccb0ac63e1 |
@@ -39,6 +39,9 @@ if [ -d /var/www/html/var/db ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start PHP-FPM
|
||||
service php8.1-fpm start
|
||||
|
||||
# first arg is `-f` or `--some-option` (taken from https://github.com/docker-library/php/blob/master/8.2/bullseye/apache/docker-php-entrypoint)
|
||||
if [ "${1#-}" != "$1" ]; then
|
||||
set -- apache2-foreground "$@"
|
||||
|
||||
@@ -27,11 +27,12 @@
|
||||
# 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 DATABASE_URL
|
||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR
|
||||
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI
|
||||
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
|
||||
PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP
|
||||
PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER
|
||||
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAMLP_SP_PRIVATE_KEY
|
||||
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
|
||||
42
.env
42
.env
@@ -31,6 +31,13 @@ INSTANCE_NAME="Part-DB"
|
||||
ALLOW_ATTACHMENT_DOWNLOADS=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
|
||||
# This must end with a slash!
|
||||
DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||
|
||||
###################################################################################
|
||||
# Email settings
|
||||
@@ -69,6 +76,41 @@ ERROR_PAGE_ADMIN_EMAIL=''
|
||||
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
|
||||
ERROR_PAGE_SHOW_HELP=1
|
||||
|
||||
###################################################################################
|
||||
# SAML Single sign on-settings
|
||||
###################################################################################
|
||||
# Set this to 1 to enable SAML single sign on
|
||||
SAML_ENABLED=0
|
||||
|
||||
# A JSON encoded array of role mappings in the form { "saml_role": PARTDB_GROUP_ID, "*": PARTDB_GROUP_ID }
|
||||
# The first match is used, so the order is important! Put the group mapping with the most privileges first.
|
||||
# Please not to only use single quotes to enclose the JSON string
|
||||
SAML_ROLE_MAPPING='{}'
|
||||
# A mapping could look like the following
|
||||
#SAML_ROLE_MAPPING='{ "*": 2, "admin": 1, "editor": 3}'
|
||||
|
||||
# When this is set to 1, the group of SAML users will be updated everytime they login based on their SAML roles
|
||||
SAML_UPDATE_GROUP_ON_LOGIN=1
|
||||
|
||||
# The entity ID of your SAML IDP (e.g. the realm name of your Keycloak server)
|
||||
SAML_IDP_ENTITY_ID="https://idp.changeme.invalid/realms/master"
|
||||
# The URL of your SAML IDP SingleSignOnService (e.g. the endpoint of your Keycloak server)
|
||||
SAML_IDP_SINGLE_SIGN_ON_SERVICE="https://idp.changeme.invalid/realms/master/protocol/saml"
|
||||
# The URL of your SAML IDP SingleLogoutService (e.g. the endpoint of your Keycloak server)
|
||||
SAML_IDP_SINGLE_LOGOUT_SERVICE="https://idp.changeme.invalid/realms/master/protocol/saml"
|
||||
# The public certificate of the SAML IDP (e.g. the certificate of your Keycloak server)
|
||||
SAML_IDP_X509_CERT="MIIC..."
|
||||
|
||||
# The entity of your SAML SP, must match the SP entityID configured in your SAML IDP (e.g. Keycloak).
|
||||
# This should be a the domain name of your Part-DB installation, followed by "/sp"
|
||||
SAML_SP_ENTITY_ID="https://partdb.changeme.invalid/sp"
|
||||
|
||||
# The public certificate of the SAML SP
|
||||
SAML_SP_X509_CERT="MIIC..."
|
||||
# The private key of the SAML SP
|
||||
SAMLP_SP_PRIVATE_KEY="MIIE..."
|
||||
|
||||
|
||||
######################################################################################
|
||||
# Other settings
|
||||
######################################################################################
|
||||
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -51,4 +51,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
3
.github/workflows/docker_build.yml
vendored
3
.github/workflows/docker_build.yml
vendored
@@ -10,9 +10,6 @@ on:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
- 'v*.*.*-**'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
|
||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
run: ./bin/console lint:xliff translations
|
||||
|
||||
- name: Check dependencies for security
|
||||
uses: symfonycorp/security-checker-action@v3
|
||||
uses: symfonycorp/security-checker-action@v5
|
||||
|
||||
- name: Check doctrine mapping
|
||||
run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
- uses: actions/cache@v1
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -9,7 +9,7 @@ RUN apt-get update && apt-get -y install apt-transport-https lsb-release ca-cert
|
||||
&& curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg \
|
||||
&& sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' \
|
||||
&& apt-get update && apt-get upgrade -y \
|
||||
&& apt-get install -y apache2 php8.1 libapache2-mod-php8.1 php8.1-opcache php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath php8.1-intl php8.1-zip php8.1-xsl php8.1-sqlite3 php8.1-mysql gpg \
|
||||
&& apt-get install -y apache2 php8.1 php8.1-fpm php8.1-opcache php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath php8.1-intl php8.1-zip php8.1-xsl php8.1-sqlite3 php8.1-mysql gpg \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
ENV APACHE_CONFDIR /etc/apache2
|
||||
@@ -34,10 +34,11 @@ RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"
|
||||
ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
|
||||
ln -sfT /dev/stderr /var/log/php8.1-fpm.log; \
|
||||
chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR";
|
||||
|
||||
# Enable mpm_prefork
|
||||
RUN a2dismod mpm_event && a2enmod mpm_prefork
|
||||
# Enable php-fpm
|
||||
RUN a2enmod proxy_fcgi setenvif && a2enconf php8.1-fpm
|
||||
|
||||
# PHP files should be handled by PHP, and should be preferred over any other file type
|
||||
RUN { \
|
||||
@@ -64,14 +65,16 @@ RUN \
|
||||
# Configure Realpath cache for performance
|
||||
echo 'realpath_cache_size=4096K'; \
|
||||
echo 'realpath_cache_ttl=600'; \
|
||||
} > /etc/php/8.1/apache2/conf.d/symfony-recommended.ini
|
||||
} > /etc/php/8.1/fpm/conf.d/symfony-recommended.ini
|
||||
|
||||
# Increase upload limit
|
||||
# Increase upload limit and enable preloading
|
||||
RUN \
|
||||
{ \
|
||||
echo 'upload_max_filesize=256M'; \
|
||||
echo 'post_max_size=300M'; \
|
||||
} > /etc/php/8.1/apache2/conf.d/partdb.ini
|
||||
echo 'opcache.preload_user=www-data'; \
|
||||
echo 'opcache.preload=/var/www/html/config/preload.php'; \
|
||||
} > /etc/php/8.1/fpm/conf.d/partdb.ini
|
||||
|
||||
# Install node and yarn
|
||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||
|
||||
@@ -41,7 +41,8 @@ and multiple store locations and price information. Parts can be grouped using t
|
||||
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped.
|
||||
* Import/Export system (partial working)
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* Import/Export system for parts and datastructure. BOM import for projects from KiCAD is supported.
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface.
|
||||
|
||||
@@ -9,4 +9,4 @@ fixed before the next release. However, if you find a security vulnerability in
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a security vulnerability, contact the maintainer directly (Email: security@part-db.de).
|
||||
If you find a security vulnerability, report a vulnerability in the [security section of GitHub](https://github.com/Part-DB/Part-DB-server/security/advisories) or contact the maintainer directly (Email: security@part-db.de)
|
||||
|
||||
@@ -27,6 +27,7 @@ export default class extends Controller {
|
||||
deleteMessage: String,
|
||||
prototype: String,
|
||||
rowsToDelete: Number, //How many rows (including the current one) shall be deleted after the current row
|
||||
fieldPlaceholder: String
|
||||
}
|
||||
|
||||
static targets = ["target"];
|
||||
@@ -65,8 +66,11 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
|
||||
const regexString = this.fieldPlaceholderValue || "__name__";
|
||||
const regex = new RegExp(regexString, "g");
|
||||
|
||||
//Apply the index to prototype to create our element to insert
|
||||
const newElementStr = this.htmlDecode(prototype.replace(/__name__/g, this.generateUID()));
|
||||
const newElementStr = this.htmlDecode(prototype.replace(regex, this.generateUID()));
|
||||
|
||||
|
||||
//Insert new html after the last child element
|
||||
|
||||
@@ -66,7 +66,14 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
stateLoadCallback(settings) {
|
||||
return JSON.parse( localStorage.getItem(this.getStateSaveKey()) );
|
||||
const data = JSON.parse( localStorage.getItem(this.getStateSaveKey()) );
|
||||
|
||||
if (data) {
|
||||
//Do not save the start value (current page), as we want to always start at the first page on a page reload
|
||||
data.start = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
connect() {
|
||||
@@ -137,6 +144,27 @@ export default class extends Controller {
|
||||
dt.fixedHeader.headerOffset($("#navbar").outerHeight());
|
||||
});
|
||||
|
||||
//Register event handler to selectAllRows checkbox if available
|
||||
if (this.isSelectable()) {
|
||||
promise.then((dt) => {
|
||||
const selectAllCheckbox = this.element.querySelector('thead th.select-checkbox');
|
||||
selectAllCheckbox.addEventListener('click', () => {
|
||||
if(selectAllCheckbox.parentElement.classList.contains('selected')) {
|
||||
dt.rows().deselect();
|
||||
selectAllCheckbox.parentElement.classList.remove('selected');
|
||||
} else {
|
||||
dt.rows().select();
|
||||
selectAllCheckbox.parentElement.classList.add('selected');
|
||||
}
|
||||
});
|
||||
|
||||
//When any row is deselected, also deselect the selectAll checkbox
|
||||
dt.on('deselect.dt', () => {
|
||||
selectAllCheckbox.parentElement.classList.remove('selected');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//Allow to further configure the datatable
|
||||
promise.then(this._afterLoaded.bind(this));
|
||||
|
||||
@@ -175,4 +203,4 @@ export default class extends Controller {
|
||||
return this.element.dataset.select ?? false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,13 @@ export default class extends DatatablesController {
|
||||
//Hide the select element (the tomselect button is the sibling of the select element)
|
||||
select_target.nextElementSibling.classList.add('d-none');
|
||||
}
|
||||
|
||||
//If the selected option has a data-turbo attribute, set it to the form
|
||||
if (selected_option.dataset.turbo) {
|
||||
this.element.dataset.turbo = selected_option.dataset.turbo;
|
||||
} else {
|
||||
this.element.dataset.turbo = true;
|
||||
}
|
||||
}
|
||||
|
||||
confirmDeletionAtSubmit(event) {
|
||||
|
||||
@@ -23,10 +23,15 @@
|
||||
|
||||
/** If darkmode is enabled revert the blening for images and videos, as these should be shown not inverted */
|
||||
.darkmode--activated img,
|
||||
.darkmode--activated video {
|
||||
.darkmode--activated video,
|
||||
.darkmode--activated object {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.darkmode--activated .hoverpic:hover {
|
||||
background: black;
|
||||
}
|
||||
|
||||
.tools-ic-logos img {
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
@@ -46,4 +46,19 @@
|
||||
height: 1.5em;
|
||||
max-width: 2.0em;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.part-table-image {
|
||||
max-height: 40px;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.part-info-image {
|
||||
max-height: 350px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.object-fit-cover {
|
||||
object-fit: cover;
|
||||
}
|
||||
@@ -89,7 +89,8 @@ th.select-checkbox {
|
||||
}
|
||||
|
||||
/** Fix datatables select-checkbox position */
|
||||
table.dataTable tr.selected td.select-checkbox:after, table.dataTable tr.selected th.select-checkbox:after {
|
||||
table.dataTable tr.selected td.select-checkbox:after
|
||||
{
|
||||
margin-top: -28px !important;
|
||||
}
|
||||
|
||||
@@ -102,4 +103,43 @@ Classes for Datatables export
|
||||
#export-messageTop,
|
||||
.export-helper{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
* Styling for the select all checkbox in the parts table
|
||||
* Should match the styling of the select checkbox
|
||||
******************************************************/
|
||||
table.dataTable > thead > tr > th.select-checkbox {
|
||||
position: relative;
|
||||
}
|
||||
table.dataTable > thead > tr > th.select-checkbox:before,
|
||||
table.dataTable > thead > tr > th.select-checkbox:after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1.2em;
|
||||
left: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
table.dataTable > thead > tr > th.select-checkbox:before {
|
||||
content: " ";
|
||||
margin-top: -5px;
|
||||
margin-left: -6px;
|
||||
border: 1px solid black;
|
||||
border-radius: 3px;
|
||||
}
|
||||
table.dataTable > thead > tr.selected > th.select-checkbox:after {
|
||||
content: "✓";
|
||||
font-size: 20px;
|
||||
margin-top: -23px;
|
||||
margin-left: -6px;
|
||||
text-align: center;
|
||||
/*text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9; */
|
||||
}
|
||||
table.dataTable.compact > thead > tr > th.select-checkbox:before {
|
||||
margin-top: -12px;
|
||||
}
|
||||
table.dataTable.compact > thead > tr.selected > th.select-checkbox:after {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ class ErrorHandlerHelper {
|
||||
console.log('Error Handler registered');
|
||||
|
||||
const content = document.getElementById('content');
|
||||
content.addEventListener('turbo:before-fetch-response', (event) => this.handleError(event));
|
||||
//content.addEventListener('turbo:before-fetch-response', (event) => this.handleError(event));
|
||||
content.addEventListener('turbo:fetch-request-error', (event) => this.handleError(event));
|
||||
content.addEventListener('turbo:frame-missing', (event) => this.handleError(event));
|
||||
|
||||
$(document).ajaxError(this.handleJqueryErrror.bind(this));
|
||||
}
|
||||
@@ -87,8 +89,10 @@ class ErrorHandlerHelper {
|
||||
}
|
||||
|
||||
handleError(event) {
|
||||
const fetchResponse = event.detail.fetchResponse;
|
||||
const response = fetchResponse.response;
|
||||
//Prevent default error handling
|
||||
event.preventDefault();
|
||||
|
||||
const response = event.detail.response;
|
||||
|
||||
//Ignore aborted requests.
|
||||
if (response.statusText === 'abort' || response.status == 0) {
|
||||
@@ -100,11 +104,11 @@ class ErrorHandlerHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
if(fetchResponse.failed) {
|
||||
if(!response.ok) {
|
||||
response.text().then(responseHTML => {
|
||||
this._showAlert(response.statusText, response.status, fetchResponse.location.toString(), responseHTML);
|
||||
this._showAlert(response.statusText, response.status, response.url, responseHTML);
|
||||
}).catch(err => {
|
||||
this._showAlert(response.statusText, response.status, fetchResponse.location.toString(), '<pre>' + err + '</pre>');
|
||||
this._showAlert(response.statusText, response.status, response.url, '<pre>' + err + '</pre>');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"florianv/swap": "^4.0",
|
||||
"florianv/swap-bundle": "dev-master",
|
||||
"gregwar/captcha-bundle": "^2.1.0",
|
||||
"hslavich/oneloginsaml-bundle": "^2.10",
|
||||
"jbtronics/2fa-webauthn": "^1.0.0",
|
||||
"league/csv": "^9.8.0",
|
||||
"league/html-to-markdown": "^5.0.1",
|
||||
"liip/imagine-bundle": "^2.2",
|
||||
"nelexa/zip": "^4.0",
|
||||
|
||||
1104
composer.lock
generated
1104
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -27,4 +27,5 @@ return [
|
||||
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
||||
SpomkyLabs\CborBundle\SpomkyLabsCborBundle::class => ['all' => true],
|
||||
Webauthn\Bundle\WebauthnBundle::class => ['all' => true],
|
||||
Hslavich\OneloginSamlBundle\HslavichOneloginSamlBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
60
config/packages/hslavich_onelogin_saml.yaml
Normal file
60
config/packages/hslavich_onelogin_saml.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings
|
||||
|
||||
hslavich_onelogin_saml:
|
||||
# Basic settings
|
||||
idp:
|
||||
entityId: '%env(string:SAML_IDP_ENTITY_ID)%'
|
||||
singleSignOnService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
singleLogoutService:
|
||||
url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_IDP_X509_CERT)%'
|
||||
sp:
|
||||
entityId: '%env(string:SAML_SP_ENTITY_ID)%'
|
||||
assertionConsumerService:
|
||||
url: '%partdb.default_uri%saml/acs'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
||||
singleLogoutService:
|
||||
url: '%partdb.default_uri%logout'
|
||||
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||
x509cert: '%env(string:SAML_SP_X509_CERT)%'
|
||||
privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%'
|
||||
|
||||
# Optional settings
|
||||
#baseurl: 'http://myapp.com'
|
||||
strict: true
|
||||
debug: false
|
||||
security:
|
||||
allowRepeatAttributeName: true
|
||||
# nameIdEncrypted: false
|
||||
authnRequestsSigned: true
|
||||
logoutRequestSigned: true
|
||||
logoutResponseSigned: true
|
||||
# wantMessagesSigned: false
|
||||
# wantAssertionsSigned: true
|
||||
# wantNameIdEncrypted: false
|
||||
# requestedAuthnContext: true
|
||||
# signMetadata: false
|
||||
# wantXMLValidation: true
|
||||
# relaxDestinationValidation: false
|
||||
# destinationStrictlyMatches: true
|
||||
# rejectUnsolicitedResponsesWithInResponseTo: false
|
||||
# signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
||||
# digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
|
||||
#contactPerson:
|
||||
# technical:
|
||||
# givenName: 'Tech User'
|
||||
# emailAddress: 'techuser@example.com'
|
||||
# support:
|
||||
# givenName: 'Support User'
|
||||
# emailAddress: 'supportuser@example.com'
|
||||
# administrative:
|
||||
# givenName: 'Administrative User'
|
||||
# emailAddress: 'administrativeuser@example.com'
|
||||
#organization:
|
||||
# en:
|
||||
# name: 'Part-DB-name'
|
||||
# displayname: 'Displayname'
|
||||
# url: 'http://example.com'
|
||||
@@ -4,7 +4,7 @@ framework:
|
||||
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
#default_uri: http://localhost
|
||||
default_uri: '%env(DEFAULT_URI)%'
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
|
||||
@@ -4,7 +4,6 @@ security:
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
|
||||
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
|
||||
providers:
|
||||
# used to reload user from session & other features (e.g. switch_user)
|
||||
app_user_provider:
|
||||
@@ -12,6 +11,7 @@ security:
|
||||
class: App\Entity\UserSystem\User
|
||||
property: name
|
||||
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
@@ -20,6 +20,7 @@ security:
|
||||
provider: app_user_provider
|
||||
lazy: true
|
||||
user_checker: App\Security\UserChecker
|
||||
entry_point: form_login
|
||||
|
||||
two_factor:
|
||||
auth_form_path: 2fa_login
|
||||
@@ -29,6 +30,14 @@ security:
|
||||
login_throttling:
|
||||
max_attempts: 5 # per minute
|
||||
|
||||
saml:
|
||||
use_referer: true
|
||||
user_factory: saml_user_factory
|
||||
persist_user: true
|
||||
check_path: saml_acs
|
||||
login_path: saml_login
|
||||
failure_path: login
|
||||
|
||||
# https://symfony.com/doc/current/security/form_login_setup.html
|
||||
form_login:
|
||||
login_path: login
|
||||
|
||||
@@ -19,6 +19,7 @@ twig:
|
||||
sidebar_tree_updater: '@App\Services\Trees\SidebarTreeUpdater'
|
||||
avatar_helper: '@App\Services\UserSystem\UserAvatarHelper'
|
||||
available_themes: '%partdb.available_themes%'
|
||||
saml_enabled: '%partdb.saml.enabled%'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
|
||||
@@ -13,6 +13,8 @@ parameters:
|
||||
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
|
||||
partdb.locale_menu: ['en', 'de', 'fr', 'ru', 'ja'] # The languages that are shown in user drop down menu
|
||||
|
||||
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
|
||||
|
||||
######################################################################################################################
|
||||
# Users and Privacy
|
||||
######################################################################################################################
|
||||
@@ -29,9 +31,10 @@ 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.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.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.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)
|
||||
|
||||
######################################################################################################################
|
||||
# Error pages
|
||||
@@ -39,6 +42,11 @@ parameters:
|
||||
partdb.error_pages.admin_email: '%env(trim:string:ERROR_PAGE_ADMIN_EMAIL)%' # You can set an email address here, which is shown on an error page, how to contact an administrator
|
||||
partdb.error_pages.show_help: '%env(trim:string:ERROR_PAGE_SHOW_HELP)%' # If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
|
||||
|
||||
######################################################################################################################
|
||||
# SAML
|
||||
######################################################################################################################
|
||||
partdb.saml.enabled: '%env(bool:SAML_ENABLED)%' # If this is set to true, SAML authentication is enabled
|
||||
|
||||
######################################################################################################################
|
||||
# Sidebar
|
||||
######################################################################################################################
|
||||
@@ -95,7 +103,7 @@ parameters:
|
||||
env(INSTANCE_NAME): 'Part-DB'
|
||||
env(BASE_CURRENCY): 'EUR'
|
||||
env(USE_GRAVATAR): '0'
|
||||
env(ALLOW_ATTACHMENT_DOWNLOADS): 0
|
||||
env(MAX_ATTACHMENT_FILE_SIZE): '100M'
|
||||
|
||||
env(ERROR_PAGE_ADMIN_EMAIL): ''
|
||||
env(ERROR_PAGE_SHOW_HELP): 1
|
||||
@@ -110,3 +118,7 @@ parameters:
|
||||
|
||||
env(TRUSTED_PROXIES): '127.0.0.1' #By default trust only our own server
|
||||
env(TRUSTED_HOSTS): '' # Trust all host names by default
|
||||
|
||||
env(DEFAULT_URI): 'https://partdb.changeme.invalid/'
|
||||
|
||||
env(SAML_ROLE_MAPPING): '{}'
|
||||
|
||||
@@ -43,6 +43,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ["read", "edit", "create", "delete", "show_history"]
|
||||
import:
|
||||
label: "perm.import"
|
||||
alsoSet: ["read", "edit", "create"]
|
||||
|
||||
parts_stock:
|
||||
group: "data"
|
||||
@@ -76,6 +79,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ["read", "edit", "create", "delete", "show_history"]
|
||||
import:
|
||||
label: "perm.import"
|
||||
alsoSet: [ "read", "edit", "create" ]
|
||||
|
||||
footprints:
|
||||
<<: *PART_CONTAINING
|
||||
@@ -156,6 +162,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ["read", "edit", "create", "delete", "edit_permissions", "show_history"]
|
||||
import:
|
||||
label: "perm.import"
|
||||
alsoSet: [ "read", "edit", "create" ]
|
||||
|
||||
users:
|
||||
label: "perm.users"
|
||||
@@ -188,6 +197,9 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ["read", "create", "delete", "edit_permissions", "show_history", "edit_infos", "edit_username"]
|
||||
import:
|
||||
label: "perm.import"
|
||||
alsoSet: [ "read", "create" ]
|
||||
|
||||
#database:
|
||||
# label: "perm.database"
|
||||
|
||||
4
config/routes/hslavich_saml.yaml
Normal file
4
config/routes/hslavich_saml.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
hslavich_saml_sp:
|
||||
resource: "@HslavichOneloginSamlBundle/Resources/config/routing.yml"
|
||||
# Only load the SAML routes if SAML is enabled
|
||||
condition: "env('SAML_ENABLED') == '1' or env('SAML_ENABLED') == 'true'"
|
||||
@@ -88,11 +88,13 @@ services:
|
||||
App\Form\AttachmentFormType:
|
||||
arguments:
|
||||
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
|
||||
$max_file_size: '%partdb.attachments.max_file_size%'
|
||||
|
||||
App\Services\Attachments\AttachmentSubmitHandler:
|
||||
arguments:
|
||||
$allow_attachments_downloads: '%partdb.attachments.allow_downloads%'
|
||||
$mimeTypes: '@mime_types'
|
||||
$max_upload_size: '%partdb.attachments.max_file_size%'
|
||||
|
||||
App\EventSubscriber\LogSystem\LogoutLoggerListener:
|
||||
tags:
|
||||
@@ -127,6 +129,15 @@ services:
|
||||
# Security
|
||||
####################################################################################################################
|
||||
|
||||
saml_user_factory:
|
||||
alias: App\Security\SamlUserFactory
|
||||
public: true
|
||||
|
||||
App\Security\SamlUserFactory:
|
||||
arguments:
|
||||
$saml_role_mapping: '%env(json:SAML_ROLE_MAPPING)%'
|
||||
$update_group_on_login: '%env(bool:SAML_UPDATE_GROUP_ON_LOGIN)%'
|
||||
|
||||
####################################################################################################################
|
||||
# Cache
|
||||
####################################################################################################################
|
||||
@@ -194,6 +205,10 @@ services:
|
||||
arguments:
|
||||
$available_themes: '%partdb.available_themes%'
|
||||
|
||||
App\Command\User\ConvertToSAMLUserCommand:
|
||||
arguments:
|
||||
$saml_enabled: '%partdb.saml.enabled%'
|
||||
|
||||
|
||||
####################################################################################################################
|
||||
# Label system
|
||||
|
||||
4
docs/assets/usage/import_export/part_import_example.csv
Normal file
4
docs/assets/usage/import_export/part_import_example.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
name;description;category;notes;footprint;tags;quantity;storage_location;mass;ipn;mpn;manufacturing_status;manufacturer;supplier;spn;price;favorite;needs_review;minamount;partUnit;manufacturing_status
|
||||
BC547;NPN transistor;Transistors -> NPN;very important notes;TO -> TO-92;NPN,Transistor;5;Room 1 -> Shelf 1 -> Box 2;10;;;Manufacturer;;You need to fill this line, to use spn and price;BC547C;2,3;0;;;;
|
||||
BC557;PNP transistor;<b>HTML</b>;;TO -> TO-92;PNP,Transistor;10;Room 2-> Box 3;;Internal1234;;;;;;;;1;;;active
|
||||
Copper Wire;;Wire;;;;;;;;;;;;;;;;;Meter;
|
||||
|
@@ -26,7 +26,8 @@ The following configuration options can only be changed by the server administra
|
||||
* `INSTANCE_NAME`: The name of your installation. It will be shown as a title in the navbar and other places. By default `Part-DB`, but you can customize it to something likes `ExampleCorp. Inventory`.
|
||||
* `ALLOW_ATTACHMENT_DOWNLOADS` (allowed values `0` or `1`): By setting this option to 1, users can make Part-DB directly download a file specified as an URL and create it as local file. Please not that this allows users access to all ressources publicly available to the server (so full access to other servers in the same local network), which could be a security risk.
|
||||
* `USE_GRAVATAR`: Set to `1` to use [gravatar.com](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 (gravatars) server, so this might be a privacy risk.
|
||||
|
||||
* `MAX_ATTACHMENT_FILE_SIZE`: The maximum file size (in bytes) for attachments. You can use the suffix `K`, `M` or `G` to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this only the limit of Part-DB. You still need to configure the php.ini `upload_max_filesize` and `post_max_size` to allow bigger files to be uploaded.
|
||||
* `DEFAULT_URI`: The default URI base to use for the Part-DB, when no URL can be determined from the browser request. This should be the primary URL/Domain, which is used to access Part-DB. This value is used to create correct links in emails and other places, where the URL is needed. It is also used, when SAML is enabled.s If you are using a reverse proxy, you should set this to the URL of the reverse proxy (e.g. `https://part-db.example.com`). **This value must end with a slash**.
|
||||
|
||||
### E-Mail settings
|
||||
* `MAILER_DSN`: You can configure the mail provider which should be used for email delivery (see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587`
|
||||
@@ -46,6 +47,22 @@ If you wanna use want to revert changes or view older revisions of entities, the
|
||||
* `ERROR_PAGE_ADMIN_EMAIL`: You can set an email-address here, which is shown on the error page, who should be contacted about the issue (e.g. an IT support email of your company)
|
||||
* `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not contain senstive informations, but could confuse end-users.
|
||||
|
||||
### SAML SSO settings
|
||||
The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to login to Part-DB without entering a username and password, but instead they are redirected to a SAML Identity Provider (IdP) and are logged in automatically. This is especially useful, when you want to use Part-DB in a company, where all users have a SAML account (e.g. via Active Directory or LDAP).
|
||||
You can find more advanced settings in the `config/packages/hslavich_onelogin_saml.yaml` file. Please note that this file is not backuped by the backup script, so you have to backup it manually, if you want to keep your changes. If you want to edit it on docker, you have to map the file to a volume.
|
||||
|
||||
* `SAML_ENABLED`: When this is set to 1, SAML SSO is enabled and the SSO Login button is shown in the login form. You have to configure the SAML settings below, before you can use this feature.
|
||||
* `SAML_ROLE_MAPPING`: A [JSON](https://en.wikipedia.org/wiki/JSON) encoded map which specifies how Part-DB should convert the user roles given by SAML attribute `group` should be converted to a Part-DB group (specified by ID). You can use a wildcard `*` to map all otherwise unmapped roles to a certain group. Example: `{"*": 1, "admin": 2, "editor": 3}`. This would map all roles to the group with ID 1, except the role `admin`, which is mapped to the group with ID 2 and the role `editor`, which is mapped to the group with ID 3.
|
||||
* `SAML_UPDATE_GROUP_ON_LOGIN`: When this is enabled the group of the user is updated on every login of the user based on the SAML role attributes. When this is disabled, the group is only assigned on the first login of the user, and a Part-DB administrator can change the group afterwards by editing the user.
|
||||
* `SAML_IDP_ENTITY_ID`: The entity ID of your SAML Identity Provider (IdP). You can find this value in the metadata XML file or configuration UI of your IdP.
|
||||
* `SAML_IDP_SINGLE_SIGN_ON_SERVICE`: The URL of the SAML IdP Single Sign-On Service (SSO). You can find this value in the metadata XML file or configuration UI of your IdP.
|
||||
* `SAML_IDP_SINGLE_LOGOUT_SERVICE`: The URL of the SAML IdP Single Logout Service (SLO). You can find this value in the metadata XML file or configuration UI of your IdP.
|
||||
* `SAML_IDP_X509_CERT`: The base64 encoded X.509 public certificate of your SAML IdP. You can find this value in the metadata XML file or configuration UI of your IdP. It should start with `MIIC` and end with `=`.
|
||||
* `SAML_SP_ENTITY_ID`: The entity ID of your SAML Service Provider (SP). This is the value you have configured for the Part-DB client in your IdP.
|
||||
* `SAML_SP_X509_CERT`: The public X.509 certificate of your SAML SP (here Part-DB). This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIC` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_PRIVATE_KEY` setting.
|
||||
* `SAML_SP_PRIVATE_KEY`: The private key of your SAML SP (here Part-DB), corresponding the public key specified in `SAML_SP_X509_CERT`. This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIE` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_X509_CERT` setting.
|
||||
|
||||
|
||||
### Other / less used options
|
||||
* `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct IP informations (see [here](https://symfony.com/doc/current/deployment/proxies.html) for more info).
|
||||
* `TRUSTED_HOSTS`: To prevent `HTTP Host header attacks` you can set a regex containing all host names via which Part-DB should be accessible. If accessed via the wrong hostname, an error will be shown.
|
||||
|
||||
@@ -27,6 +27,7 @@ It is installed on a web server and so can be accessed with any browser without
|
||||
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* Import/Export system (partial working)
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions.
|
||||
|
||||
@@ -17,3 +17,5 @@ For example, if your reverse proxy has the IP address `192.168.2.10`, your value
|
||||
```
|
||||
TRUSTED_PROXIES=192.168.2.10
|
||||
```
|
||||
|
||||
Set the `DEFAULT_URI` environment variable to the URL of your Part-DB installation, available from the outside (so via the reverse proxy).
|
||||
150
docs/installation/saml_sso.md
Normal file
150
docs/installation/saml_sso.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
title: Single Sign-On via SAML
|
||||
layout: default
|
||||
parent: Installation
|
||||
nav_order: 12
|
||||
---
|
||||
|
||||
# Single Sign-On via SAML
|
||||
|
||||
Part-DB supports Single Sign-On via SAML. This means that you can use your existing SAML identity provider to log in to Part-DB.
|
||||
Using an intermediate SAML server like [Keycloak](https://www.keycloak.org/), also allows you to connect Part-DB to a LDAP or Active Directory server.
|
||||
|
||||
{: .important }
|
||||
> This feature is for advanced users only. Single Sign-On is useful for large organizations with many users, which are already using SAML for other services.
|
||||
> If you have only one or a few users, you should use the built-in authentication system of Part-DB.
|
||||
> This guide assumes that you already have an SAML identity provider set up and working, and have a basic understanding of how SAML works.
|
||||
|
||||
{: .warning }
|
||||
> This feature is currently in beta. Please report any bugs you find.
|
||||
> So far it has only tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider.
|
||||
|
||||
This guide will show you how to configure Part-DB with [Keycloak](https://www.keycloak.org/) as the SAML identity provider,
|
||||
but it should work with any SAML 2.0 compatible identity provider.
|
||||
|
||||
This guide assumes that you have a working Keycloak installation with some users. If you don't, you can follow the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/getting_started/index.html).
|
||||
|
||||
{: .important }
|
||||
> Part-DB associates local users with SAML users by their username. That means if the username of a SAML user changes, a new local user will be created (and the old account can not be accessed).
|
||||
> You should make sure that the username of a SAML user does not change. If you use Keycloak make sure that the possibility to change the username is disabled (which is by default).
|
||||
> If you really have to rename a SAML user, a Part-DB admin can rename the local user in the Part-DB in the admin panel, to match the new username of the SAML user.
|
||||
|
||||
## Configure basic SAML connection
|
||||
|
||||
### Create a new SAML client
|
||||
1. First, you need to configure a new SAML client in Keycloak. Login in to your Keycloak admin console and go to the `Clients` page.
|
||||
2. Click on `Create client` and select `SAML` as type from the dropdown menu. For the client ID, you can use anything you want, but it should be unique.
|
||||
*It is recommended to set this value to the domain name of your Part-DB installation, with an attached `/sp` (e.g. `https://partdb.yourdomain.invalid/sp`)*.
|
||||
The name field should be set to something human-readable, like `Part-DB`.
|
||||
3. Click on `Save` to create the new client.
|
||||
|
||||
### Configure the SAML client
|
||||
|
||||
1. Now you need to configure the SAML client. Go to the `Settings` tab and set the following values:
|
||||
* Set `Home URL` to the homepage of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`).
|
||||
* Set `Valid redirect URIs` to your homepage with a wildcard at the end (e.g. `https://partdb.yourdomain.invalid/*`).
|
||||
* Set `Valid post logout redirect URIs` to `+` to allow all urls from the `Valid redirect URIs`.
|
||||
* Set `Name ID format` to `username`
|
||||
* Ensure `Force POST binding` is enabled.
|
||||
* Ensure `Sign documents` is enabled.
|
||||
* Ensure `Front channel logout` is enabled.
|
||||
* Ensure `Signature Algorithm` is set to `RSA_SHA256`.
|
||||
|
||||
Click on `Save` to save the changes.
|
||||
2. Go to the `Advanced` tab and set the following values:
|
||||
* Assertion Consumer Service POST Binding URL to your homepage with `/saml/acs` at the end (e.g. `https://partdb.yourdomain.invalid/saml/acs`).
|
||||
* Logout Service POST Binding URL to your homepage with `/logout` at the end (e.g. `https://partdb.yourdomain.invalid/logout`).
|
||||
3. Go to Keys tab and ensure `Client Signature Required` is enabled.
|
||||
4. In the Keys tab click on `Generate new keys`. This will generate a new key pair for the SAML client. The private key will be downloaded to your computer.
|
||||
|
||||
### Configure Part-DB to use SAML
|
||||
1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for edit
|
||||
2. Set the `SAMLP_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the previous step. It should start with `MIEE` and end with `=`.
|
||||
3. Set the `SAML_SP_X509_CERT` environment variable to the content of the Certificate field shown in the `Keys` tab of the SAML client in Keycloak. It should start with `MIIC` and end with `=`.
|
||||
4. Set the `SAML_SP_ENTITY_ID` environment variable to the entityID of the SAML client in Keycloak (e.g. `https://partdb.yourdomain.invalid/sp`).
|
||||
5. In Keycloak navigate to `Realm Settings` -> `SAML 2.0 Identity Provider` (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/descriptor) to show the SAML metadata.
|
||||
6. Copy the `entityID` value from the metadata to the `SAML_IDP_ENTITY_ID` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master`).
|
||||
7. Copy the `Single Sign-On Service` value from the metadata to the `SAML_IDP_SINGLE_SIGN_ON_SERVICE` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml`).
|
||||
8. Copy the `Single Logout Service` value from the metadata to the `SAML_IDP_SINGLE_LOGOUT_SERVICE` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/logout`).
|
||||
9. Copy the `X.509 Certificate` value from the metadata to the `SAML_IDP_X509_CERT` configuration variable of Part-DB (it should start with `MIIC` and should be pretty long).
|
||||
10. Set the `DEFAULT_URI` to the homepage (on the publicly available domain) of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`). It must end with a slash.
|
||||
11. Set the `SAML_ENABLED` configuration in Part-DB to 1 to enable SAML authentication.
|
||||
|
||||
When you access the Part-DB login form now, you should see a new button to log in via SSO. Click on it to be redirected to the SAML identity provider and log in.
|
||||
|
||||
In the following sections, you will learn how to configure that Part-DB uses the data provided by the SAML identity provider to fill out user informations.
|
||||
|
||||
### Set user information based on SAML attributes
|
||||
Part-DB can set basic user information like the username, the real name and the email address based on the SAML attributes provided by the SAML identity provider.
|
||||
To do this, you need to configure your SAML identity provider to provide the following attributes:
|
||||
|
||||
* `email` or `urn:oid:1.2.840.113549.1.9.1` for the email address
|
||||
* `firstName` or `urn:oid:2.5.4.42` for the first name
|
||||
* `lastName` or `urn:oid:2.5.4.4` for the last name
|
||||
* `department` for the department field of the user
|
||||
|
||||
You can omit any of these attributes, but then the corresponding field will be empty (but can be overriden by an administrator).
|
||||
These values are written to Part-DB database, whenever the user logs in via SAML (the user is created on the first login, and updated on every login).
|
||||
|
||||
To configure Keycloak to provide these attributes, you need to go to the `Client scopes` page and select the `sp-dedicatd` client scope (or create a new one).
|
||||
In the scope configuration page, click on `Add mappers` and `From predefined mappers`. Select the following mappers:
|
||||
* `X500 email`
|
||||
* `X500 givenName`
|
||||
* `X500 surname`
|
||||
|
||||
and click `Add`. Now Part-DB will be provided with the email, first name and last name of the user based on the Keycloak user database.
|
||||
|
||||
### Configure permissions for SAML users
|
||||
On the first login of a SAML user, Part-DB will create a new user in the database. This user will have the same username as the SAML user, but no password set. The user will be marked as a SAML user, so he can only login via SAML in the future. However in other aspects the user is a normal user, so Part-DB admins can set permissions for SAML users like for any other user and override permissions assigned via groups.
|
||||
|
||||
However for large organizations you maybe want to automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For this purpose Part-DB allows you to map SAML roles or groups to Part-DB groups. See the next section for details.
|
||||
|
||||
### Map SAML roles to Part-DB groups
|
||||
Part-DB allows you to configure a mapping between SAML roles or groups and Part-DB groups. This allows you to automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For example if a user at your SAML provider has the role `admin`, you can configure Part-DB to assign the `admin` group to this user. This will give the user all permissions of the `admin` group.
|
||||
|
||||
For this you need first have to create the groups in Part-DB, to which you want to assign the users and configure their permissions. You will need the IDs of the groups, which you can find in the `System->Group` page of Part-DB in the Info tab.
|
||||
|
||||
The map is provided as [JSON](https://en.wikipedia.org/wiki/JSON) encoded map between the SAML role and the group ID, which has the form `{"saml_role": group_id, "*": group_id, ...}`. You can use the `*` key to assign a group to all users, which are not in any other group. The map is configured via the `SAML_ROLE_MAPPING` environment variable, which you can configure via the `.env.local` or `docker-compose.yml` file. Please note that you have to enclose the JSON string in single quotes here, as JSON itself uses double quotes (e.g. `SAML_ROLE_MAPPING='{ "*": 2, "editor": 3, "admin": 1 }`).
|
||||
|
||||
For example if you want to assign the group with ID 1 (by default admin) to every SAML user which has the role `admin`, the role with ID 3 (by default editor) to every SAML user with the role `editor` and everybody else to the group with ID 2 (by default readonly), you can configure the following map:
|
||||
|
||||
```
|
||||
SAML_ROLE_MAPPING='{"admin": 1, "editor": 3, "*": 2}'
|
||||
```
|
||||
|
||||
Please not that the order of the mapping is important. The first matching role will be assigned to the user. So if you have a user with the roles `admin` and `editor`, he will be assigned to the group with ID 1 (admin) and not to the group with ID 3 (editor), as the `admin` role comes first in the JSON map.
|
||||
This mean that you should always put the most specific roles (e.g. admins) first of the map and the most general roles (e.g. normal users) later.
|
||||
|
||||
If you want to assign users with a certain role to a empty group, provide the group ID -1 as the value. This is not a valid group ID, so the user will not be assigned to any group.
|
||||
|
||||
The SAML roles (or groups depending on your configuration), have to be supplied via a SAML attribute `group`. You have to configure your SAML identity provider to provide this attribute. For example in Keycloak you can configure this attribute in the `Client scopes` page. Select the `sp-dedicated` client scope (or create a new one) and click on `Add mappers`. Select `Role mapping` or `Group membership`, change the field name and click `Add`. Now Part-DB will be provided with the groups of the user based on the Keycloak user database.
|
||||
|
||||
By default, the group is assigned to the user on the first login and updated on every login based on the SAML attributes. This allows you to configure the groups in the SAML identity provider and the users will automatically stay up to date with their permissions. However, if you want to disable this behavior (and let the Part-DB admins configure the groups manually, after the first login), you can set the `SAML_UPDATE_GROUP_ON_LOGIN` environment variable to `false`. If you want to disable the automatic group assignment completly (so not even on the first login of a user), set the `SAML_ROLE_MAPPING` to `{}` (empty JSON object).
|
||||
|
||||
## Overview of possible SAML attributes used by Part-DB
|
||||
The following table shows all SAML attributes, which can be usedby Part-DB. If your identity provider is configured to provide these attributes, you can use to automatically fill the corresponding fields of the user in Part-DB.
|
||||
|
||||
| SAML attribute | Part-DB user field | Description |
|
||||
|-------------------------------------------|-------------------|-------------------------------------------------------------------|
|
||||
| `urn:oid:1.2.840.113549.1.9.1` or `email` | email | The email address of the user. |
|
||||
| `urn:oid:2.5.4.42` or `firstName` | firstName | The first name of the user. |
|
||||
| `urn:oid:2.5.4.4` or `lastName` | lastName | The last name of the user. |
|
||||
| `department` | department | The department of the user. |
|
||||
| `group` | group | The group of the user (determined by `SAML_ROLE_MAPPING` option). |
|
||||
|
||||
## Use SAML Login for existing users
|
||||
Part-DB distinguishes between local users and SAML users. Local users are users, which can login via Part-DB login form and which use the password (hash) saved in the Part-DB database. SAML users are stored in the database too (they are created on the first login of the user via SAML), but they use the SAML identity provider to authenticate the user and have no password stored in the database. When you try you will get an error message.
|
||||
|
||||
For security reasons it is not possible to authenticate via SAML as a local user (and vice versa). So if you have existing users in your Part-DB database and want them to be able to login via SAML in the future, you can use the `php bin/console partdb:user:convert-to-saml-user username` command to convert them to SAML users. This will remove the password hash from the database and mark them as SAML users, so they can login via SAML in the future.
|
||||
|
||||
The reverse is also possible: If you have existing SAML users and want them to be able to login via the Part-DB login form, you can use the `php bin/console partdb:user:convert-to-saml-user --to-local username` command to convert them to local users. You have to set an password for the user afterwards.
|
||||
|
||||
{: .important }
|
||||
> It is recommended that you let the original admin user (ID: 2) be a local user, so you can still login to Part-DB if the SAML identity provider is not available.
|
||||
|
||||
## Advanced SAML configuration
|
||||
You can find some more advanced SAML configuration options in the `config/packages/hslavich_onelogin_saml.yaml` file. Refer to the file for more information.
|
||||
Normally you don't have to change anything here.
|
||||
|
||||
Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep your changes. On docker containers you have to configure a volume mapping for it.
|
||||
|
||||
@@ -23,13 +23,14 @@ Some things changed however to the old version and some features are still missi
|
||||
* Configuration is now done via configuration files / environment variables instead of the WebUI (this maybe change in the future).
|
||||
* Database updated are now done via console instead of the WebUI
|
||||
* Permission system changed: **You will have to newly set the permissions of all users and groups!**
|
||||
* Import / Export file format changed. Fields must be english now (unlike in legacy Part-DB versions, where german fields in CSV were possible)
|
||||
and you maybe have to change the header line/field names of your CSV files.
|
||||
|
||||
## Missing features
|
||||
* No possibility to mark parts for ordering (yet)
|
||||
* No import / export possibility for parts (yet), however you can import/export other datastructures like Categories, Footprints, etc. (yet)
|
||||
* No support for 3D models of footprints (yet)
|
||||
* No possibility to disable footprints, manufacturers globally (or per category). This should not have a big impact, when you forbid users to edit/create them.
|
||||
|
||||
* No resistor calculator or SMD labels tools
|
||||
|
||||
## Upgrade process
|
||||
|
||||
|
||||
29
docs/usage/bom_import.md
Normal file
29
docs/usage/bom_import.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
layout: default
|
||||
title: Import Bill of Material (BOM) for Projects
|
||||
nav_order: 5
|
||||
parent: Usage
|
||||
---
|
||||
|
||||
# Import Bill of Material (BOM) for Projects
|
||||
|
||||
Part-DB supports the import of Bill of Material (BOM) files for projects. This allows you to directly import a BOM file from your ECAD software into your Part-DB project.
|
||||
|
||||
|
||||
The import process is currently semi-automatic. This means Part-DB will take the BOM file and create entries for all parts in the BOM file in your project and assign fields like
|
||||
mountnames (e.g. 'C1, C2, C3'), quantity and more.
|
||||
However, you still have to assign the parts from Part-DB database to the entries (if applicable) after the import by hand,
|
||||
as Part-DB can not know which part you had in mind when you designed your schematic.
|
||||
|
||||
## Usage
|
||||
In the project view or edit click on the "Import BOM" button, below the BOM table. This will open a dialog where you can
|
||||
select the BOM file you want to import and some options for the import process:
|
||||
|
||||
* **Type**: The format/type of the BOM file. See below for explanations of the different types.
|
||||
* **Clear existing BOM entries before import**: If this is checked, all existing BOM entries, which are currently associated with the project, will be deleted before the import.
|
||||
|
||||
### Supported BOM file formats
|
||||
|
||||
* **KiCAD Pcbnew BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated by [KiCAD Pcbnew](https://www.kicad.org/).
|
||||
Please note that you have to export the BOM from the PCB editor, the BOM generated by the schematic editor (Eeschema) has a different format and does not work with this type.
|
||||
You can generate this BOM file by going to "File" -> "Fabrication Outputs" -> "Bill of Materials" in Pcbnew and save the file to your desired location.
|
||||
@@ -19,6 +19,7 @@ You can get help for every command with the parameter `--help`. See `php bin/con
|
||||
* `php bin/console partdb:users:permissions`: View/Change the permissions of the user with the given username
|
||||
* `php bin/console partdb:users:upgrade-permissions-schema`: Upgrade the permissions schema of users to the latest version (this is normally automatically done when the user visits a page)
|
||||
* `php bin/console partdb:logs:show`: Show the most recent entries of the Part-DB event log / recent activity
|
||||
* `php bin/console partdb:user:convert-to-saml-user`: Convert a local user to a SAML/SSO user. This is needed, if you want to use SAML/SSO authentication for a user, which was created before you enabled SAML/SSO authentication.
|
||||
|
||||
## Currency commands
|
||||
* `php bin/console partdb:currencies:update-exchange-rates`: Update the exchange rates of all currencies from the internet)
|
||||
|
||||
101
docs/usage/import_export.md
Normal file
101
docs/usage/import_export.md
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
layout: default
|
||||
title: Import & Export data
|
||||
nav_order: 4
|
||||
parent: Usage
|
||||
---
|
||||
|
||||
# Import & Export data
|
||||
|
||||
Part-DB offers the possibility to import existing data (parts, datastructures, etc.) from existing datasources into Part-DB. Data can also be exported from Part-DB into various formats.
|
||||
|
||||
## Import
|
||||
|
||||
{: .note }
|
||||
> As data import is a very powerful feature and can easily fill up your database with lots of data, import is by default only available for
|
||||
> administrators. If you want to allow other users to import data, or can not import data, check the permissions of the user. You can enable import for each data structure
|
||||
> individually in the permissions settings.
|
||||
|
||||
### Import parts
|
||||
|
||||
Part-DB supports the import of parts from CSV files and other formats. This can be used to import existing parts from other databases or datasources into Part-DB. The import can be done via the "Tools -> Import parts" page, which you can find in the "Tools" sidebar panel.
|
||||
|
||||
{: .important }
|
||||
> When importing data, the data is immediatley written to database during the import process, when the data is formally valid.
|
||||
> You will not be able to check the data before it is written to the database, so you should review the data before using the import tool.
|
||||
|
||||
You can upload the file which should be imported here and choose various options on how the data should be treated:
|
||||
* **Format**: By default "auto" is selected here and Part-DB will try to detect the format of the file automatically based on its file extension. If you want to force a specific format or Part-DB can not auto-detect the format, you can select it here.
|
||||
* **CSV delimiter**: If you upload an CSV file, you can select the delimiter character which is used to separate the columns in the CSV file. Depending on the CSV file, this might be a comma (`,`), semicolon (`;`).
|
||||
* **Category override**: You can select (or create) a category here, to which all imported parts should be assigned, no matter what was specified in the import file. This can be useful if you want to assign all imports to a certain category or if no category is specified in the data. If you leave this field empty, the category will be determined by the import file (or the export will error, if no category is specified).
|
||||
* **Mark all imported parts as "Needs review"**: If this is selected, all imported parts will be marked as "Needs review" after the import. This can be useful if you want to review all imported parts before using them.
|
||||
* **Create unknown datastructures**: If this is selected Part-DB will create new datastructures (like categories, manufacturers, etc.) if no datastructure(s) with the same name and path already exists. If this is not selected, only existing datastructures will be used and if no matching datastrucure is found, the imported parts field will be empty.
|
||||
* **Path delimiter**: Part-DB allows you to create/select nested datastructures (like categories, manufacturers, etc.) by using a path (e.g. `Category 1->Category 1.1`, which will select/create the `Category 1.1` whose parent is `Category 1`). This path is separated by the path delimiter. If you want to use a different path delimiter than the default one (which is `>`), you can select it here.
|
||||
* **Abort on validation error**: If this is selected, the import will be aborted if a validation error occurs (e.g. if a required field is empty) for any of the imported parts and validation errors will be shown on top of the page. If this is not selected, the import will continue for the other parts and only the invalid parts will be skipped.
|
||||
|
||||
After you have selected the options, you can start the import by clicking the "Import" button. When the import is finished, you will see the results of the import in the lower half of the page. You find a table with the imported parts (including links to them) there.
|
||||
|
||||
#### Fields description
|
||||
|
||||
For the importing of parts, you can use the following fields which will be imported into each part. Please note that the field names are case sensitive (so `name` is not the same as `Name`). All fields (besides name) are optional, so you can leave them empty or do not include the column in your file.
|
||||
|
||||
* **`name`** (required): The name of the part. This is the only required field, all other fields are optional.
|
||||
* **`description`**: The description of the part, you can use markdown/HTML syntax here for rich text formatting.
|
||||
* **`notes`** or **`comment`**: The notes of the part, you can use markdown/HTML syntax here for rich text formatting.
|
||||
* **`category`**: The category of the part. This can be a path (e.g. `Category 1->Category 1.1`), which will select/create the `Category 1.1` whose parent is `Category 1`. If you want to use a different path delimiter than the default one (which is `->`), you can select it in the import options. If the category does not exist and the option "Create unknown datastructures" is selected, it will be created.
|
||||
* **`footprint`**: The footprint of the part. Can be a path similar to the category field.
|
||||
* **`favorite`**: If this is set to `1`, the part will be marked as favorite.
|
||||
* **`manufacturer`**: The manufacturer of the part. Can be a path similar to the category field.
|
||||
* **`manufacturer_product_number`** or **`mpn`**: The manufacturer product number of the part.
|
||||
* **`manufacturer_product_url`: The URL to the product page of the manufacturer of the part.
|
||||
* **`manufacturing_status`**: The manufacturing status of the part, must be one of the following values: `announced`, `active`, `nrfnd`, `eol`, `discontinued` or left empty.
|
||||
* **`needs_review`** or **`needs_review`**: If this is set to `1`, the part will be marked as "needs review".
|
||||
* **`tags`**: A comma separated list of tags for the part.
|
||||
* **`mass`**: The mass of the part in grams.
|
||||
* **`ipn`**: The IPN (Item Part Number) of the part.
|
||||
* **`minamount`**: The minimum amount of the part which should be in stock.
|
||||
* **`partUnit`**: The measurement unit of the part to use. Can be a path similar to the category field.
|
||||
|
||||
With the following fields you can specify storage locations and amount / quantiy in stock of the part. An PartLot will be created automatically from the data and assigned to the part. The following fields are helpers for an easy import for parts at one storage location. If you need to create a Part with multiple PartLots you have to use JSON format (or CSV) with nested objects:
|
||||
|
||||
**`storage_location`** or **`storelocation`**: The storage location of the part. Can be a path similar to the category field.
|
||||
**`amount`**, **`quantity`** or **`instock`**: The amount of the part in stock. If this value is not set, the part lot will be marked with "unknown amount"
|
||||
|
||||
The following fields can be used to specify the supplier/distributor, supplier product number and the price of the part. This is only possible for a single supplier/distributor and price with this fields. If you need to specify multiple suppliers/distributors or prices, you have to use JSON format (or CSV) with nested objects.
|
||||
**Please note that the supplier fields is required, if you want to import prices or supplier product numbers.**. If the supplier is not specified, the price and supplier product number fields will be ignored:
|
||||
|
||||
* **`supplier`**: The supplier of the part. Can be a path similar to the category field.
|
||||
* **`supplier_product_number`** or **`supplier_part_number`** or * **`spn`**: The supplier product number of the part.
|
||||
* **`price`**: The price of the part in the base currency of the database (by default euro).
|
||||
|
||||
#### Example data
|
||||
Here you can find some example data for the import of parts, you can use it as a template for your own import (especially the CSV file).
|
||||
|
||||
* [Part import CSV example]({% link assets/usage/import_export/part_import_example.csv %}) with all possible fields
|
||||
|
||||
## Export
|
||||
|
||||
By default every user, who can read the datastructure, can also export the data of this datastructure, as this does not give the user any additional information.
|
||||
|
||||
### Exporting data structures (categories, manufacturers, etc.)
|
||||
You can export data structures (like categories, manufacturers, etc.) in the respective edit page (e.g. Tools Panel -> Edit -> Category).
|
||||
If you select a certain datastructure from your list, you can export it (and optionally all sub-datastructures) in the "Export" tab.
|
||||
If you want to export all datastructures of a certain type (e.g. all categories in your database), you can select the "Export all" function in the "Import / Export" tab of the "new element" page.
|
||||
|
||||
You can select between the following export formats:
|
||||
* **CSV** (Comma Separated Values): A semicolon separated list of values, where every line represents an element. This format can be imported into Excel or LibreOffice Calc and is easy to work with. However it does not support nested datastructures or sub data (like parameters, attachments, etc.), very well (many columns are generated, as every possible sub data is exported as a separate column).
|
||||
* **JSON** (JavaScript Object Notation): A text-based format, which is easy to work with programming laguages. It supports nested datastructures and sub data (like parameters, attachments, etc.) very well. However it is not easy to work with in Excel or LibreOffice Calc and you maybe need to write some code to work with the exported data efficiently.
|
||||
* **YAML** (Yet another Markup Language): Very similar to JSON
|
||||
* **XML** (Extensible Markup Language): Good support with nested datastructures. Similar usecase as JSON and YAML.
|
||||
|
||||
Also you can select between the following export levels:
|
||||
* **Simple**: This will only export very basic information about the name (like the name, or description for parts)
|
||||
* **Extended**: This will export all commonly used information about this datastructure (like notes, options, etc)
|
||||
* **Full**: This will export all available information about this datastructure (like all parameters, attachments)
|
||||
|
||||
Please note that the level will also be applied to all sub data or children elements. So if you select "Full" for a part, all the associated categories, manufacturers, footprints, etc. will also be exported with all available information, this can lead to very large export files.
|
||||
|
||||
### Exporting parts
|
||||
You can export parts in all part tables. Select the parts you want via the checkbox in the table line and select the export format and level in the appearing menu.
|
||||
|
||||
See the section about exporting datastructures for more information about the export formats and levels.
|
||||
524
migrations/Version20230219225340.php
Normal file
524
migrations/Version20230219225340.php
Normal file
@@ -0,0 +1,524 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20230219225340 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Change FKs for preview_attachment so that they are set to null on delete';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE attachment_types DROP FOREIGN KEY FK_EFAED7196DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_EFAED7196DEDCEC2 ON attachment_types');
|
||||
$this->addSql('ALTER TABLE attachment_types CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE attachment_types ADD CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON attachment_types (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE categories DROP FOREIGN KEY FK_3AF346686DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_3AF346686DEDCEC2 ON categories');
|
||||
$this->addSql('ALTER TABLE categories CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE categories ADD CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE currencies DROP FOREIGN KEY FK_37C446936DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_37C446936DEDCEC2 ON currencies');
|
||||
$this->addSql('ALTER TABLE currencies CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE footprints DROP FOREIGN KEY FK_A34D68A26DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_A34D68A26DEDCEC2 ON footprints');
|
||||
$this->addSql('ALTER TABLE footprints CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE footprints ADD CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON footprints (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE `groups` DROP FOREIGN KEY FK_F06D39706DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_F06D39706DEDCEC2 ON `groups`');
|
||||
$this->addSql('ALTER TABLE `groups` CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `groups` ADD CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON `groups` (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE label_profiles DROP FOREIGN KEY FK_C93E9CF56DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_C93E9CF56DEDCEC2 ON label_profiles');
|
||||
$this->addSql('ALTER TABLE label_profiles CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE label_profiles ADD CONSTRAINT FK_C93E9CF5EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_C93E9CF5EA7100A1 ON label_profiles (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE manufacturers DROP FOREIGN KEY FK_94565B126DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_94565B126DEDCEC2 ON manufacturers');
|
||||
$this->addSql('ALTER TABLE manufacturers CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE manufacturers ADD CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON manufacturers (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE measurement_units DROP FOREIGN KEY FK_F5AF83CF6DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_F5AF83CF6DEDCEC2 ON measurement_units');
|
||||
$this->addSql('ALTER TABLE measurement_units CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE measurement_units ADD CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CFEA7100A1 ON measurement_units (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE parts DROP FOREIGN KEY FK_6940A7FE6DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_6940A7FE6DEDCEC2 ON parts');
|
||||
$this->addSql('ALTER TABLE parts CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE projects DROP FOREIGN KEY FK_11074E9A6DEDCEC2');
|
||||
$this->addSql('ALTER TABLE projects DROP FOREIGN KEY FK_5C93B3A4727ACA70');
|
||||
$this->addSql('DROP INDEX IDX_5C93B3A46DEDCEC2 ON projects');
|
||||
$this->addSql('ALTER TABLE projects CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE storelocations DROP FOREIGN KEY FK_75170206DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_75170206DEDCEC2 ON storelocations');
|
||||
$this->addSql('ALTER TABLE storelocations CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE storelocations ADD CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON storelocations (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE suppliers DROP FOREIGN KEY FK_AC28B95C6DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_AC28B95C6DEDCEC2 ON suppliers');
|
||||
$this->addSql('ALTER TABLE suppliers CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE suppliers ADD CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)');
|
||||
$this->addSql('ALTER TABLE `users` DROP FOREIGN KEY FK_1483A5E96DEDCEC2');
|
||||
$this->addSql('DROP INDEX IDX_1483A5E96DEDCEC2 ON `users`');
|
||||
$this->addSql('ALTER TABLE `users` CHANGE id_preview_attachement id_preview_attachment INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `users` ADD CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES `attachments` (id) ON DELETE SET NULL');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON `users` (id_preview_attachment)');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE `attachment_types` DROP FOREIGN KEY FK_EFAED719EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_EFAED719EA7100A1 ON `attachment_types`');
|
||||
$this->addSql('ALTER TABLE `attachment_types` CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `attachment_types` ADD CONSTRAINT FK_EFAED7196DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED7196DEDCEC2 ON `attachment_types` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE `categories` DROP FOREIGN KEY FK_3AF34668EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_3AF34668EA7100A1 ON `categories`');
|
||||
$this->addSql('ALTER TABLE `categories` CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `categories` ADD CONSTRAINT FK_3AF346686DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_3AF346686DEDCEC2 ON `categories` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE currencies DROP FOREIGN KEY FK_37C44693EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_37C44693EA7100A1 ON currencies');
|
||||
$this->addSql('ALTER TABLE currencies CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE currencies ADD CONSTRAINT FK_37C446936DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_37C446936DEDCEC2 ON currencies (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE `footprints` DROP FOREIGN KEY FK_A34D68A2EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_A34D68A2EA7100A1 ON `footprints`');
|
||||
$this->addSql('ALTER TABLE `footprints` CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `footprints` ADD CONSTRAINT FK_A34D68A26DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A26DEDCEC2 ON `footprints` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE `groups` DROP FOREIGN KEY FK_F06D3970EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_F06D3970EA7100A1 ON `groups`');
|
||||
$this->addSql('ALTER TABLE `groups` CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `groups` ADD CONSTRAINT FK_F06D39706DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_F06D39706DEDCEC2 ON `groups` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE label_profiles DROP FOREIGN KEY FK_C93E9CF5EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_C93E9CF5EA7100A1 ON label_profiles');
|
||||
$this->addSql('ALTER TABLE label_profiles CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE label_profiles ADD CONSTRAINT FK_C93E9CF56DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_C93E9CF56DEDCEC2 ON label_profiles (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE log CHANGE level level TINYINT(1) NOT NULL');
|
||||
$this->addSql('ALTER TABLE `manufacturers` DROP FOREIGN KEY FK_94565B12EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_94565B12EA7100A1 ON `manufacturers`');
|
||||
$this->addSql('ALTER TABLE `manufacturers` CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `manufacturers` ADD CONSTRAINT FK_94565B126DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_94565B126DEDCEC2 ON `manufacturers` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE `measurement_units` DROP FOREIGN KEY FK_F5AF83CFEA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_F5AF83CFEA7100A1 ON `measurement_units`');
|
||||
$this->addSql('ALTER TABLE `measurement_units` CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `measurement_units` ADD CONSTRAINT FK_F5AF83CF6DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CF6DEDCEC2 ON `measurement_units` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE `parts` DROP FOREIGN KEY FK_6940A7FEEA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_6940A7FEEA7100A1 ON `parts`');
|
||||
$this->addSql('ALTER TABLE `parts` CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `parts` ADD CONSTRAINT FK_6940A7FE6DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE6DEDCEC2 ON `parts` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE projects DROP FOREIGN KEY FK_5C93B3A4EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_5C93B3A4EA7100A1 ON projects');
|
||||
$this->addSql('ALTER TABLE projects CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE projects ADD CONSTRAINT FK_11074E9A6DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_5C93B3A46DEDCEC2 ON projects (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE `storelocations` DROP FOREIGN KEY FK_7517020EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_7517020EA7100A1 ON `storelocations`');
|
||||
$this->addSql('ALTER TABLE `storelocations` CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `storelocations` ADD CONSTRAINT FK_75170206DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_75170206DEDCEC2 ON `storelocations` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE `suppliers` DROP FOREIGN KEY FK_AC28B95CEA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_AC28B95CEA7100A1 ON `suppliers`');
|
||||
$this->addSql('ALTER TABLE `suppliers` CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `suppliers` ADD CONSTRAINT FK_AC28B95C6DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95C6DEDCEC2 ON `suppliers` (id_preview_attachement)');
|
||||
$this->addSql('ALTER TABLE `users` DROP FOREIGN KEY FK_1483A5E9EA7100A1');
|
||||
$this->addSql('DROP INDEX IDX_1483A5E9EA7100A1 ON `users`');
|
||||
$this->addSql('ALTER TABLE `users` CHANGE permissions_data permissions_data LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', CHANGE id_preview_attachment id_preview_attachement INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `users` ADD CONSTRAINT FK_1483A5E96DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON `users` (id_preview_attachement)');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__attachment_types AS SELECT id, parent_id, id_preview_attachement, filetype_filter, comment, not_selectable, name, last_modified, datetime_added FROM attachment_types');
|
||||
$this->addSql('DROP TABLE attachment_types');
|
||||
$this->addSql('CREATE TABLE attachment_types (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, filetype_filter CLOB NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES attachment_types (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO attachment_types (id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachement, filetype_filter, comment, not_selectable, name, last_modified, datetime_added FROM __temp__attachment_types');
|
||||
$this->addSql('DROP TABLE __temp__attachment_types');
|
||||
$this->addSql('CREATE INDEX attachment_types_idx_parent_name ON attachment_types (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX attachment_types_idx_name ON attachment_types (name)');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON attachment_types (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON attachment_types (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__categories AS SELECT id, parent_id, id_preview_attachement, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added FROM categories');
|
||||
$this->addSql('DROP TABLE categories');
|
||||
$this->addSql('CREATE TABLE categories (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, partname_hint CLOB NOT NULL, partname_regex CLOB NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description CLOB NOT NULL, default_comment CLOB NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO categories (id, parent_id, id_preview_attachment, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachement, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added FROM __temp__categories');
|
||||
$this->addSql('DROP TABLE __temp__categories');
|
||||
$this->addSql('CREATE INDEX category_idx_parent_name ON categories (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX category_idx_name ON categories (name)');
|
||||
$this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachement, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
|
||||
$this->addSql('DROP TABLE currencies');
|
||||
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
|
||||
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C44693EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachement, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
|
||||
$this->addSql('DROP TABLE __temp__currencies');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
|
||||
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
|
||||
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693EA7100A1 ON currencies (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__footprints AS SELECT id, parent_id, id_footprint_3d, id_preview_attachement, comment, not_selectable, name, last_modified, datetime_added FROM footprints');
|
||||
$this->addSql('DROP TABLE footprints');
|
||||
$this->addSql('CREATE TABLE footprints (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_footprint_3d INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A2EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO footprints (id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_footprint_3d, id_preview_attachement, comment, not_selectable, name, last_modified, datetime_added FROM __temp__footprints');
|
||||
$this->addSql('DROP TABLE __temp__footprints');
|
||||
$this->addSql('CREATE INDEX footprint_idx_parent_name ON footprints (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX footprint_idx_name ON footprints (name)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON footprints (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON footprints (id_footprint_3d)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2EA7100A1 ON footprints (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM groups');
|
||||
$this->addSql('DROP TABLE groups');
|
||||
$this->addSql('CREATE TABLE groups (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
|
||||
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D3970EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO groups (id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
|
||||
$this->addSql('DROP TABLE __temp__groups');
|
||||
$this->addSql('CREATE INDEX group_idx_parent_name ON groups (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX group_idx_name ON groups (name)');
|
||||
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON groups (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_F06D3970EA7100A1 ON groups (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__label_profiles AS SELECT id, id_preview_attachement, comment, show_in_dropdown, name, last_modified, datetime_added, options_width, options_height, options_barcode_type, options_picture_type, options_supported_element, options_additional_css, options_lines_mode, options_lines FROM label_profiles');
|
||||
$this->addSql('DROP TABLE label_profiles');
|
||||
$this->addSql('CREATE TABLE label_profiles (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, comment CLOB NOT NULL, show_in_dropdown BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, options_width DOUBLE PRECISION NOT NULL, options_height DOUBLE PRECISION NOT NULL, options_barcode_type VARCHAR(255) NOT NULL, options_picture_type VARCHAR(255) NOT NULL, options_supported_element VARCHAR(255) NOT NULL, options_additional_css CLOB NOT NULL, options_lines_mode VARCHAR(255) NOT NULL, options_lines CLOB NOT NULL, CONSTRAINT FK_C93E9CF5EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO label_profiles (id, id_preview_attachment, comment, show_in_dropdown, name, last_modified, datetime_added, options_width, options_height, options_barcode_type, options_picture_type, options_supported_element, options_additional_css, options_lines_mode, options_lines) SELECT id, id_preview_attachement, comment, show_in_dropdown, name, last_modified, datetime_added, options_width, options_height, options_barcode_type, options_picture_type, options_supported_element, options_additional_css, options_lines_mode, options_lines FROM __temp__label_profiles');
|
||||
$this->addSql('DROP TABLE __temp__label_profiles');
|
||||
$this->addSql('CREATE INDEX IDX_C93E9CF5EA7100A1 ON label_profiles (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM log');
|
||||
$this->addSql('DROP TABLE log');
|
||||
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, datetime DATETIME NOT NULL, level TINYINT(4) NOT NULL, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --(DC2Type:json)
|
||||
, type SMALLINT NOT NULL, username VARCHAR(255) NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO log (id, id_user, datetime, level, target_id, target_type, extra, type, username) SELECT id, id_user, datetime, level, target_id, target_type, extra, type, username FROM __temp__log');
|
||||
$this->addSql('DROP TABLE __temp__log');
|
||||
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
|
||||
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
|
||||
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
|
||||
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, parent_id, id_preview_attachement, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM manufacturers');
|
||||
$this->addSql('DROP TABLE manufacturers');
|
||||
$this->addSql('CREATE TABLE manufacturers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO manufacturers (id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachement, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__manufacturers');
|
||||
$this->addSql('DROP TABLE __temp__manufacturers');
|
||||
$this->addSql('CREATE INDEX manufacturer_idx_parent_name ON manufacturers (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX manufacturer_name ON manufacturers (name)');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON manufacturers (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON manufacturers (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__measurement_units AS SELECT id, parent_id, id_preview_attachement, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added FROM measurement_units');
|
||||
$this->addSql('DROP TABLE measurement_units');
|
||||
$this->addSql('CREATE TABLE measurement_units (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_F5AF83CF727ACA70 FOREIGN KEY (parent_id) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO measurement_units (id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachement, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added FROM __temp__measurement_units');
|
||||
$this->addSql('DROP TABLE __temp__measurement_units');
|
||||
$this->addSql('CREATE INDEX unit_idx_parent_name ON measurement_units (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX unit_idx_name ON measurement_units (name)');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON measurement_units (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CFEA7100A1 ON measurement_units (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachement, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn FROM parts');
|
||||
$this->addSql('DROP TABLE parts');
|
||||
$this->addSql('CREATE TABLE parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url VARCHAR(255) NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, ipn VARCHAR(100) DEFAULT NULL, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO parts (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn) SELECT id, id_preview_attachement, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn FROM __temp__parts');
|
||||
$this->addSql('DROP TABLE __temp__parts');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)');
|
||||
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)');
|
||||
$this->addSql('CREATE INDEX parts_idx_name ON parts (name)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)');
|
||||
$this->addSql('CREATE INDEX parts_idx_ipn ON parts (ipn)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM pricedetails');
|
||||
$this->addSql('DROP TABLE pricedetails');
|
||||
$this->addSql('CREATE TABLE pricedetails (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --(DC2Type:big_decimal)
|
||||
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO pricedetails (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
|
||||
$this->addSql('DROP TABLE __temp__pricedetails');
|
||||
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON pricedetails (orderdetails_id)');
|
||||
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON pricedetails (id_currency)');
|
||||
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON pricedetails (min_discount_quantity)');
|
||||
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON pricedetails (min_discount_quantity, price_related_quantity)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
|
||||
$this->addSql('DROP TABLE project_bom_entries');
|
||||
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
|
||||
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AFC547992F180363 FOREIGN KEY (id_device) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AFC54799C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
|
||||
$this->addSql('DROP TABLE __temp__project_bom_entries');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachement, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM projects');
|
||||
$this->addSql('DROP TABLE projects');
|
||||
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, order_only_missing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, status VARCHAR(64) DEFAULT NULL, description DEFAULT \'\', CONSTRAINT FK_11074E9A727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_5C93B3A4EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachment, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description) SELECT id, parent_id, id_preview_attachement, order_quantity, order_only_missing_parts, comment, not_selectable, name, last_modified, datetime_added, status, description FROM __temp__projects');
|
||||
$this->addSql('DROP TABLE __temp__projects');
|
||||
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_5C93B3A4EA7100A1 ON projects (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__storelocations AS SELECT id, parent_id, storage_type_id, id_preview_attachement, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added FROM storelocations');
|
||||
$this->addSql('DROP TABLE storelocations');
|
||||
$this->addSql('CREATE TABLE storelocations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, storage_type_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES storelocations (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO storelocations (id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, storage_type_id, id_preview_attachement, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added FROM __temp__storelocations');
|
||||
$this->addSql('DROP TABLE __temp__storelocations');
|
||||
$this->addSql('CREATE INDEX location_idx_parent_name ON storelocations (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX location_idx_name ON storelocations (name)');
|
||||
$this->addSql('CREATE INDEX IDX_7517020727ACA70 ON storelocations (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON storelocations (storage_type_id)');
|
||||
$this->addSql('CREATE INDEX IDX_7517020EA7100A1 ON storelocations (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachement, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM suppliers');
|
||||
$this->addSql('DROP TABLE suppliers');
|
||||
$this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --(DC2Type:big_decimal)
|
||||
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachement, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
|
||||
$this->addSql('DROP TABLE __temp__suppliers');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data FROM users');
|
||||
$this->addSql('DROP TABLE users');
|
||||
$this->addSql('CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --(DC2Type:json)
|
||||
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --(DC2Type:json)
|
||||
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --(DC2Type:json)
|
||||
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES groups (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO users (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data FROM __temp__users');
|
||||
$this->addSql('DROP TABLE __temp__users');
|
||||
$this->addSql('CREATE INDEX user_idx_username ON users (name)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON users (name)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON users (group_id)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON users (currency_id)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON users (id_preview_attachment)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
|
||||
$this->addSql('DROP TABLE webauthn_keys');
|
||||
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64)
|
||||
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array)
|
||||
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path)
|
||||
, aaguid CLOB NOT NULL --(DC2Type:aaguid)
|
||||
, credential_public_key CLOB NOT NULL --(DC2Type:base64)
|
||||
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
|
||||
$this->addSql('DROP TABLE __temp__webauthn_keys');
|
||||
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__attachment_types AS SELECT id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added FROM "attachment_types"');
|
||||
$this->addSql('DROP TABLE "attachment_types"');
|
||||
$this->addSql('CREATE TABLE "attachment_types" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, filetype_filter CLOB NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EFAED7196DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "attachment_types" (id, parent_id, id_preview_attachement, filetype_filter, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, filetype_filter, comment, not_selectable, name, last_modified, datetime_added FROM __temp__attachment_types');
|
||||
$this->addSql('DROP TABLE __temp__attachment_types');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON "attachment_types" (parent_id)');
|
||||
$this->addSql('CREATE INDEX attachment_types_idx_name ON "attachment_types" (name)');
|
||||
$this->addSql('CREATE INDEX attachment_types_idx_parent_name ON "attachment_types" (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED7196DEDCEC2 ON "attachment_types" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__categories AS SELECT id, parent_id, id_preview_attachment, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added FROM "categories"');
|
||||
$this->addSql('DROP TABLE "categories"');
|
||||
$this->addSql('CREATE TABLE "categories" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, partname_hint CLOB NOT NULL, partname_regex CLOB NOT NULL, disable_footprints BOOLEAN NOT NULL, disable_manufacturers BOOLEAN NOT NULL, disable_autodatasheets BOOLEAN NOT NULL, disable_properties BOOLEAN NOT NULL, default_description CLOB NOT NULL, default_comment CLOB NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_3AF346686DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "categories" (id, parent_id, id_preview_attachement, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, partname_hint, partname_regex, disable_footprints, disable_manufacturers, disable_autodatasheets, disable_properties, default_description, default_comment, comment, not_selectable, name, last_modified, datetime_added FROM __temp__categories');
|
||||
$this->addSql('DROP TABLE __temp__categories');
|
||||
$this->addSql('CREATE INDEX IDX_3AF34668727ACA70 ON "categories" (parent_id)');
|
||||
$this->addSql('CREATE INDEX category_idx_name ON "categories" (name)');
|
||||
$this->addSql('CREATE INDEX category_idx_parent_name ON "categories" (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_3AF346686DEDCEC2 ON "categories" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__currencies AS SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM currencies');
|
||||
$this->addSql('DROP TABLE currencies');
|
||||
$this->addSql('CREATE TABLE currencies (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, exchange_rate NUMERIC(11, 5) DEFAULT NULL --
|
||||
(DC2Type:big_decimal)
|
||||
, iso_code VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_37C44693727ACA70 FOREIGN KEY (parent_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_37C446936DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO currencies (id, parent_id, id_preview_attachement, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, exchange_rate, iso_code, comment, not_selectable, name, last_modified, datetime_added FROM __temp__currencies');
|
||||
$this->addSql('DROP TABLE __temp__currencies');
|
||||
$this->addSql('CREATE INDEX IDX_37C44693727ACA70 ON currencies (parent_id)');
|
||||
$this->addSql('CREATE INDEX currency_idx_name ON currencies (name)');
|
||||
$this->addSql('CREATE INDEX currency_idx_parent_name ON currencies (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_37C446936DEDCEC2 ON currencies (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__footprints AS SELECT id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added FROM "footprints"');
|
||||
$this->addSql('DROP TABLE "footprints"');
|
||||
$this->addSql('CREATE TABLE "footprints" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_footprint_3d INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_A34D68A2727ACA70 FOREIGN KEY (parent_id) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A232A38C34 FOREIGN KEY (id_footprint_3d) REFERENCES "attachments" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_A34D68A26DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "footprints" (id, parent_id, id_footprint_3d, id_preview_attachement, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_footprint_3d, id_preview_attachment, comment, not_selectable, name, last_modified, datetime_added FROM __temp__footprints');
|
||||
$this->addSql('DROP TABLE __temp__footprints');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A2727ACA70 ON "footprints" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A232A38C34 ON "footprints" (id_footprint_3d)');
|
||||
$this->addSql('CREATE INDEX footprint_idx_name ON "footprints" (name)');
|
||||
$this->addSql('CREATE INDEX footprint_idx_parent_name ON "footprints" (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_A34D68A26DEDCEC2 ON "footprints" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__groups AS SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM "groups"');
|
||||
$this->addSql('DROP TABLE "groups"');
|
||||
$this->addSql('CREATE TABLE "groups" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, enforce_2fa BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --
|
||||
(DC2Type:json)
|
||||
, CONSTRAINT FK_F06D3970727ACA70 FOREIGN KEY (parent_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F06D39706DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "groups" (id, parent_id, id_preview_attachement, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data) SELECT id, parent_id, id_preview_attachment, enforce_2fa, comment, not_selectable, name, last_modified, datetime_added, permissions_data FROM __temp__groups');
|
||||
$this->addSql('DROP TABLE __temp__groups');
|
||||
$this->addSql('CREATE INDEX IDX_F06D3970727ACA70 ON "groups" (parent_id)');
|
||||
$this->addSql('CREATE INDEX group_idx_name ON "groups" (name)');
|
||||
$this->addSql('CREATE INDEX group_idx_parent_name ON "groups" (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_F06D39706DEDCEC2 ON "groups" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__label_profiles AS SELECT id, id_preview_attachment, comment, show_in_dropdown, name, last_modified, datetime_added, options_width, options_height, options_barcode_type, options_picture_type, options_supported_element, options_additional_css, options_lines_mode, options_lines FROM label_profiles');
|
||||
$this->addSql('DROP TABLE label_profiles');
|
||||
$this->addSql('CREATE TABLE label_profiles (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachement INTEGER DEFAULT NULL, comment CLOB NOT NULL, show_in_dropdown BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, options_width DOUBLE PRECISION NOT NULL, options_height DOUBLE PRECISION NOT NULL, options_barcode_type VARCHAR(255) NOT NULL, options_picture_type VARCHAR(255) NOT NULL, options_supported_element VARCHAR(255) NOT NULL, options_additional_css CLOB NOT NULL, options_lines_mode VARCHAR(255) NOT NULL, options_lines CLOB NOT NULL, CONSTRAINT FK_C93E9CF56DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO label_profiles (id, id_preview_attachement, comment, show_in_dropdown, name, last_modified, datetime_added, options_width, options_height, options_barcode_type, options_picture_type, options_supported_element, options_additional_css, options_lines_mode, options_lines) SELECT id, id_preview_attachment, comment, show_in_dropdown, name, last_modified, datetime_added, options_width, options_height, options_barcode_type, options_picture_type, options_supported_element, options_additional_css, options_lines_mode, options_lines FROM __temp__label_profiles');
|
||||
$this->addSql('DROP TABLE __temp__label_profiles');
|
||||
$this->addSql('CREATE INDEX IDX_C93E9CF56DEDCEC2 ON label_profiles (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__log AS SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM log');
|
||||
$this->addSql('DROP TABLE log');
|
||||
$this->addSql('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_user INTEGER DEFAULT NULL, username VARCHAR(255) NOT NULL, datetime DATETIME NOT NULL, level BOOLEAN NOT NULL, target_id INTEGER NOT NULL, target_type SMALLINT NOT NULL, extra CLOB NOT NULL --
|
||||
(DC2Type:json)
|
||||
, type SMALLINT NOT NULL, CONSTRAINT FK_8F3F68C56B3CA4B FOREIGN KEY (id_user) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO log (id, id_user, username, datetime, level, target_id, target_type, extra, type) SELECT id, id_user, username, datetime, level, target_id, target_type, extra, type FROM __temp__log');
|
||||
$this->addSql('DROP TABLE __temp__log');
|
||||
$this->addSql('CREATE INDEX IDX_8F3F68C56B3CA4B ON log (id_user)');
|
||||
$this->addSql('CREATE INDEX log_idx_type ON log (type)');
|
||||
$this->addSql('CREATE INDEX log_idx_type_target ON log (type, target_type, target_id)');
|
||||
$this->addSql('CREATE INDEX log_idx_datetime ON log (datetime)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "manufacturers"');
|
||||
$this->addSql('DROP TABLE "manufacturers"');
|
||||
$this->addSql('CREATE TABLE "manufacturers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B126DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "manufacturers" (id, parent_id, id_preview_attachement, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__manufacturers');
|
||||
$this->addSql('DROP TABLE __temp__manufacturers');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)');
|
||||
$this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)');
|
||||
$this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_94565B126DEDCEC2 ON "manufacturers" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__measurement_units AS SELECT id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added FROM "measurement_units"');
|
||||
$this->addSql('DROP TABLE "measurement_units"');
|
||||
$this->addSql('CREATE TABLE "measurement_units" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, unit VARCHAR(255) DEFAULT NULL, is_integer BOOLEAN NOT NULL, use_si_prefix BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_F5AF83CF727ACA70 FOREIGN KEY (parent_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_F5AF83CF6DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "measurement_units" (id, parent_id, id_preview_attachement, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, unit, is_integer, use_si_prefix, comment, not_selectable, name, last_modified, datetime_added FROM __temp__measurement_units');
|
||||
$this->addSql('DROP TABLE __temp__measurement_units');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CF727ACA70 ON "measurement_units" (parent_id)');
|
||||
$this->addSql('CREATE INDEX unit_idx_name ON "measurement_units" (name)');
|
||||
$this->addSql('CREATE INDEX unit_idx_parent_name ON "measurement_units" (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_F5AF83CF6DEDCEC2 ON "measurement_units" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order FROM "parts"');
|
||||
$this->addSql('DROP TABLE "parts"');
|
||||
$this->addSql('CREATE TABLE "parts" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachement INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url VARCHAR(255) NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, CONSTRAINT FK_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, CONSTRAINT FK_6940A7FE6DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "parts" (id, id_preview_attachement, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order FROM __temp__parts');
|
||||
$this->addSql('DROP TABLE __temp__parts');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)');
|
||||
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)');
|
||||
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
|
||||
$this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE6DEDCEC2 ON "parts" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__pricedetails AS SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM "pricedetails"');
|
||||
$this->addSql('DROP TABLE "pricedetails"');
|
||||
$this->addSql('CREATE TABLE "pricedetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_currency INTEGER DEFAULT NULL, orderdetails_id INTEGER NOT NULL, price NUMERIC(11, 5) NOT NULL --
|
||||
(DC2Type:big_decimal)
|
||||
, price_related_quantity DOUBLE PRECISION NOT NULL, min_discount_quantity DOUBLE PRECISION NOT NULL, manual_input BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_C68C4459398D64AA FOREIGN KEY (id_currency) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_C68C44594A01DDC7 FOREIGN KEY (orderdetails_id) REFERENCES "orderdetails" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "pricedetails" (id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added) SELECT id, id_currency, orderdetails_id, price, price_related_quantity, min_discount_quantity, manual_input, last_modified, datetime_added FROM __temp__pricedetails');
|
||||
$this->addSql('DROP TABLE __temp__pricedetails');
|
||||
$this->addSql('CREATE INDEX IDX_C68C4459398D64AA ON "pricedetails" (id_currency)');
|
||||
$this->addSql('CREATE INDEX IDX_C68C44594A01DDC7 ON "pricedetails" (orderdetails_id)');
|
||||
$this->addSql('CREATE INDEX pricedetails_idx_min_discount ON "pricedetails" (min_discount_quantity)');
|
||||
$this->addSql('CREATE INDEX pricedetails_idx_min_discount_price_qty ON "pricedetails" (min_discount_quantity, price_related_quantity)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__project_bom_entries AS SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM project_bom_entries');
|
||||
$this->addSql('DROP TABLE project_bom_entries');
|
||||
$this->addSql('CREATE TABLE project_bom_entries (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_device INTEGER DEFAULT NULL, id_part INTEGER DEFAULT NULL, price_currency_id INTEGER DEFAULT NULL, quantity DOUBLE PRECISION NOT NULL, mountnames CLOB NOT NULL, name VARCHAR(255) DEFAULT NULL, comment CLOB NOT NULL, price NUMERIC(11, 5) DEFAULT NULL --
|
||||
(DC2Type:big_decimal)
|
||||
, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_1AA2DD312F180363 FOREIGN KEY (id_device) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD31C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1AA2DD313FFDCD60 FOREIGN KEY (price_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO project_bom_entries (id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added) SELECT id, id_device, id_part, price_currency_id, quantity, mountnames, name, comment, price, last_modified, datetime_added FROM __temp__project_bom_entries');
|
||||
$this->addSql('DROP TABLE __temp__project_bom_entries');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD312F180363 ON project_bom_entries (id_device)');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD31C22F6CC4 ON project_bom_entries (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_1AA2DD313FFDCD60 ON project_bom_entries (price_currency_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__projects AS SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM projects');
|
||||
$this->addSql('DROP TABLE projects');
|
||||
$this->addSql('CREATE TABLE projects (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, order_quantity INTEGER NOT NULL, status VARCHAR(64) DEFAULT NULL, order_only_missing_parts BOOLEAN NOT NULL, description CLOB DEFAULT \'""\' NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_5C93B3A4727ACA70 FOREIGN KEY (parent_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_11074E9A6DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO projects (id, parent_id, id_preview_attachement, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, id_preview_attachment, order_quantity, status, order_only_missing_parts, description, comment, not_selectable, name, last_modified, datetime_added FROM __temp__projects');
|
||||
$this->addSql('DROP TABLE __temp__projects');
|
||||
$this->addSql('CREATE INDEX IDX_5C93B3A4727ACA70 ON projects (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_5C93B3A46DEDCEC2 ON projects (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__storelocations AS SELECT id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added FROM "storelocations"');
|
||||
$this->addSql('DROP TABLE "storelocations"');
|
||||
$this->addSql('CREATE TABLE "storelocations" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, storage_type_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, is_full BOOLEAN NOT NULL, only_single_part BOOLEAN NOT NULL, limit_to_existing_parts BOOLEAN NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_7517020727ACA70 FOREIGN KEY (parent_id) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_7517020B270BFF1 FOREIGN KEY (storage_type_id) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_75170206DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "storelocations" (id, parent_id, storage_type_id, id_preview_attachement, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, storage_type_id, id_preview_attachment, is_full, only_single_part, limit_to_existing_parts, comment, not_selectable, name, last_modified, datetime_added FROM __temp__storelocations');
|
||||
$this->addSql('DROP TABLE __temp__storelocations');
|
||||
$this->addSql('CREATE INDEX IDX_7517020727ACA70 ON "storelocations" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_7517020B270BFF1 ON "storelocations" (storage_type_id)');
|
||||
$this->addSql('CREATE INDEX location_idx_name ON "storelocations" (name)');
|
||||
$this->addSql('CREATE INDEX location_idx_parent_name ON "storelocations" (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_75170206DEDCEC2 ON "storelocations" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM "suppliers"');
|
||||
$this->addSql('DROP TABLE "suppliers"');
|
||||
$this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL --
|
||||
(DC2Type:big_decimal)
|
||||
, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95C6DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "suppliers" (id, parent_id, default_currency_id, id_preview_attachement, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added FROM __temp__suppliers');
|
||||
$this->addSql('DROP TABLE __temp__suppliers');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95C6DEDCEC2 ON "suppliers" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data FROM "users"');
|
||||
$this->addSql('DROP TABLE "users"');
|
||||
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachement INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, trusted_device_cookie_version INTEGER NOT NULL, backup_codes CLOB NOT NULL --
|
||||
(DC2Type:json)
|
||||
, google_authenticator_secret VARCHAR(255) DEFAULT NULL, config_timezone VARCHAR(255) DEFAULT NULL, config_language VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, department VARCHAR(255) DEFAULT NULL, last_name VARCHAR(255) DEFAULT NULL, first_name VARCHAR(255) DEFAULT NULL, need_pw_change BOOLEAN NOT NULL, password VARCHAR(255) DEFAULT NULL, name VARCHAR(180) NOT NULL, settings CLOB NOT NULL --
|
||||
(DC2Type:json)
|
||||
, backup_codes_generation_date DATETIME DEFAULT NULL, pw_reset_expires DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, permissions_data CLOB DEFAULT \'[]\' NOT NULL --
|
||||
(DC2Type:json)
|
||||
, CONSTRAINT FK_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E96DEDCEC2 FOREIGN KEY (id_preview_attachement) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachement, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, last_modified, datetime_added, permissions_data FROM __temp__users');
|
||||
$this->addSql('DROP TABLE __temp__users');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
|
||||
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
|
||||
$this->addSql('CREATE INDEX IDX_1483A5E96DEDCEC2 ON "users" (id_preview_attachement)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys');
|
||||
$this->addSql('DROP TABLE webauthn_keys');
|
||||
$this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --
|
||||
(DC2Type:base64)
|
||||
, type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --
|
||||
(DC2Type:array)
|
||||
, attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --
|
||||
(DC2Type:trust_path)
|
||||
, aaguid CLOB NOT NULL --
|
||||
(DC2Type:aaguid)
|
||||
, credential_public_key CLOB NOT NULL --
|
||||
(DC2Type:base64)
|
||||
, user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys');
|
||||
$this->addSql('DROP TABLE __temp__webauthn_keys');
|
||||
$this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)');
|
||||
}
|
||||
|
||||
}
|
||||
40
migrations/Version20230220221024.php
Normal file
40
migrations/Version20230220221024.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20230220221024 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Added support for SAML/Keycloak';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE `users` ADD saml_user TINYINT(1) NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE `users` DROP saml_user');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE users ADD saml_user BOOLEAN NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE `users` DROP saml_user');
|
||||
}
|
||||
}
|
||||
115
src/Command/User/ConvertToSAMLUserCommand.php
Normal file
115
src/Command/User/ConvertToSAMLUserCommand.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Command\User;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Security\SamlUserFactory;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class ConvertToSAMLUserCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user';
|
||||
|
||||
protected EntityManagerInterface $entityManager;
|
||||
protected bool $saml_enabled;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, bool $saml_enabled)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->entityManager = $entityManager;
|
||||
$this->saml_enabled = $saml_enabled;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Converts a local user to a SAML user (and vice versa)')
|
||||
->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.')
|
||||
->addArgument('user', InputArgument::REQUIRED, 'The username (or email) of the user')
|
||||
->addOption('to-local', null, InputOption::VALUE_NONE, 'Converts a SAML user to a local user')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$user_name = $input->getArgument('user');
|
||||
$to_local = $input->getOption('to-local');
|
||||
|
||||
if (!$this->saml_enabled && !$to_local) {
|
||||
$io->confirm('SAML login is not configured. You will not be able to login with this user anymore, when SSO is not configured. Do you really want to continue?');
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = $this->entityManager->getRepository(User::class)->findByEmailOrName($user_name);
|
||||
|
||||
if (!$user) {
|
||||
$io->error('User not found!');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$io->info('User found: '.$user->getFullName(true) . ': '.$user->getEmail().' [ID: ' . $user->getID() . ']');
|
||||
|
||||
if ($to_local) {
|
||||
return $this->toLocal($user, $io);
|
||||
}
|
||||
|
||||
return $this->toSAML($user, $io);
|
||||
}
|
||||
|
||||
public function toLocal(User $user, SymfonyStyle $io): int
|
||||
{
|
||||
$io->confirm('You are going to convert a SAML user to a local user. This means, that the user can only login via the login form. '
|
||||
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
|
||||
|
||||
$user->setSAMLUser(false);
|
||||
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$io->success('User converted to local user! You will need to set a password for this user, before you can login with it.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function toSAML(User $user, SymfonyStyle $io): int
|
||||
{
|
||||
$io->confirm('You are going to convert a local user to a SAML user. This means, that the user can only login via SAML afterwards. The password in the DB will be removed. '
|
||||
. 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?');
|
||||
|
||||
$user->setSAMLUser(true);
|
||||
$user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$io->success('User converted to SAML user! You can now login with this user via SAML.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class SetPasswordCommand extends Command
|
||||
$this
|
||||
->setDescription('Sets the password of a user')
|
||||
->setHelp('This password allows you to set the password of a user, without knowing the old password.')
|
||||
->addArgument('user', InputArgument::REQUIRED, 'The name of the user')
|
||||
->addArgument('user', InputArgument::REQUIRED, 'The username or email of the user')
|
||||
;
|
||||
}
|
||||
|
||||
@@ -65,19 +65,21 @@ class SetPasswordCommand extends Command
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$user_name = $input->getArgument('user');
|
||||
|
||||
/** @var User[] $users */
|
||||
$users = $this->entityManager->getRepository(User::class)->findBy(['name' => $user_name]);
|
||||
$user = $this->entityManager->getRepository(User::class)->findByEmailOrName($user_name);
|
||||
|
||||
if (empty($users)) {
|
||||
if (!$user) {
|
||||
$io->error(sprintf('No user with the given username %s found in the database!', $user_name));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$user = $users[0];
|
||||
|
||||
$io->note('User found!');
|
||||
|
||||
if ($user->isSamlUser()) {
|
||||
$io->error('This user is a SAML user, so you can not change the password!');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$proceed = $io->confirm(
|
||||
sprintf('You are going to change the password of %s with ID %d. Proceed?',
|
||||
$user->getFullName(true), $user->getID()));
|
||||
|
||||
@@ -46,22 +46,39 @@ class UserListCommand extends Command
|
||||
$this
|
||||
->setDescription('Lists all users')
|
||||
->setHelp('This command lists all users in the database.')
|
||||
->addOption('local', 'l', null, 'Only list local users')
|
||||
->addOption('saml', 's', null, 'Only list SAML users')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$only_local = $input->getOption('local');
|
||||
$only_saml = $input->getOption('saml');
|
||||
|
||||
//Get all users from database
|
||||
$users = $this->entityManager->getRepository(User::class)->findAll();
|
||||
if ($only_local && $only_saml) {
|
||||
$io->error('You can not use --local and --saml at the same time!');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$repo = $this->entityManager->getRepository(User::class);
|
||||
|
||||
if ($only_local) {
|
||||
$users = $repo->onlyLocalUsers();
|
||||
} elseif ($only_saml) {
|
||||
$users = $repo->onlySAMLUsers();
|
||||
} else {
|
||||
$users = $repo->findAll();
|
||||
}
|
||||
|
||||
$io->info(sprintf("Found %d users in database.", count($users)));
|
||||
|
||||
$io->title('Users:');
|
||||
|
||||
$table = new Table($output);
|
||||
$table->setHeaders(['ID', 'Username', 'Name', 'Email', 'Group', 'Login Disabled']);
|
||||
$table->setHeaders(['ID', 'Username', 'Name', 'Email', 'Group', 'Login Disabled', 'Type']);
|
||||
|
||||
foreach ($users as $user) {
|
||||
$table->addRow([
|
||||
@@ -71,6 +88,7 @@ class UserListCommand extends Command
|
||||
$user->getEmail(),
|
||||
$user->getGroup() !== null ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group',
|
||||
$user->isDisabled() ? 'Yes' : 'No',
|
||||
$user->isSAMLUser() ? 'SAML' : 'Local',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class UsersPermissionsCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('user', InputArgument::REQUIRED, 'The username of the user to view')
|
||||
->addArgument('user', InputArgument::REQUIRED, 'The username or email of the user to view')
|
||||
->addOption('noInherit', null, InputOption::VALUE_NONE, 'Do not inherit permissions from groups')
|
||||
->addOption('edit', '', InputOption::VALUE_NONE, 'Edit the permissions of the user')
|
||||
;
|
||||
|
||||
@@ -58,6 +58,7 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Validator\ConstraintViolationList;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
@@ -338,20 +339,39 @@ abstract class BaseAdminController extends AbstractController
|
||||
$file = $import_form['file']->getData();
|
||||
$data = $import_form->getData();
|
||||
|
||||
if ($data['format'] === 'auto') {
|
||||
$format = $importer->determineFormat($file->getClientOriginalExtension());
|
||||
if (null === $format) {
|
||||
$this->addFlash('error', 'parts.import.flash.error.unknown_format');
|
||||
goto ret;
|
||||
}
|
||||
} else {
|
||||
$format = $data['format'];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'parent' => $data['parent'],
|
||||
'preserve_children' => $data['preserve_children'],
|
||||
'format' => $data['format'],
|
||||
'csv_separator' => $data['csv_separator'],
|
||||
'parent' => $data['parent'] ?? null,
|
||||
'preserve_children' => $data['preserve_children'] ?? false,
|
||||
'format' => $format,
|
||||
'class' => $this->entity_class,
|
||||
'csv_delimiter' => $data['csv_delimiter'],
|
||||
'abort_on_validation_error' => $data['abort_on_validation_error'],
|
||||
];
|
||||
|
||||
$this->commentHelper->setMessage('Import '.$file->getClientOriginalName());
|
||||
|
||||
$errors = $importer->fileToDBEntities($file, $this->entity_class, $options);
|
||||
try {
|
||||
$errors = $importer->importFileAndPersistToDB($file, $options);
|
||||
|
||||
foreach ($errors as $name => $error) {
|
||||
/** @var ConstraintViolationList $error */
|
||||
$this->addFlash('error', $name.':'.$error);
|
||||
foreach ($errors as $name => $error) {
|
||||
foreach ($error['violations'] as $violation) {
|
||||
$this->addFlash('error', $name.': '.$violation->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UnexpectedValueException $e) {
|
||||
$this->addFlash('error', 'parts.import.flash.error.invalid_file');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +402,7 @@ abstract class BaseAdminController extends AbstractController
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
ret:
|
||||
return $this->renderForm($this->twig_template, [
|
||||
'entity' => $new_entity,
|
||||
'form' => $form,
|
||||
|
||||
@@ -70,7 +70,7 @@ class LabelProfileController extends BaseAdminController
|
||||
* @Route("/{id}/clone", name="label_profile_clone")
|
||||
* @Route("/")
|
||||
*/
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AttachmentType $entity = null): Response
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?LabelProfile $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
141
src/Controller/PartImportExportController.php
Normal file
141
src/Controller/PartImportExportController.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Form\AdminPages\ImportType;
|
||||
use App\Services\ImportExportSystem\EntityExporter;
|
||||
use App\Services\ImportExportSystem\EntityImporter;
|
||||
use App\Services\LogSystem\EventCommentHelper;
|
||||
use App\Services\Parts\PartsTableActionHandler;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class PartImportExportController extends AbstractController
|
||||
{
|
||||
private PartsTableActionHandler $partsTableActionHandler;
|
||||
private EntityImporter $entityImporter;
|
||||
private EventCommentHelper $commentHelper;
|
||||
|
||||
public function __construct(PartsTableActionHandler $partsTableActionHandler,
|
||||
EntityImporter $entityImporter, EventCommentHelper $commentHelper)
|
||||
{
|
||||
$this->partsTableActionHandler = $partsTableActionHandler;
|
||||
$this->entityImporter = $entityImporter;
|
||||
$this->commentHelper = $commentHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parts/import", name="parts_import")
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function importParts(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@parts.import');
|
||||
|
||||
$import_form = $this->createForm(ImportType::class, ['entity_class' => Part::class]);
|
||||
$import_form->handleRequest($request);
|
||||
|
||||
if ($import_form->isSubmitted() && $import_form->isValid()) {
|
||||
/** @var UploadedFile $file */
|
||||
$file = $import_form['file']->getData();
|
||||
$data = $import_form->getData();
|
||||
|
||||
if ($data['format'] === 'auto') {
|
||||
$format = $this->entityImporter->determineFormat($file->getClientOriginalExtension());
|
||||
if (null === $format) {
|
||||
$this->addFlash('error', 'parts.import.flash.error.unknown_format');
|
||||
goto ret;
|
||||
}
|
||||
} else {
|
||||
$format = $data['format'];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'create_unknown_datastructures' => $data['create_unknown_datastructures'],
|
||||
'path_delimiter' => $data['path_delimiter'],
|
||||
'format' => $format,
|
||||
'part_category' => $data['part_category'],
|
||||
'class' => Part::class,
|
||||
'csv_delimiter' => $data['csv_delimiter'],
|
||||
'part_needs_review' => $data['part_needs_review'],
|
||||
'abort_on_validation_error' => $data['abort_on_validation_error'],
|
||||
];
|
||||
|
||||
$this->commentHelper->setMessage('Import '.$file->getClientOriginalName());
|
||||
|
||||
$entities = [];
|
||||
|
||||
try {
|
||||
$errors = $this->entityImporter->importFileAndPersistToDB($file, $options, $entities);
|
||||
} catch (UnexpectedValueException $e) {
|
||||
$this->addFlash('error', 'parts.import.flash.error.invalid_file');
|
||||
if ($e instanceof NotNormalizableValueException) {
|
||||
$this->addFlash('error', $e->getMessage());
|
||||
}
|
||||
goto ret;
|
||||
}
|
||||
|
||||
if (!isset($errors) || $errors) {
|
||||
$this->addFlash('error', 'parts.import.flash.error');
|
||||
} else {
|
||||
$this->addFlash('success', 'parts.import.flash.success');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ret:
|
||||
return $this->renderForm('parts/import/parts_import.html.twig', [
|
||||
'import_form' => $import_form,
|
||||
'imported_entities' => $entities ?? [],
|
||||
'import_errors' => $errors ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/parts/export", name="parts_export", methods={"GET"})
|
||||
* @return Response
|
||||
*/
|
||||
public function exportParts(Request $request, EntityExporter $entityExporter): Response
|
||||
{
|
||||
$ids = $request->query->get('ids', '');
|
||||
$parts = $this->partsTableActionHandler->idStringToArray($ids);
|
||||
|
||||
if (empty($parts)) {
|
||||
throw new \RuntimeException('No parts found!');
|
||||
}
|
||||
|
||||
//Ensure that we have access to the parts
|
||||
foreach ($parts as $part) {
|
||||
$this->denyAccessUnlessGranted('read', $part);
|
||||
}
|
||||
|
||||
return $entityExporter->exportEntityFromRequest($parts, $request);
|
||||
}
|
||||
}
|
||||
@@ -294,6 +294,7 @@ class PartListsController extends AbstractController
|
||||
$filter->setTags($request->query->getBoolean('tags', true));
|
||||
$filter->setStorelocation($request->query->getBoolean('storelocation', true));
|
||||
$filter->setComment($request->query->getBoolean('comment', true));
|
||||
$filter->setIPN($request->query->getBoolean('ipn', true));
|
||||
$filter->setOrdernr($request->query->getBoolean('ordernr', true));
|
||||
$filter->setSupplier($request->query->getBoolean('supplier', false));
|
||||
$filter->setManufacturer($request->query->getBoolean('manufacturer', false));
|
||||
|
||||
@@ -28,17 +28,26 @@ use App\Form\ProjectSystem\ProjectBOMEntryCollectionType;
|
||||
use App\Form\ProjectSystem\ProjectBuildType;
|
||||
use App\Form\Type\StructuralEntityType;
|
||||
use App\Helpers\Projects\ProjectBuildRequest;
|
||||
use App\Services\ImportExportSystem\BOMImporter;
|
||||
use App\Services\ProjectSystem\ProjectBuildHelper;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use League\Csv\Exception;
|
||||
use League\Csv\SyntaxError;
|
||||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Validator\Constraints\NotNull;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
/**
|
||||
* @Route("/project")
|
||||
@@ -119,6 +128,82 @@ class ProjectController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/import_bom", name="project_import_bom", requirements={"id"="\d+"})
|
||||
*/
|
||||
public function importBOM(Request $request, EntityManagerInterface $entityManager, Project $project,
|
||||
BOMImporter $BOMImporter, ValidatorInterface $validator): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('edit', $project);
|
||||
|
||||
$builder = $this->createFormBuilder();
|
||||
$builder->add('file', FileType::class, [
|
||||
'label' => 'import.file',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
'accept' => '.csv'
|
||||
]
|
||||
]);
|
||||
$builder->add('type', ChoiceType::class, [
|
||||
'label' => 'project.bom_import.type',
|
||||
'required' => true,
|
||||
'choices' => [
|
||||
'project.bom_import.type.kicad_pcbnew' => 'kicad_pcbnew',
|
||||
]
|
||||
]);
|
||||
$builder->add('clear_existing_bom', CheckboxType::class, [
|
||||
'label' => 'project.bom_import.clear_existing_bom',
|
||||
'required' => false,
|
||||
'data' => false,
|
||||
'help' => 'project.bom_import.clear_existing_bom.help',
|
||||
]);
|
||||
$builder->add('submit', SubmitType::class, [
|
||||
'label' => 'import.btn',
|
||||
]);
|
||||
|
||||
$form = $builder->getForm();
|
||||
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
|
||||
//Clear existing BOM entries if requested
|
||||
if ($form->get('clear_existing_bom')->getData()) {
|
||||
$project->getBomEntries()->clear();
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
try {
|
||||
$entries = $BOMImporter->importFileIntoProject($form->get('file')->getData(), $project, [
|
||||
'type' => $form->get('type')->getData(),
|
||||
]);
|
||||
|
||||
//Validate the project entries
|
||||
$errors = $validator->validateProperty($project, 'bom_entries');
|
||||
|
||||
//If no validation errors occured, save the changes and redirect to edit page
|
||||
if (count ($errors) === 0) {
|
||||
$this->addFlash('success', t('project.bom_import.flash.success', ['%count%' => count($entries)]));
|
||||
$entityManager->flush();
|
||||
return $this->redirectToRoute('project_edit', ['id' => $project->getID()]);
|
||||
}
|
||||
|
||||
if (count ($errors) > 0) {
|
||||
$this->addFlash('error', t('project.bom_import.flash.invalid_entries'));
|
||||
}
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
$this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()]));
|
||||
} catch (SyntaxError $e) {
|
||||
$this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()]));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renderForm('projects/import_bom.html.twig', [
|
||||
'project' => $project,
|
||||
'form' => $form,
|
||||
'errors' => $errors ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/add_parts", name="project_add_parts_no_id")
|
||||
* @Route("/{id}/add_parts", name="project_add_parts", requirements={"id"="\d+"})
|
||||
|
||||
@@ -95,6 +95,25 @@ class SelectAPIController extends AbstractController
|
||||
return $this->getResponseForClass(Project::class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/export_level", name="select_export_level")
|
||||
*/
|
||||
public function exportLevel(): Response
|
||||
{
|
||||
$entries = [
|
||||
1 => $this->translator->trans('export.level.simple'),
|
||||
2 => $this->translator->trans('export.level.extended'),
|
||||
3 => $this->translator->trans('export.level.full'),
|
||||
];
|
||||
|
||||
return $this->json(array_map(function ($key, $value) {
|
||||
return [
|
||||
'text' => $value,
|
||||
'value' => $key,
|
||||
];
|
||||
}, array_keys($entries), $entries));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/label_profiles", name="select_label_profiles")
|
||||
* @return Response
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Services\Attachments\AttachmentPathResolver;
|
||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||
use App\Services\Attachments\AttachmentURLGenerator;
|
||||
use App\Services\Attachments\BuiltinAttachmentsFinder;
|
||||
use App\Services\Misc\GitVersionInfo;
|
||||
@@ -49,7 +50,8 @@ class ToolsController extends AbstractController
|
||||
/**
|
||||
* @Route("/server_infos", name="tools_server_infos")
|
||||
*/
|
||||
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper): Response
|
||||
public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper,
|
||||
AttachmentSubmitHandler $attachmentSubmitHandler): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@system.server_infos');
|
||||
|
||||
@@ -73,6 +75,9 @@ class ToolsController extends AbstractController
|
||||
'allow_attachments_downloads' => $this->getParameter('partdb.attachments.allow_downloads'),
|
||||
'detailed_error_pages' => $this->getParameter('partdb.error_pages.show_help'),
|
||||
'error_page_admin_email' => $this->getParameter('partdb.error_pages.admin_email'),
|
||||
'configured_max_file_size' => $this->getParameter('partdb.attachments.max_file_size'),
|
||||
'effective_max_file_size' => $attachmentSubmitHandler->getMaximumAllowedUploadSize(),
|
||||
'saml_enabled' => $this->getParameter('partdb.saml.enabled'),
|
||||
|
||||
//PHP section
|
||||
'php_version' => PHP_VERSION,
|
||||
|
||||
@@ -83,6 +83,10 @@ class UserSettingsController extends AbstractController
|
||||
return new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
if ($user->isSamlUser()) {
|
||||
throw new RuntimeException('You can not remove U2F keys from SAML users!');
|
||||
}
|
||||
|
||||
if (empty($user->getBackupCodes())) {
|
||||
$this->addFlash('error', 'tfa_backup.no_codes_enabled');
|
||||
|
||||
@@ -112,6 +116,10 @@ class UserSettingsController extends AbstractController
|
||||
throw new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
if ($user->isSamlUser()) {
|
||||
throw new RuntimeException('You can not remove U2F keys from SAML users!');
|
||||
}
|
||||
|
||||
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
|
||||
//Handle U2F key removal
|
||||
if ($request->request->has('key_id')) {
|
||||
@@ -192,6 +200,10 @@ class UserSettingsController extends AbstractController
|
||||
return new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
if ($user->isSamlUser()) {
|
||||
throw new RuntimeException('You can not remove U2F keys from SAML users!');
|
||||
}
|
||||
|
||||
if ($this->isCsrfTokenValid('devices_reset'.$user->getId(), $request->request->get('_token'))) {
|
||||
$user->invalidateTrustedDeviceTokens();
|
||||
$entityManager->flush();
|
||||
@@ -281,14 +293,14 @@ class UserSettingsController extends AbstractController
|
||||
])
|
||||
->add('old_password', PasswordType::class, [
|
||||
'label' => 'user.settings.pw_old.label',
|
||||
'disabled' => $this->demo_mode,
|
||||
'disabled' => $this->demo_mode || $user->isSamlUser(),
|
||||
'attr' => [
|
||||
'autocomplete' => 'current-password',
|
||||
],
|
||||
'constraints' => [new UserPassword()],
|
||||
]) //This constraint checks, if the current user pw was inputted.
|
||||
->add('new_password', RepeatedType::class, [
|
||||
'disabled' => $this->demo_mode,
|
||||
'disabled' => $this->demo_mode || $user->isSamlUser(),
|
||||
'type' => PasswordType::class,
|
||||
'first_options' => [
|
||||
'label' => 'user.settings.pw_new.label',
|
||||
@@ -307,7 +319,10 @@ class UserSettingsController extends AbstractController
|
||||
'max' => 128,
|
||||
])],
|
||||
])
|
||||
->add('submit', SubmitType::class, ['label' => 'save'])
|
||||
->add('submit', SubmitType::class, [
|
||||
'label' => 'save',
|
||||
'disabled' => $this->demo_mode || $user->isSamlUser(),
|
||||
])
|
||||
->getForm();
|
||||
|
||||
$pw_form->handleRequest($request);
|
||||
@@ -327,7 +342,9 @@ class UserSettingsController extends AbstractController
|
||||
}
|
||||
|
||||
//Handle 2FA things
|
||||
$google_form = $this->createForm(TFAGoogleSettingsType::class, $user);
|
||||
$google_form = $this->createForm(TFAGoogleSettingsType::class, $user, [
|
||||
'disabled' => $this->demo_mode || $user->isSamlUser(),
|
||||
]);
|
||||
$google_enabled = $user->isGoogleAuthenticatorEnabled();
|
||||
if (!$google_enabled && !$form->isSubmitted()) {
|
||||
$user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret());
|
||||
@@ -335,7 +352,7 @@ class UserSettingsController extends AbstractController
|
||||
}
|
||||
$google_form->handleRequest($request);
|
||||
|
||||
if (!$this->demo_mode && $google_form->isSubmitted() && $google_form->isValid()) {
|
||||
if (!$this->demo_mode && !$user->isSamlUser() && $google_form->isSubmitted() && $google_form->isValid()) {
|
||||
if (!$google_enabled) {
|
||||
//Save 2FA settings (save secrets)
|
||||
$user->setGoogleAuthenticatorSecret($google_form->get('googleAuthenticatorSecret')->getData());
|
||||
@@ -369,7 +386,7 @@ class UserSettingsController extends AbstractController
|
||||
])->getForm();
|
||||
|
||||
$backup_form->handleRequest($request);
|
||||
if (!$this->demo_mode && $backup_form->isSubmitted() && $backup_form->isValid()) {
|
||||
if (!$this->demo_mode && !$user->isSamlUser() && $backup_form->isSubmitted() && $backup_form->isValid()) {
|
||||
$backupCodeManager->regenerateBackupCodes($user);
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'user.settings.2fa.backup_codes.regenerated');
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Entity\UserSystem\WebauthnKey;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Jbtronics\TFAWebauthn\Services\TFAWebauthnRegistrationHelper;
|
||||
use RuntimeException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
@@ -31,6 +33,13 @@ use function Symfony\Component\Translation\t;
|
||||
|
||||
class WebauthnKeyRegistrationController extends AbstractController
|
||||
{
|
||||
private bool $demo_mode;
|
||||
|
||||
public function __construct(bool $demo_mode)
|
||||
{
|
||||
$this->demo_mode = $demo_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/webauthn/register", name="webauthn_register")
|
||||
*/
|
||||
@@ -39,6 +48,20 @@ class WebauthnKeyRegistrationController extends AbstractController
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
if ($this->demo_mode) {
|
||||
throw new RuntimeException('You can not do 2FA things in demo mode');
|
||||
}
|
||||
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
if ($user->isSamlUser()) {
|
||||
throw new RuntimeException('You can not remove U2F keys from SAML users!');
|
||||
}
|
||||
|
||||
//If form was submitted, check the auth response
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$webauthnResponse = $request->request->get('_auth_code');
|
||||
|
||||
54
src/DataTables/Adapters/CustomFetchJoinORMAdapter.php
Normal file
54
src/DataTables/Adapters/CustomFetchJoinORMAdapter.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/>.
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Adapters;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\FetchJoinORMAdapter;
|
||||
|
||||
/**
|
||||
* This class is a workaround for a bug (or edge case behavior) in the FetchJoinORMAdapter or better the used Paginator
|
||||
* and CountOutputWalker.
|
||||
* If the query contains multiple GROUP BY clauses, the result of the count query is wrong, as some lines are counted
|
||||
* multiple times. This is because the CountOutputWalker does not use DISTINCT in the count query, if it contains a GROUP BY.
|
||||
*
|
||||
* We work around this by removing the GROUP BY clause from the query, and only adding the first root alias as GROUP BY (the part table).
|
||||
* This way we get the correct count, without breaking the query (we need a GROUP BY for the HAVING clauses).
|
||||
*
|
||||
* As a side effect this also seems to improve the performance of the count query a bit (which makes up a lot of the total query time).
|
||||
*/
|
||||
class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter
|
||||
{
|
||||
public function getCount(QueryBuilder $queryBuilder, $identifier)
|
||||
{
|
||||
$qb_without_group_by = clone $queryBuilder;
|
||||
|
||||
//Remove the groupBy clause from the query for the count
|
||||
//And add the root alias as group by, so we can use HAVING clauses
|
||||
$qb_without_group_by->resetDQLPart('groupBy');
|
||||
$qb_without_group_by->addGroupBy($queryBuilder->getRootAliases()[0]);
|
||||
|
||||
$paginator = new Paginator($qb_without_group_by);
|
||||
|
||||
return $paginator->count();
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ class EntityColumn extends AbstractColumn
|
||||
return sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
$this->urlGenerator->listPartsURL($entity),
|
||||
$entity->getName()
|
||||
htmlspecialchars($entity->getName())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,6 @@ class SIUnitNumberColumn extends AbstractColumn
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->formatter->format((float) $value, $this->options['unit'], $this->options['precision']);
|
||||
return htmlspecialchars($this->formatter->format((float) $value, $this->options['unit'], $this->options['precision']));
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,8 @@ class PartFilter implements FilterInterface
|
||||
This seems to be related to the fact, that PDO does not have an float parameter type and using string type does not work in this situation (at least in SQLite)
|
||||
TODO: Find a better solution here
|
||||
*/
|
||||
$this->amountSum = new IntConstraint('amountSum');
|
||||
//We have to use Having here, as we use an alias column which is not supported on the where clause and would result in an error
|
||||
$this->amountSum = (new IntConstraint('amountSum'))->useHaving();
|
||||
$this->lotCount = new IntConstraint('COUNT(partLots)');
|
||||
|
||||
$this->storelocation = new EntityConstraint($nodesListBuilder, Storelocation::class, 'partLots.storage_location');
|
||||
|
||||
@@ -65,6 +65,9 @@ class PartSearchFilter implements FilterInterface
|
||||
/** @var bool Use footprint name for searching */
|
||||
protected bool $footprint = false;
|
||||
|
||||
/** @var bool Use Internal Part number for searching */
|
||||
protected bool $ipn = true;
|
||||
|
||||
public function __construct(string $query)
|
||||
{
|
||||
$this->keyword = $query;
|
||||
@@ -104,6 +107,9 @@ class PartSearchFilter implements FilterInterface
|
||||
if($this->footprint) {
|
||||
$fields_to_search[] = 'footprint.name';
|
||||
}
|
||||
if ($this->ipn) {
|
||||
$fields_to_search[] = 'part.ipn';
|
||||
}
|
||||
|
||||
return $fields_to_search;
|
||||
}
|
||||
@@ -301,6 +307,17 @@ class PartSearchFilter implements FilterInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isIPN(): bool
|
||||
{
|
||||
return $this->ipn;
|
||||
}
|
||||
|
||||
public function setIPN(bool $ipn): PartSearchFilter
|
||||
{
|
||||
$this->ipn = $ipn;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -67,7 +67,7 @@ class PartDataTableHelper
|
||||
'<a href="%s">%s%s</a>',
|
||||
$this->entityURLGenerator->infoURL($context),
|
||||
$icon,
|
||||
htmlentities($context->getName())
|
||||
htmlspecialchars($context->getName())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class PartDataTableHelper
|
||||
'Part image',
|
||||
$this->attachmentURLGenerator->getThumbnailURL($preview_attachment),
|
||||
$this->attachmentURLGenerator->getThumbnailURL($preview_attachment, 'thumbnail_md'),
|
||||
'img-fluid hoverpic',
|
||||
'hoverpic part-table-image',
|
||||
$title
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\DataTables;
|
||||
|
||||
use App\DataTables\Adapters\CustomFetchJoinORMAdapter;
|
||||
use App\DataTables\Column\EntityColumn;
|
||||
use App\DataTables\Column\IconLinkColumn;
|
||||
use App\DataTables\Column\LocaleDateTimeColumn;
|
||||
@@ -177,7 +178,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
$tmp[] = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
$this->urlGenerator->listPartsURL($lot->getStorageLocation()),
|
||||
$lot->getStorageLocation()->getName()
|
||||
htmlspecialchars($lot->getStorageLocation()->getName())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,13 +193,13 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
$amount = $context->getAmountSum();
|
||||
$expiredAmount = $context->getExpiredAmountSum();
|
||||
|
||||
$ret = $this->amountFormatter->format($amount, $context->getPartUnit());
|
||||
$ret = htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
|
||||
//If we have expired lots, we show them in parentheses behind
|
||||
if ($expiredAmount > 0) {
|
||||
$ret .= sprintf(' <span title="%s" class="text-muted">(+%s)</span>',
|
||||
$this->translator->trans('part_lots.is_expired'),
|
||||
$this->amountFormatter->format($expiredAmount, $context->getPartUnit()));
|
||||
htmlspecialchars($this->amountFormatter->format($expiredAmount, $context->getPartUnit())));
|
||||
}
|
||||
|
||||
|
||||
@@ -210,7 +211,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
'label' => $this->translator->trans('part.table.minamount'),
|
||||
'visible' => false,
|
||||
'render' => function ($value, Part $context) {
|
||||
return $this->amountFormatter->format($value, $context->getPartUnit());
|
||||
return htmlspecialchars($this->amountFormatter->format($value, $context->getPartUnit()));
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -281,7 +282,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
])
|
||||
|
||||
->addOrderBy('name')
|
||||
->createAdapter(FetchJoinORMAdapter::class, [
|
||||
->createAdapter(CustomFetchJoinORMAdapter::class, [
|
||||
'simple_total_query' => true,
|
||||
'query' => function (QueryBuilder $builder): void {
|
||||
$this->getQuery($builder);
|
||||
|
||||
@@ -78,22 +78,23 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||
->add('quantity', TextColumn::class, [
|
||||
'label' => $this->translator->trans('project.bom.quantity'),
|
||||
'className' => 'text-center',
|
||||
'orderField' => 'bom_entry.quantity',
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
//If we have a non-part entry, only show the rounded quantity
|
||||
if ($context->getPart() === null) {
|
||||
return round($context->getQuantity());
|
||||
}
|
||||
//Otherwise use the unit of the part to format the quantity
|
||||
return $this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit());
|
||||
return htmlspecialchars($this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit()));
|
||||
},
|
||||
])
|
||||
|
||||
->add('name', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.name'),
|
||||
'orderable' => false,
|
||||
'orderField' => 'part.name',
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
if($context->getPart() === null) {
|
||||
return $context->getName();
|
||||
return htmlspecialchars($context->getName());
|
||||
}
|
||||
if($context->getPart() !== null) {
|
||||
$tmp = $this->partDataTableHelper->renderName($context->getPart());
|
||||
@@ -121,15 +122,18 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||
->add('category', EntityColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.category'),
|
||||
'property' => 'part.category',
|
||||
'orderField' => 'category.name',
|
||||
])
|
||||
->add('footprint', EntityColumn::class, [
|
||||
'property' => 'part.footprint',
|
||||
'label' => $this->translator->trans('part.table.footprint'),
|
||||
'orderField' => 'footprint.name',
|
||||
])
|
||||
|
||||
->add('manufacturer', EntityColumn::class, [
|
||||
'property' => 'part.manufacturer',
|
||||
'label' => $this->translator->trans('part.table.manufacturer'),
|
||||
'orderField' => 'manufacturer.name',
|
||||
])
|
||||
|
||||
->add('mountnames', TextColumn::class, [
|
||||
@@ -155,6 +159,8 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||
])
|
||||
;
|
||||
|
||||
$dataTable->addOrderBy('name', DataTable::SORT_ASCENDING);
|
||||
|
||||
$dataTable->createAdapter(ORMAdapter::class, [
|
||||
'entity' => Attachment::class,
|
||||
'query' => function (QueryBuilder $builder) use ($options): void {
|
||||
@@ -175,6 +181,9 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||
->addSelect('part')
|
||||
->from(ProjectBOMEntry::class, 'bom_entry')
|
||||
->leftJoin('bom_entry.part', 'part')
|
||||
->leftJoin('part.category', 'category')
|
||||
->leftJoin('part.footprint', 'footprint')
|
||||
->leftJoin('part.manufacturer', 'manufacturer')
|
||||
->where('bom_entry.project = :project')
|
||||
->setParameter('project', $options['project'])
|
||||
;
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace App\Entity\Attachments;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Validator\Constraints\Selectable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use function in_array;
|
||||
use InvalidArgumentException;
|
||||
@@ -95,6 +96,14 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||
*/
|
||||
protected string $path = '';
|
||||
|
||||
/**
|
||||
* @var string the name of this element
|
||||
* @ORM\Column(type="string")
|
||||
* @Assert\NotBlank(message="validator.attachment.name_not_blank")
|
||||
* @Groups({"simple", "extended", "full"})
|
||||
*/
|
||||
protected string $name = '';
|
||||
|
||||
/**
|
||||
* ORM mapping is done in sub classes (like PartAttachment).
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,7 @@ use App\Entity\Contracts\HasMasterAttachmentInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* @ORM\MappedSuperclass()
|
||||
@@ -43,6 +44,7 @@ abstract class AttachmentContainingDBElement extends AbstractNamedDBElement impl
|
||||
* //@ORM\OneToMany(targetEntity="Attachment", mappedBy="element")
|
||||
*
|
||||
* Mapping is done in sub classes like part
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected $attachments;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use function is_string;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -36,18 +37,21 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement
|
||||
/**
|
||||
* @var string The address of the company
|
||||
* @ORM\Column(type="string")
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $address = '';
|
||||
|
||||
/**
|
||||
* @var string The phone number of the company
|
||||
* @ORM\Column(type="string")
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $phone_number = '';
|
||||
|
||||
/**
|
||||
* @var string The fax number of the company
|
||||
* @ORM\Column(type="string")
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $fax_number = '';
|
||||
|
||||
@@ -55,6 +59,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement
|
||||
* @var string The email address of the company
|
||||
* @ORM\Column(type="string")
|
||||
* @Assert\Email()
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $email_address = '';
|
||||
|
||||
@@ -62,6 +67,7 @@ abstract class AbstractCompany extends AbstractPartsContainingDBElement
|
||||
* @var string The website of the company
|
||||
* @ORM\Column(type="string")
|
||||
* @Assert\Url()
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $website = '';
|
||||
|
||||
|
||||
@@ -38,20 +38,37 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||
* @ORM\MappedSuperclass(repositoryClass="App\Repository\DBElementRepository")
|
||||
*
|
||||
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||
* "attachment_type" = "App\Entity\AttachmentType",
|
||||
* "attachment" = "App\Entity\Attachment",
|
||||
* "category" = "App\Entity\Attachment",
|
||||
* "attachment_type" = "App\Entity\Attachments\AttachmentType",
|
||||
* "attachment" = "App\Entity\Attachments\Attachment",
|
||||
* "attachment_type_attachment" = "App\Entity\Attachments\AttachmentTypeAttachment",
|
||||
* "category_attachment" = "App\Entity\Attachments\CategoryAttachment",
|
||||
* "currency_attachment" = "App\Entity\Attachments\CurrencyAttachment",
|
||||
* "footprint_attachment" = "App\Entity\Attachments\FootprintAttachment",
|
||||
* "group_attachment" = "App\Entity\Attachments\GroupAttachment",
|
||||
* "label_attachment" = "App\Entity\Attachments\LabelAttachment",
|
||||
* "manufacturer_attachment" = "App\Entity\Attachments\ManufacturerAttachment",
|
||||
* "measurement_unit_attachment" = "App\Entity\Attachments\MeasurementUnitAttachment",
|
||||
* "part_attachment" = "App\Entity\Attachments\PartAttachment",
|
||||
* "project_attachment" = "App\Entity\Attachments\ProjectAttachment",
|
||||
* "storelocation_attachment" = "App\Entity\Attachments\StorelocationAttachment",
|
||||
* "supplier_attachment" = "App\Entity\Attachments\SupplierAttachment",
|
||||
* "user_attachment" = "App\Entity\Attachments\UserAttachment",
|
||||
* "category" = "App\Entity\Parts\Category",
|
||||
* "project" = "App\Entity\ProjectSystem\Project",
|
||||
* "project_bom_entry" = "App\Entity\ProjectSystem\ProjectBOMEntry",
|
||||
* "footprint" = "App\Entity\Footprint",
|
||||
* "group" = "App\Entity\Group",
|
||||
* "manufacturer" = "App\Entity\Manufacturer",
|
||||
* "orderdetail" = "App\Entity\Orderdetail",
|
||||
* "part" = "App\Entity\Part",
|
||||
* "pricedetail" = "App\Entity\Pricedetail",
|
||||
* "storelocation" = "App\Entity\Storelocation",
|
||||
* "supplier" = "App\Entity\Supplier",
|
||||
* "user" = "App\Entity\User"
|
||||
* "footprint" = "App\Entity\Parts\Footprint",
|
||||
* "group" = "App\Entity\UserSystem\Group",
|
||||
* "manufacturer" = "App\Entity\Parts\Manufacturer",
|
||||
* "orderdetail" = "App\Entity\PriceInformations\Orderdetail",
|
||||
* "part" = "App\Entity\Parts\Part",
|
||||
* "pricedetail" = "App\Entity\PriceInformation\Pricedetail",
|
||||
* "storelocation" = "App\Entity\Parts\Storelocation",
|
||||
* "part_lot" = "App\Entity\Parts\PartLot",
|
||||
* "currency" = "App\Entity\PriceInformations\Currency",
|
||||
* "measurement_unit" = "App\Entity\Parts\MeasurementUnit",
|
||||
* "parameter" = "App\Entity\Parts\AbstractParameter",
|
||||
* "supplier" = "App\Entity\Parts\Supplier",
|
||||
* "user" = "App\Entity\UserSystem\User"
|
||||
* })
|
||||
*/
|
||||
abstract class AbstractDBElement implements JsonSerializable
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class AbstractNamedDBElement extends AbstractDBElement implements Named
|
||||
* @var string the name of this element
|
||||
* @ORM\Column(type="string")
|
||||
* @Assert\NotBlank()
|
||||
* @Groups({"simple", "extended", "full"})
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected string $name = '';
|
||||
|
||||
|
||||
@@ -23,13 +23,15 @@ declare(strict_types=1);
|
||||
namespace App\Entity\Base;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
|
||||
/**
|
||||
* Class PartsContainingDBElement.
|
||||
*
|
||||
* @ORM\MappedSuperclass(repositoryClass="App\Repository\AbstractPartsContainingRepository")
|
||||
*/
|
||||
abstract class
|
||||
AbstractPartsContainingDBElement extends AbstractStructuralDBElement
|
||||
abstract class AbstractPartsContainingDBElement extends AbstractStructuralDBElement
|
||||
{
|
||||
/** @Groups({"full"}) */
|
||||
protected $parameters;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||
/**
|
||||
* @var string The comment info for this element
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"simple", "extended", "full"})
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected string $comment = '';
|
||||
|
||||
@@ -71,6 +71,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||
* @var bool If this property is set, this element can not be selected for part properties.
|
||||
* Useful if this element should be used only for grouping, sorting.
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $not_selectable = false;
|
||||
|
||||
@@ -91,7 +92,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||
/**
|
||||
* @var AbstractStructuralDBElement
|
||||
* @NoneOfItsChildren()
|
||||
* @Groups({"include_parents"})
|
||||
* @Groups({"include_parents", "import"})
|
||||
*/
|
||||
protected $parent = null;
|
||||
|
||||
@@ -282,6 +283,12 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
|
||||
*/
|
||||
public function getSubelements(): iterable
|
||||
{
|
||||
//If the parent is equal to this object, we would get an endless loop, so just return an empty array
|
||||
//This is just a workaround, as validator should prevent this behaviour, before it gets written to the database
|
||||
if ($this->parent === $this) {
|
||||
return new ArrayCollection();
|
||||
}
|
||||
|
||||
return $this->children ?? new ArrayCollection();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ trait MasterAttachmentTrait
|
||||
/**
|
||||
* @var Attachment
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\Attachments\Attachment")
|
||||
* @ORM\JoinColumn(name="id_preview_attachement", referencedColumnName="id")
|
||||
* @ORM\JoinColumn(name="id_preview_attachment", referencedColumnName="id", onDelete="SET NULL", nullable=true)
|
||||
* @Assert\Expression("value == null or value.isPicture()", message="part.master_attachment.must_be_picture")
|
||||
*/
|
||||
protected ?Attachment $master_picture_attachment = null;
|
||||
|
||||
@@ -46,6 +46,7 @@ use App\Entity\Base\AbstractNamedDBElement;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
use function sprintf;
|
||||
@@ -84,6 +85,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement
|
||||
* @var string The mathematical symbol for this specification. Can be rendered pretty later. Should be short
|
||||
* @Assert\Length(max=20)
|
||||
* @ORM\Column(type="string", nullable=false)
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $symbol = '';
|
||||
|
||||
@@ -93,6 +95,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement
|
||||
* @Assert\LessThanOrEqual(propertyPath="value_typical", message="parameters.validator.min_lesser_typical")
|
||||
* @Assert\LessThan(propertyPath="value_max", message="parameters.validator.min_lesser_max")
|
||||
* @ORM\Column(type="float", nullable=true)
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected ?float $value_min = null;
|
||||
|
||||
@@ -100,6 +103,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement
|
||||
* @var float|null the typical value of this property
|
||||
* @Assert\Type({"null", "float"})
|
||||
* @ORM\Column(type="float", nullable=true)
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected ?float $value_typical = null;
|
||||
|
||||
@@ -108,24 +112,29 @@ abstract class AbstractParameter extends AbstractNamedDBElement
|
||||
* @Assert\Type({"float", "null"})
|
||||
* @Assert\GreaterThanOrEqual(propertyPath="value_typical", message="parameters.validator.max_greater_typical")
|
||||
* @ORM\Column(type="float", nullable=true)
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected ?float $value_max = null;
|
||||
|
||||
/**
|
||||
* @var string The unit in which the value values are given (e.g. V)
|
||||
* @ORM\Column(type="string", nullable=false)
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $unit = '';
|
||||
|
||||
/**
|
||||
* @var string a text value for the given property
|
||||
* @ORM\Column(type="string", nullable=false)
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $value_text = '';
|
||||
|
||||
/**
|
||||
* @var string the group this parameter belongs to
|
||||
* @ORM\Column(type="string", nullable=false, name="param_group")
|
||||
* @Groups({"full"})
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected string $group = '';
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Parameters\CategoryParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -56,48 +57,56 @@ class Category extends AbstractPartsContainingDBElement
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected string $partname_hint = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected string $partname_regex = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $disable_footprints = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $disable_manufacturers = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $disable_autodatasheets = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $disable_properties = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected string $default_description = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected string $default_comment = '';
|
||||
/**
|
||||
@@ -105,6 +114,7 @@ class Category extends AbstractPartsContainingDBElement
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\CategoryAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @ORM\OrderBy({"name" = "ASC"})
|
||||
* @Assert\Valid()
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected $attachments;
|
||||
|
||||
@@ -112,6 +122,7 @@ class Category extends AbstractPartsContainingDBElement
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\CategoryParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
|
||||
* @Assert\Valid()
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected $parameters;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ use App\Entity\Parameters\MeasurementUnitParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -48,6 +49,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement
|
||||
* or m (for meters).
|
||||
* @ORM\Column(type="string", name="unit", nullable=true)
|
||||
* @Assert\Length(max=10)
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?string $unit = null;
|
||||
|
||||
@@ -55,6 +57,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement
|
||||
* @var bool Determines if the amount value associated with this unit should be treated as integer.
|
||||
* Set to false, to measure continuous sizes likes masses or lengths.
|
||||
* @ORM\Column(type="boolean", name="is_integer")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected bool $is_integer = false;
|
||||
|
||||
@@ -63,6 +66,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement
|
||||
* Useful for sizes like meters. For this the unit must be set
|
||||
* @ORM\Column(type="boolean", name="use_si_prefix")
|
||||
* @Assert\Expression("this.isUseSIPrefix() == false or this.getUnit() != null", message="validator.measurement_unit.use_si_prefix_needs_unit")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $use_si_prefix = false;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
@@ -72,6 +73,7 @@ class Part extends AttachmentContainingDBElement
|
||||
* @Assert\Valid()
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\PartParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected $parameters;
|
||||
|
||||
@@ -96,6 +98,7 @@ class Part extends AttachmentContainingDBElement
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\PartAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @ORM\OrderBy({"name" = "ASC"})
|
||||
* @Assert\Valid()
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected $attachments;
|
||||
|
||||
@@ -108,7 +111,7 @@ class Part extends AttachmentContainingDBElement
|
||||
/**
|
||||
* @var Attachment
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\Attachments\Attachment")
|
||||
* @ORM\JoinColumn(name="id_preview_attachement", referencedColumnName="id")
|
||||
* @ORM\JoinColumn(name="id_preview_attachment", referencedColumnName="id", onDelete="SET NULL", nullable=true)
|
||||
* @Assert\Expression("value == null or value.isPicture()", message="part.master_attachment.must_be_picture")
|
||||
*/
|
||||
protected ?Attachment $master_picture_attachment = null;
|
||||
|
||||
@@ -31,6 +31,7 @@ use App\Validator\Constraints\ValidPartLot;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Exception;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -52,12 +53,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
||||
/**
|
||||
* @var string A short description about this lot, shown in table
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected string $description = '';
|
||||
|
||||
/**
|
||||
* @var string a comment stored with this lot
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected string $comment = '';
|
||||
|
||||
@@ -65,6 +68,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
||||
* @var ?DateTime Set a time until when the lot must be used.
|
||||
* Set to null, if the lot can be used indefinitely.
|
||||
* @ORM\Column(type="datetime", name="expiration_date", nullable=true)
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?DateTime $expiration_date = null;
|
||||
|
||||
@@ -73,12 +77,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
||||
* @ORM\ManyToOne(targetEntity="Storelocation")
|
||||
* @ORM\JoinColumn(name="id_store_location", referencedColumnName="id", nullable=true)
|
||||
* @Selectable()
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected ?Storelocation $storage_location = null;
|
||||
|
||||
/**
|
||||
* @var bool If this is set to true, the instock amount is marked as not known
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected bool $instock_unknown = false;
|
||||
|
||||
@@ -86,12 +92,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
|
||||
* @var float For continuous sizes (length, volume, etc.) the instock is saved here.
|
||||
* @ORM\Column(type="float")
|
||||
* @Assert\PositiveOrZero()
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected float $amount = 0.0;
|
||||
|
||||
/**
|
||||
* @var bool determines if this lot was manually marked for refilling
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected bool $needs_refill = false;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace App\Entity\Parts\PartTraits;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -34,12 +35,14 @@ trait AdvancedPropertyTrait
|
||||
/**
|
||||
* @var bool Determines if this part entry needs review (for example, because it is work in progress)
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected bool $needs_review = false;
|
||||
|
||||
/**
|
||||
* @var string a comma separated list of tags, associated with the part
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected string $tags = '';
|
||||
|
||||
@@ -47,6 +50,7 @@ trait AdvancedPropertyTrait
|
||||
* @var float|null how much a single part unit weighs in grams
|
||||
* @ORM\Column(type="float", nullable=true)
|
||||
* @Assert\PositiveOrZero()
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?float $mass = null;
|
||||
|
||||
@@ -54,7 +58,7 @@ trait AdvancedPropertyTrait
|
||||
* @var string The internal part number of the part
|
||||
* @ORM\Column(type="string", length=100, nullable=true, unique=true)
|
||||
* @Assert\Length(max="100")
|
||||
*
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?string $ipn = null;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Validator\Constraints\Selectable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
trait BasicPropertyTrait
|
||||
@@ -33,12 +34,14 @@ trait BasicPropertyTrait
|
||||
/**
|
||||
* @var string A text describing what this part does
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected string $description = '';
|
||||
|
||||
/**
|
||||
* @var string A comment/note related to this part
|
||||
* @ORM\Column(type="text")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected string $comment = '';
|
||||
|
||||
@@ -51,6 +54,7 @@ trait BasicPropertyTrait
|
||||
/**
|
||||
* @var bool true, if the part is marked as favorite
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected bool $favorite = false;
|
||||
|
||||
@@ -61,6 +65,7 @@ trait BasicPropertyTrait
|
||||
* @ORM\JoinColumn(name="id_category", referencedColumnName="id", nullable=false)
|
||||
* @Selectable()
|
||||
* @Assert\NotNull(message="validator.select_valid_category")
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected ?Category $category = null;
|
||||
|
||||
@@ -69,6 +74,7 @@ trait BasicPropertyTrait
|
||||
* @ORM\ManyToOne(targetEntity="Footprint")
|
||||
* @ORM\JoinColumn(name="id_footprint", referencedColumnName="id")
|
||||
* @Selectable()
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected ?Footprint $footprint = null;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use App\Entity\Parts\MeasurementUnit;
|
||||
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\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -38,6 +39,7 @@ trait InstockTrait
|
||||
* @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @Assert\Valid()
|
||||
* @ORM\OrderBy({"amount" = "DESC"})
|
||||
* @Groups({"extended", "full"})
|
||||
*/
|
||||
protected $partLots;
|
||||
|
||||
@@ -46,6 +48,7 @@ trait InstockTrait
|
||||
* Given in the partUnit.
|
||||
* @ORM\Column(type="float")
|
||||
* @Assert\PositiveOrZero()
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected float $minamount = 0;
|
||||
|
||||
@@ -53,6 +56,7 @@ trait InstockTrait
|
||||
* @var ?MeasurementUnit the unit in which the part's amount is measured
|
||||
* @ORM\ManyToOne(targetEntity="MeasurementUnit")
|
||||
* @ORM\JoinColumn(name="id_part_unit", referencedColumnName="id", nullable=true)
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?MeasurementUnit $partUnit = null;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Validator\Constraints\Selectable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -38,6 +39,7 @@ trait ManufacturerTrait
|
||||
* @ORM\ManyToOne(targetEntity="Manufacturer")
|
||||
* @ORM\JoinColumn(name="id_manufacturer", referencedColumnName="id")
|
||||
* @Selectable()
|
||||
* @Groups({"simple","extended", "full", "import"})
|
||||
*/
|
||||
protected ?Manufacturer $manufacturer = null;
|
||||
|
||||
@@ -45,12 +47,14 @@ trait ManufacturerTrait
|
||||
* @var string the url to the part on the manufacturer's homepage
|
||||
* @ORM\Column(type="string")
|
||||
* @Assert\Url()
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected string $manufacturer_product_url = '';
|
||||
|
||||
/**
|
||||
* @var string The product number used by the manufacturer. If this is set to "", the name field is used.
|
||||
* @ORM\Column(type="string")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected string $manufacturer_product_number = '';
|
||||
|
||||
@@ -58,6 +62,7 @@ trait ManufacturerTrait
|
||||
* @var string The production status of this part. Can be one of the specified ones.
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Assert\Choice({"announced", "active", "nrfnd", "eol", "discontinued", ""})
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?string $manufacturing_status = '';
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace App\Entity\Parts\PartTraits;
|
||||
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use function count;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
@@ -39,6 +40,7 @@ trait OrderTrait
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @Assert\Valid()
|
||||
* @ORM\OrderBy({"supplierpartnr" = "ASC"})
|
||||
* @Groups({"extended", "full"})
|
||||
*/
|
||||
protected $orderdetails;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ use App\Entity\Base\AbstractPartsContainingDBElement;
|
||||
use App\Entity\Parameters\StorelocationParameter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -70,20 +71,24 @@ class Storelocation extends AbstractPartsContainingDBElement
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $is_full = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $only_single_part = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected bool $limit_to_existing_parts = false;
|
||||
|
||||
/**
|
||||
* @var Collection<int, StorelocationAttachment>
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\StorelocationAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
|
||||
@@ -31,6 +31,7 @@ use App\Validator\Constraints\Selectable;
|
||||
use Brick\Math\BigDecimal;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -74,6 +75,7 @@ class Supplier extends AbstractCompany
|
||||
/**
|
||||
* @var BigDecimal|null the shipping costs that have to be paid, when ordering via this supplier
|
||||
* @ORM\Column(name="shipping_costs", nullable=true, type="big_decimal", precision=11, scale=5)
|
||||
* @Groups({"extended", "full", "import"})
|
||||
* @BigDecimalPositiveOrZero()
|
||||
*/
|
||||
protected ?BigDecimal $shipping_costs = null;
|
||||
|
||||
@@ -32,6 +32,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -60,6 +61,7 @@ class Currency extends AbstractStructuralDBElement
|
||||
* @var string the 3-letter ISO code of the currency
|
||||
* @ORM\Column(type="string")
|
||||
* @Assert\Currency()
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected string $iso_code = "";
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -54,18 +55,21 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N
|
||||
* @ORM\OneToMany(targetEntity="Pricedetail", mappedBy="orderdetail", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @Assert\Valid()
|
||||
* @ORM\OrderBy({"min_discount_quantity" = "ASC"})
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected $pricedetails;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected string $supplierpartnr = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected bool $obsolete = false;
|
||||
|
||||
@@ -73,6 +77,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N
|
||||
* @var string
|
||||
* @ORM\Column(type="string")
|
||||
* @Assert\Url()
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected string $supplier_product_url = '';
|
||||
|
||||
@@ -89,6 +94,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\Parts\Supplier", inversedBy="orderdetails")
|
||||
* @ORM\JoinColumn(name="id_supplier", referencedColumnName="id")
|
||||
* @Assert\NotNull(message="validator.orderdetail.supplier_must_not_be_null")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?Supplier $supplier = null;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ use Brick\Math\RoundingMode;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface
|
||||
* @var BigDecimal The price related to the detail. (Given in the selected currency)
|
||||
* @ORM\Column(type="big_decimal", precision=11, scale=5)
|
||||
* @BigDecimalPositive()
|
||||
* @Groups({"extended", "full"})
|
||||
*/
|
||||
protected BigDecimal $price;
|
||||
|
||||
@@ -64,6 +66,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface
|
||||
* @ORM\ManyToOne(targetEntity="Currency", inversedBy="pricedetails")
|
||||
* @ORM\JoinColumn(name="id_currency", referencedColumnName="id", nullable=true)
|
||||
* @Selectable()
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?Currency $currency = null;
|
||||
|
||||
@@ -71,6 +74,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface
|
||||
* @var float
|
||||
* @ORM\Column(type="float")
|
||||
* @Assert\Positive()
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected float $price_related_quantity = 1.0;
|
||||
|
||||
@@ -78,6 +82,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface
|
||||
* @var float
|
||||
* @ORM\Column(type="float")
|
||||
* @Assert\Positive()
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected float $min_discount_quantity = 1.0;
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
@@ -57,6 +58,7 @@ class Project extends AbstractStructuralDBElement
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="ProjectBOMEntry", mappedBy="project", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
* @Assert\Valid()
|
||||
* @Groups({"extended", "full"})
|
||||
*/
|
||||
protected $bom_entries;
|
||||
|
||||
@@ -69,6 +71,7 @@ class Project extends AbstractStructuralDBElement
|
||||
* @var string The current status of the project
|
||||
* @ORM\Column(type="string", length=64, nullable=true)
|
||||
* @Assert\Choice({"draft","planning","in_production","finished","archived"})
|
||||
* @Groups({"extended", "full"})
|
||||
*/
|
||||
protected ?string $status = null;
|
||||
|
||||
@@ -86,6 +89,7 @@ class Project extends AbstractStructuralDBElement
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, columnDefinition="DEFAULT ''")
|
||||
* @Groups({"simple", "extended", "full"})
|
||||
*/
|
||||
protected string $description = '';
|
||||
|
||||
|
||||
@@ -110,7 +110,8 @@ class ProjectBOMEntry extends AbstractDBElement
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->price = BigDecimal::zero()->toScale(5);
|
||||
//$this->price = BigDecimal::zero()->toScale(5);
|
||||
$this->price = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@ use App\Validator\Constraints\ValidPermission;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -65,6 +66,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
|
||||
/**
|
||||
* @var bool If true all users associated with this group must have enabled some kind of 2 factor authentication
|
||||
* @ORM\Column(type="boolean", name="enforce_2fa")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected $enforce2FA = false;
|
||||
/**
|
||||
@@ -79,6 +81,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
|
||||
* @var PermissionData|null
|
||||
* @ValidPermission()
|
||||
* @ORM\Embedded(class="PermissionData", columnPrefix="permissions_")
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected ?PermissionData $permissions = null;
|
||||
|
||||
|
||||
@@ -30,8 +30,10 @@ use App\Security\Interfaces\HasPermissionsInterface;
|
||||
use App\Validator\Constraints\Selectable;
|
||||
use App\Validator\Constraints\ValidPermission;
|
||||
use App\Validator\Constraints\ValidTheme;
|
||||
use Hslavich\OneloginSamlBundle\Security\User\SamlUserInterface;
|
||||
use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Webauthn\PublicKeyCredentialUserEntity;
|
||||
use function count;
|
||||
use DateTime;
|
||||
@@ -60,7 +62,8 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface
|
||||
* @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"})
|
||||
* @UniqueEntity("name", message="validator.user.username_already_used")
|
||||
*/
|
||||
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface
|
||||
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface,
|
||||
BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface, SamlUserInterface
|
||||
{
|
||||
//use MasterAttachmentTrait;
|
||||
|
||||
@@ -72,6 +75,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
/**
|
||||
* @var bool Determines if the user is disabled (user can not log in)
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected bool $disabled = false;
|
||||
|
||||
@@ -79,6 +83,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
* @var string|null The theme
|
||||
* @ORM\Column(type="string", name="config_theme", nullable=true)
|
||||
* @ValidTheme()
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected ?string $theme = null;
|
||||
|
||||
@@ -122,6 +127,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
* @ORM\ManyToOne(targetEntity="Group", inversedBy="users")
|
||||
* @ORM\JoinColumn(name="group_id", referencedColumnName="id")
|
||||
* @Selectable()
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected ?Group $group = null;
|
||||
|
||||
@@ -135,6 +141,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
* @var string|null The timezone the user prefers
|
||||
* @ORM\Column(type="string", name="config_timezone", nullable=true)
|
||||
* @Assert\Timezone()
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected ?string $timezone = '';
|
||||
|
||||
@@ -142,6 +149,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
* @var string|null The language/locale the user prefers
|
||||
* @ORM\Column(type="string", name="config_language", nullable=true)
|
||||
* @Assert\Language()
|
||||
* @Groups({"full", "import"})
|
||||
*/
|
||||
protected ?string $language = '';
|
||||
|
||||
@@ -149,30 +157,35 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
* @var string|null The email address of the user
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Assert\Email()
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected ?string $email = '';
|
||||
|
||||
/**
|
||||
* @var string|null The department the user is working
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected ?string $department = '';
|
||||
|
||||
/**
|
||||
* @var string|null The last name of the User
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected ?string $last_name = '';
|
||||
|
||||
/**
|
||||
* @var string|null The first name of the User
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected ?string $first_name = '';
|
||||
|
||||
/**
|
||||
* @var bool True if the user needs to change password after log in
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected bool $need_pw_change = true;
|
||||
|
||||
@@ -204,6 +217,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
|
||||
/** @var DateTime|null The time when the backup codes were generated
|
||||
* @ORM\Column(type="datetime", nullable=true)
|
||||
* @Groups({"full"})
|
||||
*/
|
||||
protected ?DateTime $backupCodesGenerationDate = null;
|
||||
|
||||
@@ -226,6 +240,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency")
|
||||
* @ORM\JoinColumn(name="currency_id", referencedColumnName="id")
|
||||
* @Selectable()
|
||||
* @Groups({"extended", "full", "import"})
|
||||
*/
|
||||
protected $currency;
|
||||
|
||||
@@ -233,15 +248,23 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
* @var PermissionData
|
||||
* @ValidPermission()
|
||||
* @ORM\Embedded(class="PermissionData", columnPrefix="permissions_")
|
||||
* @Groups({"simple", "extended", "full", "import"})
|
||||
*/
|
||||
protected ?PermissionData $permissions = null;
|
||||
|
||||
/**
|
||||
* @var DateTime the time until the password reset token is valid
|
||||
* @ORM\Column(type="datetime", nullable=true)
|
||||
* @ORM\Column(type="datetime", nullable=true, columnDefinition="DEFAULT NULL")
|
||||
*/
|
||||
protected $pw_reset_expires;
|
||||
|
||||
/**
|
||||
* @var bool True if the user was created by a SAML provider (and therefore cannot change its password)
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Groups({"extended", "full"})
|
||||
*/
|
||||
protected bool $saml_user = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
@@ -298,6 +321,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
// guarantee every user at least has ROLE_USER
|
||||
$roles[] = 'ROLE_USER';
|
||||
|
||||
if ($this->saml_user) {
|
||||
$roles[] = 'ROLE_SAML_USER';
|
||||
}
|
||||
|
||||
return array_unique($roles);
|
||||
}
|
||||
|
||||
@@ -860,4 +887,56 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
|
||||
{
|
||||
$this->webauthn_keys->add($webauthnKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if the user was created by the SAML authentication.
|
||||
* @return bool
|
||||
*/
|
||||
public function isSamlUser(): bool
|
||||
{
|
||||
return $this->saml_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the saml_user flag.
|
||||
* @param bool $saml_user
|
||||
* @return User
|
||||
*/
|
||||
public function setSamlUser(bool $saml_user): User
|
||||
{
|
||||
$this->saml_user = $saml_user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setSamlAttributes(array $attributes)
|
||||
{
|
||||
//When mail attribute exists, set it
|
||||
if (isset($attributes['email'])) {
|
||||
$this->setEmail($attributes['email'][0]);
|
||||
}
|
||||
//When first name attribute exists, set it
|
||||
if (isset($attributes['firstName'])) {
|
||||
$this->setFirstName($attributes['firstName'][0]);
|
||||
}
|
||||
//When last name attribute exists, set it
|
||||
if (isset($attributes['lastName'])) {
|
||||
$this->setLastName($attributes['lastName'][0]);
|
||||
}
|
||||
if (isset($attributes['department'])) {
|
||||
$this->setDepartment($attributes['department'][0]);
|
||||
}
|
||||
|
||||
//Use X500 attributes as userinfo
|
||||
if (isset($attributes['urn:oid:2.5.4.42'])) {
|
||||
$this->setFirstName($attributes['urn:oid:2.5.4.42'][0]);
|
||||
}
|
||||
if (isset($attributes['urn:oid:2.5.4.4'])) {
|
||||
$this->setLastName($attributes['urn:oid:2.5.4.4'][0]);
|
||||
}
|
||||
if (isset($attributes['urn:oid:1.2.840.113549.1.9.1'])) {
|
||||
$this->setEmail($attributes['urn:oid:1.2.840.113549.1.9.1'][0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,11 @@ final class LoginSuccessSubscriber implements EventSubscriberInterface
|
||||
$ip = $event->getRequest()->getClientIp();
|
||||
$log = new UserLoginLogEntry($ip, $this->gpdr_compliance);
|
||||
$user = $event->getAuthenticationToken()->getUser();
|
||||
if ($user instanceof User) {
|
||||
if ($user instanceof User && $user->getID()) {
|
||||
$log->setTargetElement($user);
|
||||
$this->eventLogger->logAndFlush($log);
|
||||
}
|
||||
$this->eventLogger->logAndFlush($log);
|
||||
|
||||
|
||||
$this->flashBag->add('notice', $this->translator->trans('flash.login_successful'));
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Form\Type\StructuralEntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
@@ -48,13 +50,14 @@ class ImportType extends AbstractType
|
||||
|
||||
//Disable import if user is not allowed to create elements.
|
||||
$entity = new $data['entity_class']();
|
||||
$perm_name = 'create';
|
||||
$perm_name = 'import';
|
||||
$disabled = !$this->security->isGranted($perm_name, $entity);
|
||||
|
||||
$builder
|
||||
|
||||
->add('format', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'parts.import.format.auto' => 'auto',
|
||||
'JSON' => 'json',
|
||||
'XML' => 'xml',
|
||||
'CSV' => 'csv',
|
||||
@@ -63,7 +66,7 @@ class ImportType extends AbstractType
|
||||
'label' => 'export.format',
|
||||
'disabled' => $disabled,
|
||||
])
|
||||
->add('csv_separator', TextType::class, [
|
||||
->add('csv_delimiter', TextType::class, [
|
||||
'data' => ';',
|
||||
'label' => 'import.csv_separator',
|
||||
'disabled' => $disabled,
|
||||
@@ -78,6 +81,51 @@ class ImportType extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
if ($entity instanceof Part) {
|
||||
$builder->add('part_category', StructuralEntityType::class, [
|
||||
'class' => Category::class,
|
||||
'required' => false,
|
||||
'label' => 'parts.import.part_category.label',
|
||||
'help' => 'parts.import.part_category.help',
|
||||
'disabled' => $disabled,
|
||||
'disable_not_selectable' => true,
|
||||
'allow_add' => true
|
||||
]);
|
||||
$builder->add('part_needs_review', CheckboxType::class, [
|
||||
'data' => false,
|
||||
'required' => false,
|
||||
'label' => 'parts.import.part_needs_review.label',
|
||||
'help' => 'parts.import.part_needs_review.help',
|
||||
'disabled' => $disabled,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($entity instanceof AbstractStructuralDBElement) {
|
||||
$builder->add('preserve_children', CheckboxType::class, [
|
||||
'data' => true,
|
||||
'required' => false,
|
||||
'label' => 'import.preserve_children',
|
||||
'disabled' => $disabled,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($entity instanceof Part) {
|
||||
$builder->add('create_unknown_datastructures', CheckboxType::class, [
|
||||
'data' => true,
|
||||
'required' => false,
|
||||
'label' => 'import.create_unknown_datastructures',
|
||||
'help' => 'import.create_unknown_datastructures.help',
|
||||
'disabled' => $disabled,
|
||||
]);
|
||||
|
||||
$builder->add('path_delimiter', TextType::class, [
|
||||
'data' => '->',
|
||||
'label' => 'import.path_delimiter',
|
||||
'help' => 'import.path_delimiter.help',
|
||||
'disabled' => $disabled,
|
||||
]);
|
||||
}
|
||||
|
||||
$builder->add('file', FileType::class, [
|
||||
'label' => 'import.file',
|
||||
'attr' => [
|
||||
@@ -86,21 +134,15 @@ class ImportType extends AbstractType
|
||||
'data-show-upload' => 'false',
|
||||
],
|
||||
'disabled' => $disabled,
|
||||
])
|
||||
]);
|
||||
|
||||
->add('preserve_children', CheckboxType::class, [
|
||||
'data' => true,
|
||||
'required' => false,
|
||||
'label' => 'import.preserve_children',
|
||||
'disabled' => $disabled,
|
||||
])
|
||||
->add('abort_on_validation_error', CheckboxType::class, [
|
||||
'data' => true,
|
||||
'required' => false,
|
||||
'label' => 'import.abort_on_validation',
|
||||
'help' => 'import.abort_on_validation.help',
|
||||
'disabled' => $disabled,
|
||||
])
|
||||
$builder->add('abort_on_validation_error', CheckboxType::class, [
|
||||
'data' => true,
|
||||
'required' => false,
|
||||
'label' => 'import.abort_on_validation',
|
||||
'help' => 'import.abort_on_validation.help',
|
||||
'disabled' => $disabled,
|
||||
])
|
||||
|
||||
//Buttons
|
||||
->add('import', SubmitType::class, [
|
||||
|
||||
@@ -37,11 +37,14 @@ use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Validator\Constraints\File;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\Url;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
@@ -50,13 +53,14 @@ class AttachmentFormType extends AbstractType
|
||||
protected AttachmentManager $attachment_helper;
|
||||
protected UrlGeneratorInterface $urlGenerator;
|
||||
protected bool $allow_attachments_download;
|
||||
protected string $max_file_size;
|
||||
protected Security $security;
|
||||
protected AttachmentSubmitHandler $submitHandler;
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
public function __construct(AttachmentManager $attachmentHelper,
|
||||
UrlGeneratorInterface $urlGenerator, Security $security,
|
||||
bool $allow_attachments_downloads, AttachmentSubmitHandler $submitHandler, TranslatorInterface $translator)
|
||||
public function __construct(AttachmentManager $attachmentHelper, UrlGeneratorInterface $urlGenerator,
|
||||
Security $security, AttachmentSubmitHandler $submitHandler, TranslatorInterface $translator,
|
||||
bool $allow_attachments_downloads, string $max_file_size)
|
||||
{
|
||||
$this->attachment_helper = $attachmentHelper;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
@@ -64,13 +68,17 @@ class AttachmentFormType extends AbstractType
|
||||
$this->security = $security;
|
||||
$this->submitHandler = $submitHandler;
|
||||
$this->translator = $translator;
|
||||
$this->max_file_size = $max_file_size;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('name', TextType::class, [
|
||||
'label' => 'attachment.edit.name',
|
||||
])
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'attachment.edit.name',
|
||||
'required' => false,
|
||||
'empty_data' => '',
|
||||
])
|
||||
->add('attachment_type', StructuralEntityType::class, [
|
||||
'label' => 'attachment.edit.attachment_type',
|
||||
'class' => AttachmentType::class,
|
||||
@@ -130,6 +138,7 @@ class AttachmentFormType extends AbstractType
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
|
||||
$form = $event->getForm();
|
||||
$attachment = $form->getData();
|
||||
@@ -137,13 +146,27 @@ class AttachmentFormType extends AbstractType
|
||||
$file_form = $form->get('file');
|
||||
$file = $file_form->getData();
|
||||
|
||||
if ($attachment instanceof Attachment && $file instanceof UploadedFile && $attachment->getAttachmentType(
|
||||
) && !$this->submitHandler->isValidFileExtension($attachment->getAttachmentType(), $file)) {
|
||||
$event->getForm()->get('file')->addError(
|
||||
new FormError($this->translator->trans('validator.file_ext_not_allowed'))
|
||||
);
|
||||
if (!$attachment instanceof Attachment) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (!$file instanceof UploadedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Ensure that the file extension is allowed for the selected attachment type
|
||||
if ($attachment->getAttachmentType()
|
||||
&& !$this->submitHandler->isValidFileExtension($attachment->getAttachmentType(), $file)) {
|
||||
$event->getForm()->get('file')->addError(
|
||||
new FormError($this->translator->trans('validator.file_ext_not_allowed'))
|
||||
);
|
||||
}
|
||||
|
||||
//If the name is empty, use the original file name as attachment name
|
||||
if (empty($attachment->getName())) {
|
||||
$attachment->setName($file->getClientOriginalName());
|
||||
}
|
||||
}, 100000);
|
||||
|
||||
//Check the secure file checkbox, if file is in securefile location
|
||||
$builder->get('secureFile')->addEventListener(
|
||||
@@ -161,11 +184,16 @@ class AttachmentFormType extends AbstractType
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Attachment::class,
|
||||
'max_file_size' => '16M',
|
||||
'max_file_size' => $this->max_file_size,
|
||||
'allow_builtins' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function finishView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$view->vars['max_upload_size'] = $this->submitHandler->getMaximumAllowedUploadSize();
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'attachment';
|
||||
|
||||
@@ -46,12 +46,17 @@ use Doctrine\Common\Collections\Collection;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Event\PreSubmitEvent;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormConfigBuilder;
|
||||
use Symfony\Component\Form\FormConfigInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
@@ -59,7 +64,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
* Perform a reindexing on CollectionType elements, by assigning the database id as index.
|
||||
* This prevents issues when the collection that is edited uses a OrderBy annotation and therefore the direction of the
|
||||
* elements can change during requests.
|
||||
* Must me enabled by setting reindex_enable to true in Type options.
|
||||
* Must be enabled by setting reindex_enable to true in Type options.
|
||||
*/
|
||||
class CollectionTypeExtension extends AbstractTypeExtension
|
||||
{
|
||||
@@ -87,11 +92,25 @@ class CollectionTypeExtension extends AbstractTypeExtension
|
||||
'reindex_path' => 'id',
|
||||
]);
|
||||
|
||||
//Set a unique prototype name, so that we can use nested collections
|
||||
$resolver->setDefaults([
|
||||
'prototype_name' => function (Options $options) {
|
||||
return '__name_'.uniqid("", false) . '__';
|
||||
},
|
||||
]);
|
||||
|
||||
$resolver->setAllowedTypes('reindex_enable', 'bool');
|
||||
$resolver->setAllowedTypes('reindex_prefix', 'string');
|
||||
$resolver->setAllowedTypes('reindex_path', 'string');
|
||||
}
|
||||
|
||||
public function finishView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
parent::finishView($view, $form, $options);
|
||||
//Add prototype name to view, so that we can pass it to the stimulus controller
|
||||
$view->vars['prototype_name'] = $options['prototype_name'];
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options): void {
|
||||
@@ -118,6 +137,32 @@ class CollectionTypeExtension extends AbstractTypeExtension
|
||||
}
|
||||
}
|
||||
}, 100); //We need to have a higher priority then the PRE_SET_DATA listener on CollectionType
|
||||
|
||||
// This event listener fixes the error mapping for newly created elements of collection types
|
||||
// Without this method, the errors for newly created elements are shown on the parent element, as forms
|
||||
// can not map it to the correct element.
|
||||
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event) {
|
||||
$data = $event->getData();
|
||||
$form = $event->getForm();
|
||||
$config = $form->getConfig();
|
||||
|
||||
if (!is_array($data) && !$data instanceof Collection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data instanceof Collection) {
|
||||
$data = $data->toArray();
|
||||
}
|
||||
|
||||
//The validator uses the number of the element as index, so we have to map the errors to the correct index
|
||||
$error_mapping = [];
|
||||
$n = 0;
|
||||
foreach ($data as $key => $item) {
|
||||
$error_mapping['['.$n.']'] = $key;
|
||||
$n++;
|
||||
}
|
||||
$this->setOption($config, 'error_mapping', $error_mapping);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,8 +170,12 @@ class CollectionTypeExtension extends AbstractTypeExtension
|
||||
* This a bit hacky cause we access private properties....
|
||||
*
|
||||
*/
|
||||
public function setOption(FormBuilder $builder, string $option, $value): void
|
||||
public function setOption(FormConfigInterface $builder, string $option, $value): void
|
||||
{
|
||||
if (!$builder instanceof FormConfigBuilder) {
|
||||
throw new \RuntimeException('This method only works with FormConfigBuilder instances.');
|
||||
}
|
||||
|
||||
//We have to use FormConfigBuilder::class here, because options is private and not available in sub classes
|
||||
$reflection = new ReflectionClass(FormConfigBuilder::class);
|
||||
$property = $reflection->getProperty('options');
|
||||
|
||||
@@ -65,7 +65,7 @@ class UserAdminForm extends AbstractType
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
/** @var AbstractStructuralDBElement $entity */
|
||||
/** @var User $entity */
|
||||
$entity = $options['data'];
|
||||
$is_new = null === $entity->getID();
|
||||
|
||||
@@ -164,7 +164,7 @@ class UserAdminForm extends AbstractType
|
||||
'invalid_message' => 'password_must_match',
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
'disabled' => !$this->security->isGranted('set_password', $entity),
|
||||
'disabled' => !$this->security->isGranted('set_password', $entity) || $entity->isSamlUser(),
|
||||
'constraints' => [new Length([
|
||||
'min' => 6,
|
||||
'max' => 128,
|
||||
@@ -174,7 +174,7 @@ class UserAdminForm extends AbstractType
|
||||
->add('need_pw_change', CheckboxType::class, [
|
||||
'required' => false,
|
||||
'label' => 'user.edit.needs_pw_change',
|
||||
'disabled' => !$this->security->isGranted('set_password', $entity),
|
||||
'disabled' => !$this->security->isGranted('set_password', $entity) || $entity->isSamlUser(),
|
||||
])
|
||||
|
||||
->add('disabled', CheckboxType::class, [
|
||||
|
||||
@@ -57,7 +57,7 @@ class UserSettingsType extends AbstractType
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'user.username.label',
|
||||
'disabled' => !$this->security->isGranted('edit_username', $options['data']) || $this->demo_mode,
|
||||
'disabled' => !$this->security->isGranted('edit_username', $options['data']) || $this->demo_mode || $options['data']->isSamlUser(),
|
||||
])
|
||||
->add('first_name', TextType::class, [
|
||||
'required' => false,
|
||||
|
||||
@@ -27,6 +27,8 @@ use InvalidArgumentException;
|
||||
|
||||
abstract class AbstractPartsContainingRepository extends StructuralDBElementRepository implements PartsContainingRepositoryInterface
|
||||
{
|
||||
private const RECURSION_LIMIT = 50;
|
||||
|
||||
/**
|
||||
* Returns all parts associated with this element.
|
||||
*
|
||||
@@ -55,8 +57,17 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo
|
||||
{
|
||||
$count = $this->getPartsCount($element);
|
||||
|
||||
//If the element is its own parent, we have a loop in the tree, so we stop here.
|
||||
if ($element->getParent() === $element) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$n = 0;
|
||||
foreach ($element->getChildren() as $child) {
|
||||
$count += $this->getPartsCountRecursive($child);
|
||||
if ($n++ > self::RECURSION_LIMIT) {
|
||||
throw new \RuntimeException('Recursion limit reached!');
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
|
||||
@@ -45,10 +45,16 @@ class NamedDBElementRepository extends DBElementRepository
|
||||
$node->setId($entity->getID());
|
||||
$result[] = $node;
|
||||
|
||||
if ($entity instanceof User && $entity->isDisabled()) {
|
||||
//If this is an user, then add a badge when it is disabled
|
||||
$node->setIcon('fa-fw fa-treeview fa-solid fa-user-lock text-muted');
|
||||
if ($entity instanceof User) {
|
||||
if ($entity->isDisabled()) {
|
||||
//If this is an user, then add a badge when it is disabled
|
||||
$node->setIcon('fa-fw fa-treeview fa-solid fa-user-lock text-muted');
|
||||
}
|
||||
if ($entity->isSamlUser()) {
|
||||
$node->setIcon('fa-fw fa-treeview fa-solid fa-house-user text-muted');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
@@ -29,6 +29,12 @@ use RecursiveIteratorIterator;
|
||||
|
||||
class StructuralDBElementRepository extends NamedDBElementRepository
|
||||
{
|
||||
/**
|
||||
* @var array An array containing all new entities created by getNewEntityByPath.
|
||||
* This is used to prevent creating multiple entities for the same path.
|
||||
*/
|
||||
private array $new_entity_cache = [];
|
||||
|
||||
/**
|
||||
* Finds all nodes without a parent node. They are our root nodes.
|
||||
*
|
||||
@@ -91,7 +97,7 @@ class StructuralDBElementRepository extends NamedDBElementRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a structure of AbsstractStructuralDBElements from a path separated by $separator, which splits the various levels.
|
||||
* Creates a structure of AbstractStructuralDBElements from a path separated by $separator, which splits the various levels.
|
||||
* This function will try to use existing elements, if they are already in the database. If not, they will be created.
|
||||
* An array of the created elements will be returned, with the last element being the deepest element.
|
||||
* @param string $path
|
||||
@@ -108,14 +114,67 @@ class StructuralDBElementRepository extends NamedDBElementRepository
|
||||
continue;
|
||||
}
|
||||
|
||||
//See if we already have an element with this name and parent
|
||||
$entity = $this->findOneBy(['name' => $name, 'parent' => $parent]);
|
||||
//Use the cache to prevent creating multiple entities for the same path
|
||||
$entity = $this->getNewEntityFromCache($name, $parent);
|
||||
|
||||
//See if we already have an element with this name and parent in the database
|
||||
if (!$entity) {
|
||||
$entity = $this->findOneBy(['name' => $name, 'parent' => $parent]);
|
||||
}
|
||||
if (null === $entity) {
|
||||
$class = $this->getClassName();
|
||||
/** @var AbstractStructuralDBElement $entity */
|
||||
$entity = new $class;
|
||||
$entity->setName($name);
|
||||
$entity->setParent($parent);
|
||||
|
||||
$this->setNewEntityToCache($entity);
|
||||
}
|
||||
|
||||
$result[] = $entity;
|
||||
$parent = $entity;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getNewEntityFromCache(string $name, ?AbstractStructuralDBElement $parent): ?AbstractStructuralDBElement
|
||||
{
|
||||
$key = $parent ? $parent->getFullPath('%->%').'%->%'.$name : $name;
|
||||
if (isset($this->new_entity_cache[$key])) {
|
||||
return $this->new_entity_cache[$key];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function setNewEntityToCache(AbstractStructuralDBElement $entity): void
|
||||
{
|
||||
$key = $entity->getFullPath('%->%');
|
||||
$this->new_entity_cache[$key] = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an element of AbstractStructuralDBElements queried from a path separated by $separator, which splits the various levels.
|
||||
* An array of the created elements will be returned, with the last element being the deepest element.
|
||||
* If no element was found, an empty array will be returned.
|
||||
* @param string $path
|
||||
* @param string $separator
|
||||
* @return AbstractStructuralDBElement[]
|
||||
*/
|
||||
public function getEntityByPath(string $path, string $separator = '->'): array
|
||||
{
|
||||
$parent = null;
|
||||
$result = [];
|
||||
foreach (explode($separator, $path) as $name) {
|
||||
$name = trim($name);
|
||||
if ('' === $name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//See if we already have an element with this name and parent
|
||||
$entity = $this->findOneBy(['name' => $name, 'parent' => $parent]);
|
||||
if (null === $entity) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result[] = $entity;
|
||||
|
||||
@@ -89,4 +89,26 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all local users (not SAML users).
|
||||
* @return User[]
|
||||
*/
|
||||
public function onlyLocalUsers(): array
|
||||
{
|
||||
return $this->findBy([
|
||||
'saml_user' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all SAML users.
|
||||
* @return User[]
|
||||
*/
|
||||
public function onlySAMLUsers(): array
|
||||
{
|
||||
return $this->findBy([
|
||||
'saml_user' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
63
src/Security/EnsureSAMLUserForSAMLLoginChecker.php
Normal file
63
src/Security/EnsureSAMLUserForSAMLLoginChecker.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/>.
|
||||
*/
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use Hslavich\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class EnsureSAMLUserForSAMLLoginChecker implements EventSubscriberInterface
|
||||
{
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
AuthenticationSuccessEvent::class => 'onAuthenticationSuccess',
|
||||
];
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(AuthenticationSuccessEvent $event): void
|
||||
{
|
||||
$token = $event->getAuthenticationToken();
|
||||
$user = $token->getUser();
|
||||
|
||||
//If we are using SAML, we need to check that the user is a SAML user.
|
||||
if ($token instanceof SamlToken) {
|
||||
if ($user instanceof User && !$user->isSAMLUser()) {
|
||||
throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_local_user_per_saml', [], 'security'));
|
||||
}
|
||||
} else { //Ensure that you can not login locally with a SAML user (even if this should not happen, as the password is not set)
|
||||
if ($user instanceof User && $user->isSamlUser()) {
|
||||
throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_saml_user_locally', [], 'security'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user