@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 AphrontFormControl extends AphrontView {
4
5 private $label;
6 private $ariaLabel;
7 private $caption;
8 private $error;
9 private $name;
10 private $value;
11 private $disabled;
12 private $id;
13 private $controlID;
14 private $controlStyle;
15 private $required;
16 private $hidden;
17 private $classes;
18 private $hasCopyButton;
19
20 public function setHidden($hidden) {
21 $this->hidden = $hidden;
22 return $this;
23 }
24
25 public function setID($id) {
26 $this->id = $id;
27 return $this;
28 }
29
30 public function getID() {
31 return $this->id;
32 }
33
34 public function setControlID($control_id) {
35 $this->controlID = $control_id;
36 return $this;
37 }
38
39 public function getControlID() {
40 return $this->controlID;
41 }
42
43 public function setControlStyle($control_style) {
44 $this->controlStyle = $control_style;
45 return $this;
46 }
47
48 public function getControlStyle() {
49 return $this->controlStyle;
50 }
51
52 public function setLabel($label) {
53 $this->label = $label;
54 return $this;
55 }
56
57 /**
58 * Explicitly set an aria-label attribute for accessibility. Only to be used
59 * when no visible label is already set via setLabel().
60 * @param string $aria_label aria-label text to add to the form control
61 */
62 public function setAriaLabel($aria_label) {
63 $this->ariaLabel = $aria_label;
64 return $this;
65 }
66
67 public function getAriaLabel() {
68 return $this->ariaLabel;
69 }
70
71 public function getLabel() {
72 return $this->label;
73 }
74
75 /**
76 * Set the Caption
77 * The Caption shows a tip usually nearby the related input field.
78 * @param string|PhutilSafeHTML|null $caption
79 * @return self
80 */
81 public function setCaption($caption) {
82 $this->caption = $caption;
83 return $this;
84 }
85
86 /**
87 * Get the Caption
88 * The Caption shows a tip usually nearby the related input field.
89 * @return string|PhutilSafeHTML|null
90 */
91 public function getCaption() {
92 return $this->caption;
93 }
94
95 public function setError($error) {
96 $this->error = $error;
97 return $this;
98 }
99
100 public function getError() {
101 return $this->error;
102 }
103
104 public function setName($name) {
105 $this->name = $name;
106 return $this;
107 }
108
109 public function getName() {
110 return $this->name;
111 }
112
113 public function setValue($value) {
114 $this->value = $value;
115 return $this;
116 }
117
118 /**
119 * Whether a button is displayed next to the control which allows the user to
120 * copy the value in the form control. Common use cases include <input>
121 * (AphrontFormTextControl) and <textarea> (AphrontFormTextAreaControl)
122 * elements displaying read-only data such as tokens or passphrases. This is
123 * only to get the CSS right; actual button implementation is in subclasses.
124 *
125 * @return bool
126 */
127 public function getHasCopyButton() {
128 return $this->hasCopyButton;
129 }
130
131 /**
132 * Whether to display a button next to the control which allows the user to
133 * copy the value in the form control. Common use cases include <input>
134 * (AphrontFormTextControl) and <textarea> (AphrontFormTextAreaControl)
135 * elements displaying read-only data such as tokens or passphrases. This is
136 * only to get the CSS right; actual button implementation is in subclasses.
137 *
138 * @param bool $has_copy_button
139 */
140 public function setHasCopyButton($has_copy_button) {
141 $this->hasCopyButton = $has_copy_button;
142 return $this;
143 }
144
145 public function getValue() {
146 return $this->value;
147 }
148
149 public function isValid() {
150 if ($this->error && $this->error !== true) {
151 return false;
152 }
153
154 if ($this->isRequired() && $this->isEmpty()) {
155 return false;
156 }
157
158 return true;
159 }
160
161 public function isRequired() {
162 return $this->required;
163 }
164
165 public function isEmpty() {
166 return !strlen($this->getValue());
167 }
168
169 public function getSerializedValue() {
170 return $this->getValue();
171 }
172
173 public function readSerializedValue($value) {
174 $this->setValue($value);
175 return $this;
176 }
177
178 public function readValueFromRequest(AphrontRequest $request) {
179 $this->setValue($request->getStr($this->getName()));
180 return $this;
181 }
182
183 public function readValueFromDictionary(array $dictionary) {
184 $this->setValue(idx($dictionary, $this->getName()));
185 return $this;
186 }
187
188 public function setDisabled($disabled) {
189 $this->disabled = $disabled;
190 return $this;
191 }
192
193 public function getDisabled() {
194 return $this->disabled;
195 }
196
197 abstract protected function renderInput();
198 abstract protected function getCustomControlClass();
199
200 protected function shouldRender() {
201 return true;
202 }
203
204 public function addClass($class) {
205 $this->classes[] = $class;
206 return $this;
207 }
208
209 final public function render() {
210 if (!$this->shouldRender()) {
211 return null;
212 }
213
214 $custom_class = $this->getCustomControlClass();
215
216 // If we don't have an ID yet, assign an automatic one so we can associate
217 // the label with the control. This allows assistive technologies to read
218 // form labels.
219 if (!$this->getID()) {
220 $this->setID(celerity_generate_unique_node_id());
221 }
222
223 $class = 'aphront-form-input';
224 if ($this->getHasCopyButton()) {
225 $class = $class.' has-copy-button';
226 }
227
228 $input = phutil_tag(
229 'div',
230 array('class' => $class),
231 $this->renderInput());
232
233 $error = null;
234 if ($this->getError()) {
235 $error = $this->getError();
236 if ($error === true) {
237 $error = phutil_tag(
238 'span',
239 array('class' => 'aphront-form-error aphront-form-required'),
240 pht('Required'));
241 } else {
242 $error = phutil_tag(
243 'span',
244 array('class' => 'aphront-form-error'),
245 $error);
246 }
247 }
248
249 if (phutil_nonempty_string($this->getLabel())) {
250 $label = phutil_tag(
251 'label',
252 array(
253 'class' => 'aphront-form-label',
254 'for' => $this->getID(),
255 ),
256 array(
257 $this->getLabel(),
258 $error,
259 ));
260 } else {
261 $label = null;
262 $custom_class .= ' aphront-form-control-nolabel';
263 }
264
265 // The Caption can be stuff like PhutilSafeHTML and other objects that
266 // can be casted to a string. After this cast we have never null.
267 $has_caption = phutil_string_cast($this->getCaption()) !== '';
268
269 if ($has_caption) {
270 $caption = phutil_tag(
271 'div',
272 array('class' => 'aphront-form-caption'),
273 $this->getCaption());
274 } else {
275 $caption = null;
276 }
277
278 $classes = array();
279 $classes[] = 'aphront-form-control';
280 $classes[] = 'grouped';
281 $classes[] = $custom_class;
282 if ($this->classes) {
283 foreach ($this->classes as $class) {
284 $classes[] = $class;
285 }
286 }
287
288 $style = $this->controlStyle;
289 if ($this->hidden) {
290 $style = 'display: none; '.$style;
291 }
292
293 return phutil_tag(
294 'div',
295 array(
296 'class' => implode(' ', $classes),
297 'id' => $this->controlID,
298 'style' => $style,
299 ),
300 array(
301 $label,
302 $error,
303 $input,
304 $caption,
305 ));
306 }
307}