adds visibility to aliases
This commit is contained in:
parent
89433fbcfe
commit
3785a99b94
@ -298,7 +298,7 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
|
||||
$this->assertEquals('doctrine.dbal.conn1_connection.configuration', (string) $args[1]);
|
||||
$this->assertEquals('doctrine.dbal.conn1_connection.event_manager', (string) $args[2]);
|
||||
|
||||
$this->assertEquals('doctrine.orm.dm2_entity_manager', $container->getAlias('doctrine.orm.entity_manager'));
|
||||
$this->assertEquals('doctrine.orm.dm2_entity_manager', (string) $container->getAlias('doctrine.orm.entity_manager'));
|
||||
|
||||
$definition = $container->getDefinition('doctrine.orm.dm1_entity_manager');
|
||||
$this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass());
|
||||
|
@ -179,7 +179,7 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
|
||||
$this->assertEquals('%doctrine.odm.mongodb.connection_class%', $definition->getClass());
|
||||
$this->assertEquals(array('mongodb://localhost:27017', array('connect' => true), new Reference('doctrine.odm.mongodb.conn1_configuration')), $definition->getArguments());
|
||||
|
||||
$this->assertEquals('doctrine.odm.mongodb.dm2_document_manager', $container->getAlias('doctrine.odm.mongodb.document_manager'));
|
||||
$this->assertEquals('doctrine.odm.mongodb.dm2_document_manager', (string) $container->getAlias('doctrine.odm.mongodb.document_manager'));
|
||||
|
||||
$definition = $container->getDefinition('doctrine.odm.mongodb.dm1_document_manager');
|
||||
$this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass());
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
@ -443,7 +444,7 @@ class SecurityExtension extends Extension
|
||||
|
||||
// Existing DAO service provider
|
||||
if (isset($provider['id'])) {
|
||||
$container->setAlias($name, $provider['id']);
|
||||
$container->setAlias($name, new Alias($provider['id'], false));
|
||||
|
||||
return $provider['id'];
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="security.acl.dbal.connection" alias="doctrine.dbal.default_connection" />
|
||||
<service id="security.acl.dbal.connection" alias="doctrine.dbal.default_connection" public="false" />
|
||||
|
||||
<service id="security.acl.object_identity_retrieval_strategy" class="%security.acl.object_identity_retrieval_strategy.class%" public="false"></service>
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
<argument>%security.acl.cache.doctrine.prefix%</argument>
|
||||
</service>
|
||||
|
||||
<service id="security.acl.cache.doctrine.cache_impl" alias="doctrine.orm.default_result_cache" />
|
||||
<service id="security.acl.cache.doctrine.cache_impl" alias="doctrine.orm.default_result_cache" public="false" />
|
||||
|
||||
<service id="security.acl.permission.map" class="%security.acl.permission.map.class%" public="false"></service>
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="zend.logger" class="%zend.logger.class%" />
|
||||
<service id="zend.logger" class="%zend.logger.class%" public="false" />
|
||||
|
||||
<service id="zend.logger.writer.filesystem" class="%zend.logger.writer.filesystem.class%" public="false">
|
||||
<tag name="zend.logger.writer" />
|
||||
|
30
src/Symfony/Component/DependencyInjection/Alias.php
Normal file
30
src/Symfony/Component/DependencyInjection/Alias.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection;
|
||||
|
||||
class Alias
|
||||
{
|
||||
protected $id;
|
||||
protected $public;
|
||||
|
||||
public function __construct($id, $public = true)
|
||||
{
|
||||
$this->id = strtolower($id);
|
||||
$this->public = $public;
|
||||
}
|
||||
|
||||
public function isPublic()
|
||||
{
|
||||
return $this->public;
|
||||
}
|
||||
|
||||
public function setPublic($boolean)
|
||||
{
|
||||
$this->public = (Boolean) $boolean;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
@ -23,8 +23,19 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
*/
|
||||
class InlineServiceDefinitionsPass implements CompilerPassInterface
|
||||
{
|
||||
protected $aliasMap;
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$this->aliasMap = array();
|
||||
foreach ($container->getAliases() as $id => $alias) {
|
||||
if (!$alias->isPublic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->aliasMap[$id] = (string) $alias;
|
||||
}
|
||||
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
$definition->setArguments(
|
||||
$this->inlineArguments($container, $definition->getArguments())
|
||||
@ -65,7 +76,7 @@ class InlineServiceDefinitionsPass implements CompilerPassInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
$references = count(array_keys($container->getAliases(), $id, true));
|
||||
$references = count(array_keys($this->aliasMap, $id, true));
|
||||
foreach ($container->getDefinitions() as $cDefinition)
|
||||
{
|
||||
if ($references > 1) {
|
||||
|
@ -37,6 +37,8 @@ class PassConfig
|
||||
);
|
||||
|
||||
$this->removingPasses = array(
|
||||
new RemovePrivateAliasesPass(),
|
||||
new ReplaceAliasByActualDefinitionPass(),
|
||||
new InlineServiceDefinitionsPass(),
|
||||
new RemoveUnusedDefinitionsPass(),
|
||||
);
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Remove private aliases from the container. They were only used to establish
|
||||
* dependencies between services, and these dependencies have been resolved in
|
||||
* one of the previous passes.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class RemovePrivateAliasesPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
foreach ($container->getAliases() as $id => $alias) {
|
||||
if ($alias->isPublic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$container->removeAlias($id);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Replaces aliases with actual service definitions, effectively removing these
|
||||
* aliases.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
foreach ($container->getAliases() as $id => $alias) {
|
||||
$aliasId = (string) $alias;
|
||||
|
||||
$definition = $container->getDefinition($aliasId = (string) $alias);
|
||||
|
||||
if ($definition->isPublic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition->setPublic(true);
|
||||
$container->setDefinition($id, $definition);
|
||||
$container->remove($aliasId);
|
||||
|
||||
$this->updateReferences($container, $aliasId, $id);
|
||||
|
||||
// we have to restart the process due to concurrent modification of
|
||||
// the container
|
||||
$this->process($container);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function updateReferences($container, $currentId, $newId)
|
||||
{
|
||||
foreach ($container->getAliases() as $id => $alias) {
|
||||
if ($currentId === (string) $alias) {
|
||||
$container->setAlias($id, $newId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($container->getDefinitions() as $definition) {
|
||||
$definition->setArguments(
|
||||
$this->updateArgumentReferences($definition->getArguments(), $currentId, $newId)
|
||||
);
|
||||
|
||||
$definition->setMethodCalls(
|
||||
$this->updateArgumentReferences($definition->getMethodCalls(), $currentId, $newId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function updateArgumentReferences(array $arguments, $currentId, $newId)
|
||||
{
|
||||
foreach ($arguments as $k => $argument) {
|
||||
if (is_array($argument)) {
|
||||
$arguments[$k] = $this->updateArgumentReferences($argument, $currentId, $newId);
|
||||
} else if ($argument instanceof Reference) {
|
||||
if ($currentId === (string) $argument) {
|
||||
$arguments[$k] = new Reference($newId, $argument->getInvalidBehavior());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
@ -32,6 +33,13 @@ class ResolveReferencesToAliasesPass implements CompilerPassInterface
|
||||
$definition->setArguments($this->processArguments($definition->getArguments()));
|
||||
$definition->setMethodCalls($this->processArguments($definition->getMethodCalls()));
|
||||
}
|
||||
|
||||
foreach ($container->getAliases() as $id => $alias) {
|
||||
$aliasId = (string) $alias;
|
||||
if ($aliasId !== $defId = $this->getDefinitionId($aliasId)) {
|
||||
$container->setAlias($id, new Alias($defId, $alias->isPublic()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function processArguments(array $arguments)
|
||||
@ -54,7 +62,7 @@ class ResolveReferencesToAliasesPass implements CompilerPassInterface
|
||||
protected function getDefinitionId($id)
|
||||
{
|
||||
if ($this->container->hasAlias($id)) {
|
||||
return $this->getDefinitionId($this->container->getAlias($id));
|
||||
return $this->getDefinitionId((string) $this->container->getAlias($id));
|
||||
}
|
||||
|
||||
return $id;
|
||||
|
@ -379,12 +379,17 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
* Sets an alias for an existing service.
|
||||
*
|
||||
* @param string $alias The alias to create
|
||||
* @param string $id The service to alias
|
||||
* @param mixed $id The service to alias
|
||||
*/
|
||||
public function setAlias($alias, $id)
|
||||
{
|
||||
$alias = strtolower($alias);
|
||||
$id = strtolower($id);
|
||||
|
||||
if (is_string($id)) {
|
||||
$id = new Alias($id);
|
||||
} else if (!$id instanceof Alias) {
|
||||
throw new \InvalidArgumentException('$id must be a string, or an Alias object.');
|
||||
}
|
||||
|
||||
unset($this->definitions[$alias]);
|
||||
|
||||
@ -410,7 +415,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
*/
|
||||
public function hasAlias($id)
|
||||
{
|
||||
return array_key_exists(strtolower($id), $this->aliases);
|
||||
return isset($this->aliases[strtolower($id)]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -629,7 +634,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
$id = strtolower($id);
|
||||
|
||||
if ($this->hasAlias($id)) {
|
||||
return $this->findDefinition($this->getAlias($id));
|
||||
return $this->findDefinition((string) $this->getAlias($id));
|
||||
}
|
||||
|
||||
return $this->getDefinition($id);
|
||||
|
@ -782,7 +782,7 @@ EOF;
|
||||
return sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id);
|
||||
} else {
|
||||
if ($this->container->hasAlias($id)) {
|
||||
$id = $this->container->getAlias($id);
|
||||
$id = (string) $this->container->getAlias($id);
|
||||
}
|
||||
|
||||
return sprintf('$this->get(\'%s\')', $id);
|
||||
|
@ -138,7 +138,10 @@ class XmlDumper extends Dumper
|
||||
|
||||
protected function addServiceAlias($alias, $id)
|
||||
{
|
||||
return sprintf(" <service id=\"%s\" alias=\"%s\" />\n", $alias, $id);
|
||||
if ($id->isPublic()) {
|
||||
return sprintf(" <service id=\"%s\" alias=\"%s\" />\n", $alias, $id);
|
||||
}
|
||||
return sprintf(" <service id=\"%s\" alias=\"%s\" public=\"false\" />\n", $alias, $id);
|
||||
}
|
||||
|
||||
protected function addServices()
|
||||
|
@ -116,7 +116,11 @@ class YamlDumper extends Dumper
|
||||
|
||||
protected function addServiceAlias($alias, $id)
|
||||
{
|
||||
return sprintf(" %s: @%s\n", $alias, $id);
|
||||
if ($id->isPublic()) {
|
||||
return sprintf(" %s: @%s\n", $alias, $id);
|
||||
} else {
|
||||
return sprintf(" %s:\n alias: %s\n public: false", $alias, $id);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addServices()
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
|
||||
use Symfony\Component\DependencyInjection\InterfaceInjector;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
@ -124,7 +126,11 @@ class XmlFileLoader extends FileLoader
|
||||
protected function parseDefinition($id, $service, $file)
|
||||
{
|
||||
if ((string) $service['alias']) {
|
||||
$this->container->setAlias($id, (string) $service['alias']);
|
||||
$public = true;
|
||||
if (isset($service['public'])) {
|
||||
$public = $service->getAttributeAsPhp('public');
|
||||
}
|
||||
$this->container->setAlias($id, new Alias((string) $service['alias'], $public));
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\InterfaceInjector;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
@ -128,6 +130,11 @@ class YamlFileLoader extends FileLoader
|
||||
if (is_string($service) && 0 === strpos($service, '@')) {
|
||||
$this->container->setAlias($id, substr($service, 1));
|
||||
|
||||
return;
|
||||
} else if (isset($service['alias'])) {
|
||||
$public = !array_key_exists('public', $service) || (Boolean) $service['public'];
|
||||
$this->container->setAlias($id, new Alias($service['alias'], $public));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
@ -137,7 +139,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
$builder->setAlias('bar', 'foo');
|
||||
$this->assertTrue($builder->hasAlias('bar'), '->hasAlias() returns true if the alias exists');
|
||||
$this->assertFalse($builder->hasAlias('foobar'), '->hasAlias() returns false if the alias does not exist');
|
||||
$this->assertEquals('foo', $builder->getAlias('bar'), '->getAlias() returns the aliased service');
|
||||
$this->assertEquals('foo', (string) $builder->getAlias('bar'), '->getAlias() returns the aliased service');
|
||||
$this->assertTrue($builder->has('bar'), '->setAlias() defines a new service');
|
||||
$this->assertTrue($builder->get('bar') === $builder->get('foo'), '->setAlias() creates a service that is an alias to another one');
|
||||
|
||||
@ -157,11 +159,21 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
$builder = new ContainerBuilder();
|
||||
$builder->setAlias('bar', 'foo');
|
||||
$builder->setAlias('foobar', 'foo');
|
||||
$this->assertEquals(array('bar' => 'foo', 'foobar' => 'foo'), $builder->getAliases(), '->getAliases() returns all service aliases');
|
||||
$builder->setAlias('moo', new Alias('foo', false));
|
||||
|
||||
$aliases = $builder->getAliases();
|
||||
$this->assertEquals('foo', (string) $aliases['bar']);
|
||||
$this->assertTrue($aliases['bar']->isPublic());
|
||||
$this->assertEquals('foo', (string) $aliases['foobar']);
|
||||
$this->assertEquals('foo', (string) $aliases['moo']);
|
||||
$this->assertFalse($aliases['moo']->isPublic());
|
||||
|
||||
$builder->register('bar', 'stdClass');
|
||||
$this->assertEquals(array('foobar' => 'foo'), $builder->getAliases(), '->getAliases() does not return aliased services that have been overridden');
|
||||
$this->assertFalse($builder->hasAlias('bar'));
|
||||
|
||||
$builder->set('foobar', 'stdClass');
|
||||
$this->assertEquals(array(), $builder->getAliases(), '->getAliases() does not return aliased services that have been overridden');
|
||||
$builder->set('moo', 'stdClass');
|
||||
$this->assertEquals(0, count($builder->getAliases()), '->getAliases() does not return aliased services that have been overridden');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,7 +183,10 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$builder = new ContainerBuilder();
|
||||
$builder->setAliases(array('bar' => 'foo', 'foobar' => 'foo'));
|
||||
$this->assertEquals(array('bar' => 'foo', 'foobar' => 'foo'), $builder->getAliases(), '->getAliases() returns all service aliases');
|
||||
|
||||
$aliases = $builder->getAliases();
|
||||
$this->assertTrue(isset($aliases['bar']));
|
||||
$this->assertTrue(isset($aliases['foobar']));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,7 +197,10 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
$builder = new ContainerBuilder();
|
||||
$builder->setAliases(array('bar' => 'foo'));
|
||||
$builder->addAliases(array('foobar' => 'foo'));
|
||||
$this->assertEquals(array('bar' => 'foo', 'foobar' => 'foo'), $builder->getAliases(), '->getAliases() returns all service aliases');
|
||||
|
||||
$aliases = $builder->getAliases();
|
||||
$this->assertTrue(isset($aliases['bar']));
|
||||
$this->assertTrue(isset($aliases['foobar']));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -327,7 +345,10 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
$config->setAlias('alias_for_foo', 'foo');
|
||||
$container->merge($config);
|
||||
$this->assertEquals(array('foo', 'bar', 'baz'), array_keys($container->getDefinitions()), '->merge() merges definitions already defined ones');
|
||||
$this->assertEquals(array('alias_for_foo' => 'foo'), $container->getAliases(), '->merge() registers defined aliases');
|
||||
|
||||
$aliases = $container->getAliases();
|
||||
$this->assertTrue(isset($aliases['alias_for_foo']));
|
||||
$this->assertEquals('foo', (string) $aliases['alias_for_foo']);
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', 'FooClass');
|
||||
|
@ -43,6 +43,7 @@
|
||||
</call>
|
||||
</service>
|
||||
<service id="alias_for_foo" alias="foo" />
|
||||
<service id="another_alias_for_foo" alias="foo" public="false" />
|
||||
<service id="factory_service" factory-method="getInstance" factory-service="baz_factory" />
|
||||
</services>
|
||||
</container>
|
||||
|
@ -18,4 +18,7 @@ services:
|
||||
calls:
|
||||
- [ setBar, [ foo, @foo, [true, false] ] ]
|
||||
alias_for_foo: @foo
|
||||
another_alias_for_foo:
|
||||
alias: foo
|
||||
public: false
|
||||
factory_service: { class: BazClass, factory_method: getInstance, factory_service: baz_factory }
|
||||
|
@ -146,7 +146,11 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$aliases = $container->getAliases();
|
||||
$this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses <service> elements');
|
||||
$this->assertEquals('foo', $aliases['alias_for_foo'], '->load() parses aliases');
|
||||
$this->assertEquals('foo', (string) $aliases['alias_for_foo'], '->load() parses aliases');
|
||||
$this->assertTrue($aliases['alias_for_foo']->isPublic());
|
||||
$this->assertTrue(isset($aliases['another_alias_for_foo']));
|
||||
$this->assertEquals('foo', (string) $aliases['another_alias_for_foo']);
|
||||
$this->assertFalse($aliases['another_alias_for_foo']->isPublic());
|
||||
}
|
||||
|
||||
public function testConvertDomElementToArray()
|
||||
|
@ -110,7 +110,11 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$aliases = $container->getAliases();
|
||||
$this->assertTrue(isset($aliases['alias_for_foo']), '->load() parses aliases');
|
||||
$this->assertEquals('foo', $aliases['alias_for_foo'], '->load() parses aliases');
|
||||
$this->assertEquals('foo', (string) $aliases['alias_for_foo'], '->load() parses aliases');
|
||||
$this->assertTrue($aliases['alias_for_foo']->isPublic());
|
||||
$this->assertTrue(isset($aliases['another_alias_for_foo']));
|
||||
$this->assertEquals('foo', (string) $aliases['another_alias_for_foo']);
|
||||
$this->assertFalse($aliases['another_alias_for_foo']->isPublic());
|
||||
}
|
||||
|
||||
public function testExtensions()
|
||||
|
Reference in New Issue
Block a user