[DependencyInjection] simplified loaders (the load method only accept one resource now)
This commit is contained in:
parent
a5871688a7
commit
e9e2899cda
|
@ -25,39 +25,31 @@ class IniFileLoader extends FileLoader
|
|||
/**
|
||||
* Loads a resource.
|
||||
*
|
||||
* @param mixed $resource The resource path
|
||||
* @param string $file An INI file path
|
||||
*
|
||||
* @return BuilderConfiguration A BuilderConfiguration instance
|
||||
*/
|
||||
public function load($files)
|
||||
public function load($file)
|
||||
{
|
||||
if (!is_array($files))
|
||||
{
|
||||
$files = array($files);
|
||||
}
|
||||
|
||||
$configuration = new BuilderConfiguration();
|
||||
|
||||
foreach ($files as $file)
|
||||
$path = $this->getAbsolutePath($file);
|
||||
if (!file_exists($path))
|
||||
{
|
||||
$path = $this->getAbsolutePath($file);
|
||||
if (!file_exists($path))
|
||||
{
|
||||
throw new \InvalidArgumentException(sprintf('The %s file does not exist.', $file));
|
||||
}
|
||||
throw new \InvalidArgumentException(sprintf('The %s file does not exist.', $file));
|
||||
}
|
||||
|
||||
$result = parse_ini_file($path, true);
|
||||
if (false === $result || array() === $result)
|
||||
{
|
||||
throw new \InvalidArgumentException(sprintf('The %s file is not valid.', $file));
|
||||
}
|
||||
$result = parse_ini_file($path, true);
|
||||
if (false === $result || array() === $result)
|
||||
{
|
||||
throw new \InvalidArgumentException(sprintf('The %s file is not valid.', $file));
|
||||
}
|
||||
|
||||
if (isset($result['parameters']) && is_array($result['parameters']))
|
||||
if (isset($result['parameters']) && is_array($result['parameters']))
|
||||
{
|
||||
foreach ($result['parameters'] as $key => $value)
|
||||
{
|
||||
foreach ($result['parameters'] as $key => $value)
|
||||
{
|
||||
$configuration->setParameter(strtolower($key), $value);
|
||||
}
|
||||
$configuration->setParameter(strtolower($key), $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ interface LoaderInterface
|
|||
* If you load file1.xml and file2.xml in this order, the value of complex
|
||||
* will be "foo".
|
||||
*
|
||||
* @param mixed $resource The resource path
|
||||
* @param mixed $resource The resource
|
||||
*
|
||||
* @return BuilderConfiguration A BuilderConfiguration instance
|
||||
*/
|
||||
|
|
|
@ -28,41 +28,30 @@ class XmlFileLoader extends FileLoader
|
|||
/**
|
||||
* Loads an array of XML files.
|
||||
*
|
||||
* @param array $files An array of XML files
|
||||
* @param string $file An XML file path
|
||||
*
|
||||
* @return BuilderConfiguration A BuilderConfiguration instance
|
||||
*/
|
||||
public function load($files)
|
||||
{
|
||||
if (!is_array($files))
|
||||
{
|
||||
$files = array($files);
|
||||
}
|
||||
|
||||
return $this->parse($this->getFilesAsXml($files));
|
||||
}
|
||||
|
||||
protected function parse(array $xmls)
|
||||
public function load($file)
|
||||
{
|
||||
$configuration = new BuilderConfiguration();
|
||||
|
||||
foreach ($xmls as $file => $xml)
|
||||
{
|
||||
// anonymous services
|
||||
$xml = $this->processAnonymousServices($configuration, $xml, $file);
|
||||
$xml = $this->loadFile($file);
|
||||
|
||||
// imports
|
||||
$this->parseImports($configuration, $xml, $file);
|
||||
// anonymous services
|
||||
$xml = $this->processAnonymousServices($configuration, $xml, $file);
|
||||
|
||||
// parameters
|
||||
$this->parseParameters($configuration, $xml, $file);
|
||||
// imports
|
||||
$this->parseImports($configuration, $xml, $file);
|
||||
|
||||
// services
|
||||
$this->parseDefinitions($configuration, $xml, $file);
|
||||
// parameters
|
||||
$this->parseParameters($configuration, $xml, $file);
|
||||
|
||||
// extensions
|
||||
$this->loadFromExtensions($configuration, $xml);
|
||||
}
|
||||
// services
|
||||
$this->parseDefinitions($configuration, $xml, $file);
|
||||
|
||||
// extensions
|
||||
$this->loadFromExtensions($configuration, $xml);
|
||||
|
||||
return $configuration;
|
||||
}
|
||||
|
@ -92,7 +81,7 @@ class XmlFileLoader extends FileLoader
|
|||
|
||||
protected function parseImport($import, $file)
|
||||
{
|
||||
if (isset($import['class']) && $import['class'] != get_class($this))
|
||||
if (isset($import['class']) && $import['class'] !== get_class($this))
|
||||
{
|
||||
$class = (string) $import['class'];
|
||||
$loader = new $class($this->paths);
|
||||
|
@ -176,33 +165,27 @@ class XmlFileLoader extends FileLoader
|
|||
$configuration->setDefinition($id, $definition);
|
||||
}
|
||||
|
||||
protected function getFilesAsXml(array $files)
|
||||
protected function loadFile($file)
|
||||
{
|
||||
$xmls = array();
|
||||
foreach ($files as $file)
|
||||
$path = $this->getAbsolutePath($file);
|
||||
|
||||
if (!file_exists($path))
|
||||
{
|
||||
$path = $this->getAbsolutePath($file);
|
||||
|
||||
if (!file_exists($path))
|
||||
{
|
||||
throw new \InvalidArgumentException(sprintf('The service file "%s" does not exist (in: %s).', $file, implode(', ', $this->paths)));
|
||||
}
|
||||
|
||||
$dom = new \DOMDocument();
|
||||
libxml_use_internal_errors(true);
|
||||
if (!$dom->load(realpath($path), LIBXML_COMPACT))
|
||||
{
|
||||
throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
|
||||
}
|
||||
$dom->validateOnParse = true;
|
||||
$dom->normalizeDocument();
|
||||
libxml_use_internal_errors(false);
|
||||
$this->validate($dom, $path);
|
||||
|
||||
$xmls[$path] = simplexml_import_dom($dom, 'Symfony\\Components\\DependencyInjection\\SimpleXMLElement');
|
||||
throw new \InvalidArgumentException(sprintf('The service file "%s" does not exist (in: %s).', $file, implode(', ', $this->paths)));
|
||||
}
|
||||
|
||||
return $xmls;
|
||||
$dom = new \DOMDocument();
|
||||
libxml_use_internal_errors(true);
|
||||
if (!$dom->load(realpath($path), LIBXML_COMPACT))
|
||||
{
|
||||
throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors()));
|
||||
}
|
||||
$dom->validateOnParse = true;
|
||||
$dom->normalizeDocument();
|
||||
libxml_use_internal_errors(false);
|
||||
$this->validate($dom, $path);
|
||||
|
||||
return simplexml_import_dom($dom, 'Symfony\\Components\\DependencyInjection\\SimpleXMLElement');
|
||||
}
|
||||
|
||||
protected function processAnonymousServices(BuilderConfiguration $configuration, $xml, $file)
|
||||
|
|
|
@ -31,50 +31,39 @@ class YamlFileLoader extends FileLoader
|
|||
/**
|
||||
* Loads an array of Yaml files.
|
||||
*
|
||||
* @param array $files An array of Yaml files
|
||||
* @param string $file A YAML file path
|
||||
*
|
||||
* @return BuilderConfiguration A BuilderConfiguration instance
|
||||
*/
|
||||
public function load($files)
|
||||
public function load($file)
|
||||
{
|
||||
if (!is_array($files))
|
||||
{
|
||||
$files = array($files);
|
||||
}
|
||||
$content = $this->loadFile($file);
|
||||
|
||||
return $this->parse($this->getFilesAsArray($files));
|
||||
}
|
||||
|
||||
protected function parse($data)
|
||||
{
|
||||
$configuration = new BuilderConfiguration();
|
||||
|
||||
foreach ($data as $file => $content)
|
||||
if (!$content)
|
||||
{
|
||||
if (!$content)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// imports
|
||||
$this->parseImports($configuration, $content, $file);
|
||||
|
||||
// parameters
|
||||
if (isset($content['parameters']))
|
||||
{
|
||||
foreach ($content['parameters'] as $key => $value)
|
||||
{
|
||||
$configuration->setParameter(strtolower($key), $this->resolveServices($value));
|
||||
}
|
||||
}
|
||||
|
||||
// services
|
||||
$this->parseDefinitions($configuration, $content, $file);
|
||||
|
||||
// extensions
|
||||
$this->loadFromExtensions($configuration, $content);
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
// imports
|
||||
$this->parseImports($configuration, $content, $file);
|
||||
|
||||
// parameters
|
||||
if (isset($content['parameters']))
|
||||
{
|
||||
foreach ($content['parameters'] as $key => $value)
|
||||
{
|
||||
$configuration->setParameter(strtolower($key), $this->resolveServices($value));
|
||||
}
|
||||
}
|
||||
|
||||
// services
|
||||
$this->parseDefinitions($configuration, $content, $file);
|
||||
|
||||
// extensions
|
||||
$this->loadFromExtensions($configuration, $content);
|
||||
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
|
@ -93,7 +82,7 @@ class YamlFileLoader extends FileLoader
|
|||
|
||||
protected function parseImport($import, $file)
|
||||
{
|
||||
if (isset($import['class']) && $import['class'] != get_class($this))
|
||||
if (isset($import['class']) && $import['class'] !== get_class($this))
|
||||
{
|
||||
$class = $import['class'];
|
||||
$loader = new $class($this->paths);
|
||||
|
@ -175,22 +164,16 @@ class YamlFileLoader extends FileLoader
|
|||
$configuration->setDefinition($id, $definition);
|
||||
}
|
||||
|
||||
protected function getFilesAsArray(array $files)
|
||||
protected function loadFile($file)
|
||||
{
|
||||
$yamls = array();
|
||||
foreach ($files as $file)
|
||||
$path = $this->getAbsolutePath($file);
|
||||
|
||||
if (!file_exists($path))
|
||||
{
|
||||
$path = $this->getAbsolutePath($file);
|
||||
|
||||
if (!file_exists($path))
|
||||
{
|
||||
throw new \InvalidArgumentException(sprintf('The service file "%s" does not exist (in: %s).', $file, implode(', ', $this->paths)));
|
||||
}
|
||||
|
||||
$yamls[$path] = $this->validate(YAML::load($path), $path);
|
||||
throw new \InvalidArgumentException(sprintf('The service file "%s" does not exist (in: %s).', $file, implode(', ', $this->paths)));
|
||||
}
|
||||
|
||||
return $yamls;
|
||||
return $this->validate(YAML::load($path), $path);
|
||||
}
|
||||
|
||||
protected function validate($content, $file)
|
||||
|
|
|
@ -13,22 +13,14 @@ require_once __DIR__.'/../../../../bootstrap.php';
|
|||
use Symfony\Components\DependencyInjection\Builder;
|
||||
use Symfony\Components\DependencyInjection\Loader\IniFileLoader;
|
||||
|
||||
$t = new LimeTest(5);
|
||||
$t = new LimeTest(3);
|
||||
|
||||
$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/');
|
||||
|
||||
$loader = new IniFileLoader($fixturesPath.'/ini');
|
||||
$config = $loader->load(array('parameters.ini'));
|
||||
$t->is($config->getParameters(), array('foo' => 'bar', 'bar' => '%foo%'), '->load() takes an array of file names as its first argument');
|
||||
|
||||
$loader = new IniFileLoader($fixturesPath.'/ini');
|
||||
$config = $loader->load('parameters.ini');
|
||||
$t->is($config->getParameters(), array('foo' => 'bar', 'bar' => '%foo%'), '->load() takes a single file name as its first argument');
|
||||
|
||||
$loader = new IniFileLoader($fixturesPath.'/ini');
|
||||
$config = $loader->load(array('parameters.ini', 'parameters1.ini'));
|
||||
$t->is($config->getParameters(), array('foo' => 'foo', 'bar' => '%foo%', 'baz' => 'baz'), '->load() merges parameters from all given files');
|
||||
|
||||
try
|
||||
{
|
||||
$loader->load('foo.ini');
|
||||
|
|
|
@ -16,7 +16,7 @@ use Symfony\Components\DependencyInjection\Definition;
|
|||
use Symfony\Components\DependencyInjection\Loader\Loader;
|
||||
use Symfony\Components\DependencyInjection\Loader\XmlFileLoader;
|
||||
|
||||
$t = new LimeTest(44);
|
||||
$t = new LimeTest(40);
|
||||
|
||||
$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/');
|
||||
|
||||
|
@ -24,20 +24,20 @@ require_once $fixturesPath.'/includes/ProjectExtension.php';
|
|||
|
||||
class ProjectLoader extends XmlFileLoader
|
||||
{
|
||||
public function getFilesAsXml(array $files)
|
||||
public function loadFile($file)
|
||||
{
|
||||
return parent::getFilesAsXml($files);
|
||||
return parent::loadFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
// ->getFilesAsXml()
|
||||
$t->diag('->getFilesAsXml()');
|
||||
// ->loadFile()
|
||||
$t->diag('->loadFile()');
|
||||
|
||||
$loader = new ProjectLoader($fixturesPath.'/ini');
|
||||
|
||||
try
|
||||
{
|
||||
$loader->getFilesAsXml(array('foo.xml'));
|
||||
$loader->loadFile('foo.xml');
|
||||
$t->fail('->load() throws an InvalidArgumentException if the loaded file does not exist');
|
||||
}
|
||||
catch (InvalidArgumentException $e)
|
||||
|
@ -47,7 +47,7 @@ catch (InvalidArgumentException $e)
|
|||
|
||||
try
|
||||
{
|
||||
$loader->getFilesAsXml(array('parameters.ini'));
|
||||
$loader->loadFile('parameters.ini');
|
||||
$t->fail('->load() throws an InvalidArgumentException if the loaded file is not a valid XML file');
|
||||
}
|
||||
catch (InvalidArgumentException $e)
|
||||
|
@ -59,7 +59,7 @@ $loader = new ProjectLoader($fixturesPath.'/xml');
|
|||
|
||||
try
|
||||
{
|
||||
$loader->getFilesAsXml(array('nonvalid.xml'));
|
||||
$loader->loadFile('nonvalid.xml');
|
||||
$t->fail('->load() throws an InvalidArgumentException if the loaded file does not validate the XSD');
|
||||
}
|
||||
catch (InvalidArgumentException $e)
|
||||
|
@ -67,29 +67,23 @@ catch (InvalidArgumentException $e)
|
|||
$t->pass('->load() throws an InvalidArgumentException if the loaded file does not validate the XSD');
|
||||
}
|
||||
|
||||
$xmls = $loader->getFilesAsXml(array('services1.xml'));
|
||||
$t->is(count($xmls), 1, '->getFilesAsXml() returns an array of simple xml objects');
|
||||
$t->is(key($xmls), realpath($fixturesPath.'/xml/services1.xml'), '->getFilesAsXml() returns an array where the keys are absolutes paths to the original XML file');
|
||||
$t->is(get_class(current($xmls)), 'Symfony\\Components\\DependencyInjection\\SimpleXMLElement', '->getFilesAsXml() returns an array where values are SimpleXMLElement objects');
|
||||
$xml = $loader->loadFile('services1.xml');
|
||||
$t->is(get_class($xml), 'Symfony\\Components\\DependencyInjection\\SimpleXMLElement', '->loadFile() returns an SimpleXMLElement object');
|
||||
|
||||
// ->load() # parameters
|
||||
$t->diag('->load() # parameters');
|
||||
$loader = new ProjectLoader($fixturesPath.'/xml');
|
||||
$config = $loader->load(array('services2.xml'));
|
||||
$config = $loader->load('services2.xml');
|
||||
$t->is($config->getParameters(), array('a string', 'foo' => 'bar', 'values' => array(0, 'integer' => 4, 100 => null, 'true', true, false, 'on', 'off', 'float' => 1.3, 1000.3, 'a string', array('foo', 'bar')), 'foo_bar' => new Reference('foo_bar')), '->load() converts XML values to PHP ones');
|
||||
|
||||
$loader = new ProjectLoader($fixturesPath.'/xml');
|
||||
$config = $loader->load(array('services2.xml', 'services3.xml'));
|
||||
$t->is($config->getParameters(), array('a string', 'foo' => 'foo', 'values' => array(true, false), 'foo_bar' => new Reference('foo_bar')), '->load() merges the first level of arguments when multiple files are loaded');
|
||||
|
||||
// ->load() # imports
|
||||
$t->diag('->load() # imports');
|
||||
$config = $loader->load(array('services4.xml'));
|
||||
$config = $loader->load('services4.xml');
|
||||
$t->is($config->getParameters(), array('a string', 'foo' => 'bar', 'bar' => '%foo%', 'values' => array(true, false), 'foo_bar' => new Reference('foo_bar')), '->load() imports and merges imported files');
|
||||
|
||||
// ->load() # anonymous services
|
||||
$t->diag('->load() # anonymous services');
|
||||
$config = $loader->load(array('services5.xml'));
|
||||
$config = $loader->load('services5.xml');
|
||||
$services = $config->getDefinitions();
|
||||
$t->is(count($services), 3, '->load() attributes unique ids to anonymous services');
|
||||
$args = $services['foo']->getArguments();
|
||||
|
@ -108,7 +102,7 @@ $t->is($inner->getClass(), 'BazClass', '->load() uses the same configuration as
|
|||
|
||||
// ->load() # services
|
||||
$t->diag('->load() # services');
|
||||
$config = $loader->load(array('services6.xml'));
|
||||
$config = $loader->load('services6.xml');
|
||||
$services = $config->getDefinitions();
|
||||
$t->ok(isset($services['foo']), '->load() parses <service> elements');
|
||||
$t->is(get_class($services['foo']), 'Symfony\\Components\\DependencyInjection\\Definition', '->load() converts <service> element to Definition instances');
|
||||
|
@ -127,10 +121,6 @@ $aliases = $config->getAliases();
|
|||
$t->ok(isset($aliases['alias_for_foo']), '->load() parses <service> elements');
|
||||
$t->is($aliases['alias_for_foo'], 'foo', '->load() parses aliases');
|
||||
|
||||
$config = $loader->load(array('services6.xml', 'services7.xml'));
|
||||
$services = $config->getDefinitions();
|
||||
$t->is($services['foo']->getClass(), 'BarClass', '->load() merges the services when multiple files are loaded');
|
||||
|
||||
// ::convertDomElementToArray()
|
||||
$t->diag('::convertDomElementToArray()');
|
||||
$doc = new DOMDocument("1.0");
|
||||
|
|
|
@ -16,7 +16,7 @@ use Symfony\Components\DependencyInjection\Definition;
|
|||
use Symfony\Components\DependencyInjection\Loader\Loader;
|
||||
use Symfony\Components\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
$t = new LimeTest(29);
|
||||
$t = new LimeTest(25);
|
||||
|
||||
$fixturesPath = realpath(__DIR__.'/../../../../../fixtures/Symfony/Components/DependencyInjection/');
|
||||
|
||||
|
@ -24,20 +24,20 @@ require_once $fixturesPath.'/includes/ProjectExtension.php';
|
|||
|
||||
class ProjectLoader extends YamlFileLoader
|
||||
{
|
||||
public function getFilesAsArray(array $files)
|
||||
public function loadFile($file)
|
||||
{
|
||||
return parent::getFilesAsArray($files);
|
||||
return parent::loadFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
// ->getFilesAsArray()
|
||||
$t->diag('->getFilesAsArray()');
|
||||
// ->loadFile()
|
||||
$t->diag('->loadFile()');
|
||||
|
||||
$loader = new ProjectLoader($fixturesPath.'/ini');
|
||||
|
||||
try
|
||||
{
|
||||
$loader->getFilesAsArray(array('foo.yml'));
|
||||
$loader->loadFile('foo.yml');
|
||||
$t->fail('->load() throws an InvalidArgumentException if the loaded file does not exist');
|
||||
}
|
||||
catch (InvalidArgumentException $e)
|
||||
|
@ -47,7 +47,7 @@ catch (InvalidArgumentException $e)
|
|||
|
||||
try
|
||||
{
|
||||
$loader->getFilesAsArray(array('parameters.ini'));
|
||||
$loader->loadFile('parameters.ini');
|
||||
$t->fail('->load() throws an InvalidArgumentException if the loaded file is not a valid YAML file');
|
||||
}
|
||||
catch (InvalidArgumentException $e)
|
||||
|
@ -61,7 +61,7 @@ foreach (array('nonvalid1', 'nonvalid2') as $fixture)
|
|||
{
|
||||
try
|
||||
{
|
||||
$loader->getFilesAsArray(array($fixture.'.yml'));
|
||||
$loader->loadFile($fixture.'.yml');
|
||||
$t->fail('->load() throws an InvalidArgumentException if the loaded file does not validate');
|
||||
}
|
||||
catch (InvalidArgumentException $e)
|
||||
|
@ -70,28 +70,20 @@ foreach (array('nonvalid1', 'nonvalid2') as $fixture)
|
|||
}
|
||||
}
|
||||
|
||||
$yamls = $loader->getFilesAsArray(array('services1.yml'));
|
||||
$t->ok(is_array($yamls), '->getFilesAsArray() returns an array');
|
||||
$t->is(key($yamls), realpath($fixturesPath.'/yaml/services1.yml'), '->getFilesAsArray() returns an array where the keys are absolutes paths to the original YAML file');
|
||||
|
||||
// ->load() # parameters
|
||||
$t->diag('->load() # parameters');
|
||||
$loader = new ProjectLoader($fixturesPath.'/yaml');
|
||||
$config = $loader->load(array('services2.yml'));
|
||||
$config = $loader->load('services2.yml');
|
||||
$t->is($config->getParameters(), array('foo' => 'bar', 'values' => array(true, false, 0, 1000.3), 'bar' => 'foo', 'foo_bar' => new Reference('foo_bar')), '->load() converts YAML keys to lowercase');
|
||||
|
||||
$loader = new ProjectLoader($fixturesPath.'/yaml');
|
||||
$config = $loader->load(array('services2.yml', 'services3.yml'));
|
||||
$t->is($config->getParameters(), array('foo' => 'foo', 'values' => array(true, false), 'bar' => 'foo', 'foo_bar' => new Reference('foo_bar')), '->load() merges the first level of arguments when multiple files are loaded');
|
||||
|
||||
// ->load() # imports
|
||||
$t->diag('->load() # imports');
|
||||
$config = $loader->load(array('services4.yml'));
|
||||
$config = $loader->load('services4.yml');
|
||||
$t->is($config->getParameters(), array('foo' => 'bar', 'bar' => '%foo%', 'values' => array(true, false), 'foo_bar' => new Reference('foo_bar')), '->load() imports and merges imported files');
|
||||
|
||||
// ->load() # services
|
||||
$t->diag('->load() # services');
|
||||
$config = $loader->load(array('services6.yml'));
|
||||
$config = $loader->load('services6.yml');
|
||||
$services = $config->getDefinitions();
|
||||
$t->ok(isset($services['foo']), '->load() parses service elements');
|
||||
$t->is(get_class($services['foo']), 'Symfony\\Components\\DependencyInjection\\Definition', '->load() converts service element to Definition instances');
|
||||
|
@ -110,10 +102,6 @@ $aliases = $config->getAliases();
|
|||
$t->ok(isset($aliases['alias_for_foo']), '->load() parses aliases');
|
||||
$t->is($aliases['alias_for_foo'], 'foo', '->load() parses aliases');
|
||||
|
||||
$config = $loader->load(array('services6.yml', 'services7.yml'));
|
||||
$services = $config->getDefinitions();
|
||||
$t->is($services['foo']->getClass(), 'BarClass', '->load() merges the services when multiple files are loaded');
|
||||
|
||||
// extensions
|
||||
$t->diag('extensions');
|
||||
Loader::registerExtension(new ProjectExtension());
|
||||
|
|
Reference in New Issue