diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index c64d92ba6a..9e5a8a6d05 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -121,6 +121,10 @@ class AphrontDefaultApplicationConfiguration // For non-workflow requests, return a Ajax response. if ($request->isAjax() && !$request->isJavelinWorkflow()) { + // Log these; they don't get shown on the client and can be difficult + // to debug. + phlog($ex); + $response = new AphrontAjaxResponse(); $response->setError( array( diff --git a/src/aphront/console/DarkConsoleCore.php b/src/aphront/console/DarkConsoleCore.php index 310fe04c7d..d72026af5a 100644 --- a/src/aphront/console/DarkConsoleCore.php +++ b/src/aphront/console/DarkConsoleCore.php @@ -58,7 +58,7 @@ final class DarkConsoleCore { 'name' => $plugin->getName(), 'color' => $plugin->getColor(), ); - $data[$class] = $plugin->getData(); + $data[$class] = $this->sanitizeForJSON($plugin->getData()); } $storage = array( @@ -107,5 +107,21 @@ final class DarkConsoleCore { ''); } + /** + * Sometimes, tab data includes binary information (like INSERT queries which + * write file data into the database). To successfully JSON encode it, we + * need to convert it to UTF-8. + */ + private function sanitizeForJSON($data) { + if (is_array($data)) { + foreach ($data as $key => $value) { + $data[$key] = $this->sanitizeForJSON($value); + } + return $data; + } else { + return phutil_utf8ize($data); + } + } + } diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 784463a462..28f5fb21a9 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -21,17 +21,15 @@ final class PhabricatorSlowvotePollController $poll = id(new PhabricatorSlowvoteQuery()) ->setViewer($user) ->withIDs(array($this->id)) + ->needOptions(true) + ->needChoices(true) ->executeOne(); if (!$poll) { return new Aphront404Response(); } - $options = id(new PhabricatorSlowvoteOption())->loadAllWhere( - 'pollID = %d', - $poll->getID()); - $choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( - 'pollID = %d', - $poll->getID()); + $options = $poll->getOptions(); + $choices = $poll->getChoices(); $choices_by_option = mgroup($choices, 'getOptionID'); $choices_by_user = mgroup($choices, 'getAuthorPHID'); diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php index 99facb5f5f..0772efa96e 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php @@ -19,18 +19,15 @@ final class PhabricatorSlowvoteVoteController $poll = id(new PhabricatorSlowvoteQuery()) ->setViewer($user) ->withIDs(array($this->id)) + ->needOptions(true) + ->needViewerChoices(true) ->executeOne(); if (!$poll) { return new Aphront404Response(); } - $options = id(new PhabricatorSlowvoteOption())->loadAllWhere( - 'pollID = %d', - $poll->getID()); - $user_choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( - 'pollID = %d AND authorPHID = %s', - $poll->getID(), - $user->getPHID()); + $options = $poll->getOptions(); + $user_choices = $poll->getViewerChoices($user); $old_votes = mpull($user_choices, null, 'getOptionID'); diff --git a/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php b/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php index 328f6b96f7..0f3a4d6f68 100644 --- a/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php +++ b/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php @@ -11,6 +11,10 @@ final class PhabricatorSlowvoteQuery private $authorPHIDs; private $withVotesByViewer; + private $needOptions; + private $needChoices; + private $needViewerChoices; + public function withIDs($ids) { $this->ids = $ids; return $this; @@ -31,6 +35,21 @@ final class PhabricatorSlowvoteQuery return $this; } + public function needOptions($need_options) { + $this->needOptions = $need_options; + return $this; + } + + public function needChoices($need_choices) { + $this->needChoices = $need_choices; + return $this; + } + + public function needViewerChoices($need_viewer_choices) { + $this->needViewerChoices = $need_viewer_choices; + return $this; + } + public function loadPage() { $table = new PhabricatorSlowvotePoll(); $conn_r = $table->establishConnection('r'); @@ -47,6 +66,66 @@ final class PhabricatorSlowvoteQuery return $table->loadAllFromArray($data); } + public function willFilterPage(array $polls) { + assert_instances_of($polls, 'PhabricatorSlowvotePoll'); + + if (!$polls) { + return array(); + } + + $ids = mpull($polls, 'getID'); + $viewer = $this->getViewer(); + + if ($this->needOptions) { + $options = id(new PhabricatorSlowvoteOption())->loadAllWhere( + 'pollID IN (%Ld)', + $ids); + + $options = mgroup($options, 'getPollID'); + foreach ($polls as $poll) { + $poll->attachOptions(idx($options, $poll->getID(), array())); + } + } + + if ($this->needChoices) { + $choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( + 'pollID IN (%Ld)', + $ids); + + $choices = mgroup($choices, 'getPollID'); + foreach ($polls as $poll) { + $poll->attachChoices(idx($choices, $poll->getID(), array())); + } + + // If we need the viewer's choices, we can just fill them from the data + // we already loaded. + if ($this->needViewerChoices) { + foreach ($polls as $poll) { + $poll->attachViewerChoices( + $viewer, + idx( + mgroup($poll->getChoices(), 'getAuthorPHID'), + $viewer->getPHID(), + array())); + } + } + } else if ($this->needViewerChoices) { + $choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( + 'pollID IN (%Ld) AND authorPHID = %s', + $ids, + $viewer->getPHID()); + + $choices = mgroup($choices, 'getPollID'); + foreach ($polls as $poll) { + $poll->attachViewerChoices( + $viewer, + idx($choices, $poll->getID(), array())); + } + } + + return $polls; + } + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); diff --git a/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php b/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php index 1168110302..c4db192ee7 100644 --- a/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php +++ b/src/applications/slowvote/remarkup/SlowvoteRemarkupRule.php @@ -16,21 +16,18 @@ final class SlowvoteRemarkupRule return id(new PhabricatorSlowvoteQuery()) ->setViewer($viewer) ->withIDs($ids) + ->needOptions(true) + ->needChoices(true) + ->needViewerChoices(true) ->execute(); } protected function renderObjectEmbed($object, $handle, $options) { $viewer = $this->getEngine()->getConfig('viewer'); - $options = id(new PhabricatorSlowvoteOption())->loadAllWhere( - 'pollID = %d', - $object->getID()); - $choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( - 'pollID = %d', - $object->getID()); - $choices_by_user = mgroup($choices, 'getAuthorPHID'); - - $viewer_choices = idx($choices_by_user, $viewer->getPHID(), array()); + $options = $object->getOptions(); + $choices = $object->getChoices(); + $viewer_choices = $object->getViewerChoices($viewer); $embed = id(new SlowvoteEmbedView()) ->setPoll($object) diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php index 729563b20e..0b04b86bc5 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php @@ -23,6 +23,10 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO protected $method; protected $viewPolicy; + private $options; + private $choices; + private $viewerChoices = array(); + public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -34,6 +38,46 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO PhabricatorPHIDConstants::PHID_TYPE_POLL); } + public function getOptions() { + if ($this->options === null) { + throw new Exception("Call attachOptions() before getOptions()!"); + } + return $this->options; + } + + public function attachOptions(array $options) { + assert_instances_of($options, 'PhabricatorSlowvoteOption'); + $this->options = $options; + return $this; + } + + public function getChoices() { + if ($this->choices === null) { + throw new Exception("Call attachChoices() before getChoices()!"); + } + return $this->choices; + } + + public function attachChoices(array $choices) { + assert_instances_of($choices, 'PhabricatorSlowvoteChoice'); + $this->choices = $choices; + return $this; + } + + public function getViewerChoices(PhabricatorUser $viewer) { + if (idx($this->viewerChoices, $viewer->getPHID()) === null) { + throw new Exception( + "Call attachViewerChoices() before getViewerChoices()!"); + } + return idx($this->viewerChoices, $viewer->getPHID()); + } + + public function attachViewerChoices(PhabricatorUser $viewer, array $choices) { + assert_instances_of($choices, 'PhabricatorSlowvoteOption'); + $this->viewerChoices[$viewer->getPHID()] = $choices; + return $this; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */