@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make PonderQuestionEditor use ApplicationTransactions

Summary:
Ref T3373. Make PonderQuestions editable and use transactions.

This temporarily disables some stuff:

- email;
- feed;
- comments;
- voting.

I'll restore those in followups and wait to land this until they're at least mostly back online.

The transactions themselves also need more string/color/icon work.

Test Plan: Created and edited questions. Viewed transactions.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T3373

Differential Revision: https://secure.phabricator.com/D6601

+311 -182
+5 -3
src/__phutil_library_map__.php
··· 1895 1895 'PonderPHIDTypeQuestion' => 'applications/ponder/phid/PonderPHIDTypeQuestion.php', 1896 1896 'PonderPostBodyView' => 'applications/ponder/view/PonderPostBodyView.php', 1897 1897 'PonderQuestion' => 'applications/ponder/storage/PonderQuestion.php', 1898 - 'PonderQuestionAskController' => 'applications/ponder/controller/PonderQuestionAskController.php', 1899 1898 'PonderQuestionDetailView' => 'applications/ponder/view/PonderQuestionDetailView.php', 1899 + 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', 1900 1900 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', 1901 1901 'PonderQuestionListController' => 'applications/ponder/controller/PonderQuestionListController.php', 1902 1902 'PonderQuestionMailReceiver' => 'applications/ponder/mail/PonderQuestionMailReceiver.php', ··· 1907 1907 'PonderQuestionStatusController' => 'applications/ponder/controller/PonderQuestionStatusController.php', 1908 1908 'PonderQuestionTransaction' => 'applications/ponder/storage/PonderQuestionTransaction.php', 1909 1909 'PonderQuestionTransactionComment' => 'applications/ponder/storage/PonderQuestionTransactionComment.php', 1910 + 'PonderQuestionTransactionQuery' => 'applications/ponder/query/PonderQuestionTransactionQuery.php', 1910 1911 'PonderQuestionViewController' => 'applications/ponder/controller/PonderQuestionViewController.php', 1911 1912 'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php', 1912 1913 'PonderReplyHandler' => 'applications/ponder/mail/PonderReplyHandler.php', ··· 4025 4026 4 => 'PhabricatorPolicyInterface', 4026 4027 5 => 'PhabricatorTokenReceiverInterface', 4027 4028 ), 4028 - 'PonderQuestionAskController' => 'PonderController', 4029 4029 'PonderQuestionDetailView' => 'AphrontView', 4030 - 'PonderQuestionEditor' => 'PhabricatorEditor', 4030 + 'PonderQuestionEditController' => 'PonderController', 4031 + 'PonderQuestionEditor' => 'PhabricatorApplicationTransactionEditor', 4031 4032 'PonderQuestionListController' => 4032 4033 array( 4033 4034 0 => 'PonderController', ··· 4041 4042 'PonderQuestionStatusController' => 'PonderController', 4042 4043 'PonderQuestionTransaction' => 'PhabricatorApplicationTransaction', 4043 4044 'PonderQuestionTransactionComment' => 'PhabricatorApplicationTransactionComment', 4045 + 'PonderQuestionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4044 4046 'PonderQuestionViewController' => 'PonderController', 4045 4047 'PonderRemarkupRule' => 'PhabricatorRemarkupRuleObject', 4046 4048 'PonderReplyHandler' => 'PhabricatorMailReplyHandler',
+1 -1
src/applications/ponder/application/PhabricatorApplicationPonder.php
··· 52 52 '(?:query/(?P<queryKey>[^/]+)/)?' => 'PonderQuestionListController', 53 53 'answer/add/' => 'PonderAnswerSaveController', 54 54 'answer/preview/' => 'PonderAnswerPreviewController', 55 - 'question/ask/' => 'PonderQuestionAskController', 55 + 'question/edit/(?:(?P<id>\d+)/)?' => 'PonderQuestionEditController', 56 56 'question/preview/' => 'PonderQuestionPreviewController', 57 57 'question/(?P<status>open|close)/(?P<id>[1-9]\d*)/' => 58 58 'PonderQuestionStatusController',
+1 -1
src/applications/ponder/controller/PonderController.php
··· 23 23 ->addAction( 24 24 id(new PHUIListItemView()) 25 25 ->setName(pht('Create Question')) 26 - ->setHref('/ponder/question/ask/') 26 + ->setHref('/ponder/question/edit/') 27 27 ->setIcon('create')); 28 28 29 29 return $crumbs;
-113
src/applications/ponder/controller/PonderQuestionAskController.php
··· 1 - <?php 2 - 3 - final class PonderQuestionAskController extends PonderController { 4 - 5 - public function processRequest() { 6 - $request = $this->getRequest(); 7 - $user = $request->getUser(); 8 - 9 - $question = id(new PonderQuestion()) 10 - ->setAuthorPHID($user->getPHID()) 11 - ->setVoteCount(0) 12 - ->setAnswerCount(0) 13 - ->setHeat(0.0); 14 - 15 - $errors = array(); 16 - $e_title = true; 17 - if ($request->isFormPost()) { 18 - $question->setTitle($request->getStr('title')); 19 - $question->setContent($request->getStr('content')); 20 - $question->setStatus(PonderQuestionStatus::STATUS_OPEN); 21 - 22 - $len = phutil_utf8_strlen($question->getTitle()); 23 - if ($len < 1) { 24 - $errors[] = pht('Title must not be empty.'); 25 - $e_title = pht('Required'); 26 - } else if ($len > 255) { 27 - $errors[] = pht('Title is too long.'); 28 - $e_title = pht('Too Long'); 29 - } 30 - 31 - if (!$errors) { 32 - $content_source = PhabricatorContentSource::newForSource( 33 - PhabricatorContentSource::SOURCE_WEB, 34 - array( 35 - 'ip' => $request->getRemoteAddr(), 36 - )); 37 - $question->setContentSource($content_source); 38 - 39 - id(new PonderQuestionEditor()) 40 - ->setQuestion($question) 41 - ->setActor($user) 42 - ->save(); 43 - 44 - return id(new AphrontRedirectResponse()) 45 - ->setURI('/Q'.$question->getID()); 46 - } 47 - } 48 - 49 - $error_view = null; 50 - if ($errors) { 51 - $error_view = id(new AphrontErrorView()) 52 - ->setTitle(pht('Form Errors')) 53 - ->setErrors($errors); 54 - } 55 - 56 - $form = id(new AphrontFormView()) 57 - ->setUser($user) 58 - ->setFlexible(true) 59 - ->appendChild( 60 - id(new AphrontFormTextControl()) 61 - ->setLabel(pht('Question')) 62 - ->setName('title') 63 - ->setValue($question->getTitle()) 64 - ->setError($e_title)) 65 - ->appendChild( 66 - id(new PhabricatorRemarkupControl()) 67 - ->setName('content') 68 - ->setID('content') 69 - ->setValue($question->getContent()) 70 - ->setLabel(pht('Description')) 71 - ->setUser($user)) 72 - ->appendChild( 73 - id(new AphrontFormSubmitControl()) 74 - ->addCancelButton($this->getApplicationURI()) 75 - ->setValue(pht('Ask Away!'))); 76 - 77 - $preview = hsprintf( 78 - '<div class="aphront-panel-flush">'. 79 - '<div id="question-preview">'. 80 - '<span class="aphront-panel-preview-loading-text">%s</span>'. 81 - '</div>'. 82 - '</div>', 83 - pht('Loading question preview...')); 84 - 85 - Javelin::initBehavior( 86 - 'ponder-feedback-preview', 87 - array( 88 - 'uri' => '/ponder/question/preview/', 89 - 'content' => 'content', 90 - 'preview' => 'question-preview', 91 - 'question_id' => null 92 - )); 93 - 94 - $crumbs = $this->buildApplicationCrumbs(); 95 - $crumbs->addCrumb( 96 - id(new PhabricatorCrumbView()) 97 - ->setName(pht('Ask Question'))); 98 - 99 - return $this->buildApplicationPage( 100 - array( 101 - $crumbs, 102 - $error_view, 103 - $form, 104 - $preview, 105 - ), 106 - array( 107 - 'device' => true, 108 - 'dust' => true, 109 - 'title' => pht('Ask a Question'), 110 - )); 111 - } 112 - 113 - }
+155
src/applications/ponder/controller/PonderQuestionEditController.php
··· 1 + <?php 2 + 3 + final class PonderQuestionEditController extends PonderController { 4 + 5 + private $id; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->id = idx($data, 'id'); 9 + } 10 + 11 + public function processRequest() { 12 + $request = $this->getRequest(); 13 + $user = $request->getUser(); 14 + 15 + if ($this->id) { 16 + $question = id(new PonderQuestionQuery()) 17 + ->setViewer($user) 18 + ->withIDs(array($this->id)) 19 + ->requireCapabilities( 20 + array( 21 + PhabricatorPolicyCapability::CAN_VIEW, 22 + PhabricatorPolicyCapability::CAN_EDIT, 23 + )) 24 + ->executeOne(); 25 + if (!$question) { 26 + return new Aphront404Response(); 27 + } 28 + } else { 29 + $question = id(new PonderQuestion()) 30 + ->setStatus(PonderQuestionStatus::STATUS_OPEN) 31 + ->setAuthorPHID($user->getPHID()) 32 + ->setVoteCount(0) 33 + ->setAnswerCount(0) 34 + ->setHeat(0.0); 35 + } 36 + 37 + $v_title = $question->getTitle(); 38 + $v_content = $question->getContent(); 39 + 40 + $errors = array(); 41 + $e_title = true; 42 + if ($request->isFormPost()) { 43 + $v_title = $request->getStr('title'); 44 + $v_content = $request->getStr('content'); 45 + 46 + $len = phutil_utf8_strlen($v_title); 47 + if ($len < 1) { 48 + $errors[] = pht('Title must not be empty.'); 49 + $e_title = pht('Required'); 50 + } else if ($len > 255) { 51 + $errors[] = pht('Title is too long.'); 52 + $e_title = pht('Too Long'); 53 + } 54 + 55 + if (!$errors) { 56 + $template = id(new PonderQuestionTransaction()); 57 + $xactions = array(); 58 + 59 + $xactions[] = id(clone $template) 60 + ->setTransactionType(PonderQuestionTransaction::TYPE_TITLE) 61 + ->setNewValue($v_title); 62 + 63 + $xactions[] = id(clone $template) 64 + ->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT) 65 + ->setNewValue($v_content); 66 + 67 + $editor = id(new PonderQuestionEditor()) 68 + ->setActor($user) 69 + ->setContentSourceFromRequest($request) 70 + ->setContinueOnNoEffect(true); 71 + 72 + $editor->applyTransactions($question, $xactions); 73 + 74 + return id(new AphrontRedirectResponse()) 75 + ->setURI('/Q'.$question->getID()); 76 + } 77 + } 78 + 79 + $error_view = null; 80 + if ($errors) { 81 + $error_view = id(new AphrontErrorView()) 82 + ->setTitle(pht('Form Errors')) 83 + ->setErrors($errors); 84 + } 85 + 86 + $form = id(new AphrontFormView()) 87 + ->setUser($user) 88 + ->setFlexible(true) 89 + ->appendChild( 90 + id(new AphrontFormTextControl()) 91 + ->setLabel(pht('Question')) 92 + ->setName('title') 93 + ->setValue($v_title) 94 + ->setError($e_title)) 95 + ->appendChild( 96 + id(new PhabricatorRemarkupControl()) 97 + ->setName('content') 98 + ->setID('content') 99 + ->setValue($v_content) 100 + ->setLabel(pht('Description')) 101 + ->setUser($user)) 102 + ->appendChild( 103 + id(new AphrontFormSubmitControl()) 104 + ->addCancelButton($this->getApplicationURI()) 105 + ->setValue(pht('Ask Away!'))); 106 + 107 + $preview = hsprintf( 108 + '<div class="aphront-panel-flush">'. 109 + '<div id="question-preview">'. 110 + '<span class="aphront-panel-preview-loading-text">%s</span>'. 111 + '</div>'. 112 + '</div>', 113 + pht('Loading question preview...')); 114 + 115 + Javelin::initBehavior( 116 + 'ponder-feedback-preview', 117 + array( 118 + 'uri' => '/ponder/question/preview/', 119 + 'content' => 'content', 120 + 'preview' => 'question-preview', 121 + 'question_id' => null 122 + )); 123 + 124 + $crumbs = $this->buildApplicationCrumbs(); 125 + 126 + $id = $question->getID(); 127 + if ($id) { 128 + $crumbs->addCrumb( 129 + id(new PhabricatorCrumbView()) 130 + ->setName("Q{$id}") 131 + ->setHref("/Q{$id}")); 132 + $crumbs->addCrumb( 133 + id(new PhabricatorCrumbView()) 134 + ->setName(pht('Edit'))); 135 + } else { 136 + $crumbs->addCrumb( 137 + id(new PhabricatorCrumbView()) 138 + ->setName(pht('Ask Question'))); 139 + } 140 + 141 + return $this->buildApplicationPage( 142 + array( 143 + $crumbs, 144 + $error_view, 145 + $form, 146 + $preview, 147 + ), 148 + array( 149 + 'title' => pht('Ask a Question'), 150 + 'device' => true, 151 + 'dust' => true, 152 + )); 153 + } 154 + 155 + }
+77 -28
src/applications/ponder/controller/PonderQuestionViewController.php
··· 41 41 $this->loadHandles($object_phids); 42 42 $handles = $this->getLoadedHandles(); 43 43 44 - $detail_panel = new PonderQuestionDetailView(); 45 - $detail_panel 46 - ->setQuestion($question) 47 - ->setUser($user) 48 - ->setHandles($handles); 44 + $question_xactions = $this->buildQuestionTransactions($question); 49 45 50 46 $responses_panel = new PonderAnswerListView(); 51 47 $responses_panel ··· 79 75 $header, 80 76 $actions, 81 77 $properties, 82 - $detail_panel, 78 + $question_xactions, 83 79 $responses_panel, 84 80 $answer_add_panel 85 81 ), ··· 92 88 93 89 private function buildActionListView(PonderQuestion $question) { 94 90 $request = $this->getRequest(); 95 - $user = $request->getUser(); 91 + $viewer = $request->getUser(); 92 + 93 + $id = $question->getID(); 94 + 95 + $can_edit = PhabricatorPolicyFilter::hasCapability( 96 + $viewer, 97 + $question, 98 + PhabricatorPolicyCapability::CAN_EDIT); 96 99 97 - $action_list = id(new PhabricatorActionListView()) 98 - ->setUser($user) 100 + $view = id(new PhabricatorActionListView()) 101 + ->setUser($request->getUser()) 99 102 ->setObject($question) 100 103 ->setObjectURI($request->getRequestURI()); 101 104 102 - if ($user->getPhid() === $question->getAuthorPhid()) { 103 - if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { 104 - $name = pht("Close Question"); 105 - $icon = "delete"; 106 - $href = "close"; 107 - } else { 108 - $name = pht("Open Question"); 109 - $icon = "enable"; 110 - $href = "open"; 111 - } 112 - $action_list->addAction( 113 - id(new PhabricatorActionView()) 114 - ->setName($name) 115 - ->setIcon($icon) 116 - ->setRenderAsForm(true) 117 - ->setHref( 118 - $this->getApplicationURI( 119 - "/question/{$href}/{$this->questionID}/"))); 105 + $view->addAction( 106 + id(new PhabricatorActionView()) 107 + ->setIcon('edit') 108 + ->setName(pht('Edit Question')) 109 + ->setHref($this->getApplicationURI("/question/edit/{$id}/")) 110 + ->setDisabled(!$can_edit) 111 + ->setWorkflow(!$can_edit)); 112 + 113 + if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { 114 + $name = pht("Close Question"); 115 + $icon = "delete"; 116 + $href = "close"; 117 + } else { 118 + $name = pht("Reopen Question"); 119 + $icon = "enable"; 120 + $href = "open"; 120 121 } 121 122 122 - return $action_list; 123 + $view->addAction( 124 + id(new PhabricatorActionView()) 125 + ->setName($name) 126 + ->setIcon($icon) 127 + ->setRenderAsForm($can_edit) 128 + ->setWorkflow(!$can_edit) 129 + ->setDisabled(!$can_edit) 130 + ->setHref($this->getApplicationURI("/question/{$href}/{$id}/"))); 131 + 132 + return $view; 123 133 } 124 134 125 135 private function buildPropertyListView( ··· 142 152 pht('Created'), 143 153 phabricator_datetime($question->getDateCreated(), $viewer)); 144 154 155 + $view->invokeWillRenderEvent(); 156 + 157 + $view->addTextContent( 158 + PhabricatorMarkupEngine::renderOneObject( 159 + $question, 160 + $question->getMarkupField(), 161 + $viewer)); 162 + 163 + 145 164 return $view; 146 165 } 166 + 167 + private function buildQuestionTransactions(PonderQuestion $question) { 168 + $viewer = $this->getRequest()->getUser(); 169 + 170 + $xactions = id(new PonderQuestionTransactionQuery()) 171 + ->setViewer($viewer) 172 + ->withObjectPHIDs(array($question->getPHID())) 173 + ->execute(); 174 + 175 + $engine = id(new PhabricatorMarkupEngine()) 176 + ->setViewer($viewer); 177 + foreach ($xactions as $xaction) { 178 + if ($xaction->getComment()) { 179 + $engine->addObject( 180 + $xaction->getComment(), 181 + PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); 182 + } 183 + } 184 + $engine->process(); 185 + 186 + $timeline = id(new PhabricatorApplicationTransactionView()) 187 + ->setUser($viewer) 188 + ->setTransactions($xactions) 189 + ->setMarkupEngine($engine); 190 + 191 + // TODO: Add comment form. 192 + 193 + return $timeline; 194 + } 195 + 147 196 }
+59 -36
src/applications/ponder/editor/PonderQuestionEditor.php
··· 1 1 <?php 2 2 3 - final class PonderQuestionEditor extends PhabricatorEditor { 3 + final class PonderQuestionEditor 4 + extends PhabricatorApplicationTransactionEditor { 4 5 5 - private $question; 6 - private $shouldEmail = true; 6 + public function getTransactionTypes() { 7 + $types = parent::getTransactionTypes(); 7 8 8 - public function setQuestion(PonderQuestion $question) { 9 - $this->question = $question; 10 - return $this; 9 + $types[] = PhabricatorTransactions::TYPE_COMMENT; 10 + $types[] = PonderQuestionTransaction::TYPE_TITLE; 11 + $types[] = PonderQuestionTransaction::TYPE_CONTENT; 12 + 13 + return $types; 11 14 } 15 + protected function getCustomTransactionOldValue( 16 + PhabricatorLiskDAO $object, 17 + PhabricatorApplicationTransaction $xaction) { 12 18 13 - public function setShouldEmail($se) { 14 - $this->shouldEmail = $se; 15 - return $this; 19 + switch ($xaction->getTransactionType()) { 20 + case PonderQuestionTransaction::TYPE_TITLE: 21 + return $object->getTitle(); 22 + case PonderQuestionTransaction::TYPE_CONTENT: 23 + return $object->getContent(); 24 + } 16 25 } 17 26 18 - public function save() { 19 - $actor = $this->requireActor(); 20 - if (!$this->question) { 21 - throw new Exception("Must set question before saving it"); 27 + protected function getCustomTransactionNewValue( 28 + PhabricatorLiskDAO $object, 29 + PhabricatorApplicationTransaction $xaction) { 30 + 31 + switch ($xaction->getTransactionType()) { 32 + case PonderQuestionTransaction::TYPE_TITLE: 33 + case PonderQuestionTransaction::TYPE_CONTENT: 34 + return $xaction->getNewValue(); 22 35 } 36 + } 23 37 24 - $question = $this->question; 25 - $question->save(); 38 + protected function applyCustomInternalTransaction( 39 + PhabricatorLiskDAO $object, 40 + PhabricatorApplicationTransaction $xaction) { 26 41 27 - $question->attachRelated(); 28 - id(new PhabricatorSearchIndexer()) 29 - ->indexDocumentByPHID($question->getPHID()); 42 + switch ($xaction->getTransactionType()) { 43 + case PonderQuestionTransaction::TYPE_TITLE: 44 + $object->setTitle($xaction->getNewValue()); 45 + break; 46 + case PonderQuestionTransaction::TYPE_CONTENT: 47 + $object->setContent($xaction->getNewValue()); 48 + break; 49 + } 50 + } 30 51 31 - // subscribe author and @mentions 32 - $subeditor = id(new PhabricatorSubscriptionsEditor()) 33 - ->setObject($question) 34 - ->setActor($actor); 52 + protected function applyCustomExternalTransaction( 53 + PhabricatorLiskDAO $object, 54 + PhabricatorApplicationTransaction $xaction) { 55 + return; 56 + } 35 57 36 - $subeditor->subscribeExplicit(array($question->getAuthorPHID())); 58 + protected function mergeTransactions( 59 + PhabricatorApplicationTransaction $u, 60 + PhabricatorApplicationTransaction $v) { 37 61 38 - $content = $question->getContent(); 39 - $at_mention_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions( 40 - array($content)); 41 - $subeditor->subscribeImplicit($at_mention_phids); 42 - $subeditor->save(); 62 + $type = $u->getTransactionType(); 63 + switch ($type) { 64 + case PonderQuestionTransaction::TYPE_TITLE: 65 + case PonderQuestionTransaction::TYPE_CONTENT: 66 + return $v; 67 + } 43 68 44 - if ($this->shouldEmail && $at_mention_phids) { 45 - id(new PonderMentionMail( 46 - $question, 47 - $question, 48 - $actor)) 49 - ->setToPHIDs($at_mention_phids) 50 - ->send(); 51 - } 69 + return parent::mergeTransactions($u, $v); 52 70 } 71 + 72 + // TODO: Feed support 73 + // TODO: Mail support 74 + // TODO: Add/remove answers 75 + 53 76 }
+10
src/applications/ponder/query/PonderQuestionTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PonderQuestionTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + protected function getTemplateApplicationTransaction() { 7 + return new PonderQuestionTransaction(); 8 + } 9 + 10 + }
+3
src/applications/ponder/storage/PonderQuestionTransaction.php
··· 3 3 final class PonderQuestionTransaction 4 4 extends PhabricatorApplicationTransaction { 5 5 6 + const TYPE_TITLE = 'ponder.question:question'; 7 + const TYPE_CONTENT = 'ponder.question:content'; 8 + 6 9 public function getApplicationName() { 7 10 return 'ponder'; 8 11 }