mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75b4b88c5b | ||
|
|
d9e59d6862 | ||
|
|
5fa94969de | ||
|
|
f2c3ddac97 | ||
|
|
46f0da9ffa | ||
|
|
1e832a6782 | ||
|
|
56bdb44efd | ||
|
|
fffac44991 | ||
|
|
e42b3d6584 | ||
|
|
7ab2c3abbd | ||
|
|
498c816b65 | ||
|
|
eec740079d | ||
|
|
c359715a97 |
@@ -21,6 +21,7 @@
|
||||
"name": "2.9",
|
||||
"branchName": "2.9.x",
|
||||
"slug": "2.9",
|
||||
"current": true,
|
||||
"aliases": [
|
||||
"current",
|
||||
"stable"
|
||||
@@ -30,7 +31,7 @@
|
||||
"name": "2.8",
|
||||
"branchName": "2.8.x",
|
||||
"slug": "2.8",
|
||||
"current": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.7",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"ext-pdo": "*",
|
||||
"composer/package-versions-deprecated": "^1.8",
|
||||
"doctrine/annotations": "^1.13",
|
||||
"doctrine/cache": "^1.11|^2.0",
|
||||
"doctrine/cache": "^1.11.3|^2.0.3",
|
||||
"doctrine/collections": "^1.5",
|
||||
"doctrine/common": "^3.0.3",
|
||||
"doctrine/dbal": "^2.13.0",
|
||||
|
||||
@@ -10,7 +10,7 @@ code should look like. We will implement it on a
|
||||
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||
for all our domain objects.
|
||||
|
||||
.. warning::
|
||||
.. note::
|
||||
|
||||
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
|
||||
(`Details <https://github.com/doctrine/orm/issues/8383>`_)
|
||||
|
||||
@@ -113,7 +113,7 @@ Optional attributes:
|
||||
- **unique**: Boolean value to determine if the value of the column
|
||||
should be unique across all rows of the underlying entities table.
|
||||
|
||||
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false. When using typed properties on entity class defaults to true when property is nullable.
|
||||
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
|
||||
|
||||
- **options**: Array of additional options:
|
||||
|
||||
@@ -350,7 +350,7 @@ in order to specify that it is an embedded class.
|
||||
|
||||
Required attributes:
|
||||
|
||||
- **class**: The embeddable class
|
||||
- **class**: The embeddable class. You can omit this value if you use a PHP property type instead.
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
@@ -649,8 +649,6 @@ Optional attributes:
|
||||
constraint level. Defaults to false.
|
||||
- **nullable**: Determine whether the related entity is required, or if
|
||||
null is an allowed state for the relation. Defaults to true.
|
||||
When using typed properties on entity class defaults to false when
|
||||
property is not nullable.
|
||||
- **onDelete**: Cascade Action (Database-level)
|
||||
- **columnDefinition**: DDL SQL snippet that starts after the column
|
||||
name and specifies the complete (non-portable!) column definition.
|
||||
|
||||
@@ -61,7 +61,7 @@ This policy can be configured as follows:
|
||||
Notify
|
||||
~~~~~~
|
||||
|
||||
.. warning::
|
||||
.. note::
|
||||
|
||||
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
|
||||
(`Details <https://github.com/doctrine/orm/issues/8383>`_)
|
||||
|
||||
@@ -274,6 +274,9 @@ be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
There is also another important performance consideration that it is *NOT POSSIBLE*
|
||||
to query for the base entity without any LEFT JOINs to the sub-types.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ Partial Objects
|
||||
===============
|
||||
|
||||
|
||||
.. warning::
|
||||
.. note::
|
||||
|
||||
Creating Partial Objects through DQL is deprecated and
|
||||
will be removed in the future, use data transfer object
|
||||
|
||||
@@ -134,6 +134,10 @@ optimize the performance of the Flush Operation:
|
||||
explicit strategies of notifying the UnitOfWork what objects/properties
|
||||
changed.
|
||||
|
||||
.. note::
|
||||
|
||||
Flush only a single entity with ``$entityManager->flush($entity)`` is deprecated and will be removed in ORM 3.0.
|
||||
(`Details <https://github.com/doctrine/orm/issues/8459>`_)
|
||||
|
||||
Query Internals
|
||||
---------------
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
|
||||
<xs:complexType name="embedded">
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="class" type="orm:fqcn" use="required" />
|
||||
<xs:attribute name="class" type="orm:fqcn" use="optional" />
|
||||
<xs:attribute name="column-prefix" type="xs:string" use="optional" />
|
||||
<xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" />
|
||||
</xs:complexType>
|
||||
|
||||
@@ -26,6 +26,7 @@ use Doctrine\Common\Annotations\CachedReader;
|
||||
use Doctrine\Common\Annotations\SimpleAnnotationReader;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache as CacheDriver;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
|
||||
use Doctrine\Common\Proxy\AbstractProxyFactory;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
@@ -326,6 +327,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
);
|
||||
|
||||
$this->_attributes['metadataCacheImpl'] = $cacheImpl;
|
||||
$this->_attributes['metadataCache'] = CacheAdapter::wrap($cacheImpl);
|
||||
}
|
||||
|
||||
public function getMetadataCache(): ?CacheItemPoolInterface
|
||||
@@ -335,7 +337,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
public function setMetadataCache(CacheItemPoolInterface $cache): void
|
||||
{
|
||||
$this->_attributes['metadataCache'] = $cache;
|
||||
$this->_attributes['metadataCache'] = $cache;
|
||||
$this->_attributes['metadataCacheImpl'] = DoctrineProvider::wrap($cache);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,16 +433,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
throw ORMException::proxyClassesAlwaysRegenerating();
|
||||
}
|
||||
|
||||
if ($this->getMetadataCache()) {
|
||||
return;
|
||||
if (! $this->getMetadataCache()) {
|
||||
throw ORMException::metadataCacheNotConfigured();
|
||||
}
|
||||
|
||||
$metadataCacheImpl = $this->getMetadataCacheImpl();
|
||||
|
||||
if (! $metadataCacheImpl) {
|
||||
throw ORMException::metadataCacheNotConfigured();
|
||||
}
|
||||
|
||||
if ($metadataCacheImpl instanceof ArrayCache) {
|
||||
throw ORMException::metadataCacheUsesNonPersistentCache($metadataCacheImpl);
|
||||
}
|
||||
|
||||
@@ -710,6 +710,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* metadata of the class with the given name.
|
||||
*
|
||||
* @param string $entityName The name of the entity class the new instance is used for.
|
||||
* @psalm-param class-string<T> $entityName
|
||||
*/
|
||||
public function __construct($entityName, ?NamingStrategy $namingStrategy = null)
|
||||
{
|
||||
@@ -1467,10 +1468,6 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
|
||||
|
||||
if ($type) {
|
||||
if (! isset($mapping['nullable'])) {
|
||||
$mapping['nullable'] = $type->allowsNull();
|
||||
}
|
||||
|
||||
if (
|
||||
! isset($mapping['type'])
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
@@ -1526,14 +1523,6 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
$mapping['targetEntity'] = $type->getName();
|
||||
}
|
||||
|
||||
if (isset($mapping['joinColumns'])) {
|
||||
foreach ($mapping['joinColumns'] as &$joinColumn) {
|
||||
if ($type->allowsNull() === false) {
|
||||
$joinColumn['nullable'] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
@@ -3604,9 +3593,16 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
{
|
||||
$this->assertFieldNotMapped($mapping['fieldName']);
|
||||
|
||||
if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) {
|
||||
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
|
||||
if ($type instanceof ReflectionNamedType) {
|
||||
$mapping['class'] = $type->getName();
|
||||
}
|
||||
}
|
||||
|
||||
$this->embeddedClasses[$mapping['fieldName']] = [
|
||||
'class' => $this->fullyQualifiedClassName($mapping['class']),
|
||||
'columnPrefix' => $mapping['columnPrefix'],
|
||||
'columnPrefix' => $mapping['columnPrefix'] ?? null,
|
||||
'declaredField' => $mapping['declaredField'] ?? null,
|
||||
'originalField' => $mapping['originalField'] ?? null,
|
||||
];
|
||||
|
||||
@@ -57,8 +57,8 @@ final class Column implements Annotation
|
||||
/** @var bool */
|
||||
public $unique = false;
|
||||
|
||||
/** @var bool|null */
|
||||
public $nullable;
|
||||
/** @var bool */
|
||||
public $nullable = false;
|
||||
|
||||
/** @var array<string,mixed> */
|
||||
public $options = [];
|
||||
@@ -76,7 +76,7 @@ final class Column implements Annotation
|
||||
?int $precision = null,
|
||||
?int $scale = null,
|
||||
bool $unique = false,
|
||||
?bool $nullable = null,
|
||||
bool $nullable = false,
|
||||
array $options = [],
|
||||
?string $columnDefinition = null
|
||||
) {
|
||||
|
||||
@@ -324,7 +324,7 @@ class XmlDriver extends FileDriver
|
||||
|
||||
$mapping = [
|
||||
'fieldName' => (string) $embeddedMapping['name'],
|
||||
'class' => (string) $embeddedMapping['class'],
|
||||
'class' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null,
|
||||
'columnPrefix' => $useColumnPrefix ? $columnPrefix : false,
|
||||
];
|
||||
|
||||
|
||||
@@ -408,7 +408,7 @@ class YamlDriver extends FileDriver
|
||||
foreach ($element['embedded'] as $name => $embeddedMapping) {
|
||||
$mapping = [
|
||||
'fieldName' => $name,
|
||||
'class' => $embeddedMapping['class'],
|
||||
'class' => $embeddedMapping['class'] ?? null,
|
||||
'columnPrefix' => $embeddedMapping['columnPrefix'] ?? null,
|
||||
];
|
||||
$metadata->mapEmbedded($mapping);
|
||||
|
||||
@@ -31,16 +31,13 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
final class Embedded implements Annotation
|
||||
{
|
||||
/**
|
||||
* @Required
|
||||
* @var string
|
||||
*/
|
||||
/** @var string|null */
|
||||
public $class;
|
||||
|
||||
/** @var string|bool|null */
|
||||
public $columnPrefix;
|
||||
|
||||
public function __construct(string $class, $columnPrefix = null)
|
||||
public function __construct(?string $class = null, $columnPrefix = null)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->columnPrefix = $columnPrefix;
|
||||
|
||||
@@ -105,6 +105,7 @@ class ConvertDoctrine1Schema
|
||||
|
||||
/**
|
||||
* @param mixed[] $mappingInformation
|
||||
* @psalm-param class-string $className
|
||||
*/
|
||||
private function convertToClassMetadataInfo(
|
||||
string $className,
|
||||
|
||||
@@ -630,9 +630,13 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*
|
||||
* @param ClassMetadata $class The class descriptor of the entity.
|
||||
* @param object $entity The entity for which to compute the changes.
|
||||
* @psalm-param ClassMetadata<T> $class
|
||||
* @psalm-param T $entity
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @template T of object
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
public function computeChangeSet(ClassMetadata $class, $entity)
|
||||
@@ -959,6 +963,10 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
* @psalm-param ClassMetadata<T> $class
|
||||
* @psalm-param T $entity
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
private function persistNew(ClassMetadata $class, $entity): void
|
||||
{
|
||||
@@ -1017,11 +1025,14 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*
|
||||
* @param ClassMetadata $class The class descriptor of the entity.
|
||||
* @param object $entity The entity for which to (re)calculate the change set.
|
||||
* @psalm-param ClassMetadata<T> $class
|
||||
* @psalm-param T $entity
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
|
||||
*
|
||||
* @template T of object
|
||||
* @ignore
|
||||
*/
|
||||
public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity)
|
||||
@@ -1144,6 +1155,10 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
* @psalm-param ClassMetadata<T> $class
|
||||
* @psalm-param T $entity
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
private function addToEntityIdentifiersAndEntityMap(
|
||||
ClassMetadata $class,
|
||||
@@ -2020,8 +2035,13 @@ class UnitOfWork implements PropertyChangedListener
|
||||
/**
|
||||
* @param object $entity
|
||||
* @param object $managedCopy
|
||||
* @psalm-param ClassMetadata<T> $class
|
||||
* @psalm-param T $entity
|
||||
* @psalm-param T $managedCopy
|
||||
*
|
||||
* @throws OptimisticLockException
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
private function ensureVersionMatch(
|
||||
ClassMetadata $class,
|
||||
|
||||
@@ -557,7 +557,7 @@ parameters:
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined method ReflectionProperty\\:\\:getType\\(\\)\\.$#"
|
||||
count: 2
|
||||
count: 3
|
||||
path: lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
|
||||
|
||||
-
|
||||
|
||||
@@ -1125,9 +1125,7 @@
|
||||
<code>$table</code>
|
||||
<code>$tableGeneratorDefinition</code>
|
||||
</PropertyNotSetInConstructor>
|
||||
<PropertyTypeCoercion occurrences="13">
|
||||
<code>$entityName</code>
|
||||
<code>$entityName</code>
|
||||
<PropertyTypeCoercion occurrences="11">
|
||||
<code>$this->entityListeners</code>
|
||||
<code>$this->fieldMappings</code>
|
||||
<code>$this->fullyQualifiedClassName($repositoryClassName)</code>
|
||||
|
||||
18
tests/Doctrine/Tests/Models/TypedProperties/Contact.php
Normal file
18
tests/Doctrine/Tests/Models/TypedProperties/Contact.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\TypedProperties;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Embeddable()
|
||||
*/
|
||||
#[ORM\Embeddable]
|
||||
class Contact
|
||||
{
|
||||
/** @Column() */
|
||||
#[ORM\Column]
|
||||
public ?string $email = null;
|
||||
}
|
||||
@@ -24,6 +24,7 @@ class UserTyped
|
||||
*/
|
||||
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
|
||||
public int $id;
|
||||
|
||||
/** @Column(length=50) */
|
||||
#[ORM\Column(length: 50)]
|
||||
public ?string $status;
|
||||
@@ -67,6 +68,10 @@ class UserTyped
|
||||
#[ORM\ManyToOne]
|
||||
public ?CmsEmail $mainEmail;
|
||||
|
||||
/** @Embedded */
|
||||
#[ORM\Embedded]
|
||||
public ?Contact $contact = null;
|
||||
|
||||
public static function loadMetadata(ClassMetadataInfo $metadata): void
|
||||
{
|
||||
$metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE);
|
||||
@@ -131,5 +136,7 @@ class UserTyped
|
||||
$metadata->mapManyToOne(
|
||||
['fieldName' => 'mainEmail']
|
||||
);
|
||||
|
||||
$metadata->mapEmbedded(['fieldName' => 'contact']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ class ConfigurationTest extends DoctrineTestCase
|
||||
$queryCacheImpl = $this->createMock(Cache::class);
|
||||
$this->configuration->setMetadataCacheImpl($queryCacheImpl);
|
||||
$this->assertSame($queryCacheImpl, $this->configuration->getMetadataCacheImpl());
|
||||
$this->assertNotNull($this->configuration->getMetadataCache());
|
||||
}
|
||||
|
||||
public function testSetGetMetadataCache(): void
|
||||
@@ -141,6 +142,7 @@ class ConfigurationTest extends DoctrineTestCase
|
||||
$cache = $this->createStub(CacheItemPoolInterface::class);
|
||||
$this->configuration->setMetadataCache($cache);
|
||||
$this->assertSame($cache, $this->configuration->getMetadataCache());
|
||||
$this->assertNotNull($this->configuration->getMetadataCacheImpl());
|
||||
}
|
||||
|
||||
public function testAddGetNamedQuery(): void
|
||||
|
||||
@@ -42,6 +42,7 @@ use Doctrine\Tests\Models\DDC889\DDC889Class;
|
||||
use Doctrine\Tests\Models\DDC889\DDC889Entity;
|
||||
use Doctrine\Tests\Models\DDC964\DDC964Admin;
|
||||
use Doctrine\Tests\Models\DDC964\DDC964Guest;
|
||||
use Doctrine\Tests\Models\TypedProperties\Contact;
|
||||
use Doctrine\Tests\Models\TypedProperties\UserTyped;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
|
||||
@@ -265,24 +266,6 @@ abstract class AbstractMappingDriverTest extends OrmTestCase
|
||||
return $class;
|
||||
}
|
||||
|
||||
public function testFieldIsNullableByType(): void
|
||||
{
|
||||
if (PHP_VERSION_ID < 70400) {
|
||||
$this->markTestSkipped('requies PHP 7.4');
|
||||
}
|
||||
|
||||
$class = $this->createClassMetadata(UserTyped::class);
|
||||
|
||||
// Explicit Nullable
|
||||
$this->assertTrue($class->isNullable('status'));
|
||||
|
||||
// Explicit Not Nullable
|
||||
$this->assertFalse($class->isNullable('username'));
|
||||
|
||||
$this->assertEquals(CmsEmail::class, $class->getAssociationMapping('email')['targetEntity']);
|
||||
$this->assertEquals(CmsEmail::class, $class->getAssociationMapping('mainEmail')['targetEntity']);
|
||||
}
|
||||
|
||||
public function testFieldTypeFromReflection(): void
|
||||
{
|
||||
if (PHP_VERSION_ID < 70400) {
|
||||
@@ -299,6 +282,10 @@ abstract class AbstractMappingDriverTest extends OrmTestCase
|
||||
$this->assertEquals('json', $class->getTypeOfField('array'));
|
||||
$this->assertEquals('boolean', $class->getTypeOfField('boolean'));
|
||||
$this->assertEquals('float', $class->getTypeOfField('float'));
|
||||
|
||||
$this->assertEquals(CmsEmail::class, $class->getAssociationMapping('email')['targetEntity']);
|
||||
$this->assertEquals(CmsEmail::class, $class->getAssociationMapping('mainEmail')['targetEntity']);
|
||||
$this->assertEquals(Contact::class, $class->embeddedClasses['contact']['class']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,19 +120,14 @@ class ClassMetadataTest extends OrmTestCase
|
||||
$cm = new ClassMetadata(TypedProperties\UserTyped::class);
|
||||
$cm->initializeReflection(new RuntimeReflectionService());
|
||||
|
||||
// Explicit Nullable
|
||||
$cm->mapField(['fieldName' => 'status', 'length' => 50]);
|
||||
$this->assertTrue($cm->isNullable('status'));
|
||||
|
||||
// Explicit Not Nullable
|
||||
$cm->mapField(['fieldName' => 'username', 'length' => 50]);
|
||||
$this->assertFalse($cm->isNullable('username'));
|
||||
|
||||
$cm->mapOneToOne(['fieldName' => 'email', 'joinColumns' => [[]]]);
|
||||
$this->assertEquals(CmsEmail::class, $cm->getAssociationMapping('email')['targetEntity']);
|
||||
|
||||
$cm->mapManyToOne(['fieldName' => 'mainEmail']);
|
||||
$this->assertEquals(CmsEmail::class, $cm->getAssociationMapping('mainEmail')['targetEntity']);
|
||||
|
||||
$cm->mapEmbedded(['fieldName' => 'contact']);
|
||||
$this->assertEquals(TypedProperties\Contact::class, $cm->embeddedClasses['contact']['class']);
|
||||
}
|
||||
|
||||
public function testFieldTypeFromReflection(): void
|
||||
|
||||
@@ -61,3 +61,5 @@ $metadata->mapOneToOne(
|
||||
$metadata->mapManyToOne(
|
||||
['fieldName' => 'mainEmail']
|
||||
);
|
||||
|
||||
$metadata->mapEmbedded(['fieldName' => 'contact']);
|
||||
|
||||
@@ -25,5 +25,7 @@
|
||||
</one-to-one>
|
||||
|
||||
<many-to-one field="mainEmail"/>
|
||||
|
||||
<embedded name="contact" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
@@ -24,3 +24,5 @@ Doctrine\Tests\Models\TypedProperties\UserTyped:
|
||||
joinColumn: []
|
||||
manyToOne:
|
||||
mainEmail: []
|
||||
embedded:
|
||||
contact: ~
|
||||
|
||||
Reference in New Issue
Block a user