Merge remote-tracking branch 'origin/master'

This commit is contained in:
Jan Böhmer 2023-04-08 21:02:51 +02:00
commit 4ace7dd370
47 changed files with 741 additions and 149 deletions

View file

@ -26,7 +26,7 @@
# 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 DATABASE_URL ENFORCE_CHANGE_COMMENTS_FOR
PassEnv DEFAULT_LANG DEFAULT_TIMEZONE BASE_CURRENCY INSTANCE_NAME ALLOW_ATTACHMENT_DOWNLOADS USE_GRAVATAR MAX_ATTACHMENT_FILE_SIZE DEFAULT_URI
PassEnv 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

5
.env
View file

@ -39,6 +39,11 @@ MAX_ATTACHMENT_FILE_SIZE="100M"
# This must end with a slash!
DEFAULT_URI="https://partdb.changeme.invalid/"
# With this option you can configure, where users are enforced to give a change reason, which will be logged
# This is a comma separated list of values, see documentation for available values
# Leave this empty, to make all change reasons optional
ENFORCE_CHANGE_COMMENTS_FOR=""
###################################################################################
# Email settings
###################################################################################

View file

@ -155,6 +155,11 @@ class ErrorHandlerHelper {
return;
}
//Skip 404 errors, on admin pages (as this causes a popup on deletion in firefox)
if (response.status == 404 && event.target.id === 'admin-content-frame') {
return;
}
if(!response.ok) {
response.text().then(responseHTML => {

View file

@ -19,7 +19,7 @@
"use strict";
import {Tab} from "bootstrap";
import {Tab, Dropdown} from "bootstrap";
import tab from "bootstrap/js/src/tab";
/**
@ -63,6 +63,16 @@ class TabRememberHelper {
*/
onInvalid(event) {
this.revealElementOnTab(event.target);
this.revealElementInDropdown(event.target);
}
revealElementInDropdown(element) {
let dropdown = element.closest('.dropdown-menu');
if(dropdown) {
let bs_dropdown = Dropdown.getOrCreateInstance(dropdown);
bs_dropdown.show();
}
}
revealElementOnTab(element) {

96
composer.lock generated
View file

@ -4771,16 +4771,16 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.16.1",
"version": "1.18.1",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571"
"reference": "22dcdfd725ddf99583bfe398fc624ad6c5004a0f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/e27e92d939e2e3636f0a1f0afaba59692c0bf571",
"reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/22dcdfd725ddf99583bfe398fc624ad6c5004a0f",
"reference": "22dcdfd725ddf99583bfe398fc624ad6c5004a0f",
"shasum": ""
},
"require": {
@ -4810,9 +4810,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.16.1"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.18.1"
},
"time": "2023-02-07T18:11:17+00:00"
"time": "2023-04-07T11:51:11+00:00"
},
{
"name": "psr/cache",
@ -5070,25 +5070,25 @@
},
{
"name": "psr/http-message",
"version": "1.0.1",
"version": "1.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.1.x-dev"
}
},
"autoload": {
@ -5117,9 +5117,9 @@
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/master"
"source": "https://github.com/php-fig/http-message/tree/1.1"
},
"time": "2016-08-06T14:39:51+00:00"
"time": "2023-04-04T09:50:52+00:00"
},
{
"name": "psr/link",
@ -14120,20 +14120,21 @@
},
{
"name": "doctrine/data-fixtures",
"version": "1.6.3",
"version": "1.6.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/data-fixtures.git",
"reference": "c27821d038e64f1bfc852a94064d65d2a75ad01f"
"reference": "e6b97f557942ea17564bbc30ae3ebc9bd2209363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/c27821d038e64f1bfc852a94064d65d2a75ad01f",
"reference": "c27821d038e64f1bfc852a94064d65d2a75ad01f",
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/e6b97f557942ea17564bbc30ae3ebc9bd2209363",
"reference": "e6b97f557942ea17564bbc30ae3ebc9bd2209363",
"shasum": ""
},
"require": {
"doctrine/persistence": "^1.3.3|^2.0|^3.0",
"doctrine/deprecations": "^0.5.3 || ^1.0",
"doctrine/persistence": "^1.3.3 || ^2.0 || ^3.0",
"php": "^7.2 || ^8.0"
},
"conflict": {
@ -14142,16 +14143,15 @@
"doctrine/phpcr-odm": "<1.3.0"
},
"require-dev": {
"doctrine/coding-standard": "^10.0",
"doctrine/coding-standard": "^11.0",
"doctrine/dbal": "^2.13 || ^3.0",
"doctrine/deprecations": "^1.0",
"doctrine/mongodb-odm": "^1.3.0 || ^2.0.0",
"doctrine/orm": "^2.12",
"ext-sqlite3": "*",
"phpstan/phpstan": "^1.5",
"phpunit/phpunit": "^8.5 || ^9.5",
"phpunit/phpunit": "^8.5 || ^9.5 || ^10.0",
"symfony/cache": "^5.0 || ^6.0",
"vimeo/psalm": "^4.10"
"vimeo/psalm": "^4.10 || ^5.9"
},
"suggest": {
"alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)",
@ -14162,7 +14162,7 @@
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Common\\DataFixtures\\": "lib/Doctrine/Common/DataFixtures"
"Doctrine\\Common\\DataFixtures\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -14182,7 +14182,7 @@
],
"support": {
"issues": "https://github.com/doctrine/data-fixtures/issues",
"source": "https://github.com/doctrine/data-fixtures/tree/1.6.3"
"source": "https://github.com/doctrine/data-fixtures/tree/1.6.5"
},
"funding": [
{
@ -14198,7 +14198,7 @@
"type": "tidelift"
}
],
"time": "2023-01-07T15:10:22+00:00"
"time": "2023-04-03T14:58:58+00:00"
},
{
"name": "doctrine/doctrine-fixtures-bundle",
@ -14607,16 +14607,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.9",
"version": "1.10.11",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "9b13dafe3d66693d20fe5729c3dde1d31bb64703"
"reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b13dafe3d66693d20fe5729c3dde1d31bb64703",
"reference": "9b13dafe3d66693d20fe5729c3dde1d31bb64703",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8aa62e6ea8b58ffb650e02940e55a788cbc3fe21",
"reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21",
"shasum": ""
},
"require": {
@ -14665,7 +14665,7 @@
"type": "tidelift"
}
],
"time": "2023-03-30T08:58:01+00:00"
"time": "2023-04-04T19:17:42+00:00"
},
{
"name": "phpstan/phpstan-doctrine",
@ -14739,16 +14739,16 @@
},
{
"name": "phpstan/phpstan-symfony",
"version": "1.2.24",
"version": "1.2.25",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-symfony.git",
"reference": "db81b1861aac7cc2e66115cb33b4d1ea2a73d096"
"reference": "1da7bf450c6b351fec08ca0aa97298473d4f6ab3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/db81b1861aac7cc2e66115cb33b4d1ea2a73d096",
"reference": "db81b1861aac7cc2e66115cb33b4d1ea2a73d096",
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/1da7bf450c6b351fec08ca0aa97298473d4f6ab3",
"reference": "1da7bf450c6b351fec08ca0aa97298473d4f6ab3",
"shasum": ""
},
"require": {
@ -14804,9 +14804,9 @@
"description": "Symfony Framework extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.2.24"
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.2.25"
},
"time": "2023-03-30T08:38:10+00:00"
"time": "2023-04-05T12:16:20+00:00"
},
{
"name": "psalm/plugin-symfony",
@ -14879,12 +14879,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "945aadf4c174c61973464b4000f4d7529aeb84a0"
"reference": "6efa800243b92a3601e0101b0333d45df35832a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/945aadf4c174c61973464b4000f4d7529aeb84a0",
"reference": "945aadf4c174c61973464b4000f4d7529aeb84a0",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/6efa800243b92a3601e0101b0333d45df35832a4",
"reference": "6efa800243b92a3601e0101b0333d45df35832a4",
"shasum": ""
},
"conflict": {
@ -14902,6 +14902,7 @@
"amphp/http-client": ">=4,<4.4",
"anchorcms/anchor-cms": "<=0.12.7",
"andreapollastri/cipi": "<=3.1.15",
"andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<=1.0.1|>=2,<=2.2.4",
"apereo/phpcas": "<1.6",
"api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3",
"appwrite/server-ce": "<0.11.1|>=0.12,<0.12.2",
@ -14920,6 +14921,7 @@
"barzahlen/barzahlen-php": "<2.0.1",
"baserproject/basercms": "<4.7.5",
"bassjobsen/bootstrap-3-typeahead": ">4.0.2",
"bigfork/silverstripe-form-capture": ">=3,<=3.1",
"billz/raspap-webgui": "<=2.6.6",
"bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3",
"bmarshall511/wordpress_zero_spam": "<5.2.13",
@ -15003,7 +15005,7 @@
"ezsystems/ezplatform-graphql": ">=1-rc.1,<1.0.13|>=2-beta.1,<2.3.12",
"ezsystems/ezplatform-kernel": "<1.2.5.1|>=1.3,<1.3.26",
"ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8",
"ezsystems/ezplatform-richtext": ">=2.3,<=2.3.7",
"ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1",
"ezsystems/ezplatform-user": ">=1,<1.0.1",
"ezsystems/ezpublish-kernel": "<6.13.8.2|>=7,<7.5.30",
"ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.3.5.1",
@ -15051,7 +15053,7 @@
"gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3",
"gree/jose": "<2.2.1",
"gregwar/rst": "<1.0.3",
"grumpydictator/firefly-iii": "<5.8",
"grumpydictator/firefly-iii": "<6",
"guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5",
"guzzlehttp/psr7": "<1.8.4|>=2,<2.1.1",
"harvesthq/chosen": "<1.8.7",
@ -15096,7 +15098,7 @@
"kimai/kimai": "<1.1",
"kitodo/presentation": "<3.1.2",
"klaviyo/magento2-extension": ">=1,<3",
"knplabs/knp-snappy": "<=1.4.1",
"knplabs/knp-snappy": "<1.4.2",
"krayin/laravel-crm": "<1.2.2",
"kreait/firebase-php": ">=3.2,<3.8.1",
"la-haute-societe/tcpdf": "<6.2.22",
@ -15136,7 +15138,7 @@
"melisplatform/melis-front": "<5.0.1",
"mezzio/mezzio-swoole": "<3.7|>=4,<4.3",
"mgallegos/laravel-jqgrid": "<=1.3",
"microweber/microweber": "<=1.3.2",
"microweber/microweber": "<1.3.3",
"miniorange/miniorange-saml": "<1.4.3",
"mittwald/typo3_forum": "<1.2.1",
"mobiledetect/mobiledetectlib": "<2.8.32",
@ -15204,6 +15206,7 @@
"phpxmlrpc/extras": "<0.6.1",
"phpxmlrpc/phpxmlrpc": "<4.9.2",
"pimcore/data-hub": "<1.2.4",
"pimcore/perspective-editor": "<1.5.1",
"pimcore/pimcore": "<10.5.20",
"pixelfed/pixelfed": "<=0.11.4",
"pocketmine/bedrock-protocol": "<8.0.2",
@ -15230,6 +15233,7 @@
"rainlab/debugbar-plugin": "<3.1",
"rankmath/seo-by-rank-math": "<=1.0.95",
"react/http": ">=0.7,<1.7",
"really-simple-plugins/complianz-gdpr": "<6.4.2",
"remdex/livehelperchat": "<3.99",
"rmccue/requests": ">=1.6,<1.8",
"robrichards/xmlseclibs": "<3.0.4",
@ -15339,7 +15343,7 @@
"thelia/thelia": ">=2.1-beta.1,<2.1.3",
"theonedemon/phpwhois": "<=4.2.5",
"thinkcmf/thinkcmf": "<=5.1.7",
"thorsten/phpmyfaq": "<3.1.11",
"thorsten/phpmyfaq": "<3.1.12",
"tinymce/tinymce": "<5.10.7|>=6,<6.3.1",
"tinymighty/wiki-seo": "<1.2.2",
"titon/framework": ">=0,<9.9.99",
@ -15467,7 +15471,7 @@
"type": "tidelift"
}
],
"time": "2023-03-30T21:04:19+00:00"
"time": "2023-04-06T17:04:19+00:00"
},
{
"name": "sebastian/diff",

View file

@ -12,6 +12,7 @@ parameters:
partdb.default_currency: '%env(string:BASE_CURRENCY)%' # The currency that is used inside the DB (and is assumed when no currency is set). This can not be changed later, so be sure to set it the currency used in your country
partdb.global_theme: '' # The theme to use globally (see public/build/themes/ for choices, use name without .css). Set to '' for default bootstrap theme
partdb.locale_menu: ['en', 'de', 'fr', 'ru', 'ja'] # The languages that are shown in user drop down menu
partdb.enforce_change_comments_for: '%env(csv:ENFORCE_CHANGE_COMMENTS_FOR)%' # The actions for which a change comment is required (e.g. "part_edit", "part_create", etc.). If this is empty, change comments are not required at all.
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
@ -105,6 +106,8 @@ parameters:
env(USE_GRAVATAR): '0'
env(MAX_ATTACHMENT_FILE_SIZE): '100M'
env(ENFORCE_CHANGE_COMMENTS_FOR): ''
env(ERROR_PAGE_ADMIN_EMAIL): ''
env(ERROR_PAGE_SHOW_HELP): 1

View file

@ -102,6 +102,10 @@ services:
event: 'Symfony\Component\Security\Http\Event\LogoutEvent'
dispatcher: security.event_dispatcher.main
App\Services\LogSystem\EventCommentNeededHelper:
arguments:
$enforce_change_comments_for: '%partdb.enforce_change_comments_for%'
####################################################################################################################
# Attachment system
####################################################################################################################

View file

@ -28,6 +28,14 @@ The following configuration options can only be changed by the server administra
* `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**.
* `ENFORCE_CHANGE_COMMENTS_FOR`: With this option you can configure, where users are enforced to give a change reason, which will be written to the log. This is a comma separated list of values (e.g. `part_edit,part_delete`). Leave empty to make change comments optional everywhere. Possible values are:
* `part_edit`: Edit operation of a existing part
* `part_delete`: Delete operation of a existing part
* `part_create`: Creation of a new part
* `part_stock_operation`: Stock operation on a part (therefore withdraw, add or move stock)
* `datastructure_edit`: Edit operation of a existing datastructure (e.g. category, manufacturer, ...)
* `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...)
* `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...)
### E-Mail settings
* `MAILER_DSN`: You can configure the mail provider which should be used for email delivery (see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587`

View file

@ -64,6 +64,12 @@ If you call this command regularly (e.g. with a cronjob), you can keep the excha
Please note that if you use a base currency, which is not the Euro, you have to configure an exchange rate API, as the
free API used by default only supports the Euro as base currency.
## Enforce log comments
On almost any editing operation it is possible to add a comment describing, what or why you changed something.
This comment will be written to change log and can be viewed later.
If you want to enforce your users to add comments to certain operations, you can do this by setting the `ENFORCE_CHANGE_COMMENTS_FOR` option.
See the configuration reference for more information.
## Personal stocks and stock locations
For makerspaces and universities with a lot of users, where each user can have his own stock, which only he should be able to access, you can assign
the user as "owner" of a part lot. This way, only him is allowed to add or remove parts from this lot.

View file

@ -27,7 +27,7 @@ final class Version20230402170923 extends AbstractMultiPlatformMigration
$this->addSql('ALTER TABLE storelocations ADD id_owner INT DEFAULT NULL, ADD part_owner_must_match TINYINT(1) DEFAULT 0 NOT NULL');
$this->addSql('ALTER TABLE storelocations ADD CONSTRAINT FK_751702021E5A74C FOREIGN KEY (id_owner) REFERENCES `users` (id) ON DELETE SET NULL');
$this->addSql('CREATE INDEX IDX_751702021E5A74C ON storelocations (id_owner)');
$this->addSql('ALTER TABLE users ADD about_me LONGTEXT NOT NULL');
$this->addSql('ALTER TABLE `users` ADD about_me LONGTEXT NOT NULL');
$this->addSql('ALTER TABLE projects CHANGE description description LONGTEXT NOT NULL');
}

View file

@ -0,0 +1,52 @@
<?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 Version20230408170059 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return '';
}
public function mySQLUp(Schema $schema): void
{
$this->addSql('ALTER TABLE `users` ADD show_email_on_profile TINYINT(1) DEFAULT 0 NOT NULL');
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('ALTER TABLE `users` DROP show_email_on_profile');
}
public function sqLiteUp(Schema $schema): void
{
$this->addSql('ALTER TABLE users ADD COLUMN show_email_on_profile BOOLEAN DEFAULT 0 NOT NULL');
}
public function sqLiteDown(Schema $schema): void
{
$this->addSql('CREATE TEMPORARY TABLE __temp__users AS SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM "users"');
$this->addSql('DROP TABLE "users"');
$this->addSql('CREATE TABLE "users" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, group_id INTEGER DEFAULT NULL, currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, disabled BOOLEAN NOT NULL, config_theme VARCHAR(255) DEFAULT NULL, pw_reset_token VARCHAR(255) DEFAULT NULL, config_instock_comment_a CLOB NOT NULL, config_instock_comment_w CLOB NOT NULL, about_me CLOB DEFAULT \'\' 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, saml_user BOOLEAN 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_1483A5E9FE54D947 FOREIGN KEY (group_id) REFERENCES "groups" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E938248176 FOREIGN KEY (currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_1483A5E9EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO "users" (id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data) SELECT id, group_id, currency_id, id_preview_attachment, disabled, config_theme, pw_reset_token, config_instock_comment_a, config_instock_comment_w, about_me, trusted_device_cookie_version, backup_codes, google_authenticator_secret, config_timezone, config_language, email, department, last_name, first_name, need_pw_change, password, name, settings, backup_codes_generation_date, pw_reset_expires, saml_user, last_modified, datetime_added, permissions_data FROM __temp__users');
$this->addSql('DROP TABLE __temp__users');
$this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E95E237E06 ON "users" (name)');
$this->addSql('CREATE INDEX IDX_1483A5E9FE54D947 ON "users" (group_id)');
$this->addSql('CREATE INDEX IDX_1483A5E938248176 ON "users" (currency_id)');
$this->addSql('CREATE INDEX IDX_1483A5E9EA7100A1 ON "users" (id_preview_attachment)');
$this->addSql('CREATE INDEX user_idx_username ON "users" (name)');
}
}

View file

@ -147,11 +147,21 @@ class ShowEventLogCommand extends Command
$target_class = $this->elementTypeNameGenerator->getLocalizedTypeLabel($entry->getTargetClass());
}
if ($entry->getUser()) {
$user = $entry->getUser()->getFullName(true);
} else {
if ($entry->isCLIEntry()) {
$user = $entry->getCLIUsername() . ' [CLI]';
} else {
$user = $entry->getUsername() . ' [deleted]';
}
}
$row = [
$entry->getID(),
$entry->getTimestamp()->format('Y-m-d H:i:s'),
$entry->getType(),
$entry->getUser()->getFullName(true),
$user,
$target_class,
$target_name,
];

View file

@ -202,21 +202,24 @@ class UserController extends AdminPages\BaseAdminController
$user = $tmp;
} else {
//Else we must check, if the current user is allowed to access $user
$this->denyAccessUnlessGranted('read', $user);
$this->denyAccessUnlessGranted('info', $user);
}
$table = $this->dataTableFactory->createFromType(
LogDataTable::class,
[
'filter_elements' => $user,
'mode' => 'element_history',
],
['pageLength' => 10]
)
->handleRequest($request);
//Only show the history table, if the user is the current user
if ($user === $this->getUser()) {
$table = $this->dataTableFactory->createFromType(
LogDataTable::class,
[
'filter_elements' => $user,
'mode' => 'element_history',
],
['pageLength' => 10]
)
->handleRequest($request);
if ($table->isCallback()) {
return $table->getResponse();
if ($table->isCallback()) {
return $table->getResponse();
}
}
//Show permissions to user
@ -230,7 +233,7 @@ class UserController extends AdminPages\BaseAdminController
return $this->renderForm('users/user_info.html.twig', [
'user' => $user,
'form' => $builder->getForm(),
'datatable' => $table,
'datatable' => $table ?? null,
]);
}
}

View file

@ -0,0 +1,47 @@
<?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\Filters\Constraints\Part;
use App\DataTables\Filters\Constraints\BooleanConstraint;
use Doctrine\ORM\QueryBuilder;
class LessThanDesiredConstraint extends BooleanConstraint
{
public function __construct(string $property = null, string $identifier = null, ?bool $default_value = null)
{
parent::__construct($property ?? 'amountSum', $identifier, $default_value);
}
public function apply(QueryBuilder $queryBuilder): void
{
//Do not apply a filter if value is null (filter is set to ignore)
if(!$this->isEnabled()) {
return;
}
//If value is true, we want to filter for parts with stock < desired stock
if ($this->value) {
$queryBuilder->andHaving('amountSum < part.minamount');
} else {
$queryBuilder->andHaving('amountSum >= part.minamount');
}
}
}

View file

@ -26,6 +26,7 @@ use App\DataTables\Filters\Constraints\DateTimeConstraint;
use App\DataTables\Filters\Constraints\EntityConstraint;
use App\DataTables\Filters\Constraints\IntConstraint;
use App\DataTables\Filters\Constraints\NumberConstraint;
use App\DataTables\Filters\Constraints\Part\LessThanDesiredConstraint;
use App\DataTables\Filters\Constraints\Part\ParameterConstraint;
use App\DataTables\Filters\Constraints\Part\TagsConstraint;
use App\DataTables\Filters\Constraints\TextConstraint;
@ -36,6 +37,8 @@ use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier;
use App\Entity\UserSystem\User;
use App\Form\Filters\Constraints\UserEntityConstraintType;
use App\Services\Trees\NodesListBuilder;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\QueryBuilder;
@ -68,10 +71,14 @@ class PartFilter implements FilterInterface
protected EntityConstraint $storelocation;
protected IntConstraint $lotCount;
protected IntConstraint $amountSum;
protected LessThanDesiredConstraint $lessThanDesired;
protected BooleanConstraint $lotNeedsRefill;
protected TextConstraint $lotDescription;
protected BooleanConstraint $lotUnknownAmount;
protected DateTimeConstraint $lotExpirationDate;
protected EntityConstraint $lotOwner;
protected EntityConstraint $measurementUnit;
protected TextConstraint $manufacturer_product_url;
protected TextConstraint $manufacturer_product_number;
@ -108,12 +115,14 @@ class PartFilter implements FilterInterface
//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->lessThanDesired = new LessThanDesiredConstraint();
$this->storelocation = new EntityConstraint($nodesListBuilder, Storelocation::class, 'partLots.storage_location');
$this->lotNeedsRefill = new BooleanConstraint('partLots.needs_refill');
$this->lotUnknownAmount = new BooleanConstraint('partLots.instock_unknown');
$this->lotExpirationDate = new DateTimeConstraint('partLots.expiration_date');
$this->lotDescription = new TextConstraint('partLots.description');
$this->lotOwner = new EntityConstraint($nodesListBuilder, User::class, 'partLots.owner');
$this->manufacturer = new EntityConstraint($nodesListBuilder, Manufacturer::class, 'part.manufacturer');
$this->manufacturer_product_number = new TextConstraint('part.manufacturer_product_number');
@ -280,6 +289,14 @@ class PartFilter implements FilterInterface
return $this->lotCount;
}
/**
* @return EntityConstraint
*/
public function getLotOwner(): EntityConstraint
{
return $this->lotOwner;
}
/**
* @return TagsConstraint
*/
@ -383,7 +400,13 @@ class PartFilter implements FilterInterface
return $this->obsolete;
}
/**
* @return LessThanDesiredConstraint
*/
public function getLessThanDesired(): LessThanDesiredConstraint
{
return $this->lessThanDesired;
}
}

View file

@ -226,6 +226,14 @@ class LogDataTable implements DataTableTypeInterface
//If user was deleted, show the info from the username field
if ($user === null) {
if ($context->isCLIEntry()) {
return sprintf('%s [%s]',
htmlentities($context->getCLIUsername()),
$this->translator->trans('log.cli_user')
);
}
//Else we just deal with a deleted user
return sprintf(
'@%s [%s]',
htmlentities($context->getUsername()),

View file

@ -216,6 +216,41 @@ abstract class AbstractLogEntry extends AbstractDBElement
return $this;
}
/**
* Returns true if this log entry was created by a CLI command, false otherwise.
* @return bool
*/
public function isCLIEntry(): bool
{
return strpos($this->username, '!!!CLI ') === 0;
}
/**
* Marks this log entry as a CLI entry, and set the username of the CLI user.
* This removes the association to a user object in database, as CLI users are not really related to logged in
* Part-DB users.
* @param string $cli_username
* @return $this
*/
public function setCLIUsername(string $cli_username): self
{
$this->user = null;
$this->username = '!!!CLI ' . $cli_username;
return $this;
}
/**
* Retrieves the username of the CLI user that caused the event.
* @return string|null The username of the CLI user, or null if this log entry was not created by a CLI command.
*/
public function getCLIUsername(): ?string
{
if ($this->isCLIEntry()) {
return substr($this->username, 7);
}
return null;
}
/**
* Retuns the username of the user that caused the event (useful if the user was deleted).
*

View file

@ -343,6 +343,13 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
*/
public function validate(ExecutionContextInterface $context, $payload)
{
//Ensure that the owner is not the anonymous user
if ($this->getOwner() && $this->getOwner()->isAnonymousUser()) {
$context->buildViolation('validator.part_lot.owner_must_not_be_anonymous')
->atPath('owner')
->addViolation();
}
//When the storage location sets the owner must match, the part lot owner must match the storage location owner
if ($this->getStorageLocation() && $this->getStorageLocation()->isPartOwnerMustMatch()
&& $this->getStorageLocation()->getOwner() && $this->getOwner()) {

View file

@ -94,6 +94,7 @@ class Storelocation extends AbstractPartsContainingDBElement
* @var User|null The owner of this storage location
* @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User")
* @ORM\JoinColumn(name="id_owner", referencedColumnName="id", nullable=true, onDelete="SET NULL")
* @Assert\Expression("this.getOwner() == null or this.getOwner().isAnonymousUser() === false", message="validator.part_lot.owner_must_not_be_anonymous")
*/
protected ?User $owner = null;

View file

@ -168,6 +168,12 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
*/
protected ?string $email = '';
/**
* @var bool True if the user wants to show his email address on his (public) profile
* @ORM\Column(type="boolean", options={"default": false})
*/
protected bool $show_email_on_profile = false;
/**
* @var string|null The department the user is working
* @ORM\Column(type="string", length=255, nullable=true)
@ -632,6 +638,28 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
return $this;
}
/**
* Gets whether the email address of the user is shown on the public profile page.
* @return bool
*/
public function isShowEmailOnProfile(): bool
{
return $this->show_email_on_profile;
}
/**
* Sets whether the email address of the user is shown on the public profile page.
* @param bool $show_email_on_profile
* @return User
*/
public function setShowEmailOnProfile(bool $show_email_on_profile): User
{
$this->show_email_on_profile = $show_email_on_profile;
return $this;
}
/**
* Returns the about me text of the user.
* @return string

View file

@ -24,6 +24,7 @@ namespace App\Form\AdminPages;
use App\Entity\Base\AbstractNamedDBElement;
use App\Services\Attachments\FileTypeFilterTools;
use App\Services\LogSystem\EventCommentNeededHelper;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
@ -33,10 +34,10 @@ class AttachmentTypeAdminForm extends BaseEntityAdminForm
{
protected FileTypeFilterTools $filterTools;
public function __construct(Security $security, FileTypeFilterTools $filterTools)
public function __construct(Security $security, FileTypeFilterTools $filterTools, EventCommentNeededHelper $eventCommentNeededHelper)
{
$this->filterTools = $filterTools;
parent::__construct($security);
parent::__construct($security, $eventCommentNeededHelper);
}
protected function additionalFormElements(FormBuilderInterface $builder, array $options, AbstractNamedDBElement $entity): void

View file

@ -31,6 +31,7 @@ use App\Form\ParameterType;
use App\Form\Type\MasterPictureAttachmentType;
use App\Form\Type\RichTextEditorType;
use App\Form\Type\StructuralEntityType;
use App\Services\LogSystem\EventCommentNeededHelper;
use FOS\CKEditorBundle\Form\Type\CKEditorType;
use function get_class;
use Symfony\Component\Form\AbstractType;
@ -46,10 +47,12 @@ use Symfony\Component\Security\Core\Security;
class BaseEntityAdminForm extends AbstractType
{
protected Security $security;
protected EventCommentNeededHelper $eventCommentNeededHelper;
public function __construct(Security $security)
public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper)
{
$this->security = $security;
$this->eventCommentNeededHelper = $eventCommentNeededHelper;
}
public function configureOptions(OptionsResolver $resolver): void
@ -141,7 +144,7 @@ class BaseEntityAdminForm extends AbstractType
$builder->add('log_comment', TextType::class, [
'label' => 'edit.log_comment',
'mapped' => false,
'required' => false,
'required' => $this->eventCommentNeededHelper->isCommentNeeded($is_new ? 'datastructure_create': 'datastructure_edit'),
'empty_data' => null,
]);

View file

@ -24,6 +24,7 @@ namespace App\Form\AdminPages;
use App\Entity\Base\AbstractNamedDBElement;
use App\Form\Type\BigDecimalMoneyType;
use App\Services\LogSystem\EventCommentNeededHelper;
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
@ -33,9 +34,9 @@ class CurrencyAdminForm extends BaseEntityAdminForm
{
private string $default_currency;
public function __construct(Security $security, string $default_currency)
public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, string $default_currency)
{
parent::__construct($security);
parent::__construct($security, $eventCommentNeededHelper);
$this->default_currency = $default_currency;
}

View file

@ -26,6 +26,7 @@ use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\PriceInformations\Currency;
use App\Form\Type\BigDecimalMoneyType;
use App\Form\Type\StructuralEntityType;
use App\Services\LogSystem\EventCommentNeededHelper;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Security;
@ -33,9 +34,9 @@ class SupplierForm extends CompanyForm
{
protected string $default_currency;
public function __construct(Security $security, string $default_currency)
public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, string $default_currency)
{
parent::__construct($security);
parent::__construct($security, $eventCommentNeededHelper);
$this->default_currency = $default_currency;
}

View file

@ -24,6 +24,8 @@ use App\DataTables\Filters\Constraints\BooleanConstraint;
use App\Form\Type\TriStateCheckboxType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class BooleanConstraintType extends AbstractType
@ -43,4 +45,10 @@ class BooleanConstraintType extends AbstractType
'required' => false,
]);
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
//Remove the label from the compound form, as the checkbox already has a label
$view->vars['label'] = false;
}
}

View file

@ -36,6 +36,7 @@ use App\Form\Filters\Constraints\ParameterConstraintType;
use App\Form\Filters\Constraints\StructuralEntityConstraintType;
use App\Form\Filters\Constraints\TagsConstraintType;
use App\Form\Filters\Constraints\TextConstraintType;
use App\Form\Filters\Constraints\UserEntityConstraintType;
use Svg\Tag\Text;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
@ -206,6 +207,10 @@ class PartFilterType extends AbstractType
'min' => 0,
]);
$builder->add('lessThanDesired', BooleanConstraintType::class, [
'label' => 'part.filter.lessThanDesired'
]);
$builder->add('lotNeedsRefill', BooleanConstraintType::class, [
'label' => 'part.filter.lotNeedsRefill'
]);
@ -223,6 +228,10 @@ class PartFilterType extends AbstractType
'label' => 'part.filter.lotDescription',
]);
$builder->add('lotOwner', UserEntityConstraintType::class, [
'label' => 'part.filter.lotOwner',
]);
/**
* Attachments count
*/

View file

@ -37,6 +37,7 @@ use App\Form\Type\RichTextEditorType;
use App\Form\Type\SIUnitType;
use App\Form\Type\StructuralEntityType;
use App\Form\WorkaroundCollectionType;
use App\Services\LogSystem\EventCommentNeededHelper;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
@ -54,17 +55,20 @@ class PartBaseType extends AbstractType
{
protected Security $security;
protected UrlGeneratorInterface $urlGenerator;
protected EventCommentNeededHelper $event_comment_needed_helper;
public function __construct(Security $security, UrlGeneratorInterface $urlGenerator)
public function __construct(Security $security, UrlGeneratorInterface $urlGenerator, EventCommentNeededHelper $event_comment_needed_helper)
{
$this->security = $security;
$this->urlGenerator = $urlGenerator;
$this->event_comment_needed_helper = $event_comment_needed_helper;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var Part $part */
$part = $builder->getData();
$new_part = null === $part->getID();
$status_choices = [
'm_status.unknown' => '',
@ -250,7 +254,7 @@ class PartBaseType extends AbstractType
$builder->add('log_comment', TextType::class, [
'label' => 'edit.log_comment',
'mapped' => false,
'required' => false,
'required' => $this->event_comment_needed_helper->isCommentNeeded($new_part ? 'part_create' : 'part_edit'),
'empty_data' => null,
]);

View file

@ -117,7 +117,11 @@ class UserAdminForm extends AbstractType
'required' => false,
'disabled' => !$this->security->isGranted('edit_infos', $entity),
])
->add('showEmailOnProfile', CheckboxType::class, [
'required' => false,
'label' => 'user.show_email_on_profile.label',
'disabled' => !$this->security->isGranted('edit_infos', $entity),
])
->add('department', TextType::class, [
'empty_data' => '',
'label' => 'user.department.label',

View file

@ -28,6 +28,7 @@ use App\Form\Type\RichTextEditorType;
use App\Form\Type\ThemeChoiceType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Event\PreSetDataEvent;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
@ -80,6 +81,11 @@ class UserSettingsType extends AbstractType
'label' => 'user.email.label',
'disabled' => !$this->security->isGranted('edit_infos', $options['data']) || $this->demo_mode,
])
->add('showEmailOnProfile', CheckboxType::class, [
'required' => false,
'label' => 'user.show_email_on_profile.label',
'disabled' => !$this->security->isGranted('edit_infos', $options['data']) || $this->demo_mode,
])
->add('avatar_file', FileType::class, [
'label' => 'user_settings.change_avatar.label',
'mapped' => false,

View file

@ -38,10 +38,13 @@ class UserVoter extends ExtendedVoter
protected function supports(string $attribute, $subject): bool
{
if (is_a($subject, User::class, true)) {
return in_array($attribute, array_merge(
$this->resolver->listOperationsForPermission('users'),
$this->resolver->listOperationsForPermission('self')),
false
return in_array($attribute,
array_merge(
$this->resolver->listOperationsForPermission('users'),
$this->resolver->listOperationsForPermission('self'),
['info']
),
false
);
}
@ -56,6 +59,16 @@ class UserVoter extends ExtendedVoter
*/
protected function voteOnUser(string $attribute, $subject, User $user): bool
{
if ($attribute === 'info') {
//Every logged-in user (non-anonymous) can see the info pages of other users
if (!$user->isAnonymousUser()) {
return true;
}
//For the anonymous user, use the user read permission
$attribute = 'read';
}
//Check if the checked user is the user itself
if (($subject instanceof User) && $subject->getID() === $user->getID() &&
$this->resolver->isValidOperation('self', $attribute)) {

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Services\LogSystem;
/**
* This service is used to check if a log change comment is needed for a given operation type.
* It is configured using the "enforce_change_comments_for" config parameter.
*/
class EventCommentNeededHelper
{
protected array $enforce_change_comments_for;
public const VALID_OPERATION_TYPES = [
'part_edit',
'part_create',
'part_delete',
'part_stock_operation',
'datastructure_edit',
'datastructure_create',
'datastructure_delete',
];
public function __construct(array $enforce_change_comments_for)
{
$this->enforce_change_comments_for = $enforce_change_comments_for;
}
/**
* Checks if a log change comment is needed for the given operation type
* @param string $comment_type
* @return bool
*/
public function isCommentNeeded(string $comment_type): bool
{
//Check if the comment type is valid
if (! in_array($comment_type, self::VALID_OPERATION_TYPES, true)) {
throw new \InvalidArgumentException('The comment type "'.$comment_type.'" is not valid!');
}
return in_array($comment_type, $this->enforce_change_comments_for, true);
}
}

View file

@ -24,6 +24,7 @@ namespace App\Services\LogSystem;
use App\Entity\LogSystem\AbstractLogEntry;
use App\Entity\UserSystem\User;
use App\Services\Misc\ConsoleInfoHelper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
@ -34,14 +35,17 @@ class EventLogger
protected array $whitelist;
protected EntityManagerInterface $em;
protected Security $security;
protected ConsoleInfoHelper $console_info_helper;
public function __construct(int $minimum_log_level, array $blacklist, array $whitelist, EntityManagerInterface $em, Security $security)
public function __construct(int $minimum_log_level, array $blacklist, array $whitelist, EntityManagerInterface $em,
Security $security, ConsoleInfoHelper $console_info_helper)
{
$this->minimum_log_level = $minimum_log_level;
$this->blacklist = $blacklist;
$this->whitelist = $whitelist;
$this->em = $em;
$this->security = $security;
$this->console_info_helper = $console_info_helper;
}
/**
@ -67,6 +71,11 @@ class EventLogger
$logEntry->setUser($user);
}
//Set the console user info, if the log entry was created in a console command
if ($this->console_info_helper->isCLI()) {
$logEntry->setCLIUsername($this->console_info_helper->getCLIUser() ?? 'Unknown');
}
if ($this->shouldBeAdded($logEntry)) {
$this->em->persist($logEntry);

View 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\Services\Misc;
class ConsoleInfoHelper
{
/**
* Returns true if the current script is executed in a CLI environment.
* @return bool true if the current script is executed in a CLI environment, false otherwise
*/
public function isCLI(): bool
{
return \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true);
}
/**
* Returns the username of the user who started the current script if possible.
* @return string|null the username of the user who started the current script if possible, null otherwise
*/
public function getCLIUser(): ?string
{
if (!$this->isCLI()) {
return null;
}
//Try to use the posix extension if available (Linux)
if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
$user = posix_getpwuid(posix_geteuid());
return $user['name'];
}
//Try to retrieve the name via the environment variable Username (Windows)
if (isset($_SERVER['USERNAME'])) {
return $_SERVER['USERNAME'];
}
//Try to retrieve the name via the environment variable USER (Linux)
if (isset($_SERVER['USER'])) {
return $_SERVER['USER'];
}
//Otherwise we can't determine the username
return null;
}
}

View file

@ -0,0 +1,43 @@
<?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\Twig;
use App\Services\LogSystem\EventCommentNeededHelper;
use Twig\Extension\AbstractExtension;
final class MiscExtension extends AbstractExtension
{
private EventCommentNeededHelper $eventCommentNeededHelper;
public function __construct(EventCommentNeededHelper $eventCommentNeededHelper)
{
$this->eventCommentNeededHelper = $eventCommentNeededHelper;
}
public function getFunctions()
{
return [
new \Twig\TwigFunction('event_comment_needed',
fn(string $operation_type) => $this->eventCommentNeededHelper->isCommentNeeded($operation_type)
),
];
}
}

View file

@ -14,7 +14,8 @@
</button>
<div class="dropdown-menu p-2">
<div class="form-group"><label for="delete_log_comment">{% trans %}edit.log_comment{% endtrans %}</label>
<input type="text" id="delete_log_comment" name="log_comment" class="form-control">
<input type="text" id="delete_log_comment" name="log_comment" class="form-control"
{% if event_comment_needed('datastructure_delete') %}required{% endif %}>
</div>
</div>
</div>

View file

@ -21,6 +21,7 @@
{{ form_row(form.first_name) }}
{{ form_row(form.last_name) }}
{{ form_row(form.email) }}
{{ form_row(form.showEmailOnProfile) }}
{{ form_row(form.department) }}
{{ form_row(form.aboutMe) }}
{% endblock %}

View file

@ -106,9 +106,9 @@
{{ form_widget(form.save_and_clone, {'attr': {'class': 'dropdown-item'}}) }}
{{ form_widget(form.save_and_new, {'attr': {'class': 'dropdown-item'}}) }}
<div class="dropdown-divider"></div>
<div class="p-2">
{{ form_row(form.log_comment)}}
<div class="px-2">
<label class="form-label">{% trans %}edit.log_comment{% endtrans %}</label>
{{ form_widget(form.log_comment)}}
</div>
</div>
</div>

View file

@ -44,7 +44,9 @@
</button>
<div class="dropdown-menu p-2">
<div class="form-group"><label for="delete_log_comment">{% trans %}edit.log_comment{% endtrans %}</label>
<input type="text" id="delete_log_comment" name="log_comment" class="form-control">
<input type="text" id="delete_log_comment" name="log_comment" class="form-control"
{% if event_comment_needed('part_delete') %}required{% endif %}
>
</div>
</div>
</div>

View file

@ -46,7 +46,7 @@
{% trans %}part.info.withdraw_modal.comment{% endtrans %}
</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="comment" value="">
<input type="text" class="form-control" name="comment" value="" {% if event_comment_needed('part_stock_operation') %}required{% endif %}>
<div id="emailHelp" class="form-text">{% trans %}part.info.withdraw_modal.comment.hint{% endtrans %}</div>
</div>
</div>

View file

@ -62,9 +62,11 @@
{{ form_row(filterForm.storelocation) }}
{{ form_row(filterForm.minAmount) }}
{{ form_row(filterForm.amountSum) }}
{{ form_row(filterForm.lessThanDesired) }}
{{ form_row(filterForm.lotCount) }}
{{ form_row(filterForm.lotExpirationDate) }}
{{ form_row(filterForm.lotDescription) }}
{{ form_row(filterForm.lotOwner) }}
{{ form_row(filterForm.lotNeedsRefill) }}
{{ form_row(filterForm.lotUnknownAmount) }}
</div>

View file

@ -29,8 +29,11 @@
<div class="form-group row">
<label class="col-form-label col-md-4">{% trans %}user.email.label{% endtrans %}</label>
<div class="col-md-8">
{# <p class="form-control-plaintext">{{ user.email }}</p>#}
<a class="form-control-plaintext" href="mailto:{{ user.email }}">{{ user.email }}</a>
{% if user.showEmailOnProfile %}
<a class="form-control-plaintext" href="mailto:{{ user.email }}">{{ user.email }}</a>
{% else %}
<span class="form-control-plaintext text-muted">-</span>
{% endif %}
</div>
</div>
<div class="form-group row">

View file

@ -25,6 +25,7 @@
{{ form_row(settings_form.last_name) }}
{{ form_row(settings_form.department) }}
{{ form_row(settings_form.email) }}
{{ form_row(settings_form.showEmailOnProfile) }}
{{ form_row(settings_form.avatar_file) }}
<div class="mb-3 row {% if user.masterPictureAttachment is null %}d-none{% endif %}">
<div class="offset-sm-3 col-sm-9">

View file

@ -44,6 +44,7 @@ namespace App\Tests\Entity\LogSystem;
use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\AttachmentType;
use App\Entity\Attachments\PartAttachment;
use App\Entity\LogSystem\UserLoginLogEntry;
use App\Entity\ProjectSystem\Project;
use App\Entity\ProjectSystem\ProjectBOMEntry;
use App\Entity\LogSystem\AbstractLogEntry;
@ -160,4 +161,25 @@ class AbstractLogEntryTest extends TestCase
$this->assertNull($log->getTargetClass());
$this->assertNull($log->getTargetID());
}
public function testCLIUsername(): void
{
$log = new UserLoginLogEntry('1.1.1.1');
//By default no no CLI username is set
$this->assertNull($log->getCLIUsername());
$this->assertFalse($log->isCLIEntry());
$user = new User();
$user->setName('test');
$log->setUser($user);
//Set a CLI username
$log->setCLIUsername('root');
$this->assertSame('root', $log->getCLIUsername());
$this->assertTrue($log->isCLIEntry());
//Normal user must be null now
$this->assertNull($log->getUser());
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Tests\Services\LogSystem;
use App\Services\LogSystem\EventCommentNeededHelper;
use PHPUnit\Framework\TestCase;
class EventCommentNeededHelperTest extends TestCase
{
public function testIsCommentNeeded()
{
$service = new EventCommentNeededHelper(['part_edit', 'part_create']);
$this->assertTrue($service->isCommentNeeded('part_edit'));
$this->assertTrue($service->isCommentNeeded('part_create'));
$this->assertFalse($service->isCommentNeeded('part_delete'));
$this->assertFalse($service->isCommentNeeded('part_lot_operation'));
}
public function testIsCommentNeededInvalidTypeException()
{
$service = new EventCommentNeededHelper(['part_edit', 'part_create']);
$this->expectException(\InvalidArgumentException::class);
$service->isCommentNeeded('this_is_not_valid');
}
}

View file

@ -11259,5 +11259,35 @@ Element 3</target>
<target>Less than desired</target>
</segment>
</unit>
<unit id="cdnsW4q" name="log.cli_user">
<segment>
<source>log.cli_user</source>
<target>CLI user</target>
</segment>
</unit>
<unit id="4GTAJ9E" name="log.element_edited.changed_fields.part_owner_must_match">
<segment>
<source>log.element_edited.changed_fields.part_owner_must_match</source>
<target>Part owner must match storage location owner</target>
</segment>
</unit>
<unit id="u6qFa_j" name="part.filter.lessThanDesired">
<segment>
<source>part.filter.lessThanDesired</source>
<target><![CDATA[In stock less than desired (total amount < min. amount)]]></target>
</segment>
</unit>
<unit id="lHTN.a1" name="part.filter.lotOwner">
<segment>
<source>part.filter.lotOwner</source>
<target>Lot owner</target>
</segment>
</unit>
<unit id="47OCK_W" name="user.show_email_on_profile.label">
<segment>
<source>user.show_email_on_profile.label</source>
<target>Show email on public profile page</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -311,5 +311,11 @@
<target>The owner of this lot must match the owner of the selected storage location (%owner_name%)!</target>
</segment>
</unit>
<unit id="HXSz3nQ" name="validator.part_lot.owner_must_not_be_anonymous">
<segment>
<source>validator.part_lot.owner_must_not_be_anonymous</source>
<target>A lot owner must not be the anonymous user!</target>
</segment>
</unit>
</file>
</xliff>

119
yarn.lock
View file

@ -3,11 +3,11 @@
"@ampproject/remapping@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==
version "2.2.1"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
dependencies:
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4":
@ -1411,18 +1411,10 @@
"@types/yargs" "^17.0.8"
chalk "^4.0.0"
"@jridgewell/gen-mapping@^0.1.0":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996"
integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==
dependencies:
"@jridgewell/set-array" "^1.0.0"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
version "0.3.3"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
@ -1433,28 +1425,33 @@
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/source-map@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
version "0.3.3"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda"
integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
"@jridgewell/sourcemap-codec@1.4.14":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.17"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==
version "0.3.18"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
dependencies:
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
@ -2403,9 +2400,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449:
version "1.0.30001473"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz#3859898b3cab65fc8905bb923df36ad35058153c"
integrity sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==
version "1.0.30001474"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001474.tgz#13b6fe301a831fe666cce8ca4ef89352334133d5"
integrity sha512-iaIZ8gVrWfemh5DG3T9/YqarVZoYf0r188IjaGwx68j4Pf0SGY6CQkmJUIE+NZHkkecQGohzXmBGEwWDr9aM3Q==
chalk@^2.0.0, chalk@^2.3.2:
version "2.4.2"
@ -2500,9 +2497,9 @@ cli-cursor@^3.1.0:
restore-cursor "^3.1.0"
cli-spinners@^2.6.1:
version "2.7.0"
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a"
integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==
version "2.8.0"
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.8.0.tgz#e97a3e2bd00e6d85aa0c13d7f9e3ce236f7787fc"
integrity sha512-/eG5sJcvEIwxcdYM86k5tPwn0MUzkX5YY3eImTGpJOZgVe4SdTMY14vQpcxgBzJ0wXwAYrS8E+c3uHeK4JNyzQ==
clipboard@^2.0.4:
version "2.0.11"
@ -2669,16 +2666,16 @@ cookie@0.5.0:
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
core-js-compat@^3.25.1:
version "3.29.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.29.1.tgz#15c0fb812ea27c973c18d425099afa50b934b41b"
integrity sha512-QmchCua884D8wWskMX8tW5ydINzd8oSJVx38lx/pVkFGqztxt73GYre3pm/hyYq8bPf+MW5In4I/uRShFDsbrA==
version "3.30.0"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.0.tgz#99aa2789f6ed2debfa1df3232784126ee97f4d80"
integrity sha512-P5A2h/9mRYZFIAP+5Ab8ns6083IyVpSclU74UNvbGVQ8VM7n3n3/g2yF3AkKQ9NXz2O+ioxLbEWKnDtgsFamhg==
dependencies:
browserslist "^4.21.5"
core-js@^3.23.0:
version "3.29.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.29.1.tgz#40ff3b41588b091aaed19ca1aa5cb111803fa9a6"
integrity sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==
version "3.30.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.0.tgz#64ac6f83bc7a49fd42807327051701d4b1478dea"
integrity sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==
core-util-is@~1.0.0:
version "1.0.3"
@ -3174,9 +3171,9 @@ ee-first@1.1.1:
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.4.284:
version "1.4.348"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz#f49379dc212d79f39112dd026f53e371279e433d"
integrity sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ==
version "1.4.356"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.356.tgz#b75a8a8c31d571f6024310cc980a08cd6c15a8c5"
integrity sha512-nEftV1dRX3omlxAj42FwqRZT0i4xd2dIg39sog/CnCJeCcL1TRd2Uh0i9Oebgv8Ou0vzTPw++xc+Z20jzS2B6A==
emoji-regex@^8.0.0:
version "8.0.0"
@ -3794,9 +3791,9 @@ has@^1.0.1, has@^1.0.3:
function-bind "^1.1.1"
hotkeys-js@>=3:
version "3.10.1"
resolved "https://registry.yarnpkg.com/hotkeys-js/-/hotkeys-js-3.10.1.tgz#0c67e72298f235c9200e421ab112d156dc81356a"
integrity sha512-mshqjgTqx8ee0qryHvRgZaZDxTwxam/2yTQmQlqAWS3+twnq1jsY9Yng9zB7lWq6WRrjTbTOc7knNwccXQiAjQ==
version "3.10.2"
resolved "https://registry.yarnpkg.com/hotkeys-js/-/hotkeys-js-3.10.2.tgz#cf52661904f5a13a973565cb97085fea2f5ae257"
integrity sha512-Z6vLmJTYzkbZZXlBkhrYB962Q/rZGc/WHQiyEGu9ZZVF7bAeFDjjDa31grWREuw9Ygb4zmlov2bTkPYqj0aFnQ==
hpack.js@^2.1.6:
version "2.1.6"
@ -4026,7 +4023,7 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.9.0:
is-core-module@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
@ -4436,9 +4433,9 @@ media-typer@0.3.0:
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
memfs@^3.4.3:
version "3.4.13"
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.13.tgz#248a8bd239b3c240175cd5ec548de5227fc4f345"
integrity sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==
version "3.5.0"
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.5.0.tgz#9da86405fca0a539addafd37dbd452344fd1c0bd"
integrity sha512-yK6o8xVJlQerz57kvPROwTMgx5WtGwC2ZxDtOUsnGl49rHjYkfQoPNZPCKH73VdLE1BwBu/+Fx/NL8NYMUw2aA==
dependencies:
fs-monkey "^1.0.3"
@ -4664,9 +4661,9 @@ nth-check@^2.0.1:
boolbase "^1.0.0"
nwsapi@^2.2.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0"
integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==
version "2.2.3"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.3.tgz#00e04dfd5a4a751e5ec2fecdc75dfd2f0db820fa"
integrity sha512-jscxIO4/VKScHlbmFBdV1Z6LXnLO+ZR4VMtypudUdfwtKxUN3TQcNFIHLwKtrUbDyHN4/GycY9+oRGZ2XMXYPw==
object-assign@^4.0.1:
version "4.1.1"
@ -5512,11 +5509,11 @@ resolve@1.1.7:
integrity sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==
resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.14.2, resolve@^1.9.0:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
version "1.22.2"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
dependencies:
is-core-module "^2.9.0"
is-core-module "^2.11.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
@ -5752,9 +5749,9 @@ shebang-regex@^3.0.0:
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shell-quote@^1.7.3:
version "1.8.0"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.0.tgz#20d078d0eaf71d54f43bd2ba14a1b5b9bfa5c8ba"
integrity sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==
version "1.8.1"
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
shelljs@^0.8.1:
version "0.8.5"
@ -6444,9 +6441,9 @@ webpack-dev-middleware@^5.3.1:
schema-utils "^4.0.0"
webpack-dev-server@^4.8.0:
version "4.13.1"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.13.1.tgz#6417a9b5d2f528e7644b68d6ed335e392dccffe8"
integrity sha512-5tWg00bnWbYgkN+pd5yISQKDejRBYGEw15RaEEslH+zdbNDxxaZvEAO2WulaSaFKb5n3YG8JXsGaDsut1D0xdA==
version "4.13.2"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz#d97445481d78691efe6d9a3b230833d802fc31f9"
integrity sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==
dependencies:
"@types/bonjour" "^3.5.9"
"@types/connect-history-api-fallback" "^1.3.5"
@ -6517,9 +6514,9 @@ webpack-sources@^3.2.3:
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.74.0:
version "5.77.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.77.0.tgz#dea3ad16d7ea6b84aa55fa42f4eac9f30e7eb9b4"
integrity sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q==
version "5.78.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.78.0.tgz#836452a12416af2a7beae906b31644cb2562f9e6"
integrity sha512-gT5DP72KInmE/3azEaQrISjTvLYlSM0j1Ezhht/KLVkrqtv10JoP/RXhwmX/frrutOPuSq3o5Vq0ehR/4Vmd1g==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"