Compare commits

..

No commits in common. "7054c51490833015451b63e5eb75a5a4a7f83f21" and "247fed7d742e705d036d51467c7aee8b27954e24" have entirely different histories.

19 changed files with 1229 additions and 1653 deletions

View file

@ -13,8 +13,8 @@
"ext-mbstring": "*",
"amphp/http-client": "^5.1",
"api-platform/doctrine-orm": "^4.1",
"api-platform/json-api": "^4.0.0",
"api-platform/symfony": "^4.0.0",
"api-platform/json-api": "^4.0.0",
"beberlei/doctrineextensions": "^1.2",
"brick/math": "^0.13.1",
"composer/ca-bundle": "^1.5",
@ -25,16 +25,16 @@
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^3.2.0",
"dompdf/dompdf": "^v3.0.0",
"erusev/parsedown": "^1.7",
"florianv/swap": "^4.0",
"florianv/swap-bundle": "dev-master",
"gregwar/captcha-bundle": "^2.1.0",
"hshn/base64-encoded-file": "^5.0",
"jbtronics/2fa-webauthn": "^3.0.0",
"jbtronics/2fa-webauthn": "^v2.2.0",
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
"jbtronics/settings-bundle": "^v2.6.0",
"jfcherng/php-diff": "^6.14",
"knpuniversity/oauth2-client-bundle": "^2.15",
"league/commonmark": "^2.7",
"league/csv": "^9.8.0",
"league/html-to-markdown": "^5.0.1",
"liip/imagine-bundle": "^2.2",
@ -95,7 +95,7 @@
"twig/intl-extra": "^3.8",
"twig/markdown-extra": "^3.8",
"twig/string-extra": "^3.8",
"web-auth/webauthn-symfony-bundle": "^5.0.0"
"web-auth/webauthn-symfony-bundle": "^4.0.0"
},
"require-dev": {
"dama/doctrine-test-bundle": "^v8.0.0",

1075
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -32,5 +32,5 @@ return [
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Jbtronics\SettingsBundle\JbtronicsSettingsBundle::class => ['all' => true],
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
\ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
];

View file

@ -25,6 +25,10 @@ doctrine:
tinyint:
class: App\Doctrine\Types\TinyIntType
# This was removed in doctrine/orm 4.0 but we need it for the WebauthnKey entity
array:
class: App\Doctrine\Types\ArrayType
schema_filter: ~^(?!internal)~
# Only enable this when needed
profiling_collect_backtrace: false

View file

@ -13,7 +13,7 @@ security:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js|\.well-known)/
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
provider: app_user_provider

View file

@ -1,27 +0,0 @@
---
layout: default
title: Upgrade from Part-DB 1.x to 2.x
nav_order: 1
has_children: false
---
# Upgrade from Part-DB 1.x to 2.x
Part-DB 2.0 is a major release that changes a lot of things internally, but it is still compatible with Part-DB 1.x.
Depending on your preferences, you will have to do some changes to your Part-DB installation, this document will guide
you through the upgrade process.
## New requirements
*If you are running Part-DB inside a docker container, you can skip this section, as the new requirements are already
fulfilled by the official Part-DB docker image.*
Part-DB 2.0 requires at least PHP 8.2 (newer versions are recommended). So if your existing Part-DB installation is still
running PHP 8.1, you will have to upgrade your PHP version first.
The minimum required version of node.js is now 20.0 or newer, so if you are using 18.0, you will have to upgrade it too.
Most distributions should have the possibility to get backports for PHP 8.4 and modern nodejs, so you should be able to
easily upgrade your system to the new requirements. Otherwise, you can use the official Part-DB docker image, which
ships all required dependencies and is always up to date with the latest requirements, so that you do not have to worry
about the requirements at all.

View file

@ -1,9 +0,0 @@
---
layout: default
title: Upgrade
nav_order: 7
has_children: true
---
This section provides information on how to upgrade Part-DB to the latest version.
This is intended for major release upgrades, where requirements or things changes significantly.

View file

@ -2,7 +2,6 @@
layout: default
title: Upgrade from legacy Part-DB version (<1.0)
nav_order: 100
redirect_from: /upgrade_legacy
---
# Upgrade from legacy Part-DB version

View file

@ -1,75 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Migration\AbstractMultiPlatformMigration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250813214628 extends AbstractMultiPlatformMigration
{
public function getDescription(): string
{
return 'Migrate webauthn_keys transports and other_ui fields to JSON type';
}
public function convertArrayToJson(): void
{
$connection = $this->connection;
$rows = $connection->fetchAllAssociative('SELECT id, transports, other_ui FROM webauthn_keys');
foreach ($rows as $row) {
$id = $row['id'];
$new_transports = json_encode(unserialize($row['transports'], ['allowed_classes' => false]),
JSON_THROW_ON_ERROR);
$new_other_ui = json_encode(unserialize($row['other_ui'], ['allowed_classes' => false]),
JSON_THROW_ON_ERROR);
$connection->executeStatement(
'UPDATE webauthn_keys SET transports = :transports, other_ui = :other_ui WHERE id = :id',
[
'transports' => $new_transports,
'other_ui' => $new_other_ui,
'id' => $id,
]
);
}
}
public function mySQLUp(Schema $schema): void
{
$this->convertArrayToJson();
$this->addSql('ALTER TABLE webauthn_keys CHANGE transports transports JSON NOT NULL, CHANGE other_ui other_ui JSON DEFAULT NULL');
}
public function mySQLDown(Schema $schema): void
{
$this->addSql('ALTER TABLE webauthn_keys CHANGE transports transports LONGTEXT NOT NULL, CHANGE other_ui other_ui LONGTEXT DEFAULT NULL');
}
public function sqLiteUp(Schema $schema): void
{
//As there is no JSON type in SQLite, we only need to convert the data.
$this->convertArrayToJson();
}
public function sqLiteDown(Schema $schema): void
{
//Nothing to do here, as SQLite does not support JSON type and we are not changing the column type.
}
public function postgreSQLUp(Schema $schema): void
{
$this->convertArrayToJson();
$this->addSql('ALTER TABLE webauthn_keys ALTER transports TYPE JSON USING transports::JSON');
$this->addSql('ALTER TABLE webauthn_keys ALTER other_ui TYPE JSON USING other_ui::JSON');
}
public function postgreSQLDown(Schema $schema): void
{
$this->addSql('ALTER TABLE webauthn_keys ALTER transports TYPE TEXT');
$this->addSql('ALTER TABLE webauthn_keys ALTER other_ui TYPE TEXT');
}
}

View file

@ -0,0 +1,116 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Doctrine\Types;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Exception\SerializationFailed;
use Doctrine\DBAL\Types\Type;
use Doctrine\Deprecations\Deprecation;
use function is_resource;
use function restore_error_handler;
use function serialize;
use function set_error_handler;
use function stream_get_contents;
use function unserialize;
use const E_DEPRECATED;
use const E_USER_DEPRECATED;
/**
* This class is taken from doctrine ORM 3.8. https://github.com/doctrine/dbal/blob/3.8.x/src/Types/ArrayType.php
*
* It was removed in doctrine ORM 4.0. However, we require it for backward compatibility with WebauthnKey.
* Therefore, we manually added it here as a custom type as a forward compatibility layer.
*/
class ArrayType extends Type
{
/**
* {@inheritDoc}
*/
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
return $platform->getClobTypeDeclarationSQL($column);
}
/**
* {@inheritDoc}
*/
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): string
{
return serialize($value);
}
/**
* {@inheritDoc}
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
{
if ($value === null) {
return null;
}
$value = is_resource($value) ? stream_get_contents($value) : $value;
set_error_handler(function (int $code, string $message): bool {
if ($code === E_DEPRECATED || $code === E_USER_DEPRECATED) {
return false;
}
//Change to original code. Use SerializationFailed instead of ConversionException.
throw new SerializationFailed("Serialization failed (Code $code): " . $message);
});
try {
//Change to original code. Use false for allowed_classes, to avoid unsafe unserialization of objects.
return unserialize($value, ['allowed_classes' => false]);
} finally {
restore_error_handler();
}
}
/**
* {@inheritDoc}
*/
public function getName(): string
{
return "array";
}
/**
* {@inheritDoc}
*
* @deprecated
*/
public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5509',
'%s is deprecated.',
__METHOD__,
);
return true;
}
}

View file

@ -100,19 +100,16 @@ class WebauthnKey extends BasePublicKeyCredentialSource implements TimeStampable
public static function fromRegistration(BasePublicKeyCredentialSource $registration): self
{
return new self(
publicKeyCredentialId: $registration->publicKeyCredentialId,
type: $registration->type,
transports: $registration->transports,
attestationType: $registration->attestationType,
trustPath: $registration->trustPath,
aaguid: $registration->aaguid,
credentialPublicKey: $registration->credentialPublicKey,
userHandle: $registration->userHandle,
counter: $registration->counter,
otherUI: $registration->otherUI,
backupEligible: $registration->backupEligible,
backupStatus: $registration->backupStatus,
uvInitialized: $registration->uvInitialized,
$registration->getPublicKeyCredentialId(),
$registration->getType(),
$registration->getTransports(),
$registration->getAttestationType(),
$registration->getTrustPath(),
$registration->getAaguid(),
$registration->getCredentialPublicKey(),
$registration->getUserHandle(),
$registration->getCounter(),
$registration->getOtherUI()
);
}
}

View file

@ -33,7 +33,6 @@ use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
use Webauthn\PublicKeyCredential;
/**
* This class decorates the Webauthn TwoFactorProvider and adds additional logic which allows us to set a last used date
@ -89,12 +88,10 @@ class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface
private function getWebauthnKeyFromCode(string $authenticationCode): ?WebauthnKey
{
$serializer = $this->webauthnProvider->getWebauthnSerializer();
$publicKeyCredentialLoader = $this->webauthnProvider->getPublicKeyCredentialLoader();
//Try to load the public key credential from the code
$publicKeyCredential = $serializer->deserialize($authenticationCode, PublicKeyCredential::class, 'json', [
'json_decode_options' => JSON_THROW_ON_ERROR
]);
$publicKeyCredential = $publicKeyCredentialLoader->load($authenticationCode);
//Find the credential source for the given credential id
$publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($publicKeyCredential->rawId);
@ -106,4 +103,4 @@ class WebauthnKeyLastUseTwoFactorProvider implements TwoFactorProviderInterface
return $publicKeyCredentialSource;
}
}
}

View file

@ -112,12 +112,12 @@ class AttachmentURLGenerator
/**
* Returns a URL to a thumbnail of the attachment file.
* For external files the original URL is returned.
* @return string|null The URL or null if the attachment file is not existing or is invalid
* @return string|null The URL or null if the attachment file is not existing
*/
public function getThumbnailURL(Attachment $attachment, string $filter_name = 'thumbnail_sm'): ?string
{
if (!$attachment->isPicture()) {
return null;
throw new InvalidArgumentException('Thumbnail creation only works for picture attachments!');
}
if (!$attachment->hasInternal()){

View file

@ -57,7 +57,6 @@ class EntityImporter
/**
* Creates many entries at once, based on a (text) list of name.
* The created entities are not persisted to database yet, so you have to do it yourself.
* It returns all entities in the hierachy chain (even if they are already persisted).
*
* @template T of AbstractNamedDBElement
* @param string $lines The list of names seperated by \n
@ -133,38 +132,32 @@ class EntityImporter
//We can only use the getNewEntityFromPath function, if the repository is a StructuralDBElementRepository
if ($repo instanceof StructuralDBElementRepository) {
$entities = $repo->getNewEntityFromPath($new_path);
if ($entities === []) {
$entity = end($entities);
if ($entity === false) {
throw new InvalidArgumentException('getNewEntityFromPath returned an empty array!');
}
} else { //Otherwise just create a new entity
$entity = new $class_name;
$entity->setName($name);
$entities = [$entity];
}
//Validate entity
foreach ($entities as $entity) {
$tmp = $this->validator->validate($entity);
//If no error occured, write entry to DB:
if (0 === count($tmp)) {
$valid_entities[] = $entity;
} else { //Otherwise log error
$errors[] = [
'entity' => $entity,
'violations' => $tmp,
];
}
$tmp = $this->validator->validate($entity);
//If no error occured, write entry to DB:
if (0 === count($tmp)) {
$valid_entities[] = $entity;
} else { //Otherwise log error
$errors[] = [
'entity' => $entity,
'violations' => $tmp,
];
}
$last_element = end($entities);
if ($last_element === false) {
$last_element = null;
}
$last_element = $entity;
}
//Only return objects once
return array_values(array_unique($valid_entities));
return $valid_entities;
}
/**

View file

@ -46,9 +46,7 @@ use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Part;
use App\Services\Formatters\SIFormatter;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
use League\CommonMark\MarkdownConverter;
use Parsedown;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
@ -56,13 +54,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
final class PartProvider implements PlaceholderProviderInterface
{
private readonly MarkdownConverter $inlineConverter;
public function __construct(private readonly SIFormatter $siFormatter, private readonly TranslatorInterface $translator)
{
$environment = new Environment();
$environment->addExtension(new InlinesOnlyExtension());
$this->inlineConverter = new MarkdownConverter($environment);
}
public function replace(string $placeholder, object $part, array $options = []): ?string
@ -119,20 +112,22 @@ final class PartProvider implements PlaceholderProviderInterface
return $this->translator->trans($part->getManufacturingStatus()->toTranslationKey());
}
$parsedown = new Parsedown();
if ('[[DESCRIPTION]]' === $placeholder) {
return trim($this->inlineConverter->convert($part->getDescription())->getContent());
return $parsedown->line($part->getDescription());
}
if ('[[DESCRIPTION_T]]' === $placeholder) {
return trim(strip_tags($this->inlineConverter->convert($part->getDescription())->getContent()));
return strip_tags((string) $parsedown->line($part->getDescription()));
}
if ('[[COMMENT]]' === $placeholder) {
return trim($this->inlineConverter->convert($part->getComment())->getContent());
return $parsedown->line($part->getComment());
}
if ('[[COMMENT_T]]' === $placeholder) {
return trim(strip_tags($this->inlineConverter->convert($part->getComment())->getContent()));
return strip_tags((string) $parsedown->line($part->getComment()));
}
return null;

View file

@ -133,6 +133,9 @@
"ekino/phpstan-banned-code": {
"version": "v0.3.1"
},
"erusev/parsedown": {
"version": "1.7.4"
},
"florianv/exchanger": {
"version": "1.4.1"
},

View file

@ -75,8 +75,8 @@ class EntityImporterTest extends WebTestCase
$em = self::getContainer()->get(EntityManagerInterface::class);
$parent = $em->find(AttachmentType::class, 1);
$results = $this->service->massCreation($lines, AttachmentType::class, $parent, $errors);
$this->assertCount(4, $results);
$this->assertSame("Test 1", $results[1]->getName());
$this->assertCount(3, $results);
$this->assertSame($parent, $results[0]->getParent());
//Test for addition of existing elements
$errors = [];
@ -113,31 +113,6 @@ EOT;
}
public function testMassCreationArrow(): void
{
$input = <<<EOT
Test1 -> Test1.1
Test1 -> Test1.2
Test2 -> Test2.1
Test1
Test1.3
EOT;
$errors = [];
$results = $this->service->massCreation($input, AttachmentType::class, null, $errors);
//We have 6 elements, and 0 errors
$this->assertCount(0, $errors);
$this->assertCount(6, $results);
$this->assertEquals('Test1', $results[0]->getName());
$this->assertEquals('Test1.1', $results[1]->getName());
$this->assertEquals('Test1.2', $results[2]->getName());
$this->assertEquals('Test2', $results[3]->getName());
$this->assertEquals('Test2.1', $results[4]->getName());
$this->assertEquals('Test1.3', $results[5]->getName());
}
public function testMassCreationNested(): void
{
$input = <<<EOT
@ -157,15 +132,15 @@ EOT;
//We have 7 elements, and 0 errors
$this->assertCount(0, $errors);
$this->assertCount(8, $results);
$this->assertCount(7, $results);
$element1 = $results[1];
$element11 = $results[2];
$element111 = $results[3];
$element112 = $results[4];
$element12 = $results[5];
$element121 = $results[6];
$element2 = $results[7];
$element1 = $results[0];
$element11 = $results[1];
$element111 = $results[2];
$element112 = $results[3];
$element12 = $results[4];
$element121 = $results[5];
$element2 = $results[6];
$this->assertSame('Test 1', $element1->getName());
$this->assertSame('Test 1.1', $element11->getName());

View file

@ -7157,15 +7157,12 @@ Exampletown</target>
</notes>
<segment state="translated">
<source>mass_creation.lines.placeholder</source>
<target><![CDATA[Element 1
<target>Element 1
Element 1.1
Element 1.1.1
Element 1.2
Element 2
Element 3
Element 1 -> Element 1.1
Element 1 -> Element 1.2]]></target>
Element 3</target>
</segment>
</unit>
<unit id="TWSqPFi" name="entity.mass_creation.btn">

1420
yarn.lock

File diff suppressed because it is too large Load diff