@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
3/**
4 * @return PhutilSafeHTML
5 */
6function javelin_tag(
7 $tag,
8 array $attributes = array(),
9 $content = null) {
10
11 if (isset($attributes['sigil']) ||
12 isset($attributes['meta']) ||
13 isset($attributes['mustcapture'])) {
14 foreach ($attributes as $k => $v) {
15 switch ($k) {
16 case 'sigil':
17 if ($v !== null) {
18 $attributes['data-sigil'] = $v;
19 }
20 unset($attributes[$k]);
21 break;
22 case 'meta':
23 if ($v !== null) {
24 $response = CelerityAPI::getStaticResourceResponse();
25 $id = $response->addMetadata($v);
26 $attributes['data-meta'] = $id;
27 }
28 unset($attributes[$k]);
29 break;
30 case 'mustcapture':
31 if ($v) {
32 $attributes['data-mustcapture'] = '1';
33 } else {
34 unset($attributes['data-mustcapture']);
35 }
36 unset($attributes[$k]);
37 break;
38 }
39 }
40 }
41
42 if (isset($attributes['aural'])) {
43 if ($attributes['aural']) {
44 $class = idx($attributes, 'class', '');
45 $class = rtrim('aural-only '.$class);
46 $attributes['class'] = $class;
47 } else {
48 $class = idx($attributes, 'class', '');
49 $class = rtrim('visual-only '.$class);
50 $attributes['class'] = $class;
51 $attributes['aria-hidden'] = 'true';
52 }
53 unset($attributes['aural']);
54 }
55
56 if (isset($attributes['print'])) {
57 if ($attributes['print']) {
58 $class = idx($attributes, 'class', '');
59 $class = rtrim('print-only '.$class);
60 $attributes['class'] = $class;
61
62 // NOTE: Alternative print content is hidden from screen readers.
63 $attributes['aria-hidden'] = 'true';
64 } else {
65 $class = idx($attributes, 'class', '');
66 $class = rtrim('screen-only '.$class);
67 $attributes['class'] = $class;
68 }
69 unset($attributes['print']);
70 }
71
72 return phutil_tag($tag, $attributes, $content);
73}
74
75/**
76 * @return PhutilSafeHTML
77 */
78function phabricator_form(PhabricatorUser $user, $attributes, $content) {
79 $body = array();
80
81 $http_method = idx($attributes, 'method');
82 $is_post = $http_method && (strcasecmp($http_method, 'POST') === 0);
83
84 $http_action = idx($attributes, 'action');
85
86 if ($http_action === null) {
87 // Not sure what this is.
88 $is_absolute_uri = false;
89
90 } else if ($http_action instanceof PhutilURI) {
91 // This is the happy path, I think
92
93 // For now, this is close enough - I suspect we'll stay with "https" schema
94 // for the rest of eternity.
95 $protocol = $http_action->getProtocol();
96 $is_absolute_uri = ($protocol == 'http' || $protocol == 'https');
97
98 } else if (is_string($http_action)) {
99 // Also good path?
100 $is_absolute_uri = preg_match('#^(https?:|//)#', $http_action);
101 } else {
102 throw new Exception(
103 pht(
104 'Unexpected object type provided as `action` - %s',
105 gettype($http_action)));
106 }
107
108 if ($is_post) {
109
110 // NOTE: We only include CSRF tokens if a URI is a local URI on the same
111 // domain. This is an important security feature and prevents forms which
112 // submit to foreign sites from leaking CSRF tokens.
113
114 // In some cases, we may construct a fully-qualified local URI. For example,
115 // we can construct these for download links, depending on configuration.
116
117 // These forms do not receive CSRF tokens, even though they safely could.
118 // This can be confusing, if you're developing for Phabricator and
119 // manage to construct a local form with a fully-qualified URI, since it
120 // won't get CSRF tokens and you'll get an exception at the other end of
121 // the request which is a bit disconnected from the actual root cause.
122
123 // However, this is rare, and there are reasonable cases where this
124 // construction occurs legitimately, and the simplest fix is to omit CSRF
125 // tokens for these URIs in all cases. The error message you receive also
126 // gives you some hints as to this potential source of error.
127
128 if (!$is_absolute_uri) {
129 $body[] = phutil_tag(
130 'input',
131 array(
132 'type' => 'hidden',
133 'name' => AphrontRequest::getCSRFTokenName(),
134 'value' => $user->getCSRFToken(),
135 ));
136
137 $body[] = phutil_tag(
138 'input',
139 array(
140 'type' => 'hidden',
141 'name' => '__form__',
142 'value' => true,
143 ));
144
145 // If the profiler was active for this request, keep it active for any
146 // forms submitted from this page.
147 if (DarkConsoleXHProfPluginAPI::isProfilerRequested()) {
148 $body[] = phutil_tag(
149 'input',
150 array(
151 'type' => 'hidden',
152 'name' => '__profile__',
153 'value' => true,
154 ));
155 }
156
157 }
158 }
159
160 if (is_array($content)) {
161 $body = array_merge($body, $content);
162 } else {
163 $body[] = $content;
164 }
165
166 return javelin_tag('form', $attributes, $body);
167}