From 8a445d6180590d52975a642d4a9506a4ba1d9e73 Mon Sep 17 00:00:00 2001 From: Visman Date: Wed, 30 Sep 2020 22:21:44 +0700 Subject: [PATCH] Add bbcode management 2 --- app/Models/BBCodeList/Insert.php | 33 ++ app/Models/BBCodeList/Structure.php | 381 ++++++++++++++++++++--- app/Models/BBCodeList/Update.php | 34 ++ app/Models/Pages/Admin/Parser/BBCode.php | 278 ++++++++++++----- app/lang/en/admin_parser.po | 48 +++ app/lang/ru/admin_parser.po | 48 +++ 6 files changed, 699 insertions(+), 123 deletions(-) create mode 100644 app/Models/BBCodeList/Insert.php create mode 100644 app/Models/BBCodeList/Update.php diff --git a/app/Models/BBCodeList/Insert.php b/app/Models/BBCodeList/Insert.php new file mode 100644 index 00000000..c475f10b --- /dev/null +++ b/app/Models/BBCodeList/Insert.php @@ -0,0 +1,33 @@ +getError()) { + throw new RuntimeException('BBCode structure has error'); + } + + $this->model->reset(); // ???? + + $vars = [ + ':tag' => $structure->tag, + ':structure' => $structure->toString(), + ]; + $query = 'INSERT INTO ::bbcode (bb_tag, bb_edit, bb_delete, bb_structure) + VALUES (?s:tag, 1, 1, ?s:structure)'; + + $this->c->DB->exec($query, $vars); + + return (int) $this->c->DB->lastInsertId(); + } +} diff --git a/app/Models/BBCodeList/Structure.php b/app/Models/BBCodeList/Structure.php index ef9df6a7..ccf4d329 100644 --- a/app/Models/BBCodeList/Structure.php +++ b/app/Models/BBCodeList/Structure.php @@ -5,9 +5,15 @@ namespace ForkBB\Models\BBCodeList; use ForkBB\Core\Container; use ForkBB\Models\Model as ParentModel; use RuntimeException; +use Throwable; class Structure extends ParentModel { + const TAG_PATTERN = '%^(?:ROOT|[a-z\*][a-z\d-]{0,10})$%D'; + const ATTR_PATTERN = '%^[a-z-]{2,15}$%D'; + + const JSON_OPTIONS = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR; + public function __construct(Container $container) { parent::__construct($container); @@ -17,7 +23,7 @@ class Structure extends ParentModel 'text only' => ['text_only'], 'tags only' => ['tags_only'], 'self nesting' => ['self_nesting'], - 'attrs' => ['no_attr', 'def_attr'], + 'attrs' => ['no_attr', 'def_attr', 'other_attrs'], ]; } @@ -28,7 +34,71 @@ class Structure extends ParentModel public function toString(): string { + $a = [ + 'tag' => $this->tag, + 'type' => $this->type, + 'parents' => $this->parents, + ]; + if (! empty($this->handler) && \is_string($this->handler)) { + $a['handler'] = $this->handler; + } + + if (! empty($this->text_handler) && \is_string($this->text_handler)) { + $a['text handler'] = $this->text_handler; + } + + if (null !== $this->auto) { + $a['auto'] = (bool) $this->auto; + } + + if (null !== $this->self_nesting) { + $a['self nesting'] = (int) $this->self_nesting > 0 ? (int) $this->self_nesting : false; + } + + if (null !== $this->recursive) { + $a['recursive'] = true; + } + + if (null !== $this->text_only) { + $a['text only'] = true; + } + + if (null !== $this->tags_only) { + $a['tags only'] = true; + } + + if (null !== $this->single) { + $a['single'] = true; + } + + if (null !== $this->pre) { + $a['pre'] = true; + } + + if ( + \is_array($this->new_attr) + && ! empty($this->new_attr['allowed']) + && ! empty($this->new_attr['name']) + ) { + $this->setBBAttr($this->new_attr['name'], $this->new_attr, ['required', 'format', 'body format', 'text only']); + } + + $a['attrs'] = $this->other_attrs; + + if (null !== $this->no_attr) { + $a['attrs']['no attr'] = $this->no_attr; + } + + if (null !== $this->def_attr) { + $a['attrs']['Def'] = $this->def_attr; + } + + if (empty($a['attrs'])) { + unset($a['attrs']); + } + + return \json_encode($a, self::JSON_OPTIONS); } protected function gettype(): string @@ -131,8 +201,14 @@ class Structure extends ParentModel $this->setAttr('self nesting', $value); } - protected function getBBAttr(/* mixed */ $data, array $fields) /* : mixed */ + protected function getBBAttr(string $name, array $fields) /* : mixed */ { + if (empty($this->attrs[$name])) { + return null; + } + + $data = $this->attrs[$name]; + if (true === $data) { return true; } elseif (! \is_array($data)) { @@ -148,7 +224,7 @@ class Structure extends ParentModel break; case 'required': case 'text only': - $value = isset($data[$field]) && true === $data[$field] ? true : null; + $value = isset($data[$field]) ? true : null; break; default: throw new RuntimeException('Unknown attribute property'); @@ -162,74 +238,291 @@ class Structure extends ParentModel } } - protected function setBBAttr(/* mixed */ $data, array $fields) /* : mixed */ + protected function setBBAttr(string $name, /* mixed */ $data, array $fields): void { + $attrs = $this->getAttr('attrs'); + if ( empty($data['allowed']) || $data['allowed'] < 1 ) { - return null; - } + unset($attrs[$name]); + } else { + $result = []; + foreach ($fields as $field) { + $key = \str_replace(' ', '_', $field); - $result = []; - foreach ($fields as $field) { - switch ($field) { - case 'format': - case 'body format': - $value = isset($data[$field]) && \is_string($data[$field]) ? $data[$field] : null; - break; - case 'required': - case 'text only': - $value = isset($data[$field]) && true === $data[$field] ? true : null; - break; - default: - throw new RuntimeException('Unknown attribute property'); + switch ($field) { + case 'format': + case 'body format': + $value = ! empty($data[$key]) && \is_string($data[$key]) ? $data[$key] : null; + break; + case 'required': + case 'text only': + $value = ! empty($data[$key]) ? true : null; + break; + default: + throw new RuntimeException('Unknown attribute property'); + } + + if (isset($value)) { + $result[$field] = $value; + } } - if (isset($value)) { - $key = \str_replace(' ', '_', $field); - $result[$key] = $value; - } + $attrs[$name] = empty($result) ? true : $result; } - return empty($result) ? true : $result; + $this->setAttr('attrs', $attrs); } - protected function getno_attr() /* mixed */ + protected function getno_attr() /* : mixed */ { - return $this->getBBAttr($this->attrs['no attr'] ?? null, ['body format', 'text only']); + return $this->getBBAttr('no attr', ['body format', 'text only']); } protected function setno_attr(array $value): void { - $value = $this->getBBAttr($value, ['body format', 'text only']); - $attrs = $this->getAttr('attrs'); - - if (null === $value) { - unset($attrs['no attr']); - } else { - $attrs['no attr'] = $value; - } - - $this->setAttr('attrs', $attrs); + $this->setBBAttr('no attr', $value, ['body format', 'text only']); } - protected function getdef_attr() /* mixed */ + protected function getdef_attr() /* : mixed */ { - return $this->getBBAttr($this->attrs['Def'] ?? null, ['required', 'format', 'body format', 'text only']); + return $this->getBBAttr('Def', ['required', 'format', 'body format', 'text only']); } protected function setdef_attr(array $value): void { - $value = $this->getBBAttr($value, ['required', 'format', 'body format', 'text only']); + $this->setBBAttr('Def', $value, ['required', 'format', 'body format', 'text only']); + } + + protected function getother_attrs(): array + { $attrs = $this->getAttr('attrs'); - if (null === $value) { - unset($attrs['Def']); - } else { - $attrs['Def'] = $value; + if (! \is_array($attrs)) { + return []; } - $this->setAttr('attrs', $attrs); + unset($attrs['no attr'], $attrs['Def'], $attrs['New']); + + $result = []; + foreach ($attrs as $name => $attr) { + $value = $this->getBBAttr($name, ['required', 'format', 'body format', 'text only']); + + if (null === $value) { + continue; + } + + $result[$name] = $value; + } + + return $result; + } + + protected function setother_attrs(array $attrs): void + { + unset($attrs['no attr'], $attrs['Def']); + + foreach ($attrs as $name => $attr) { + $this->setBBAttr($name, $attr, ['required', 'format', 'body format', 'text only']); + } + } + + /** + * Ищет ошибку в структуре bb-кода + */ + public function getError(): ?array + { + if ( + ! \is_string($this->tag) + || ! \preg_match(self::TAG_PATTERN, $this->tag) + ) { + return ['Tag name not specified']; + } + + $result = $this->testPHP($this->handler); + if (null !== $result) { + return ['PHP code error in Handler: %s', $result]; + } + + $result = $this->testPHP($this->text_handler); + if (null !== $result ) { + return ['PHP code error in Text handler: %s', $result]; + } + + if ( + null !== $this->recursive + && null !== $this->tags_only + ) { + return ['Recursive and Tags only are enabled at the same time']; + } + + if ( + null !== $this->recursive + && null !== $this->single + ) { + return ['Recursive and Single are enabled at the same time']; + } + + if ( + null !== $this->text_only + && null !== $this->tags_only + ) { + return ['Text only and Tags only are enabled at the same time']; + } + + if (\is_array($this->attrs)) { + foreach ($this->attrs as $name => $attr) { + if ( + 'no attr' !== $name + && 'Def' !== $name + && ! preg_match(self::ATTR_PATTERN, $name) + ) { + return ['Attribute name %s is not valid', $name]; + } + + if (isset($attr['format'])) { + if ( + ! \is_string($attr['format']) + || false === @\preg_match($attr['format'], 'abcdef') + ) { + return ['Attribute %1$s, %2$s - regular expression error', $name, 'Format']; + } + } + + if (isset($attr['body format'])) { + if ( + ! \is_string($attr['body format']) + || false === @\preg_match($attr['body format'], 'abcdef') + ) { + return ['Attribute %1$s, %2$s - regular expression error', $name, 'Body format']; + } + } + } + } + + if ( + \is_array($this->new_attr) + && ! empty($this->new_attr['allowed']) + && ! empty($this->new_attr['name']) + ) { + $name = $this->new_attr['name']; + + if ( + 'no attr' === $name + || 'Def' === $name + || isset($this->attrs[$name]) + || ! preg_match(self::ATTR_PATTERN, $name) + ) { + return ['Attribute name %s is not valid', $name]; + } + + if (isset($this->new_attr['format'])) { + if ( + ! \is_string($this->new_attr['format']) + || false === @\preg_match($this->new_attr['format'], 'abcdef') + ) { + return ['Attribute %1$s, %2$s - regular expression error', $name, 'Format']; + } + } + + if (isset($this->new_attr['body format'])) { + if ( + ! \is_string($this->new_attr['body format']) + || false === @\preg_match($this->new_attr['body format'], 'abcdef') + ) { + return ['Attribute %1$s, %2$s - regular expression error', $name, 'Body format']; + } + } + } + + return null; + } + + protected function testPHP(?string $code): ?string + { + if ( + null === $code + || '' === $code + ) { + return null; + } + + // тест на парность скобок + $testCode = \preg_replace('%//[^\r\n]*+|#[^\r\n]*+|/\*.*?\*/|\'.*?(? \'(\'.'; + } + break; + case '[': + ++$square; + break; + case ']': + --$square; + + if ($square < 0) { + return '\']\' > \'[\'.'; + } + break; + case '{': + ++$curly; + break; + case '}': + --$curly; + + if ($curly < 0) { + return '\'}\' > \'{\'.'; + } + break; + default: + throw new RuntimeException('Unknown bracket type'); + } + } + + if (0 !== $round) { + return '\'(\' != \')\'.'; + } + if (0 !== $square) { + return '\'[\' != \']\'.'; + } + if (0 !== $curly) { + return '\'{\' != \'}\'.'; + } + + // тест на выполнение DANGER! DANGER! DANGER! O_o + $testCode = "\$testVar = function(\$body, \$attrs, \$parser) { {$code} };\nreturn true;"; + + try { + $result = @eval($testCode); + + if (true !== $result) { + $error = error_get_last(); + $message = $error['message'] ?? 'Unknown error'; + $line = $error['line'] ?? ''; + + return "{$message}: [$line]"; + } + } catch (Throwable $e) { + return "{$e->getMessage()}: [{$e->getLine()}]"; + } + + return null; } } diff --git a/app/Models/BBCodeList/Update.php b/app/Models/BBCodeList/Update.php new file mode 100644 index 00000000..e058bccb --- /dev/null +++ b/app/Models/BBCodeList/Update.php @@ -0,0 +1,34 @@ +getError()) { + throw new RuntimeException('BBCode structure has error'); + } + + $vars = [ + ':id' => $id, + ':tag' => $structure->tag, + ':structure' => $structure->toString(), + ]; + $query = 'UPDATE ::bbcode + SET bb_structure=?s:structure + WHERE id=?i:id AND bb_tag=?s:tag AND bb_edit=1'; + + $this->c->DB->exec($query, $vars); + + return $this->model->reset(); + } +} diff --git a/app/Models/Pages/Admin/Parser/BBCode.php b/app/Models/Pages/Admin/Parser/BBCode.php index 8ee7dcaf..7a5560dd 100644 --- a/app/Models/Pages/Admin/Parser/BBCode.php +++ b/app/Models/Pages/Admin/Parser/BBCode.php @@ -246,19 +246,79 @@ class BBCode extends Parser } $this->bbTypes = $bbTypes; - if ('POST' === $method) { - } - - if ($id > 0) { $title = __('Edit bbcode head'); - $this->formAction = $this->c->Router->link('AdminBBCodeEdit', ['id' => $id]); - $this->formToken = $this->c->Csrf->create('AdminBBCodeEdit', ['id' => $id]); + $page = 'AdminBBCodeEdit'; + $pageArgs = ['id' => $id]; } else { $title = __('Add bbcode head'); - $this->formAction = $this->c->Router->link('AdminBBCodeNew'); - $this->formToken = $this->c->Csrf->create('AdminBBCodeNew'); + $page = 'AdminBBCodeNew'; + $pageArgs = []; } + $this->formAction = $this->c->Router->link($page, $pageArgs); + $this->formToken = $this->c->Csrf->create($page, $pageArgs); + + if ('POST' === $method) { + $v = $this->c->Validator->reset() + ->addValidators([ + 'check_all' => [$this, 'vCheckAll'], + ])->addRules([ + 'token' => 'token:' . $page, + 'tag' => $id > 0 ? 'absent' : 'required|string:trim|regex:%^[a-z\*][a-z\d-]{0,10}$%', + 'type' => 'required|string|in:' . \implode(',', $bbTypes), + 'type_new' => 'string:trim|regex:%^[a-z][a-z\d-]{0,19}$%', + 'parents.*' => 'required|string|in:' . \implode(',', $bbTypes), + 'handler' => 'string:trim|max:65535', + 'text_handler' => 'string:trim|max:65535', + 'recursive' => 'required|integer|in:0,1', + 'text_only' => 'required|integer|in:0,1', + 'tags_only' => 'required|integer|in:0,1', + 'pre' => 'required|integer|in:0,1', + 'single' => 'required|integer|in:0,1', + 'auto' => 'required|integer|in:0,1', + 'self_nesting' => 'required|integer|min:0|max:10', + 'no_attr.allowed' => 'required|integer|in:0,1', + 'no_attr.body_format' => 'string:trim|max:1024', + 'no_attr.text_only' => 'required|integer|in:0,1', + 'def_attr.allowed' => 'required|integer|in:0,1', + 'def_attr.required' => 'required|integer|in:0,1', + 'def_attr.format' => 'string:trim|max:1024', + 'def_attr.body_format' => 'string:trim|max:1024', + 'def_attr.text_only' => 'required|integer|in:0,1', + 'other_attrs.*.allowed' => 'required|integer|in:0,1', + 'other_attrs.*.required' => 'required|integer|in:0,1', + 'other_attrs.*.format' => 'string:trim|max:1024', + 'other_attrs.*.body_format' => 'string:trim|max:1024', + 'other_attrs.*.text_only' => 'required|integer|in:0,1', + 'new_attr.name' => 'string:trim|regex:%^(?:\|[a-z-]{2,15})$%', + 'new_attr.allowed' => 'required|integer|in:0,1', + 'new_attr.required' => 'required|integer|in:0,1', + 'new_attr.format' => 'string:trim|max:1024', + 'new_attr.body_format' => 'string:trim|max:1024', + 'new_attr.text_only' => 'required|integer|in:0,1', + 'save' => 'check_all', + ])->addAliases([ + ])->addArguments([ + 'token' => $pageArgs, + 'save' => $structure, + ])->addMessages([ + ]); + + if ($v->validation($_POST)) { + if ($id > 0) { + $this->c->bbcode->update($id, $structure); + $message = 'BBCode updated redirect'; + } else { + $id = $this->c->bbcode->insert($structure); + $message = 'BBCode added redirect'; + } + + return $this->c->Redirect->page('AdminBBCodeEdit', ['id' => $id])->message($message); + } + + $this->fIswev = $v->getErrors(); + } + $this->aCrumbs[] = [ $this->formAction, $title, @@ -278,6 +338,38 @@ class BBCode extends Parser return $this; } + /** + * Проверяет данные bb-кода + */ + public function vCheckAll(Validator $v, string $txt, $attrs, Structure $structure): string + { + if (! empty($v->getErrors())) { + return $txt; + } + + $data = $v->getData(); + unset($data['token'], $data['save']); + + foreach ($data as $key => $value) { + if ('type_new' === $key) { + if (isset($value[0])) { + $structure->type = $value; + } + } else { + $structure->{$key} = $value; + } + } + + $error = $structure->getError(); + + if (\is_array($error)) { + $v->addError(__(...$error)); + } + + return $txt; + } + + /** * Формирует данные для формы */ @@ -298,9 +390,9 @@ class BBCode extends Parser ], ]; - $yn = [1 => __('Yes'), 0 => __('No')]; + $yn = [1 => __('Yes'), 0 => __('No')]; - $form['sets']["structure"] = [ + $form['sets']['structure'] = [ 'class' => 'structure', // 'legend' => , 'fields' => [ @@ -406,78 +498,106 @@ class BBCode extends Parser $tagStr = $id > 0 ? $structure->tag : 'TAG'; - $form['sets']["no_attr"] = [ - 'class' => ['attr', 'no_attr'], - 'legend' => __('No attr subhead', $tagStr), - 'fields' => [ - 'no_attr[allowed]' => [ - 'type' => 'radio', - 'value' => null === $structure->no_attr ? 0 : 1, - 'values' => $yn, - 'caption' => __('Allowed label'), - 'info' => __('Allowed no_attr info'), - ], - 'no_attr[body_format]' => [ - 'class' => 'format', - 'type' => 'text', - 'value' => $structure->no_attr['body_format'] ?? '', - 'caption' => __('Body format label'), - 'info' => __('Body format info'), - ], - 'no_attr[text_only]' => [ - 'type' => 'radio', - 'value' => empty($structure->no_attr['text_only']) ? 0 : 1, - 'values' => $yn, - 'caption' => __('Text only label'), - 'info' => __('Text only info'), - ], - ], - ]; + $form['sets']['no_attr'] = $this->formEditSub( + $structure->no_attr, + 'no_attr', + 'no_attr', + __('No attr subhead', $tagStr), + __('Allowed no_attr info') + ); - $form['sets']["def_attr"] = [ - 'class' => ['attr', 'def_attr'], - 'legend' => __('Def attr subhead', $tagStr), - 'fields' => [ - 'def_attr[allowed]' => [ - 'type' => 'radio', - 'value' => null === $structure->def_attr ? 0 : 1, - 'values' => $yn, - 'caption' => __('Allowed label'), - 'info' => __('Allowed def_attr info'), - ], - 'def_attr[required]' => [ - 'type' => 'radio', - 'value' => empty($structure->def_attr['required']) ? 0 : 1, - 'values' => $yn, - 'caption' => __('Required label'), - 'info' => __('Required info'), - ], - 'def_attr[format]' => [ - 'class' => 'format', - 'type' => 'text', - 'value' => $structure->def_attr['format'] ?? '', - 'caption' => __('Format label'), - 'info' => __('Format info'), - ], - 'def_attr[body_format]' => [ - 'class' => 'format', - 'type' => 'text', - 'value' => $structure->def_attr['body_format'] ?? '', - 'caption' => __('Body format label'), - 'info' => __('Body format info'), - ], - 'def_attr[text_only]' => [ - 'type' => 'radio', - 'value' => empty($structure->def_attr['text_only']) ? 0 : 1, - 'values' => $yn, - 'caption' => __('Text only label'), - 'info' => __('Text only info'), - ], - ], - ]; + $form['sets']['def_attr'] = $this->formEditSub( + $structure->def_attr, + 'def_attr', + 'def_attr', + __('Def attr subhead', $tagStr), + __('Allowed def_attr info') + ); - $fields = []; + foreach ($structure->other_attrs as $name => $attr) { + $form['sets']["{$name}_attr"] = $this->formEditSub( + $attr, + $name, + "{$name}_attr", + __('Other attr subhead', $tagStr, $name), + __('Allowed %s attr info', $name) + ); + } + + $form['sets']['new_attr'] = $this->formEditSub( + $structure->new_attr, + 'new_attr', + 'new_attr', + __('New attr subhead'), + __('Allowed new_attr info') + ); return $form; } + + /** + * Формирует данные для формы + */ + protected function formEditSub(/* mixed */ $data, string $name, string $class, string $legend, string $info): array + { + $yn = [1 => __('Yes'), 0 => __('No')]; + $fields = []; + $other = '_attr' !== \substr($name, -5); + $key = $other ? "other_attrs[{$name}]" : $name; + + if ('new_attr' === $name) { + $fields["{$key}[name]"] = [ + 'type' => 'text', + 'value' => $data['name'] ?? '', + 'caption' => __('Attribute name label'), + 'info' => __('Attribute name info'), + 'maxlength' => 15, + 'pattern' => '^[a-z-]{2,15}$', + ]; + } + + $fields["{$key}[allowed]"] = [ + 'type' => 'radio', + 'value' => null === $data ? 0 : 1, + 'values' => $yn, + 'caption' => __('Allowed label'), + 'info' => $info, + ]; + if ('no_attr' !== $name) { + $fields["{$key}[required]"] = [ + 'type' => 'radio', + 'value' => empty($data['required']) ? 0 : 1, + 'values' => $yn, + 'caption' => __('Required label'), + 'info' => __('Required info'), + ]; + $fields["{$key}[format]"] = [ + 'class' => 'format', + 'type' => 'text', + 'value' => $data['format'] ?? '', + 'caption' => __('Format label'), + 'info' => __('Format info'), + ]; + } + $fields["{$key}[body_format]"] = [ + 'class' => 'format', + 'type' => 'text', + 'value' => $data['body_format'] ?? '', + 'caption' => __('Body format label'), + 'info' => __('Body format info'), + ]; + $fields["{$key}[text_only]"] = [ + 'type' => 'radio', + 'value' => empty($data['text_only']) ? 0 : 1, + 'values' => $yn, + 'caption' => __('Text only label'), + 'info' => __('Text only info'), + ]; + + return [ + 'class' => ['attr', $class], + 'legend' => $legend, + 'fields' => $fields, + ]; + } } diff --git a/app/lang/en/admin_parser.po b/app/lang/en/admin_parser.po index e897e5b2..070c2b1b 100644 --- a/app/lang/en/admin_parser.po +++ b/app/lang/en/admin_parser.po @@ -269,3 +269,51 @@ msgstr "Format" msgid "Format info" msgstr "The regular expression, sets the rule for this attribute." + +msgid "Other attr subhead" +msgstr "Settings for BBCode type [%1$s %2$s=value]..." + +msgid "Allowed %s attr info" +msgstr "Allow use of this BBCode with %s attribute. No - this attribute will be deleted." + +msgid "New attr subhead" +msgstr "New attribute" + +msgid "Allowed new_attr info" +msgstr "Add new attribute" + +msgid "Attribute name label" +msgstr "New attribute name" + +msgid "Attribute name info" +msgstr "Format: ^[a-z-]{2,15}$" + +msgid "Tag name not specified" +msgstr "Tag name not specified." + +msgid "PHP code error in Handler: %s" +msgstr "PHP code error in Handler: %s" + +msgid "PHP code error in Text handler: %s" +msgstr "PHP code error in Text handler: %s" + +msgid "Recursive and Tags only are enabled at the same time" +msgstr "Recursive and Tags only are enabled at the same time." + +msgid "Recursive and Single are enabled at the same time" +msgstr "Recursive and Single are enabled at the same time." + +msgid "Text only and Tags only are enabled at the same time" +msgstr "Text only and Tags only are enabled at the same time." + +msgid "Attribute %1$s, %2$s - regular expression error" +msgstr "Attribute %1$s, field %2$s - regular expression error." + +msgid "Attribute name %s is not valid" +msgstr "Attribute name %s is not valid." + +msgid "BBCode updated redirect" +msgstr "BBCode updated." + +msgid "BBCode added redirect" +msgstr "BBCode added." diff --git a/app/lang/ru/admin_parser.po b/app/lang/ru/admin_parser.po index dfc74284..2a24ce9e 100644 --- a/app/lang/ru/admin_parser.po +++ b/app/lang/ru/admin_parser.po @@ -269,3 +269,51 @@ msgstr "Format" msgid "Format info" msgstr "Регулярное выражение, задает правило для атрибута." + +msgid "Other attr subhead" +msgstr "Настройки для BB-кода вида [%1$s %2$s=значение]..." + +msgid "Allowed %s attr info" +msgstr "Разрешить использовать этот BB-код с атрибутом %s. Нет - атрибут буден удален." + +msgid "New attr subhead" +msgstr "Новый атрибут" + +msgid "Allowed new_attr info" +msgstr "Добавить новый атрибут" + +msgid "Attribute name label" +msgstr "Имя нового атрибута" + +msgid "Attribute name info" +msgstr "Формат: ^[a-z-]{2,15}$" + +msgid "Tag name not specified" +msgstr "Имя тега не указано." + +msgid "PHP code error in Handler: %s" +msgstr "Ошибка PHP кода в Handler: %s" + +msgid "PHP code error in Text handler: %s" +msgstr "Ошибка PHP кода в Text handler: %s" + +msgid "Recursive and Tags only are enabled at the same time" +msgstr "Одновременно включены Recursive и Tags only." + +msgid "Recursive and Single are enabled at the same time" +msgstr "Одновременно включены Recursive и Single." + +msgid "Text only and Tags only are enabled at the same time" +msgstr "Одновременно включены Text only и Tags only." + +msgid "Attribute %1$s, %2$s - regular expression error" +msgstr "Атрибут %1$s, поле %2$s - ошибка в регулярном выражении." + +msgid "Attribute name %s is not valid" +msgstr "Имя %s для атрибута недопустимо." + +msgid "BBCode updated redirect" +msgstr "BB-код обновлен." + +msgid "BBCode added redirect" +msgstr "BB-код добавлен."