Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
35 / 35 |
|
100.00% |
9 / 9 |
CRAP | |
100.00% |
1 / 1 |
| bbcode_merger | |
100.00% |
35 / 35 |
|
100.00% |
9 / 9 |
13 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| merge_bbcodes | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
| create_bbcode | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
| indent_template | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
| is_content_bbcode | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| is_optional_bbcode | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| merge_content_bbcode | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| merge_optional_bbcode | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| normalize_template | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * |
| 4 | * This file is part of the phpBB Forum Software package. |
| 5 | * |
| 6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
| 7 | * @license GNU General Public License, version 2 (GPL-2.0) |
| 8 | * |
| 9 | * For full copyright and license information, please see |
| 10 | * the docs/CREDITS.txt file. |
| 11 | * |
| 12 | */ |
| 13 | |
| 14 | namespace phpbb\textformatter\s9e; |
| 15 | |
| 16 | use phpbb\textformatter\s9e\factory; |
| 17 | use s9e\TextFormatter\Configurator\Helpers\TemplateLoader; |
| 18 | use s9e\TextFormatter\Configurator\Items\UnsafeTemplate; |
| 19 | |
| 20 | class bbcode_merger |
| 21 | { |
| 22 | /** |
| 23 | * @var \s9e\TextFormatter\Configurator $configurator Configurator instance used to inspect BBCodes |
| 24 | */ |
| 25 | protected $configurator; |
| 26 | |
| 27 | /** |
| 28 | * @param factory $factory |
| 29 | */ |
| 30 | public function __construct(factory $factory) |
| 31 | { |
| 32 | $this->configurator = $factory->get_configurator(); |
| 33 | } |
| 34 | |
| 35 | /** |
| 36 | * Merge two BBCode definitions |
| 37 | * |
| 38 | * All of the arrays contain a "usage" element and a "template" element |
| 39 | * |
| 40 | * @throws \InvalidArgumentException if a definition cannot be interpreted |
| 41 | * @throws \RuntimeException if something unexpected occurs |
| 42 | * |
| 43 | * @param array $without BBCode definition without an attribute |
| 44 | * @param array $with BBCode definition with an attribute |
| 45 | * @return array Merged definition |
| 46 | */ |
| 47 | public function merge_bbcodes(array $without, array $with) |
| 48 | { |
| 49 | $without = $this->create_bbcode($without); |
| 50 | $with = $this->create_bbcode($with); |
| 51 | |
| 52 | // Select the appropriate strategy for merging this BBCode |
| 53 | if (!$this->is_optional_bbcode($without, $with) && $this->is_content_bbcode($without, $with)) |
| 54 | { |
| 55 | $merged = $this->merge_content_bbcode($without, $with); |
| 56 | } |
| 57 | else |
| 58 | { |
| 59 | $merged = $this->merge_optional_bbcode($without, $with); |
| 60 | } |
| 61 | |
| 62 | $merged['template'] = $this->normalize_template($merged['template']); |
| 63 | |
| 64 | return $merged; |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Create a custom BBCode for inspection |
| 69 | * |
| 70 | * @param array $definition Original BBCode definition |
| 71 | * @return array Updated definition containing a BBCode object and a Tag |
| 72 | */ |
| 73 | protected function create_bbcode(array $definition) |
| 74 | { |
| 75 | $bbcode = $this->configurator->BBCodes->addCustom( |
| 76 | $definition['usage'], |
| 77 | new UnsafeTemplate($definition['template']) |
| 78 | ); |
| 79 | |
| 80 | $definition['bbcode'] = $bbcode; |
| 81 | $definition['tag'] = $this->configurator->tags[$bbcode->tagName]; |
| 82 | |
| 83 | return $definition; |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Indent given template for readability |
| 88 | * |
| 89 | * @param string $template |
| 90 | * @return string |
| 91 | */ |
| 92 | protected function indent_template($template) |
| 93 | { |
| 94 | $dom = TemplateLoader::load($template); |
| 95 | $dom->formatOutput = true; |
| 96 | $template = TemplateLoader::save($dom); |
| 97 | |
| 98 | // Remove the first level of indentation if the template starts with whitespace |
| 99 | if (preg_match('(^\\n +)', $template, $m)) |
| 100 | { |
| 101 | $template = str_replace($m[0], "\n", $template); |
| 102 | } |
| 103 | |
| 104 | return trim($template); |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Test whether the two definitions form a "content"-style BBCode |
| 109 | * |
| 110 | * Such BBCodes include the [url] BBCode, which uses its text content as |
| 111 | * attribute if none is provided |
| 112 | * |
| 113 | * @param array $without BBCode definition without an attribute |
| 114 | * @param array $with BBCode definition with an attribute |
| 115 | * @return bool |
| 116 | */ |
| 117 | protected function is_content_bbcode(array $without, array $with) |
| 118 | { |
| 119 | // Test whether we find the same non-TEXT token between "]" and "[" in the usage |
| 120 | // as between ">" and "<" in the template |
| 121 | return (preg_match('(\\]\\s*(\\{(?!TEXT)[^}]+\\})\\s*\\[)', $without['usage'], $m) |
| 122 | && preg_match('(>[^<]*?' . preg_quote($m[1]) . '[^>]*?<)s', $without['template'])); |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Test whether the two definitions form BBCode with an optional attribute |
| 127 | * |
| 128 | * @param array $without BBCode definition without an attribute |
| 129 | * @param array $with BBCode definition with an attribute |
| 130 | * @return bool |
| 131 | */ |
| 132 | protected function is_optional_bbcode(array $without, array $with) |
| 133 | { |
| 134 | // Remove the default attribute from the definition |
| 135 | $with['usage'] = preg_replace('(=[^\\]]++)', '', $with['usage']); |
| 136 | |
| 137 | // Test whether both definitions are the same, regardless of case |
| 138 | return strcasecmp($without['usage'], $with['usage']) === 0; |
| 139 | } |
| 140 | |
| 141 | /** |
| 142 | * Merge the two BBCode definitions of a "content"-style BBCode |
| 143 | * |
| 144 | * @param array $without BBCode definition without an attribute |
| 145 | * @param array $with BBCode definition with an attribute |
| 146 | * @return array Merged definition |
| 147 | */ |
| 148 | protected function merge_content_bbcode(array $without, array $with) |
| 149 | { |
| 150 | // Convert [x={X}] into [x={X;useContent}] |
| 151 | $usage = preg_replace('(\\})', ';useContent}', $with['usage'], 1); |
| 152 | |
| 153 | // Use the template from the definition that uses an attribute |
| 154 | $template = $with['tag']->template; |
| 155 | |
| 156 | return ['usage' => $usage, 'template' => $template]; |
| 157 | } |
| 158 | |
| 159 | /** |
| 160 | * Merge the two BBCode definitions of a BBCode with an optional argument |
| 161 | * |
| 162 | * Such BBCodes include the [quote] BBCode, which takes an optional argument |
| 163 | * but otherwise does not behave differently |
| 164 | * |
| 165 | * @param array $without BBCode definition without an attribute |
| 166 | * @param array $with BBCode definition with an attribute |
| 167 | * @return array Merged definition |
| 168 | */ |
| 169 | protected function merge_optional_bbcode(array $without, array $with) |
| 170 | { |
| 171 | // Convert [X={X}] into [X={X?}] |
| 172 | $usage = preg_replace('(\\})', '?}', $with['usage'], 1); |
| 173 | |
| 174 | // Build a template for both versions |
| 175 | $template = '<xsl:choose><xsl:when test="@' . $with['bbcode']->defaultAttribute . '">' . $with['tag']->template . '</xsl:when><xsl:otherwise>' . $without['tag']->template . '</xsl:otherwise></xsl:choose>'; |
| 176 | |
| 177 | return ['usage' => $usage, 'template' => $template]; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Normalize a template |
| 182 | * |
| 183 | * @param string $template |
| 184 | * @return string |
| 185 | */ |
| 186 | protected function normalize_template($template) |
| 187 | { |
| 188 | // Normalize the template to simplify it |
| 189 | $template = $this->configurator->templateNormalizer->normalizeTemplate($template); |
| 190 | |
| 191 | // Convert xsl:value-of elements back to {L_} tokens where applicable |
| 192 | $template = preg_replace('(<xsl:value-of select="\\$(L_\\w+)"/>)', '{$1}', $template); |
| 193 | |
| 194 | // Beautify the template |
| 195 | $template = $this->indent_template($template); |
| 196 | |
| 197 | return $template; |
| 198 | } |
| 199 | } |