mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
Allow ponder answers to be edited
Summary: Ref T3373. Use applicationtransactions to edit ponder answers. Also enable tokens and subscriptions. Test Plan: edited an answer Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T3373 Differential Revision: https://secure.phabricator.com/D6607
This commit is contained in:
parent
71841e262a
commit
946a5cd5ce
8 changed files with 199 additions and 98 deletions
|
@ -1870,6 +1870,7 @@ phutil_register_library_map(array(
|
||||||
'PonderAddAnswerView' => 'applications/ponder/view/PonderAddAnswerView.php',
|
'PonderAddAnswerView' => 'applications/ponder/view/PonderAddAnswerView.php',
|
||||||
'PonderAddCommentView' => 'applications/ponder/view/PonderAddCommentView.php',
|
'PonderAddCommentView' => 'applications/ponder/view/PonderAddCommentView.php',
|
||||||
'PonderAnswer' => 'applications/ponder/storage/PonderAnswer.php',
|
'PonderAnswer' => 'applications/ponder/storage/PonderAnswer.php',
|
||||||
|
'PonderAnswerEditController' => 'applications/ponder/controller/PonderAnswerEditController.php',
|
||||||
'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php',
|
'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php',
|
||||||
'PonderAnswerListView' => 'applications/ponder/view/PonderAnswerListView.php',
|
'PonderAnswerListView' => 'applications/ponder/view/PonderAnswerListView.php',
|
||||||
'PonderAnswerPreviewController' => 'applications/ponder/controller/PonderAnswerPreviewController.php',
|
'PonderAnswerPreviewController' => 'applications/ponder/controller/PonderAnswerPreviewController.php',
|
||||||
|
@ -3990,8 +3991,11 @@ phutil_register_library_map(array(
|
||||||
1 => 'PhabricatorMarkupInterface',
|
1 => 'PhabricatorMarkupInterface',
|
||||||
2 => 'PonderVotableInterface',
|
2 => 'PonderVotableInterface',
|
||||||
3 => 'PhabricatorPolicyInterface',
|
3 => 'PhabricatorPolicyInterface',
|
||||||
|
4 => 'PhabricatorSubscribableInterface',
|
||||||
|
5 => 'PhabricatorTokenReceiverInterface',
|
||||||
),
|
),
|
||||||
'PonderAnswerEditor' => 'PhabricatorEditor',
|
'PonderAnswerEditController' => 'PonderController',
|
||||||
|
'PonderAnswerEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'PonderAnswerListView' => 'AphrontView',
|
'PonderAnswerListView' => 'AphrontView',
|
||||||
'PonderAnswerPreviewController' => 'PonderController',
|
'PonderAnswerPreviewController' => 'PonderController',
|
||||||
'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
|
|
|
@ -51,6 +51,7 @@ final class PhabricatorApplicationPonder extends PhabricatorApplication {
|
||||||
'/ponder/' => array(
|
'/ponder/' => array(
|
||||||
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PonderQuestionListController',
|
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PonderQuestionListController',
|
||||||
'answer/add/' => 'PonderAnswerSaveController',
|
'answer/add/' => 'PonderAnswerSaveController',
|
||||||
|
'answer/edit/(?P<id>\d+)/' => 'PonderAnswerEditController',
|
||||||
'answer/preview/' => 'PonderAnswerPreviewController',
|
'answer/preview/' => 'PonderAnswerPreviewController',
|
||||||
'question/edit/(?:(?P<id>\d+)/)?' => 'PonderQuestionEditController',
|
'question/edit/(?:(?P<id>\d+)/)?' => 'PonderQuestionEditController',
|
||||||
'question/preview/' => 'PonderQuestionPreviewController',
|
'question/preview/' => 'PonderQuestionPreviewController',
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PonderAnswerEditController extends PonderController {
|
||||||
|
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
public function willProcessRequest(array $data) {
|
||||||
|
$this->id = $data['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
|
$answer = id(new PonderAnswerQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($this->id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$answer) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$v_content = $answer->getContent();
|
||||||
|
$e_content = true;
|
||||||
|
|
||||||
|
|
||||||
|
$question = $answer->getQuestion();
|
||||||
|
$qid = $question->getID();
|
||||||
|
$aid = $answer->getID();
|
||||||
|
|
||||||
|
$question_uri = "/Q{$qid}#A{$aid}";
|
||||||
|
|
||||||
|
$errors = array();
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$v_content = $request->getStr('content');
|
||||||
|
|
||||||
|
if (!strlen($v_content)) {
|
||||||
|
$errors[] = pht('You must provide some substance in your answer.');
|
||||||
|
$e_content = pht('Required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errors) {
|
||||||
|
$xactions = array();
|
||||||
|
$xactions[] = id(new PonderAnswerTransaction())
|
||||||
|
->setTransactionType(PonderAnswerTransaction::TYPE_CONTENT)
|
||||||
|
->setNewValue($v_content);
|
||||||
|
|
||||||
|
$editor = id(new PonderAnswerEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setContinueOnNoEffect(true);
|
||||||
|
|
||||||
|
$editor->applyTransactions($answer, $xactions);
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI($question_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors) {
|
||||||
|
$errors = id(new AphrontErrorView())->setErrors($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormStaticControl())
|
||||||
|
->setLabel(pht('Question'))
|
||||||
|
->setValue($question->getTitle()))
|
||||||
|
->appendChild(
|
||||||
|
id(new PhabricatorRemarkupControl())
|
||||||
|
->setLabel(pht('Answer'))
|
||||||
|
->setName('content')
|
||||||
|
->setValue($v_content)
|
||||||
|
->setError($e_content))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormSubmitControl())
|
||||||
|
->setValue(pht('Update Answer'))
|
||||||
|
->addCancelButton($question_uri));
|
||||||
|
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$crumbs->addCrumb(
|
||||||
|
id(new PhabricatorCrumbView())
|
||||||
|
->setName("Q{$qid}")
|
||||||
|
->setHref($question_uri));
|
||||||
|
$crumbs->addCrumb(
|
||||||
|
id(new PhabricatorCrumbView())
|
||||||
|
->setName(pht('Edit Answer')));
|
||||||
|
|
||||||
|
return $this->buildApplicationPage(
|
||||||
|
array(
|
||||||
|
$crumbs,
|
||||||
|
$errors,
|
||||||
|
$form,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => pht('Edit Answer'),
|
||||||
|
'dust' => true,
|
||||||
|
'device' => true,
|
||||||
|
));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -237,6 +237,8 @@ final class PonderQuestionViewController extends PonderController {
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
$viewer = $request->getUser();
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
|
$id = $answer->getID();
|
||||||
|
|
||||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
$viewer,
|
$viewer,
|
||||||
$answer,
|
$answer,
|
||||||
|
@ -247,10 +249,6 @@ final class PonderQuestionViewController extends PonderController {
|
||||||
->setObject($answer)
|
->setObject($answer)
|
||||||
->setObjectURI($request->getRequestURI());
|
->setObjectURI($request->getRequestURI());
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
$view->addAction(
|
$view->addAction(
|
||||||
id(new PhabricatorActionView())
|
id(new PhabricatorActionView())
|
||||||
->setIcon('edit')
|
->setIcon('edit')
|
||||||
|
@ -259,8 +257,6 @@ final class PonderQuestionViewController extends PonderController {
|
||||||
->setDisabled(!$can_edit)
|
->setDisabled(!$can_edit)
|
||||||
->setWorkflow(!$can_edit));
|
->setWorkflow(!$can_edit));
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
return $view;
|
return $view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,99 +1,66 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
final class PonderAnswerEditor extends PhabricatorEditor {
|
final class PonderAnswerEditor
|
||||||
|
extends PhabricatorApplicationTransactionEditor {
|
||||||
|
|
||||||
private $question;
|
public function getTransactionTypes() {
|
||||||
private $answer;
|
$types = parent::getTransactionTypes();
|
||||||
private $shouldEmail = true;
|
|
||||||
|
|
||||||
public function setQuestion($question) {
|
$types[] = PhabricatorTransactions::TYPE_COMMENT;
|
||||||
$this->question = $question;
|
$types[] = PonderAnswerTransaction::TYPE_CONTENT;
|
||||||
return $this;
|
|
||||||
|
return $types;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAnswer($answer) {
|
protected function getCustomTransactionOldValue(
|
||||||
$this->answer = $answer;
|
PhabricatorLiskDAO $object,
|
||||||
return $this;
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
}
|
|
||||||
|
|
||||||
public function saveAnswer() {
|
switch ($xaction->getTransactionType()) {
|
||||||
$actor = $this->requireActor();
|
case PonderAnswerTransaction::TYPE_CONTENT:
|
||||||
if (!$this->question) {
|
return $object->getContent();
|
||||||
throw new Exception("Must set question before saving answer");
|
|
||||||
}
|
|
||||||
if (!$this->answer) {
|
|
||||||
throw new Exception("Must set answer before saving it");
|
|
||||||
}
|
|
||||||
|
|
||||||
$question = $this->question;
|
|
||||||
$answer = $this->answer;
|
|
||||||
$conn = $answer->establishConnection('w');
|
|
||||||
$trans = $conn->openTransaction();
|
|
||||||
$trans->beginReadLocking();
|
|
||||||
|
|
||||||
$question->reload();
|
|
||||||
|
|
||||||
queryfx($conn,
|
|
||||||
'UPDATE %T as t
|
|
||||||
SET t.`answerCount` = t.`answerCount` + 1
|
|
||||||
WHERE t.`PHID` = %s',
|
|
||||||
$question->getTableName(),
|
|
||||||
$question->getPHID());
|
|
||||||
|
|
||||||
$answer->setQuestionID($question->getID());
|
|
||||||
$answer->save();
|
|
||||||
|
|
||||||
$trans->endReadLocking();
|
|
||||||
$trans->saveTransaction();
|
|
||||||
|
|
||||||
$question->attachRelated();
|
|
||||||
id(new PhabricatorSearchIndexer())
|
|
||||||
->indexDocumentByPHID($question->getPHID());
|
|
||||||
|
|
||||||
// subscribe author and @mentions
|
|
||||||
$subeditor = id(new PhabricatorSubscriptionsEditor())
|
|
||||||
->setObject($question)
|
|
||||||
->setActor($actor);
|
|
||||||
|
|
||||||
$subeditor->subscribeExplicit(array($answer->getAuthorPHID()));
|
|
||||||
|
|
||||||
$content = $answer->getContent();
|
|
||||||
$at_mention_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(
|
|
||||||
array($content));
|
|
||||||
$subeditor->subscribeImplicit($at_mention_phids);
|
|
||||||
$subeditor->save();
|
|
||||||
|
|
||||||
if ($this->shouldEmail) {
|
|
||||||
// now load subscribers, including implicitly-added @mention victims
|
|
||||||
$subscribers = PhabricatorSubscribersQuery
|
|
||||||
::loadSubscribersForPHID($question->getPHID());
|
|
||||||
|
|
||||||
|
|
||||||
// @mention emails (but not for anyone who has explicitly unsubscribed)
|
|
||||||
if (array_intersect($at_mention_phids, $subscribers)) {
|
|
||||||
id(new PonderMentionMail(
|
|
||||||
$question,
|
|
||||||
$answer,
|
|
||||||
$actor))
|
|
||||||
->setToPHIDs($at_mention_phids)
|
|
||||||
->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
$other_subs =
|
|
||||||
array_diff(
|
|
||||||
$subscribers,
|
|
||||||
$at_mention_phids);
|
|
||||||
|
|
||||||
// 'Answered' emails for subscribers who are not @mentiond (and excluding
|
|
||||||
// author depending on their MetaMTA settings).
|
|
||||||
if ($other_subs) {
|
|
||||||
id(new PonderAnsweredMail(
|
|
||||||
$question,
|
|
||||||
$answer,
|
|
||||||
$actor))
|
|
||||||
->setToPHIDs($other_subs)
|
|
||||||
->send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getCustomTransactionNewValue(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
|
switch ($xaction->getTransactionType()) {
|
||||||
|
case PonderAnswerTransaction::TYPE_CONTENT:
|
||||||
|
return $xaction->getNewValue();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyCustomInternalTransaction(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
||||||
|
switch ($xaction->getTransactionType()) {
|
||||||
|
case PonderAnswerTransaction::TYPE_CONTENT:
|
||||||
|
$object->setContent($xaction->getNewValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyCustomExternalTransaction(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function mergeTransactions(
|
||||||
|
PhabricatorApplicationTransaction $u,
|
||||||
|
PhabricatorApplicationTransaction $v) {
|
||||||
|
|
||||||
|
$type = $u->getTransactionType();
|
||||||
|
switch ($type) {
|
||||||
|
case PonderAnswerTransaction::TYPE_CONTENT:
|
||||||
|
return $v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::mergeTransactions($u, $v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,9 @@ final class PonderAnswer extends PonderDAO
|
||||||
implements
|
implements
|
||||||
PhabricatorMarkupInterface,
|
PhabricatorMarkupInterface,
|
||||||
PonderVotableInterface,
|
PonderVotableInterface,
|
||||||
PhabricatorPolicyInterface {
|
PhabricatorPolicyInterface,
|
||||||
|
PhabricatorSubscribableInterface,
|
||||||
|
PhabricatorTokenReceiverInterface {
|
||||||
|
|
||||||
const MARKUP_FIELD_CONTENT = 'markup:content';
|
const MARKUP_FIELD_CONTENT = 'markup:content';
|
||||||
|
|
||||||
|
@ -145,4 +147,23 @@ final class PonderAnswer extends PonderDAO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getUsersToNotifyOfTokenGiven() {
|
||||||
|
return array(
|
||||||
|
$this->getAuthorPHID(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function isAutomaticallySubscribed($phid) {
|
||||||
|
return ($phid == $this->getAuthorPHID());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
final class PonderAnswerTransaction
|
final class PonderAnswerTransaction
|
||||||
extends PhabricatorApplicationTransaction {
|
extends PhabricatorApplicationTransaction {
|
||||||
|
|
||||||
|
const TYPE_CONTENT = 'ponder.answer:content';
|
||||||
|
|
||||||
public function getApplicationName() {
|
public function getApplicationName() {
|
||||||
return 'ponder';
|
return 'ponder';
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,7 @@ final class PonderQuestion extends PonderDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAutomaticallySubscribed($phid) {
|
public function isAutomaticallySubscribed($phid) {
|
||||||
return false;
|
return ($phid == $this->getAuthorPHID());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save() {
|
public function save() {
|
||||||
|
@ -198,8 +198,10 @@ final class PonderQuestion extends PonderDAO
|
||||||
return ($viewer->getPHID() == $this->getAuthorPHID());
|
return ($viewer->getPHID() == $this->getAuthorPHID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
|
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
public function getUsersToNotifyOfTokenGiven() {
|
public function getUsersToNotifyOfTokenGiven() {
|
||||||
return array(
|
return array(
|
||||||
$this->getAuthorPHID(),
|
$this->getAuthorPHID(),
|
||||||
|
|
Loading…
Reference in a new issue