Page MenuHomestyx hydra

No OneTemporary

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
@@ -1,358 +1,362 @@
<?php
/**
* @group aphront
*/
class AphrontDefaultApplicationConfiguration
extends AphrontApplicationConfiguration {
public function __construct() {
}
public function getApplicationName() {
return 'aphront-default';
}
public function getURIMap() {
return $this->getResourceURIMapRules() + array(
'/(?:(?P<filter>(?:jump))/)?' =>
'PhabricatorDirectoryMainController',
'/typeahead/' => array(
'common/(?P<type>\w+)/'
=> 'PhabricatorTypeaheadCommonDatasourceController',
),
'/oauthserver/' => array(
'auth/' => 'PhabricatorOAuthServerAuthController',
'test/' => 'PhabricatorOAuthServerTestController',
'token/' => 'PhabricatorOAuthServerTokenController',
'clientauthorization/' => array(
'' => 'PhabricatorOAuthClientAuthorizationListController',
'delete/(?P<phid>[^/]+)/' =>
'PhabricatorOAuthClientAuthorizationDeleteController',
'edit/(?P<phid>[^/]+)/' =>
'PhabricatorOAuthClientAuthorizationEditController',
),
'client/' => array(
'' => 'PhabricatorOAuthClientListController',
'create/' => 'PhabricatorOAuthClientEditController',
'delete/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientDeleteController',
'edit/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientEditController',
'view/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientViewController',
),
),
'/~/' => array(
'' => 'DarkConsoleController',
'data/(?P<key>[^/]+)/' => 'DarkConsoleDataController',
),
'/status/' => 'PhabricatorStatusController',
'/help/' => array(
'keyboardshortcut/' => 'PhabricatorHelpKeyboardShortcutController',
),
'/notification/' => array(
'(?:(?P<filter>all|unread)/)?'
=> 'PhabricatorNotificationListController',
'panel/' => 'PhabricatorNotificationPanelController',
'individual/' => 'PhabricatorNotificationIndividualController',
'status/' => 'PhabricatorNotificationStatusController',
'clear/' => 'PhabricatorNotificationClearController',
),
'/debug/' => 'PhabricatorDebugController',
);
}
protected function getResourceURIMapRules() {
return array(
'/res/' => array(
'(?:(?P<mtime>[0-9]+)T/)?'.
'(?P<package>pkg/)?'.
'(?P<hash>[a-f0-9]{8})/'.
'(?P<path>.+\.(?:css|js|jpg|png|swf|gif))'
=> 'CelerityPhabricatorResourceController',
),
);
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public function buildRequest() {
$parser = new PhutilQueryStringParser();
$data = array();
$raw_input = PhabricatorStartup::getRawInput();
if (strlen($raw_input)) {
$data += $parser->parseQueryString($raw_input);
} else if ($_POST) {
$data += $_POST;
}
$data += $parser->parseQueryString(
isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : "");
$request = new AphrontRequest($this->getHost(), $this->getPath());
$request->setRequestData($data);
$request->setApplicationConfiguration($this);
return $request;
}
public function handleException(Exception $ex) {
$request = $this->getRequest();
// For Conduit requests, return a Conduit response.
if ($request->isConduit()) {
$response = new ConduitAPIResponse();
$response->setErrorCode(get_class($ex));
$response->setErrorInfo($ex->getMessage());
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
}
// 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(
'code' => get_class($ex),
'info' => $ex->getMessage(),
));
return $response;
}
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$user = $request->getUser();
if (!$user) {
// If we hit an exception very early, we won't have a user.
$user = new PhabricatorUser();
}
if ($ex instanceof PhabricatorPolicyException) {
if (!$user->isLoggedIn()) {
// If the user isn't logged in, just give them a login form. This is
// probably a generally more useful response than a policy dialog that
// they have to click through to get a login form.
//
// Possibly we should add a header here like "you need to login to see
// the thing you are trying to look at".
$login_controller = new PhabricatorAuthStartController($request);
return $login_controller->processRequest();
}
$content = hsprintf(
'<div class="aphront-policy-exception">%s</div>',
$ex->getMessage());
$dialog = new AphrontDialogView();
$dialog
->setTitle(
$is_serious
? 'Access Denied'
: "You Shall Not Pass")
->setClass('aphront-access-dialog')
->setUser($user)
->appendChild($content);
if ($this->getRequest()->isAjax()) {
$dialog->addCancelButton('/', 'Close');
} else {
$dialog->addCancelButton('/', $is_serious ? 'OK' : 'Away With Thee');
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
if ($ex instanceof AphrontUsageException) {
$error = new AphrontErrorView();
$error->setTitle($ex->getTitle());
$error->appendChild($ex->getMessage());
$view = new PhabricatorStandardPageView();
$view->setRequest($this->getRequest());
$view->appendChild($error);
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
}
// Always log the unhandled exception.
phlog($ex);
$class = get_class($ex);
$message = $ex->getMessage();
if ($ex instanceof AphrontQuerySchemaException) {
$message .=
"\n\n".
"NOTE: This usually indicates that the MySQL schema has not been ".
"properly upgraded. Run 'bin/storage upgrade' to ensure your ".
"schema is up to date.";
}
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$trace = $this->renderStackTrace($ex->getTrace(), $user);
} else {
$trace = null;
}
$content = hsprintf(
'<div class="aphront-unhandled-exception">'.
'<div class="exception-message">%s</div>'.
'%s'.
'</div>',
$message,
$trace);
$dialog = new AphrontDialogView();
$dialog
->setTitle('Unhandled Exception ("'.$class.'")')
->setClass('aphront-exception-dialog')
->setUser($user)
->appendChild($content);
if ($this->getRequest()->isAjax()) {
$dialog->addCancelButton('/', 'Close');
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
public function willSendResponse(AphrontResponse $response) {
return $response;
}
public function build404Controller() {
return array(new Phabricator404Controller($this->getRequest()), array());
}
public function buildRedirectController($uri) {
return array(
new PhabricatorRedirectController($this->getRequest()),
array(
'uri' => $uri,
));
}
private function renderStackTrace($trace, PhabricatorUser $user) {
$libraries = PhutilBootloader::getInstance()->getAllLibraries();
// TODO: Make this configurable?
$path = 'https://secure.phabricator.com/diffusion/%s/browse/master/src/';
$callsigns = array(
'arcanist' => 'ARC',
'phutil' => 'PHU',
'phabricator' => 'P',
);
$rows = array();
$depth = count($trace);
foreach ($trace as $part) {
$lib = null;
$file = idx($part, 'file');
$relative = $file;
foreach ($libraries as $library) {
$root = phutil_get_library_root($library);
if (Filesystem::isDescendant($file, $root)) {
$lib = $library;
$relative = Filesystem::readablePath($file, $root);
break;
}
}
$where = '';
if (isset($part['class'])) {
$where .= $part['class'].'::';
}
if (isset($part['function'])) {
$where .= $part['function'].'()';
}
if ($file) {
if (isset($callsigns[$lib])) {
$attrs = array('title' => $file);
try {
$attrs['href'] = $user->loadEditorLink(
'/src/'.$relative,
$part['line'],
$callsigns[$lib]);
} catch (Exception $ex) {
// The database can be inaccessible.
}
if (empty($attrs['href'])) {
$attrs['href'] = sprintf($path, $callsigns[$lib]).
str_replace(DIRECTORY_SEPARATOR, '/', $relative).
'$'.$part['line'];
$attrs['target'] = '_blank';
}
$file_name = phutil_tag(
'a',
$attrs,
$relative);
} else {
$file_name = phutil_tag(
'span',
array(
'title' => $file,
),
$relative);
}
$file_name = hsprintf('%s : %d', $file_name, $part['line']);
} else {
$file_name = phutil_tag('em', array(), '(Internal)');
}
$rows[] = array(
$depth--,
$lib,
$file_name,
$where,
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Depth',
'Library',
'File',
'Where',
));
$table->setColumnClasses(
array(
'n',
'',
'',
'wide',
));
return hsprintf(
'<div class="exception-trace">'.
'<div class="exception-trace-header">Stack Trace</div>'.
'%s'.
'</div>',
$table->render());
}
}
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
@@ -1,111 +1,127 @@
<?php
/**
* @group console
*/
final class DarkConsoleCore {
private $plugins = array();
const STORAGE_VERSION = 1;
public function __construct() {
$symbols = id(new PhutilSymbolLoader())
->setType('class')
->setAncestorClass('DarkConsolePlugin')
->selectAndLoadSymbols();
foreach ($symbols as $symbol) {
$plugin = newv($symbol['name'], array());
if (!$plugin->shouldStartup()) {
continue;
}
$plugin->setConsoleCore($this);
$plugin->didStartup();
$this->plugins[$symbol['name']] = $plugin;
}
}
public function getPlugins() {
return $this->plugins;
}
public function getKey(AphrontRequest $request) {
$plugins = $this->getPlugins();
foreach ($plugins as $plugin) {
$plugin->setRequest($request);
$plugin->willShutdown();
}
foreach ($plugins as $plugin) {
$plugin->didShutdown();
}
foreach ($plugins as $plugin) {
$plugin->setData($plugin->generateData());
}
$plugins = msort($plugins, 'getOrderKey');
$key = Filesystem::readRandomCharacters(24);
$tabs = array();
$data = array();
foreach ($plugins as $plugin) {
$class = get_class($plugin);
$tabs[] = array(
'class' => $class,
'name' => $plugin->getName(),
'color' => $plugin->getColor(),
);
- $data[$class] = $plugin->getData();
+ $data[$class] = $this->sanitizeForJSON($plugin->getData());
}
$storage = array(
'vers' => self::STORAGE_VERSION,
'tabs' => $tabs,
'data' => $data,
'user' => $request->getUser()
? $request->getUser()->getPHID()
: null,
);
$cache = new PhabricatorKeyValueDatabaseCache();
$cache = new PhutilKeyValueCacheProfiler($cache);
$cache->setProfiler(PhutilServiceProfiler::getInstance());
$cache->setKeys(
array(
'darkconsole:'.$key => json_encode($storage),
),
$ttl = (60 * 60 * 6));
return $key;
}
public function getColor() {
foreach ($this->getPlugins() as $plugin) {
if ($plugin->getColor()) {
return $plugin->getColor();
}
}
}
public function render(AphrontRequest $request) {
$user = $request->getUser();
$visible = $user ? $user->getConsoleVisible() : true;
return javelin_tag(
'div',
array(
'id' => 'darkconsole',
'class' => 'dark-console',
'style' => $visible ? '' : 'display: none;',
'data-console-key' => $this->getKey($request),
'data-console-color' => $this->getColor(),
),
'');
}
+ /**
+ * 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
@@ -1,463 +1,461 @@
<?php
/**
* @group slowvote
*/
final class PhabricatorSlowvotePollController
extends PhabricatorSlowvoteController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer_phid = $user->getPHID();
$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');
$viewer_choices = idx($choices_by_user, $viewer_phid, array());
if ($request->isAjax()) {
$embed = id(new SlowvoteEmbedView())
->setPoll($poll)
->setOptions($options)
->setViewerChoices($viewer_choices);
return id(new AphrontAjaxResponse())
->setContent(array(
'pollID' => $poll->getID(),
'contentHTML' => $embed->render()));
}
require_celerity_resource('phabricator-slowvote-css');
$phids = array_merge(
mpull($choices, 'getAuthorPHID'),
array(
$poll->getAuthorPHID(),
));
$query = new PhabricatorObjectHandleData($phids);
$query->setViewer($user);
$handles = $query->loadHandles();
$objects = $query->loadObjects();
if ($poll->getShuffle()) {
shuffle($options);
}
$option_markup = array();
foreach ($options as $option) {
$option_markup[] = $this->renderPollOption(
$poll,
$viewer_choices,
$option);
}
switch ($poll->getMethod()) {
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
$choice_ids = array();
foreach ($choices_by_user as $user_phid => $user_choices) {
$choice_ids[$user_phid] = head($user_choices)->getOptionID();
}
break;
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
break;
default:
throw new Exception("Unknown poll method!");
}
$result_markup = $this->renderResultMarkup(
$poll,
$options,
$choices,
$viewer_choices,
$choices_by_option,
$handles,
$objects);
if ($viewer_choices) {
$instructions =
pht('Your vote has been recorded... but there is still ample time to '.
'rethink your position. Have you thoroughly considered all possible '.
'eventualities?');
} else {
$instructions =
pht('This is a weighty matter indeed. Consider your choices with the '.
'greatest of care.');
}
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->setAction(sprintf('/vote/%d/', $poll->getID()))
->appendChild(hsprintf(
'<p class="aphront-form-instructions">%s</p>',
$instructions))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Vote'))
->setValue($option_markup))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Engage in Deliberations')));
$header = id(new PhabricatorHeaderView())
->setHeader($poll->getQuestion());
$actions = $this->buildActionView($poll);
$properties = $this->buildPropertyView($poll);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('V'.$poll->getID()));
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
$panel->appendChild($result_markup);
$content = array(
$form,
hsprintf('<br /><br />'),
$panel);
$xactions = $this->buildTransactions($poll);
$add_comment = $this->buildCommentForm($poll);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$content,
$xactions,
$add_comment,
),
array(
'title' => 'V'.$poll->getID().' '.$poll->getQuestion(),
'device' => true,
'dust' => true,
));
}
private function renderPollOption(
PhabricatorSlowvotePoll $poll,
array $viewer_choices,
PhabricatorSlowvoteOption $option) {
assert_instances_of($viewer_choices, 'PhabricatorSlowvoteChoice');
$id = $option->getID();
switch ($poll->getMethod()) {
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
// Render a radio button.
$selected_option = head($viewer_choices);
if ($selected_option) {
$selected = $selected_option->getOptionID();
} else {
$selected = null;
}
if ($selected == $id) {
$checked = "checked";
} else {
$checked = null;
}
$input = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'vote[]',
'value' => $id,
'checked' => $checked,
));
break;
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
// Render a check box.
$checked = null;
foreach ($viewer_choices as $choice) {
if ($choice->getOptionID() == $id) {
$checked = 'checked';
break;
}
}
$input = phutil_tag(
'input',
array(
'type' => 'checkbox',
'name' => 'vote[]',
'checked' => $checked,
'value' => $id,
));
break;
default:
throw new Exception("Unknown poll method!");
}
if ($checked) {
$checked_class = 'phabricator-slowvote-checked';
} else {
$checked_class = null;
}
return phutil_tag(
'label',
array(
'class' => 'phabricator-slowvote-label '.$checked_class,
),
array($input, $option->getName()));
}
private function renderVoteCount(
PhabricatorSlowvotePoll $poll,
array $choices,
array $chosen) {
assert_instances_of($choices, 'PhabricatorSlowvoteChoice');
assert_instances_of($chosen, 'PhabricatorSlowvoteChoice');
switch ($poll->getMethod()) {
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
$out_of_total = count($choices);
break;
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
// Count unique respondents for approval votes.
$out_of_total = count(mpull($choices, null, 'getAuthorPHID'));
break;
default:
throw new Exception("Unknown poll method!");
}
return sprintf(
'%d / %d (%d%%)',
number_format(count($chosen)),
number_format($out_of_total),
$out_of_total
? round(100 * count($chosen) / $out_of_total)
: 0);
}
private function renderResultMarkup(
PhabricatorSlowvotePoll $poll,
array $options,
array $choices,
array $viewer_choices,
array $choices_by_option,
array $handles,
array $objects) {
assert_instances_of($options, 'PhabricatorSlowvoteOption');
assert_instances_of($choices, 'PhabricatorSlowvoteChoice');
assert_instances_of($viewer_choices, 'PhabricatorSlowvoteChoice');
assert_instances_of($handles, 'PhabricatorObjectHandle');
assert_instances_of($objects, 'PhabricatorLiskDAO');
$viewer_phid = $this->getRequest()->getUser()->getPHID();
$can_see_responses = false;
$need_vote = false;
switch ($poll->getResponseVisibility()) {
case PhabricatorSlowvotePoll::RESPONSES_VISIBLE:
$can_see_responses = true;
break;
case PhabricatorSlowvotePoll::RESPONSES_VOTERS:
$can_see_responses = (bool)$viewer_choices;
$need_vote = true;
break;
case PhabricatorSlowvotePoll::RESPONSES_OWNER:
$can_see_responses = ($viewer_phid == $poll->getAuthorPHID());
break;
}
$result_markup = id(new AphrontFormLayoutView())
->appendChild(phutil_tag('h1', array(), pht('Ongoing Deliberation')));
if (!$can_see_responses) {
if ($need_vote) {
$reason = pht("You must vote to see the results.");
} else {
$reason = pht("The results are not public.");
}
$result_markup
->appendChild(hsprintf(
'<p class="aphront-form-instructions"><em>%s</em></p>',
$reason));
return $result_markup;
}
foreach ($options as $option) {
$id = $option->getID();
$chosen = idx($choices_by_option, $id, array());
$users = array_select_keys($handles, mpull($chosen, 'getAuthorPHID'));
if ($users) {
$user_markup = array();
foreach ($users as $handle) {
$object = idx($objects, $handle->getPHID());
if (!$object) {
continue;
}
$profile_image = $handle->getImageURI();
$user_markup[] = phutil_tag(
'a',
array(
'href' => $handle->getURI(),
'class' => 'phabricator-slowvote-facepile',
),
phutil_tag(
'img',
array(
'src' => $profile_image,
)));
}
} else {
$user_markup = pht('This option has failed to appeal to anyone.');
}
$vote_count = $this->renderVoteCount(
$poll,
$choices,
$chosen);
$result_markup->appendChild(hsprintf(
'<div>'.
'<div class="phabricator-slowvote-count">%s</div>'.
'<h1>%s</h1>'.
'<hr class="phabricator-slowvote-hr" />'.
'%s'.
'<div style="clear: both;"></div>'.
'<hr class="phabricator-slowvote-hr" />'.
'</div>',
$vote_count,
$option->getName(),
phutil_tag('div', array(), $user_markup)));
}
return $result_markup;
}
private function buildActionView(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($poll);
return $view;
}
private function buildPropertyView(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($poll);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$poll);
$view->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$view->invokeWillRenderEvent();
if (strlen($poll->getDescription())) {
$view->addTextSection(
$output = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent(
$poll->getDescription()),
'default',
$viewer));
}
return $view;
}
private function buildTransactions(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$xactions = id(new PhabricatorSlowvoteTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($poll->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setTransactions($xactions)
->setMarkupEngine($engine);
return $timeline;
}
private function buildCommentForm(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$add_comment_header = id(new PhabricatorHeaderView())
->setHeader(
$is_serious
? pht('Add Comment')
: pht('Enter Deliberations'));
$submit_button_name = $is_serious
? pht('Add Comment')
: pht('Perhaps');
$draft = PhabricatorDraft::newFromUserAndKey($viewer, $poll->getPHID());
$add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setDraft($draft)
->setAction($this->getApplicationURI('/comment/'.$poll->getID().'/'))
->setSubmitButtonName($submit_button_name);
return array(
$add_comment_header,
$add_comment_form,
);
}
}
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
@@ -1,115 +1,112 @@
<?php
/**
* @group slowvote
*/
final class PhabricatorSlowvoteVoteController
extends PhabricatorSlowvoteController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$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');
if ($request->isAjax()) {
$vote = $request->getInt('vote');
$votes = array_keys($old_votes);
$votes = array_fuse($votes, $votes);
if ($poll->getMethod() == PhabricatorSlowvotePoll::METHOD_PLURALITY) {
if (idx($votes, $vote, false)) {
$votes = array();
} else {
$votes = array($vote);
}
} else {
if (idx($votes, $vote, false)) {
unset($votes[$vote]);
} else {
$votes[$vote] = $vote;
}
}
$this->updateVotes($user, $poll, $old_votes, $votes);
$updated_choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
'pollID = %d AND authorPHID = %s',
$poll->getID(),
$user->getPHID());
$embed = id(new SlowvoteEmbedView())
->setPoll($poll)
->setOptions($options)
->setViewerChoices($updated_choices);
return id(new AphrontAjaxResponse())
->setContent(array(
'pollID' => $poll->getID(),
'contentHTML' => $embed->render()));
}
if (!$request->isFormPost()) {
return id(new Aphront404Response());
}
$votes = $request->getArr('vote');
$votes = array_fuse($votes, $votes);
$this->updateVotes($user, $poll, $old_votes, $votes);
return id(new AphrontRedirectResponse())->setURI('/V'.$poll->getID());
}
private function updateVotes($user, $poll, $old_votes, $votes) {
if (!empty($votes) && count($votes) > 1 &&
$poll->getMethod() == PhabricatorSlowvotePoll::METHOD_PLURALITY) {
return id(new Aphront400Response());
}
foreach ($old_votes as $old_vote) {
if (!idx($votes, $old_vote->getOptionID(), false)) {
$old_vote->delete();
}
}
foreach ($votes as $vote) {
if (idx($old_votes, $vote, false)) {
continue;
}
id(new PhabricatorSlowvoteChoice())
->setAuthorPHID($user->getPHID())
->setPollID($poll->getID())
->setOptionID($vote)
->save();
}
}
}
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
@@ -1,96 +1,175 @@
<?php
/**
* @group slowvote
*/
final class PhabricatorSlowvoteQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $authorPHIDs;
private $withVotesByViewer;
+ private $needOptions;
+ private $needChoices;
+ private $needViewerChoices;
+
public function withIDs($ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs($phids) {
$this->phids = $phids;
return $this;
}
public function withAuthorPHIDs($author_phids) {
$this->authorPHIDs = $author_phids;
return $this;
}
public function withVotesByViewer($with_vote) {
$this->withVotesByViewer = $with_vote;
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');
$data = queryfx_all(
$conn_r,
'SELECT p.* FROM %T p %Q %Q %Q %Q',
$table->getTableName(),
$this->buildJoinsClause($conn_r),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
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();
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'p.id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'p.phid IN (%Ls)',
$this->phids);
}
if ($this->authorPHIDs) {
$where[] = qsprintf(
$conn_r,
'p.authorPHID IN (%Ls)',
$this->authorPHIDs);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
private function buildJoinsClause(AphrontDatabaseConnection $conn_r) {
$joins = array();
if ($this->withVotesByViewer) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T vv ON vv.pollID = p.id AND vv.authorPHID = %s',
id(new PhabricatorSlowvoteChoice())->getTableName(),
$this->getViewer()->getPHID());
}
return implode(' ', $joins);
}
protected function getPagingColumn() {
return 'p.id';
}
}
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
@@ -1,43 +1,40 @@
<?php
/**
* @group slowvote
*/
final class SlowvoteRemarkupRule
extends PhabricatorRemarkupRuleObject {
protected function getObjectNamePrefix() {
return 'V';
}
protected function loadObjects(array $ids) {
$viewer = $this->getEngine()->getConfig('viewer');
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)
->setOptions($options)
->setViewerChoices($viewer_choices);
return $embed->render();
}
}
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
@@ -1,70 +1,114 @@
<?php
/**
* @group slowvote
*/
final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO
implements
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface {
const RESPONSES_VISIBLE = 0;
const RESPONSES_VOTERS = 1;
const RESPONSES_OWNER = 2;
const METHOD_PLURALITY = 0;
const METHOD_APPROVAL = 1;
protected $question;
protected $description;
protected $authorPHID;
protected $responseVisibility;
protected $shuffle;
protected $method;
protected $viewPolicy;
+ private $options;
+ private $choices;
+ private $viewerChoices = array();
+
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
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 )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->viewPolicy;
case PhabricatorPolicyCapability::CAN_EDIT:
return PhabricatorPolicies::POLICY_NOONE;
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getAuthorPHID());
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return ($phid == $this->getAuthorPHID());
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 16, 9:48 AM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
272601
Default Alt Text
(40 KB)

Event Timeline