@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
at recaptime-dev/main 271 lines 7.8 kB view raw
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}