@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 DarkConsoleCore extends Phobject {
4
5 private $plugins = array();
6 const STORAGE_VERSION = 1;
7
8 public function __construct() {
9 $this->plugins = id(new PhutilClassMapQuery())
10 ->setAncestorClass(DarkConsolePlugin::class)
11 ->execute();
12
13 foreach ($this->plugins as $plugin) {
14 $plugin->setConsoleCore($this);
15 $plugin->didStartup();
16 }
17 }
18
19 public function getPlugins() {
20 return $this->plugins;
21 }
22
23 public function getKey(AphrontRequest $request) {
24 $plugins = $this->getPlugins();
25
26 foreach ($plugins as $plugin) {
27 $plugin->setRequest($request);
28 $plugin->willShutdown();
29 }
30
31 foreach ($plugins as $plugin) {
32 $plugin->didShutdown();
33 }
34
35 foreach ($plugins as $plugin) {
36 $plugin->setData($plugin->generateData());
37 }
38
39 $plugins = msort($plugins, 'getOrderKey');
40
41 $key = Filesystem::readRandomCharacters(24);
42
43 $tabs = array();
44 $data = array();
45 foreach ($plugins as $plugin) {
46 $class = get_class($plugin);
47 $tabs[] = array(
48 'class' => $class,
49 'name' => $plugin->getName(),
50 'color' => $plugin->getColor(),
51 );
52 $data[$class] = $this->sanitizeForJSON($plugin->getData());
53 }
54
55 $storage = array(
56 'vers' => self::STORAGE_VERSION,
57 'tabs' => $tabs,
58 'data' => $data,
59 'user' => $request->getUser()
60 ? $request->getUser()->getPHID()
61 : null,
62 );
63
64 $cache = new PhabricatorKeyValueDatabaseCache();
65 $cache = new PhutilKeyValueCacheProfiler($cache);
66 $cache->setProfiler(PhutilServiceProfiler::getInstance());
67
68 // This encoding may fail if there are, e.g., database queries which
69 // include binary data. It would be a little cleaner to try to strip these,
70 // but just do something non-broken here if we end up with unrepresentable
71 // data.
72 $json = @json_encode($storage);
73 if (!$json) {
74 $json = '{}';
75 }
76
77 $cache->setKeys(
78 array(
79 'darkconsole:'.$key => $json,
80 ),
81 $ttl = (60 * 60 * 6));
82
83 return $key;
84 }
85
86 public function getColor() {
87 foreach ($this->getPlugins() as $plugin) {
88 if ($plugin->getColor()) {
89 return $plugin->getColor();
90 }
91 }
92 }
93
94 public function render(AphrontRequest $request) {
95 $user = $request->getUser();
96 $visible = $user->getUserSetting(
97 PhabricatorDarkConsoleVisibleSetting::SETTINGKEY);
98
99 return javelin_tag(
100 'div',
101 array(
102 'id' => 'darkconsole',
103 'class' => 'dark-console',
104 'style' => $visible ? '' : 'display: none;',
105 'data-console-key' => $this->getKey($request),
106 'data-console-color' => $this->getColor(),
107 ),
108 '');
109 }
110
111 /**
112 * Sometimes, tab data includes binary information (like INSERT queries which
113 * write file data into the database). To successfully JSON encode it, we
114 * need to convert it to UTF-8.
115 */
116 private function sanitizeForJSON($data) {
117 if (is_object($data)) {
118 return '<object:'.get_class($data).'>';
119 } else if (is_array($data)) {
120 foreach ($data as $key => $value) {
121 $data[$key] = $this->sanitizeForJSON($value);
122 }
123 return $data;
124 } else if (is_resource($data)) {
125 return '<resource>';
126 } else {
127 // This is very probably not a string in strict sense
128 $data = phutil_string_cast($data);
129
130 // Truncate huge strings. Since the data doesn't really matter much,
131 // just truncate bytes to avoid PhutilUTF8StringTruncator overhead.
132 $length = strlen($data);
133 $max = 4096;
134 if ($length > $max) {
135 $data = substr($data, 0, $max).'...<'.$length.' bytes>...';
136 }
137 return phutil_utf8ize($data);
138 }
139 }
140
141}