@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3abstract class PhutilRemarkupRule extends Phobject {
4
5 private $engine;
6 private $replaceCallback;
7
8 public function setEngine(PhutilRemarkupEngine $engine) {
9 $this->engine = $engine;
10 return $this;
11 }
12
13 public function getEngine() {
14 return $this->engine;
15 }
16
17 public function getPriority() {
18 return 500.0;
19 }
20
21 /**
22 * Check input whether to apply RemarkupRule. If true, apply formatting.
23 * @param string|PhutilSafeHTML $text String to check and potentially
24 * format.
25 * @return string|PhutilSafeHTML Unchanged input if no match, or input after
26 * matching the formatting rule and applying the formatting.
27 */
28 abstract public function apply($text);
29
30 public function getPostprocessKey() {
31 return spl_object_hash($this);
32 }
33
34 public function didMarkupText() {
35 return;
36 }
37
38 protected function replaceHTML($pattern, $callback, $text) {
39 $this->replaceCallback = $callback;
40 return phutil_safe_html(preg_replace_callback(
41 $pattern,
42 array($this, 'replaceHTMLCallback'),
43 phutil_escape_html($text)));
44 }
45
46 private function replaceHTMLCallback(array $match) {
47 return phutil_escape_html(call_user_func(
48 $this->replaceCallback,
49 array_map('phutil_safe_html', $match)));
50 }
51
52
53 /**
54 * Safely generate a tag.
55 *
56 * In Remarkup contexts, it's not safe to use arbitrary text in tag
57 * attributes: even though it will be escaped, it may contain replacement
58 * tokens which are then replaced with markup.
59 *
60 * This method acts as @{function:phutil_tag}, but checks attributes before
61 * using them.
62 *
63 * @param string $name Tag name.
64 * @param array<string, mixed> $attrs Dictionary of tag attributes.
65 * @param mixed $content (optional) Tag content.
66 * @return PhutilSafeHTML Tag object.
67 */
68 protected function newTag($name, array $attrs, $content = null) {
69 foreach ($attrs as $key => $attr) {
70 if ($attr !== null) {
71 $attrs[$key] = $this->assertFlatText($attr);
72 }
73 }
74
75 return phutil_tag($name, $attrs, $content);
76 }
77
78 /**
79 * Assert that a text token is flat (it contains no replacement tokens).
80 *
81 * Because tokens can be replaced with markup, it is dangerous to use
82 * arbitrary input text in tag attributes. Normally, rule precedence should
83 * prevent this. Asserting that text is flat before using it as an attribute
84 * provides an extra layer of security.
85 *
86 * Normally, you can call @{method:newTag} rather than calling this method
87 * directly. @{method:newTag} will check attributes for you.
88 *
89 * @param mixed $text Ostensibly flat text.
90 * @return string Flat text.
91 */
92 protected function assertFlatText($text) {
93 $text = (string)hsprintf('%s', phutil_safe_html($text));
94 $rich = (strpos($text, PhutilRemarkupBlockStorage::MAGIC_BYTE) !== false);
95 if ($rich) {
96 throw new Exception(
97 pht(
98 'Remarkup rule precedence is dangerous: rendering text with tokens '.
99 'as flat text!'));
100 }
101
102 return $text;
103 }
104
105 /**
106 * Check whether text is flat (contains no replacement tokens) or not.
107 *
108 * @param mixed $text Ostensibly flat text.
109 * @return bool True if the text is flat.
110 */
111 protected function isFlatText($text) {
112 $text = (string)hsprintf('%s', phutil_safe_html($text));
113 return (strpos($text, PhutilRemarkupBlockStorage::MAGIC_BYTE) === false);
114 }
115
116 /**
117 * Get the CSS class="" attribute for a Remarkup link.
118 * It's just "remarkup-link" for all cases, plus the possibility for
119 * designers to style external links differently.
120 * @param boolean $is_internal Whenever the link was internal or not.
121 * @return string
122 */
123 protected function getRemarkupLinkClass($is_internal) {
124 // Allow developers to style external links differently
125 $classes = array('remarkup-link');
126 if (!$is_internal) {
127 $classes[] = 'remarkup-link-ext';
128 }
129 return implode(' ', $classes);
130 }
131
132}