diff --git a/src/applications/ponder/controller/PonderAnswerSaveController.php b/src/applications/ponder/controller/PonderAnswerSaveController.php index 97cadbe3cb..b720015ff0 100644 --- a/src/applications/ponder/controller/PonderAnswerSaveController.php +++ b/src/applications/ponder/controller/PonderAnswerSaveController.php @@ -1,59 +1,69 @@ getRequest(); $viewer = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $question_id = $request->getInt('question_id'); $question = id(new PonderQuestionQuery()) ->setViewer($viewer) ->withIDs(array($question_id)) + ->needAnswers(true) ->executeOne(); if (!$question) { return new Aphront404Response(); } $answer = $request->getStr('answer'); if (!strlen(trim($answer))) { $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle(pht('Empty Answer')) ->appendChild( phutil_tag('p', array(), pht( 'Your answer must not be empty.'))) ->addCancelButton('/Q'.$question_id); return id(new AphrontDialogResponse())->setDialog($dialog); } $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_WEB, array( 'ip' => $request->getRemoteAddr(), )); - $res = new PonderAnswer(); - $res - ->setContent($answer) + $res = id(new PonderAnswer()) ->setAuthorPHID($viewer->getPHID()) + ->setQuestionID($question->getID()) + ->setContent($answer) ->setVoteCount(0) - ->setQuestionID($question_id) ->setContentSource($content_source); - id(new PonderAnswerEditor()) + $xactions = array(); + $xactions[] = id(new PonderQuestionTransaction()) + ->setTransactionType(PonderQuestionTransaction::TYPE_ANSWERS) + ->setNewValue( + array( + '+' => array( + array('answer' => $res), + ), + )); + + $editor = id(new PonderQuestionEditor()) ->setActor($viewer) - ->setQuestion($question) - ->setAnswer($res) - ->saveAnswer(); + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($question, $xactions); return id(new AphrontRedirectResponse())->setURI( - id(new PhutilURI('/Q'. $question->getID()))); + id(new PhutilURI('/Q'.$question->getID()))); } } diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index 3106b4250f..b5c5078cb0 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -1,76 +1,144 @@ getTransactionType()) { + case PonderQuestionTransaction::TYPE_ANSWERS: + return true; + } + } + + return false; + } + + protected function applyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PonderQuestionTransaction::TYPE_ANSWERS: + $new_value = $xaction->getNewValue(); + $new = idx($new_value, '+', array()); + foreach ($new as $new_answer) { + $answer = idx($new_answer, 'answer'); + if (!$answer) { + continue; + } + $answer->save(); + } + break; + } + } + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PonderQuestionTransaction::TYPE_TITLE; $types[] = PonderQuestionTransaction::TYPE_CONTENT; + $types[] = PonderQuestionTransaction::TYPE_ANSWERS; return $types; } + protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PonderQuestionTransaction::TYPE_TITLE: return $object->getTitle(); case PonderQuestionTransaction::TYPE_CONTENT: return $object->getContent(); + case PonderQuestionTransaction::TYPE_ANSWERS: + return mpull($object->getAnswers(), 'getPHID'); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PonderQuestionTransaction::TYPE_TITLE: case PonderQuestionTransaction::TYPE_CONTENT: return $xaction->getNewValue(); + case PonderQuestionTransaction::TYPE_ANSWERS: + $raw_new_value = $xaction->getNewValue(); + $new_value = array(); + foreach ($raw_new_value as $key => $answers) { + $phids = array(); + foreach ($answers as $answer) { + $obj = idx($answer, 'answer'); + if (!$answer) { + continue; + } + $phids[] = $obj->getPHID(); + } + $new_value[$key] = $phids; + } + $xaction->setNewValue($new_value); + return $this->getPHIDTransactionNewValue($xaction); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PonderQuestionTransaction::TYPE_TITLE: $object->setTitle($xaction->getNewValue()); break; case PonderQuestionTransaction::TYPE_CONTENT: $object->setContent($xaction->getNewValue()); break; + case PonderQuestionTransaction::TYPE_ANSWERS: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + $add = array_diff_key($new, $old); + $rem = array_diff_key($old, $new); + + $count = $object->getAnswerCount(); + $count += count($add); + $count -= count($rem); + + $object->setAnswerCount($count); + break; } } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return; } protected function mergeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { $type = $u->getTransactionType(); switch ($type) { case PonderQuestionTransaction::TYPE_TITLE: case PonderQuestionTransaction::TYPE_CONTENT: return $v; } return parent::mergeTransactions($u, $v); } // TODO: Feed support // TODO: Mail support // TODO: Add/remove answers } diff --git a/src/applications/ponder/query/PonderQuestionQuery.php b/src/applications/ponder/query/PonderQuestionQuery.php index ce671fa9f6..211e3a6329 100644 --- a/src/applications/ponder/query/PonderQuestionQuery.php +++ b/src/applications/ponder/query/PonderQuestionQuery.php @@ -1,142 +1,165 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $phids) { $this->authorPHIDs = $phids; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withAnswererPHIDs(array $phids) { $this->answererPHIDs = $phids; return $this; } + public function needAnswers($need_answers) { + $this->needAnswers = $need_answers; + return $this; + } + public function setOrder($order) { $this->order = $order; return $this; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'q.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'q.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'q.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->status) { switch ($this->status) { case self::STATUS_ANY: break; case self::STATUS_OPEN: $where[] = qsprintf( $conn_r, 'q.status = %d', PonderQuestionStatus::STATUS_OPEN); break; case self::STATUS_CLOSED: $where[] = qsprintf( $conn_r, 'q.status = %d', PonderQuestionStatus::STATUS_CLOSED); break; default: throw new Exception("Unknown status query '{$this->status}'!"); } } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function buildOrderByClause(AphrontDatabaseConnection $conn_r) { switch ($this->order) { case self::ORDER_HOTTEST: return qsprintf($conn_r, 'ORDER BY q.heat DESC, q.id DESC'); case self::ORDER_CREATED: return qsprintf($conn_r, 'ORDER BY q.id DESC'); default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function loadPage() { $question = new PonderQuestion(); $conn_r = $question->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT q.* FROM %T q %Q %Q %Q %Q', $question->getTableName(), $this->buildJoinsClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderByClause($conn_r), $this->buildLimitClause($conn_r)); return $question->loadAllFromArray($data); } + public function willFilterPage(array $questions) { + if ($this->needAnswers) { + $answers = id(new PonderAnswerQuery()) + ->setViewer($this->getViewer()) + ->withQuestionIDs(mpull($questions, 'getID')) + ->execute(); + $answers = mgroup($answers, 'getQuestionID'); + + foreach ($questions as $question) { + $question->attachAnswers(idx($answers, $question->getID(), array())); + } + } + + return $questions; + } + private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { $joins = array(); if ($this->answererPHIDs) { $answer_table = new PonderAnswer(); $joins[] = qsprintf( $conn_r, 'JOIN %T a ON a.questionID = q.id AND a.authorPHID IN (%Ls)', $answer_table->getTableName(), $this->answererPHIDs); } return implode(' ', $joins); } } diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index ebaaf18158..83cdfb0a61 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -1,203 +1,209 @@ true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(PonderPHIDTypeQuestion::TYPECONST); } public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source->serialize(); return $this; } public function getContentSource() { return PhabricatorContentSource::newFromSerialized($this->contentSource); } public function attachRelated() { $this->answers = $this->loadRelatives(new PonderAnswer(), "questionID"); $qa_phids = mpull($this->answers, 'getPHID') + array($this->getPHID()); if ($qa_phids) { $comments = id(new PonderCommentQuery()) ->withTargetPHIDs($qa_phids) ->execute(); $comments = mgroup($comments, 'getTargetPHID'); } else { $comments = array(); } $this->setComments(idx($comments, $this->getPHID(), array())); foreach ($this->answers as $answer) { $answer->setQuestion($this); $answer->setComments(idx($comments, $answer->getPHID(), array())); } } public function attachVotes($user_phid) { $qa_phids = mpull($this->answers, 'getPHID') + array($this->getPHID()); $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($user_phid)) ->withDestinationPHIDs($qa_phids) ->withEdgeTypes( array( PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_QUESTION, PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_ANSWER )) ->needEdgeData(true) ->execute(); $question_edge = $edges[$user_phid][PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_QUESTION]; $answer_edges = $edges[$user_phid][PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_ANSWER]; $edges = null; $this->setUserVote(idx($question_edge, $this->getPHID())); foreach ($this->answers as $answer) { $answer->setUserVote(idx($answer_edges, $answer->getPHID())); } } public function setUserVote($vote) { $this->vote = $vote['data']; if (!$this->vote) { $this->vote = PonderVote::VOTE_NONE; } return $this; } public function getUserVote() { return $this->vote; } public function setComments($comments) { $this->comments = $comments; return $this; } public function getComments() { return $this->comments; } + public function attachAnswers(array $answers) { + assert_instances_of($answers, 'PonderAnswer'); + $this->answers = $answers; + return $this; + } + public function getAnswers() { return $this->answers; } public function getMarkupField() { return self::MARKUP_FIELD_CONTENT; } // Markup interface public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digest($this->getMarkupText($field)); $id = $this->getID(); return "ponder:Q{$id}:{$field}:{$hash}"; } public function getMarkupText($field) { return $this->getContent(); } public function newMarkupEngine($field) { return PhabricatorMarkupEngine::newPonderMarkupEngine(); } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { return $output; } public function shouldUseMarkupCache($field) { return (bool)$this->getID(); } // votable interface public function getUserVoteEdgeType() { return PhabricatorEdgeConfig::TYPE_VOTING_USER_HAS_QUESTION; } public function getVotablePHID() { return $this->getPHID(); } public function isAutomaticallySubscribed($phid) { return false; } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { $policy = PhabricatorPolicies::POLICY_NOONE; switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: $policy = PhabricatorPolicies::POLICY_USER; break; } return $policy; } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return ($viewer->getPHID() == $this->getAuthorPHID()); } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array( $this->getAuthorPHID(), ); } } diff --git a/src/applications/ponder/storage/PonderQuestionTransaction.php b/src/applications/ponder/storage/PonderQuestionTransaction.php index b343963855..e63bbbed4b 100644 --- a/src/applications/ponder/storage/PonderQuestionTransaction.php +++ b/src/applications/ponder/storage/PonderQuestionTransaction.php @@ -1,31 +1,32 @@