fix parsing inline YAML spanning multiple lines
This commit is contained in:
parent
789448b65c
commit
85a5c31e05
@ -4,6 +4,7 @@ CHANGELOG
|
|||||||
4.4.0
|
4.4.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* Added support for parsing the inline notation spanning multiple lines.
|
||||||
* Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag.
|
* Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag.
|
||||||
* deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit.
|
* deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit.
|
||||||
|
|
||||||
|
@ -274,7 +274,7 @@ class Inline
|
|||||||
$output = self::parseQuotedScalar($scalar, $i);
|
$output = self::parseQuotedScalar($scalar, $i);
|
||||||
|
|
||||||
if (null !== $delimiters) {
|
if (null !== $delimiters) {
|
||||||
$tmp = ltrim(substr($scalar, $i), ' ');
|
$tmp = ltrim(substr($scalar, $i), " \n");
|
||||||
if ('' === $tmp) {
|
if ('' === $tmp) {
|
||||||
throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
|
throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
|
||||||
}
|
}
|
||||||
@ -419,6 +419,7 @@ class Inline
|
|||||||
switch ($mapping[$i]) {
|
switch ($mapping[$i]) {
|
||||||
case ' ':
|
case ' ':
|
||||||
case ',':
|
case ',':
|
||||||
|
case "\n":
|
||||||
++$i;
|
++$i;
|
||||||
continue 2;
|
continue 2;
|
||||||
case '}':
|
case '}':
|
||||||
@ -450,7 +451,7 @@ class Inline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}'], true))) {
|
if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) {
|
||||||
throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping);
|
throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,7 +460,7 @@ class Inline
|
|||||||
}
|
}
|
||||||
|
|
||||||
while ($i < $len) {
|
while ($i < $len) {
|
||||||
if (':' === $mapping[$i] || ' ' === $mapping[$i]) {
|
if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) {
|
||||||
++$i;
|
++$i;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@ -508,7 +509,7 @@ class Inline
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$value = self::parseScalar($mapping, $flags, [',', '}'], $i, null === $tag, $references);
|
$value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references);
|
||||||
// Spec: Keys MUST be unique; first one wins.
|
// Spec: Keys MUST be unique; first one wins.
|
||||||
// Parser cannot abort this mapping earlier, since lines
|
// Parser cannot abort this mapping earlier, since lines
|
||||||
// are processed sequentially.
|
// are processed sequentially.
|
||||||
|
@ -353,6 +353,61 @@ class Parser
|
|||||||
$this->refs[$isRef] = $data[$key];
|
$this->refs[$isRef] = $data[$key];
|
||||||
array_pop($this->refsBeingParsed);
|
array_pop($this->refsBeingParsed);
|
||||||
}
|
}
|
||||||
|
} elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) {
|
||||||
|
if (null !== $context) {
|
||||||
|
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Inline::parse($this->parseQuotedString($this->currentLine), $flags, $this->refs);
|
||||||
|
} catch (ParseException $e) {
|
||||||
|
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||||
|
$e->setSnippet($this->currentLine);
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
} elseif ('{' === $this->currentLine[0]) {
|
||||||
|
if (null !== $context) {
|
||||||
|
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parsedMapping = Inline::parse($this->lexInlineMapping($this->currentLine), $flags, $this->refs);
|
||||||
|
|
||||||
|
while ($this->moveToNextLine()) {
|
||||||
|
if (!$this->isCurrentLineEmpty()) {
|
||||||
|
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parsedMapping;
|
||||||
|
} catch (ParseException $e) {
|
||||||
|
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||||
|
$e->setSnippet($this->currentLine);
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
} elseif ('[' === $this->currentLine[0]) {
|
||||||
|
if (null !== $context) {
|
||||||
|
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parsedSequence = Inline::parse($this->lexInlineSequence($this->currentLine), $flags, $this->refs);
|
||||||
|
|
||||||
|
while ($this->moveToNextLine()) {
|
||||||
|
if (!$this->isCurrentLineEmpty()) {
|
||||||
|
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parsedSequence;
|
||||||
|
} catch (ParseException $e) {
|
||||||
|
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
|
||||||
|
$e->setSnippet($this->currentLine);
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// multiple documents are not supported
|
// multiple documents are not supported
|
||||||
if ('---' === $this->currentLine) {
|
if ('---' === $this->currentLine) {
|
||||||
@ -678,6 +733,12 @@ class Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if ('' !== $value && '{' === $value[0]) {
|
||||||
|
return Inline::parse($this->lexInlineMapping($value), $flags, $this->refs);
|
||||||
|
} elseif ('' !== $value && '[' === $value[0]) {
|
||||||
|
return Inline::parse($this->lexInlineSequence($value), $flags, $this->refs);
|
||||||
|
}
|
||||||
|
|
||||||
$quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
|
$quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
|
||||||
|
|
||||||
// do not take following lines into account when the current line is a quoted single line value
|
// do not take following lines into account when the current line is a quoted single line value
|
||||||
@ -1072,4 +1133,122 @@ class Parser
|
|||||||
|
|
||||||
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
|
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function parseQuotedString($yaml)
|
||||||
|
{
|
||||||
|
if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml));
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [$yaml];
|
||||||
|
|
||||||
|
while ($this->moveToNextLine()) {
|
||||||
|
$lines[] = $this->currentLine;
|
||||||
|
|
||||||
|
if (!$this->isCurrentLineEmpty() && $yaml[0] === $this->currentLine[-1]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = '';
|
||||||
|
|
||||||
|
for ($i = 0, $linesCount = \count($lines), $previousLineWasNewline = false, $previousLineWasTerminatedWithBackslash = false; $i < $linesCount; ++$i) {
|
||||||
|
if ('' === trim($lines[$i])) {
|
||||||
|
$value .= "\n";
|
||||||
|
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
|
||||||
|
$value .= ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== trim($lines[$i]) && '\\' === substr($lines[$i], -1)) {
|
||||||
|
$value .= ltrim(substr($lines[$i], 0, -1));
|
||||||
|
} elseif ('' !== trim($lines[$i])) {
|
||||||
|
$value .= trim($lines[$i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === trim($lines[$i])) {
|
||||||
|
$previousLineWasNewline = true;
|
||||||
|
$previousLineWasTerminatedWithBackslash = false;
|
||||||
|
} elseif ('\\' === substr($lines[$i], -1)) {
|
||||||
|
$previousLineWasNewline = false;
|
||||||
|
$previousLineWasTerminatedWithBackslash = true;
|
||||||
|
} else {
|
||||||
|
$previousLineWasNewline = false;
|
||||||
|
$previousLineWasTerminatedWithBackslash = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
|
||||||
|
for ($i = 1; isset($yaml[$i]) && $quotation !== $yaml[$i]; ++$i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// quoted single line string
|
||||||
|
if (isset($yaml[$i]) && $quotation === $yaml[$i]) {
|
||||||
|
return $yaml;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [$yaml];
|
||||||
|
|
||||||
|
while ($this->moveToNextLine()) {
|
||||||
|
for ($i = 1; isset($this->currentLine[$i]) && $quotation !== $this->currentLine[$i]; ++$i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines[] = trim($this->currentLine);
|
||||||
|
|
||||||
|
if (isset($this->currentLine[$i]) && $quotation === $this->currentLine[$i]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lexInlineMapping(string $yaml): string
|
||||||
|
{
|
||||||
|
if ('' === $yaml || '{' !== $yaml[0]) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($yaml[$i]) && '}' === $yaml[$i]) {
|
||||||
|
return $yaml;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [$yaml];
|
||||||
|
|
||||||
|
while ($this->moveToNextLine()) {
|
||||||
|
$lines[] = $this->currentLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode("\n", $lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lexInlineSequence($yaml)
|
||||||
|
{
|
||||||
|
if ('' === $yaml || '[' !== $yaml[0]) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($yaml[$i]) && ']' === $yaml[$i]) {
|
||||||
|
return $yaml;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $yaml;
|
||||||
|
|
||||||
|
while ($this->moveToNextLine()) {
|
||||||
|
for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) {
|
||||||
|
}
|
||||||
|
|
||||||
|
$value .= trim($this->currentLine);
|
||||||
|
|
||||||
|
if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -711,7 +711,7 @@ class InlineTest extends TestCase
|
|||||||
public function testUnfinishedInlineMap()
|
public function testUnfinishedInlineMap()
|
||||||
{
|
{
|
||||||
$this->expectException('Symfony\Component\Yaml\Exception\ParseException');
|
$this->expectException('Symfony\Component\Yaml\Exception\ParseException');
|
||||||
$this->expectExceptionMessage('Unexpected end of line, expected one of ",}" at line 1 (near "{abc: \'def\'").');
|
$this->expectExceptionMessage("Unexpected end of line, expected one of \",}\n\" at line 1 (near \"{abc: 'def'\").");
|
||||||
Inline::parse("{abc: 'def'");
|
Inline::parse("{abc: 'def'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\Yaml\Tests;
|
namespace Symfony\Component\Yaml\Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Yaml\Exception\ParseException;
|
||||||
use Symfony\Component\Yaml\Parser;
|
use Symfony\Component\Yaml\Parser;
|
||||||
use Symfony\Component\Yaml\Tag\TaggedValue;
|
use Symfony\Component\Yaml\Tag\TaggedValue;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@ -1614,6 +1615,206 @@ EOF;
|
|||||||
return $tests;
|
return $tests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider inlineNotationSpanningMultipleLinesProvider
|
||||||
|
*/
|
||||||
|
public function testInlineNotationSpanningMultipleLines($expected, string $yaml)
|
||||||
|
{
|
||||||
|
$this->assertEquals($expected, $this->parser->parse($yaml));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function inlineNotationSpanningMultipleLinesProvider(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'mapping' => [
|
||||||
|
['foo' => 'bar', 'bar' => 'baz'],
|
||||||
|
<<<YAML
|
||||||
|
{
|
||||||
|
'foo': 'bar',
|
||||||
|
'bar': 'baz'
|
||||||
|
}
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'sequence' => [
|
||||||
|
['foo', 'bar'],
|
||||||
|
<<<YAML
|
||||||
|
[
|
||||||
|
'foo',
|
||||||
|
'bar'
|
||||||
|
]
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'sequence nested in mapping' => [
|
||||||
|
['foo' => ['bar', 'foobar'], 'bar' => ['baz']],
|
||||||
|
<<<YAML
|
||||||
|
{
|
||||||
|
'foo': ['bar', 'foobar'],
|
||||||
|
'bar': ['baz']
|
||||||
|
}
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'sequence spanning multiple lines nested in mapping' => [
|
||||||
|
[
|
||||||
|
'foobar' => [
|
||||||
|
'foo',
|
||||||
|
'bar',
|
||||||
|
'baz',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
<<<YAML
|
||||||
|
foobar: [foo,
|
||||||
|
bar,
|
||||||
|
baz
|
||||||
|
]
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'nested sequence nested in mapping starting on the same line' => [
|
||||||
|
[
|
||||||
|
'foo' => [
|
||||||
|
'foobar',
|
||||||
|
[
|
||||||
|
'bar',
|
||||||
|
'baz',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
<<<YAML
|
||||||
|
foo: [foobar, [
|
||||||
|
bar,
|
||||||
|
baz
|
||||||
|
]]
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'nested sequence nested in mapping starting on the following line' => [
|
||||||
|
[
|
||||||
|
'foo' => [
|
||||||
|
'foobar',
|
||||||
|
[
|
||||||
|
'bar',
|
||||||
|
'baz',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
<<<YAML
|
||||||
|
foo: [foobar,
|
||||||
|
[
|
||||||
|
bar,
|
||||||
|
baz
|
||||||
|
]]
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'mapping nested in sequence' => [
|
||||||
|
['foo', ['bar' => 'baz']],
|
||||||
|
<<<YAML
|
||||||
|
[
|
||||||
|
'foo',
|
||||||
|
{
|
||||||
|
'bar': 'baz'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'mapping spanning multiple lines nested in sequence' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foo' => 'bar',
|
||||||
|
'bar' => 'baz',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
<<<YAML
|
||||||
|
- {
|
||||||
|
foo: bar,
|
||||||
|
bar: baz
|
||||||
|
}
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'nested mapping nested in sequence starting on the same line' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foo' => [
|
||||||
|
'bar' => 'foobar',
|
||||||
|
],
|
||||||
|
'bar' => 'baz',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
<<<YAML
|
||||||
|
- { foo: {
|
||||||
|
bar: foobar
|
||||||
|
},
|
||||||
|
bar: baz
|
||||||
|
}
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'nested mapping nested in sequence starting on the following line' => [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'foo' => [
|
||||||
|
'bar' => 'foobar',
|
||||||
|
],
|
||||||
|
'bar' => 'baz',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
<<<YAML
|
||||||
|
- { foo:
|
||||||
|
{
|
||||||
|
bar: foobar
|
||||||
|
},
|
||||||
|
bar: baz
|
||||||
|
}
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'single quoted multi-line string' => [
|
||||||
|
"foo\nbar",
|
||||||
|
<<<YAML
|
||||||
|
'foo
|
||||||
|
|
||||||
|
bar'
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'double quoted multi-line string' => [
|
||||||
|
"foo\nbar",
|
||||||
|
<<<YAML
|
||||||
|
'foo
|
||||||
|
|
||||||
|
bar'
|
||||||
|
YAML
|
||||||
|
,
|
||||||
|
],
|
||||||
|
'single-quoted multi-line mapping value' => [
|
||||||
|
['foo' => "bar\nbaz"],
|
||||||
|
<<<YAML
|
||||||
|
foo: 'bar
|
||||||
|
|
||||||
|
baz'
|
||||||
|
YAML
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRootLevelInlineMappingFollowedByMoreContentIsInvalid()
|
||||||
|
{
|
||||||
|
$this->expectException(ParseException::class);
|
||||||
|
$this->expectExceptionMessage('Unable to parse at line 2 (near "foobar").');
|
||||||
|
|
||||||
|
$yaml = <<<YAML
|
||||||
|
{ foo: bar }
|
||||||
|
foobar
|
||||||
|
YAML;
|
||||||
|
|
||||||
|
$this->parser->parse($yaml);
|
||||||
|
}
|
||||||
|
|
||||||
public function testTaggedInlineMapping()
|
public function testTaggedInlineMapping()
|
||||||
{
|
{
|
||||||
$this->assertEquals(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS));
|
$this->assertEquals(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS));
|
||||||
@ -1749,7 +1950,7 @@ YAML;
|
|||||||
public function testParsingIniThrowsException()
|
public function testParsingIniThrowsException()
|
||||||
{
|
{
|
||||||
$this->expectException('Symfony\Component\Yaml\Exception\ParseException');
|
$this->expectException('Symfony\Component\Yaml\Exception\ParseException');
|
||||||
$this->expectExceptionMessage('Unable to parse at line 1 (near "[parameters]").');
|
$this->expectExceptionMessage('Unable to parse at line 2 (near " foo = bar").');
|
||||||
$ini = <<<INI
|
$ini = <<<INI
|
||||||
[parameters]
|
[parameters]
|
||||||
foo = bar
|
foo = bar
|
||||||
|
Reference in New Issue
Block a user