@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
3final class PhutilRemarkupAnchorRule extends PhutilRemarkupRule {
4
5 public function getPriority() {
6 return 200.0;
7 }
8
9 public function apply($text) {
10 return preg_replace_callback(
11 '/{anchor\s+#([^\s}]+)}/s',
12 array($this, 'markupAnchor'),
13 $text);
14 }
15
16 protected function markupAnchor(array $matches) {
17 $engine = $this->getEngine();
18
19 if ($engine->isTextMode()) {
20 return null;
21 }
22
23 if ($engine->isHTMLMailMode()) {
24 return null;
25 }
26
27 if ($engine->isAnchorMode()) {
28 return null;
29 }
30
31 if (!$this->isFlatText($matches[0])) {
32 return $matches[0];
33 }
34
35 if (!self::isValidAnchorName($matches[1])) {
36 return $matches[0];
37 }
38
39 $tag_view = phutil_tag(
40 'a',
41 array(
42 'name' => $matches[1],
43 ),
44 '');
45
46 return $this->getEngine()->storeText($tag_view);
47 }
48
49 public static function isValidAnchorName($anchor_name) {
50 $normal_anchor = self::normalizeAnchor($anchor_name);
51
52 if ($normal_anchor === $anchor_name) {
53 return true;
54 }
55
56 return false;
57 }
58
59 public static function normalizeAnchor($anchor) {
60 // Replace all latin characters which are not "a-z" or "0-9" with "-".
61 // Preserve other characters, since non-latin letters and emoji work
62 // fine in anchors.
63 $anchor = preg_replace('/[\x00-\x2F\x3A-\x60\x7B-\x7F]+/', '-', $anchor);
64 $anchor = trim($anchor, '-');
65
66 return $anchor;
67 }
68
69}