@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 PhabricatorSetupIssueView extends AphrontView {
4
5 private $issue;
6
7 public function setIssue(PhabricatorSetupIssue $issue) {
8 $this->issue = $issue;
9 return $this;
10 }
11
12 public function getIssue() {
13 return $this->issue;
14 }
15
16 public function renderInFlight() {
17 $issue = $this->getIssue();
18
19 return id(new PhabricatorInFlightErrorView())
20 ->setMessage($issue->getName())
21 ->render();
22 }
23
24 public function render() {
25 $issue = $this->getIssue();
26
27 $description = array();
28 $description[] = phutil_tag(
29 'div',
30 array(
31 'class' => 'setup-issue-instructions',
32 ),
33 phutil_escape_html_newlines($issue->getMessage()));
34
35 $configs = $issue->getPHPConfig();
36 if ($configs) {
37 $description[] = $this->renderPHPConfig($configs, $issue);
38 }
39
40 $configs = $issue->getMySQLConfig();
41 if ($configs) {
42 $description[] = $this->renderMySQLConfig($configs);
43 }
44
45 $configs = $issue->getPhabricatorConfig();
46 if ($configs) {
47 $description[] = $this->renderPhabricatorConfig($configs);
48 }
49
50 $related_configs = $issue->getRelatedPhabricatorConfig();
51 if ($related_configs) {
52 $description[] = $this->renderPhabricatorConfig($related_configs,
53 $related = true);
54 }
55
56 $commands = $issue->getCommands();
57
58 $or = null;
59 if ($configs && $commands) {
60 $or = pht('Or:');
61 }
62
63 if ($commands) {
64 $run_these = pht('Run these %d command(s):', count($commands));
65 $description[] = phutil_tag(
66 'div',
67 array(
68 'class' => 'setup-issue-config',
69 ),
70 array(
71 phutil_tag('p', array(), $or),
72 phutil_tag('p', array(), $run_these),
73 phutil_tag('pre', array(), phutil_implode_html("\n", $commands)),
74 ));
75 }
76
77 $extensions = $issue->getPHPExtensions();
78 if ($extensions) {
79 $install_these = pht(
80 'Install these %d PHP extension(s):', count($extensions));
81
82 $install_info = pht(
83 'You can usually install a PHP extension using %s, %s, or %s. A '.
84 'common package name is %s. Try commands like these:',
85 phutil_tag('kbd', array(), 'apt-get'),
86 phutil_tag('kbd', array(), 'dnf'),
87 phutil_tag('kbd', array(), 'yum'),
88 hsprintf('<kbd>php-<em>%s</em></kbd>', pht('extname')));
89
90 // TODO: We should do a better job of detecting how to install extensions
91 // on the current system.
92 $install_commands = hsprintf(
93 "<samp>$</samp><kbd>sudo apt-get install php-<em>extname</em></kbd> ".
94 "# Debian / Ubuntu\n".
95 "<samp>$</samp><kbd>sudo dnf install php-<em>extname</em></kbd> ".
96 "# Red Hat / Derivatives\n".
97 "<samp>$</samp><kbd>sudo yum install php-<em>extname</em></kbd> ".
98 "# Older Red Hat versions");
99
100 $fallback_info = pht(
101 "If those commands don't work, try Google. The process of installing ".
102 "PHP extensions is not specific to this software, and any ".
103 "instructions you can find for installing them on your system should ".
104 "work. On Mac OS X, you might want to try Homebrew.");
105
106 $restart_info = pht(
107 'After installing new PHP extensions, <strong>restart everything '.
108 'for the changes to take effect</strong>. For help with restarting '.
109 'everything, see %s in the documentation.',
110 $this->renderRestartLink());
111
112 $description[] = phutil_tag(
113 'div',
114 array(
115 'class' => 'setup-issue-config',
116 ),
117 array(
118 phutil_tag('p', array(), $install_these),
119 phutil_tag('pre', array(), implode("\n", $extensions)),
120 phutil_tag('p', array(), $install_info),
121 phutil_tag('pre', array(), $install_commands),
122 phutil_tag('p', array(), $fallback_info),
123 phutil_tag('p', array(), $restart_info),
124 ));
125
126 }
127
128 $related_links = $issue->getLinks();
129 if ($related_links) {
130 $description[] = $this->renderRelatedLinks($related_links);
131 }
132
133 $actions = array();
134 if (!$issue->getIsFatal()) {
135 if ($issue->getIsIgnored()) {
136 $actions[] = javelin_tag(
137 'a',
138 array(
139 'href' => '/config/unignore/'.$issue->getIssueKey().'/',
140 'sigil' => 'workflow',
141 'class' => 'button button-grey',
142 ),
143 pht('Unignore Setup Issue'));
144 } else {
145 $actions[] = javelin_tag(
146 'a',
147 array(
148 'href' => '/config/ignore/'.$issue->getIssueKey().'/',
149 'sigil' => 'workflow',
150 'class' => 'button button-grey',
151 ),
152 pht('Ignore Setup Issue'));
153 }
154
155 $actions[] = javelin_tag(
156 'a',
157 array(
158 'href' => '/config/issue/'.$issue->getIssueKey().'/',
159 'class' => 'button button-grey',
160 ),
161 pht('Reload Page'));
162 }
163
164 if ($actions) {
165 $actions = phutil_tag(
166 'div',
167 array(
168 'class' => 'setup-issue-actions',
169 ),
170 $actions);
171 }
172
173 if ($issue->getIsIgnored()) {
174 $status = phutil_tag(
175 'div',
176 array(
177 'class' => 'setup-issue-status',
178 ),
179 pht(
180 'This issue is currently ignored, and does not show a global '.
181 'warning.'));
182 $next = null;
183 } else {
184 $status = null;
185 $next = phutil_tag(
186 'div',
187 array(
188 'class' => 'setup-issue-next',
189 ),
190 pht('To continue, resolve this problem and reload the page.'));
191 }
192
193 $name = phutil_tag(
194 'div',
195 array(
196 'class' => 'setup-issue-name',
197 ),
198 $issue->getName());
199
200 $head = phutil_tag(
201 'div',
202 array(
203 'class' => 'setup-issue-head',
204 ),
205 $name);
206
207 $body = phutil_tag(
208 'div',
209 array(
210 'class' => 'setup-issue-body',
211 ),
212 array(
213 $status,
214 $description,
215 ));
216
217 $tail = phutil_tag(
218 'div',
219 array(
220 'class' => 'setup-issue-tail',
221 ),
222 $actions);
223
224 $issue = phutil_tag(
225 'div',
226 array(
227 'class' => 'setup-issue',
228 ),
229 array(
230 $head,
231 $body,
232 $tail,
233 ));
234
235 $debug_info = phutil_tag(
236 'div',
237 array(
238 'class' => 'setup-issue-debug',
239 ),
240 pht('Host: %s', php_uname('n')));
241
242 return phutil_tag(
243 'div',
244 array(
245 'class' => 'setup-issue-shell',
246 ),
247 array(
248 $issue,
249 $next,
250 $debug_info,
251 ));
252 }
253
254 private function renderPhabricatorConfig(array $configs, $related = false) {
255 $issue = $this->getIssue();
256
257 $table_info = phutil_tag(
258 'p',
259 array(),
260 pht(
261 'The current configuration has these %d value(s):',
262 count($configs)));
263
264 $options = PhabricatorApplicationConfigOptions::loadAllOptions();
265 $hidden = array();
266 foreach ($options as $key => $option) {
267 if ($option->getHidden()) {
268 $hidden[$key] = true;
269 }
270 }
271
272 $table = null;
273 $dict = array();
274 foreach ($configs as $key) {
275 if (isset($hidden[$key])) {
276 $dict[$key] = null;
277 } else {
278 $dict[$key] = PhabricatorEnv::getUnrepairedEnvConfig($key);
279 }
280 }
281
282 $table = $this->renderValueTable($dict, $hidden);
283
284 if ($this->getIssue()->getIsFatal()) {
285 $update_info = phutil_tag(
286 'p',
287 array(),
288 pht(
289 'To update these %d value(s), run these command(s) from the command '.
290 'line:',
291 count($configs)));
292
293 $update = array();
294 foreach ($configs as $key) {
295 $update[] = hsprintf(
296 '<samp>%s $</samp><kbd>./bin/config set %s <em>value</em></kbd>',
297 PlatformSymbols::getPlatformServerPath(),
298 $key);
299 }
300 $update = phutil_tag('pre', array(), phutil_implode_html("\n", $update));
301 } else {
302 $update = array();
303 foreach ($configs as $config) {
304 if (idx($options, $config) && $options[$config]->getLocked()) {
305 $name = pht('View "%s"', $config);
306 } else {
307 $name = pht('Edit "%s"', $config);
308 }
309 $link = phutil_tag(
310 'a',
311 array(
312 'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(),
313 ),
314 $name);
315 $update[] = phutil_tag('li', array(), $link);
316 }
317 if ($update) {
318 $update = phutil_tag('ul', array(), $update);
319 if (!$related) {
320 $update_info = phutil_tag(
321 'p',
322 array(),
323 pht('You can update these %d value(s) here:', count($configs)));
324 } else {
325 $update_info = phutil_tag(
326 'p',
327 array(),
328 pht('These %d configuration value(s) are related:', count($configs)));
329 }
330 } else {
331 $update = null;
332 $update_info = null;
333 }
334 }
335
336 return phutil_tag(
337 'div',
338 array(
339 'class' => 'setup-issue-config',
340 ),
341 array(
342 $table_info,
343 $table,
344 $update_info,
345 $update,
346 ));
347 }
348
349 private function renderPHPConfig(array $configs, $issue) {
350 $table_info = phutil_tag(
351 'p',
352 array(),
353 pht(
354 'The current PHP configuration has these %d value(s):',
355 count($configs)));
356
357 $dict = array();
358 foreach ($configs as $key) {
359 $dict[$key] = $issue->getPHPConfigOriginalValue(
360 $key,
361 ini_get($key));
362 }
363
364 $table = $this->renderValueTable($dict);
365
366 ob_start();
367 phpinfo();
368 $phpinfo = ob_get_clean();
369
370
371 $rex = '@Loaded Configuration File\s*</td><td class="v">(.*?)</td>@i';
372 $matches = null;
373
374 $ini_loc = null;
375 if ($phpinfo && preg_match($rex, $phpinfo, $matches)) {
376 $ini_loc = trim($matches[1]);
377 }
378
379 $rex = '@Additional \.ini files parsed\s*</td><td class="v">(.*?)</td>@i';
380
381 $more_loc = array();
382 if ($phpinfo && preg_match($rex, $phpinfo, $matches)) {
383 $more_loc = trim($matches[1]);
384 if ($more_loc == '(none)') {
385 $more_loc = array();
386 } else {
387 $more_loc = preg_split('/\s*,\s*/', $more_loc);
388 }
389 }
390
391 $info = array();
392 if (!$ini_loc) {
393 $info[] = phutil_tag(
394 'p',
395 array(),
396 pht(
397 'To update these %d value(s), edit your PHP configuration file.',
398 count($configs)));
399 } else {
400 $info[] = phutil_tag(
401 'p',
402 array(),
403 pht(
404 'To update these %d value(s), edit your PHP configuration file, '.
405 'located here:',
406 count($configs)));
407 $info[] = phutil_tag(
408 'pre',
409 array(),
410 $ini_loc);
411 }
412
413 if ($more_loc) {
414 $info[] = phutil_tag(
415 'p',
416 array(),
417 pht(
418 'PHP also loaded these %s configuration file(s):',
419 phutil_count($more_loc)));
420 $info[] = phutil_tag(
421 'pre',
422 array(),
423 implode("\n", $more_loc));
424 }
425
426 $show_standard = false;
427 $show_opcache = false;
428
429 foreach ($configs as $key) {
430 if (preg_match('/^opcache\./', $key)) {
431 $show_opcache = true;
432 } else {
433 $show_standard = true;
434 }
435 }
436
437 if ($show_standard) {
438 $info[] = phutil_tag(
439 'p',
440 array(),
441 pht(
442 'You can find more information about PHP configuration values '.
443 'in the %s.',
444 phutil_tag(
445 'a',
446 array(
447 'href' => 'https://www.php.net/manual/ini.list.php',
448 'target' => '_blank',
449 ),
450 pht('PHP Documentation'))));
451 }
452
453 if ($show_opcache) {
454 $info[] = phutil_tag(
455 'p',
456 array(),
457 pht(
458 'You can find more information about configuring OPcache in '.
459 'the %s.',
460 phutil_tag(
461 'a',
462 array(
463 'href' => 'https://www.php.net/manual/opcache.configuration.php',
464 'target' => '_blank',
465 ),
466 pht('PHP OPcache Documentation'))));
467 }
468
469 $info[] = phutil_tag(
470 'p',
471 array(),
472 pht(
473 'After editing the PHP configuration, <strong>restart everything for '.
474 'the changes to take effect</strong>. For help with restarting '.
475 'everything, see %s in the documentation.',
476 $this->renderRestartLink()));
477
478 return phutil_tag(
479 'div',
480 array(
481 'class' => 'setup-issue-config',
482 ),
483 array(
484 $table_info,
485 $table,
486 $info,
487 ));
488 }
489
490 private function renderMySQLConfig(array $config) {
491 $values = array();
492 $issue = $this->getIssue();
493 $ref = $issue->getDatabaseRef();
494 if ($ref) {
495 foreach ($config as $key) {
496 $value = $ref->loadRawMySQLConfigValue($key);
497 if ($value === null) {
498 $value = phutil_tag(
499 'em',
500 array(),
501 pht('(Not Supported)'));
502 }
503 $values[$key] = $value;
504 }
505 }
506
507 $table = $this->renderValueTable($values);
508
509 $doc_href = PhabricatorEnv::getDoclink('User Guide: Amazon RDS');
510 $doc_link = phutil_tag(
511 'a',
512 array(
513 'href' => $doc_href,
514 'target' => '_blank',
515 ),
516 pht('User Guide: Amazon RDS'));
517
518 $info = array();
519 $info[] = phutil_tag(
520 'p',
521 array(),
522 pht(
523 'If you are using Amazon RDS, some of the instructions above may '.
524 'not apply to you. See %s for discussion of Amazon RDS.',
525 $doc_link));
526
527 $table_info = phutil_tag(
528 'p',
529 array(),
530 pht(
531 'The current MySQL configuration has these %d value(s):',
532 count($config)));
533
534 return phutil_tag(
535 'div',
536 array(
537 'class' => 'setup-issue-config',
538 ),
539 array(
540 $table_info,
541 $table,
542 $info,
543 ));
544 }
545
546 private function renderValueTable(array $dict, array $hidden = array()) {
547 $rows = array();
548 foreach ($dict as $key => $value) {
549 if (isset($hidden[$key])) {
550 $value = phutil_tag('em', array(), 'hidden');
551 } else {
552 $value = $this->renderValueForDisplay($value);
553 }
554
555 $cols = array(
556 phutil_tag('th', array(), $key),
557 phutil_tag('td', array(), $value),
558 );
559 $rows[] = phutil_tag('tr', array(), $cols);
560 }
561 return phutil_tag('table', array(), $rows);
562 }
563
564 private function renderValueForDisplay($value) {
565 if ($value === null) {
566 return phutil_tag('em', array(), 'null');
567 } else if ($value === false) {
568 return phutil_tag('em', array(), 'false');
569 } else if ($value === true) {
570 return phutil_tag('em', array(), 'true');
571 } else if ($value === '') {
572 return phutil_tag('em', array(), 'empty string');
573 } else if ($value instanceof PhutilSafeHTML) {
574 return $value;
575 } else {
576 return PhabricatorConfigJSON::prettyPrintJSON($value);
577 }
578 }
579
580 private function renderRelatedLinks(array $links) {
581 $link_info = phutil_tag(
582 'p',
583 array(),
584 pht(
585 '%d related link(s):',
586 count($links)));
587
588 $link_list = array();
589 foreach ($links as $link) {
590 $link_tag = phutil_tag(
591 'a',
592 array(
593 'target' => '_blank',
594 'href' => $link['href'],
595 ),
596 $link['name']);
597 $link_item = phutil_tag('li', array(), $link_tag);
598 $link_list[] = $link_item;
599 }
600 $link_list = phutil_tag('ul', array(), $link_list);
601
602 return phutil_tag(
603 'div',
604 array(
605 'class' => 'setup-issue-config',
606 ),
607 array(
608 $link_info,
609 $link_list,
610 ));
611 }
612
613 private function renderRestartLink() {
614 $doc_href = PhabricatorEnv::getDoclink('Restarting Phorge');
615 return phutil_tag(
616 'a',
617 array(
618 'href' => $doc_href,
619 'target' => '_blank',
620 ),
621 pht('Restarting'));
622 }
623
624}