@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 PonderQuestionViewController extends PonderController {
4
5 public function shouldAllowPublic() {
6 return true;
7 }
8
9 public function handleRequest(AphrontRequest $request) {
10 $viewer = $request->getViewer();
11 $id = $request->getURIData('id');
12
13 $question = id(new PonderQuestionQuery())
14 ->setViewer($viewer)
15 ->withIDs(array($id))
16 ->needAnswers(true)
17 ->needProjectPHIDs(true)
18 ->executeOne();
19 if (!$question) {
20 return new Aphront404Response();
21 }
22
23 $answers = $this->buildAnswers($question);
24
25 $answer_add_panel = id(new PonderAddAnswerView())
26 ->setQuestion($question)
27 ->setUser($viewer)
28 ->setActionURI('/ponder/answer/add/');
29
30 $header = new PHUIHeaderView();
31 $header->setHeader($question->getTitle());
32 $header->setUser($viewer);
33 $header->setPolicyObject($question);
34 $header->setHeaderIcon('fa-university');
35
36 if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) {
37 $header->setStatus('fa-square-o', 'bluegrey', pht('Open'));
38 } else {
39 $text = PonderQuestionStatus::getQuestionStatusFullName(
40 $question->getStatus());
41 $icon = PonderQuestionStatus::getQuestionStatusIcon(
42 $question->getStatus());
43 $header->setStatus($icon, 'dark', $text);
44 }
45
46 $curtain = $this->buildCurtain($question);
47 $details = $this->buildPropertySectionView($question);
48
49 $can_edit = PhabricatorPolicyFilter::hasCapability(
50 $viewer,
51 $question,
52 PhabricatorPolicyCapability::CAN_EDIT);
53
54 $content_id = celerity_generate_unique_node_id();
55 $timeline = $this->buildTransactionTimeline(
56 $question,
57 id(new PonderQuestionTransactionQuery())
58 ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)));
59 $xactions = $timeline->getTransactions();
60
61 $add_comment = id(new PhabricatorApplicationTransactionCommentView())
62 ->setUser($viewer)
63 ->setObject($question)
64 ->setShowPreview(false)
65 ->setAction($this->getApplicationURI("/question/comment/{$id}/"))
66 ->setSubmitButtonName(pht('Comment'));
67
68 $add_comment = phutil_tag_div(
69 'ponder-question-add-comment-view', $add_comment);
70
71 $comment_view = phutil_tag(
72 'div',
73 array(
74 'id' => $content_id,
75 'style' => 'display: none;',
76 ),
77 array(
78 $timeline,
79 $add_comment,
80 ));
81
82 $footer = id(new PonderFooterView())
83 ->setContentID($content_id)
84 ->setCount(count($xactions));
85
86 $crumbs = $this->buildApplicationCrumbs();
87 $crumbs->addTextCrumb('Q'.$id, '/Q'.$id);
88 $crumbs->setBorder(true);
89
90 $subheader = $this->buildSubheaderView($question);
91
92 $answer_wiki = null;
93 if ($question->getAnswerWiki()) {
94 $wiki = new PHUIRemarkupView($viewer, $question->getAnswerWiki());
95 $answer_wiki = id(new PHUIObjectBoxView())
96 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
97 ->setHeaderText(pht('Answer Summary'))
98 ->appendChild($wiki)
99 ->addClass('ponder-answer-wiki');
100 }
101
102 require_celerity_resource('ponder-view-css');
103
104 $ponder_content = phutil_tag(
105 'div',
106 array(
107 'class' => 'ponder-question-content',
108 ),
109 array(
110 $answer_wiki,
111 $footer,
112 $comment_view,
113 $answers,
114 $answer_add_panel,
115 ));
116
117 $ponder_view = id(new PHUITwoColumnView())
118 ->setHeader($header)
119 ->setSubheader($subheader)
120 ->setCurtain($curtain)
121 ->setMainColumn($ponder_content)
122 ->addPropertySection(pht('Details'), $details)
123 ->addClass('ponder-question-view');
124
125 $page_objects = array_merge(
126 array($question->getPHID()),
127 mpull($question->getAnswers(), 'getPHID'));
128
129 return $this->newPage()
130 ->setTitle('Q'.$question->getID().' '.$question->getTitle())
131 ->setCrumbs($crumbs)
132 ->setPageObjectPHIDs($page_objects)
133 ->appendChild($ponder_view);
134 }
135
136 private function buildCurtain(PonderQuestion $question) {
137 $viewer = $this->getViewer();
138 $id = $question->getID();
139
140 $can_edit = PhabricatorPolicyFilter::hasCapability(
141 $viewer,
142 $question,
143 PhabricatorPolicyCapability::CAN_EDIT);
144
145 $curtain = $this->newCurtainView($question);
146
147 $name = pht('Change Question Status');
148 $icon = 'fa-square-o';
149
150 $curtain->addAction(
151 id(new PhabricatorActionView())
152 ->setIcon('fa-pencil')
153 ->setName(pht('Edit Question'))
154 ->setHref($this->getApplicationURI("/question/edit/{$id}/"))
155 ->setDisabled(!$can_edit)
156 ->setWorkflow(!$can_edit));
157
158 $curtain->addAction(
159 id(new PhabricatorActionView())
160 ->setName($name)
161 ->setIcon($icon)
162 ->setWorkflow(true)
163 ->setDisabled(!$can_edit)
164 ->setHref($this->getApplicationURI("/question/status/{$id}/")));
165
166 $curtain->addAction(
167 id(new PhabricatorActionView())
168 ->setIcon('fa-list')
169 ->setName(pht('View History'))
170 ->setHref($this->getApplicationURI("/question/history/{$id}/")));
171
172 return $curtain;
173 }
174
175 private function buildSubheaderView(
176 PonderQuestion $question) {
177 $viewer = $this->getViewer();
178
179 $asker = $viewer->renderHandle($question->getAuthorPHID())->render();
180 $date = phabricator_datetime($question->getDateCreated(), $viewer);
181 $asker = phutil_tag('strong', array(), $asker);
182
183 $author = id(new PhabricatorPeopleQuery())
184 ->setViewer($viewer)
185 ->withPHIDs(array($question->getAuthorPHID()))
186 ->needProfileImage(true)
187 ->executeOne();
188
189 $image_uri = $author->getProfileImageURI();
190 $image_href = '/p/'.$author->getUsername();
191
192 $content = pht('Asked by %s on %s.', $asker, $date);
193
194 return id(new PHUIHeadThingView())
195 ->setImage($image_uri)
196 ->setImageHref($image_href)
197 ->setContent($content);
198 }
199
200 private function buildPropertySectionView(
201 PonderQuestion $question) {
202 $viewer = $this->getViewer();
203
204 $question_details = PhabricatorMarkupEngine::renderOneObject(
205 $question,
206 $question->getMarkupField(),
207 $viewer);
208
209 if (!$question_details) {
210 $question_details = phutil_tag(
211 'em',
212 array(),
213 pht('No further details for this question.'));
214 }
215
216 $question_details = phutil_tag_div(
217 'phabricator-remarkup ml', $question_details);
218
219 return $question_details;
220 }
221
222 /**
223 * This is fairly non-standard; building N timelines at once (N = number of
224 * answers) is tricky business.
225 *
226 * TODO - re-factor this to ajax in one answer panel at a time in a more
227 * standard fashion. This is necessary to scale this application.
228 */
229 private function buildAnswers(PonderQuestion $question) {
230 $viewer = $this->getViewer();
231 $answers = $question->getAnswers();
232
233 if ($answers) {
234 $author_phids = mpull($answers, 'getAuthorPHID');
235 $handles = $this->loadViewerHandles($author_phids);
236
237 $view = array();
238 foreach ($answers as $answer) {
239 $id = $answer->getID();
240 $handle = $handles[$answer->getAuthorPHID()];
241
242 $timeline = $this->buildTransactionTimeline(
243 $answer,
244 id(new PonderAnswerTransactionQuery())
245 ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)));
246 $xactions = $timeline->getTransactions();
247
248 $view[] = id(new PonderAnswerView())
249 ->setUser($viewer)
250 ->setAnswer($answer)
251 ->setTransactions($xactions)
252 ->setTimeline($timeline)
253 ->setHandle($handle);
254
255 }
256
257 $header = id(new PHUIHeaderView())
258 ->setHeader('Answers');
259
260
261 return id(new PHUIBoxView())
262 ->addClass('ponder-answer-section')
263 ->appendChild($header)
264 ->appendChild($view);
265 }
266
267 return null;
268
269 }
270
271}