@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 PhabricatorChangesetViewStateEngine
4 extends Phobject {
5
6 private $viewer;
7 private $objectPHID;
8 private $changeset;
9 private $storage;
10
11 public function setViewer(PhabricatorUser $viewer) {
12 $this->viewer = $viewer;
13 return $this;
14 }
15
16 public function getViewer() {
17 return $this->viewer;
18 }
19
20 public function setObjectPHID($object_phid) {
21 $this->objectPHID = $object_phid;
22 return $this;
23 }
24
25 public function getObjectPHID() {
26 return $this->objectPHID;
27 }
28
29 public function setChangeset(DifferentialChangeset $changeset) {
30 $this->changeset = $changeset;
31 return $this;
32 }
33
34 public function getChangeset() {
35 return $this->changeset;
36 }
37
38 public function newViewStateFromRequest(AphrontRequest $request) {
39 $storage = $this->loadViewStateStorage();
40
41 $this->setStorage($storage);
42
43 $highlight = $request->getStr('highlight');
44 if ($highlight !== null) {
45 $this->setChangesetProperty('highlight', $highlight);
46 }
47
48 $encoding = $request->getStr('encoding');
49 if ($encoding !== null) {
50 $this->setChangesetProperty('encoding', $encoding);
51 }
52
53 $engine = $request->getStr('engine');
54 if ($engine !== null) {
55 $this->setChangesetProperty('engine', $engine);
56 }
57
58 $renderer = $request->getStr('renderer');
59 if ($renderer !== null) {
60 $this->setChangesetProperty('renderer', $renderer);
61 }
62
63 $hidden = $request->getStr('hidden');
64 if ($hidden !== null) {
65 $this->setChangesetProperty('hidden', (int)$hidden);
66 }
67
68 $this->saveViewStateStorage();
69
70 $state = new PhabricatorChangesetViewState();
71
72 $highlight_language = $this->getChangesetProperty('highlight');
73 $state->setHighlightLanguage($highlight_language);
74
75 $encoding = $this->getChangesetProperty('encoding');
76 $state->setCharacterEncoding($encoding);
77
78 $document_engine = $this->getChangesetProperty('engine');
79 $state->setDocumentEngineKey($document_engine);
80
81 $renderer = $this->getChangesetProperty('renderer');
82 $state->setRendererKey($renderer);
83
84 $this->updateHiddenState($state);
85
86 // This is the client-selected default renderer based on viewport
87 // dimensions.
88
89 $device_key = $request->getStr('device');
90 if ($device_key !== null) {
91 $state->setDefaultDeviceRendererKey($device_key);
92 }
93
94 $discard_response = $request->getStr('discard');
95 if ($discard_response !== null) {
96 $state->setDiscardResponse(true);
97 }
98
99 return $state;
100 }
101
102 private function setStorage(DifferentialViewState $storage) {
103 $this->storage = $storage;
104 return $this;
105 }
106
107 private function getStorage() {
108 return $this->storage;
109 }
110
111 private function setChangesetProperty(
112 $key,
113 $value) {
114
115 $storage = $this->getStorage();
116 $changeset = $this->getChangeset();
117
118 $storage->setChangesetProperty($changeset, $key, $value);
119 }
120
121 private function getChangesetProperty(
122 $key,
123 $default = null) {
124
125 $storage = $this->getStorage();
126 $changeset = $this->getChangeset();
127
128 return $storage->getChangesetProperty($changeset, $key, $default);
129 }
130
131 private function loadViewStateStorage() {
132 $viewer = $this->getViewer();
133
134 $object_phid = $this->getObjectPHID();
135 $viewer_phid = $viewer->getPHID();
136
137 $storage = null;
138
139 if ($viewer_phid !== null) {
140 $storage = id(new DifferentialViewStateQuery())
141 ->setViewer($viewer)
142 ->withViewerPHIDs(array($viewer_phid))
143 ->withObjectPHIDs(array($object_phid))
144 ->executeOne();
145 }
146
147 if ($storage === null) {
148 $storage = id(new DifferentialViewState())
149 ->setObjectPHID($object_phid);
150
151 if ($viewer_phid !== null) {
152 $storage->setViewerPHID($viewer_phid);
153 } else {
154 $storage->makeEphemeral();
155 }
156 }
157
158 return $storage;
159 }
160
161 private function saveViewStateStorage() {
162 if (PhabricatorEnv::isReadOnly()) {
163 return;
164 }
165
166 $storage = $this->getStorage();
167
168 $viewer_phid = $storage->getViewerPHID();
169 if ($viewer_phid === null) {
170 return;
171 }
172
173 if (!$storage->getHasModifications()) {
174 return;
175 }
176
177 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
178
179 try {
180 $storage->save();
181 } catch (AphrontDuplicateKeyQueryException $ex) {
182 // We may race another process to save view state. For now, just discard
183 // our state if we do.
184 }
185
186 unset($unguarded);
187 }
188
189 private function updateHiddenState(PhabricatorChangesetViewState $state) {
190 $is_hidden = false;
191 $was_modified = false;
192
193 $storage = $this->getStorage();
194 $changeset = $this->getChangeset();
195
196 $entries = $storage->getChangesetPropertyEntries($changeset, 'hidden');
197 $entries = isort($entries, 'epoch');
198
199 if ($entries) {
200 $other_spec = last($entries);
201
202 $this_version = (int)$changeset->getDiffID();
203 $other_version = (int)idx($other_spec, 'diffID');
204 $other_value = (bool)idx($other_spec, 'value', false);
205 $other_id = (int)idx($other_spec, 'changesetID');
206
207 if ($other_value === false) {
208 $is_hidden = false;
209 } else if ($other_version >= $this_version) {
210 $is_hidden = $other_value;
211 } else {
212 $viewer = $this->getViewer();
213
214 if ($other_id) {
215 $other_changeset = id(new DifferentialChangesetQuery())
216 ->setViewer($viewer)
217 ->withIDs(array($other_id))
218 ->executeOne();
219 } else {
220 $other_changeset = null;
221 }
222
223 $is_modified = false;
224 if ($other_changeset) {
225 if (!$changeset->hasSameEffectAs($other_changeset)) {
226 $is_modified = true;
227 }
228 }
229
230 $is_hidden = false;
231 $was_modified = true;
232 }
233 }
234
235 $state->setHidden($is_hidden);
236 $state->setModifiedSinceHide($was_modified);
237 }
238
239}