2011-07-08 20:13:11 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright 2011 Facebook, Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2011-09-14 17:02:31 +02:00
|
|
|
/**
|
|
|
|
* @group slowvote
|
|
|
|
*/
|
2011-07-08 20:13:11 +02:00
|
|
|
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 PhabricatorSlowvotePoll())->load($this->id);
|
|
|
|
if (!$poll) {
|
|
|
|
return new Aphront404Response();
|
|
|
|
}
|
|
|
|
|
|
|
|
$options = id(new PhabricatorSlowvoteOption())->loadAllWhere(
|
|
|
|
'pollID = %d',
|
|
|
|
$poll->getID());
|
|
|
|
$choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
|
|
|
|
'pollID = %d',
|
|
|
|
$poll->getID());
|
|
|
|
$comments = id(new PhabricatorSlowvoteComment())->loadAllWhere(
|
|
|
|
'pollID = %d',
|
|
|
|
$poll->getID());
|
|
|
|
|
|
|
|
$choices_by_option = mgroup($choices, 'getOptionID');
|
|
|
|
$comments_by_user = mpull($comments, null, 'getAuthorPHID');
|
|
|
|
$choices_by_user = mgroup($choices, 'getAuthorPHID');
|
|
|
|
$viewer_choices = idx($choices_by_user, $viewer_phid, array());
|
|
|
|
$viewer_comment = idx($comments_by_user, $viewer_phid, null);
|
|
|
|
|
|
|
|
$comment_text = null;
|
|
|
|
if ($viewer_comment) {
|
|
|
|
$comment_text = $viewer_comment->getCommentText();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($request->isFormPost()) {
|
|
|
|
$comment = idx($comments_by_user, $viewer_phid, null);
|
|
|
|
if ($comment) {
|
|
|
|
$comment->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
$comment_text = $request->getStr('comments');
|
|
|
|
if (strlen($comment_text)) {
|
|
|
|
id(new PhabricatorSlowvoteComment())
|
|
|
|
->setAuthorPHID($viewer_phid)
|
|
|
|
->setPollID($poll->getID())
|
|
|
|
->setCommentText($comment_text)
|
|
|
|
->save();
|
|
|
|
}
|
|
|
|
|
|
|
|
$votes = $request->getArr('vote');
|
|
|
|
|
|
|
|
switch ($poll->getMethod()) {
|
|
|
|
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
|
|
|
|
// Enforce only one vote.
|
|
|
|
$votes = array_slice($votes, 0, 1);
|
|
|
|
break;
|
|
|
|
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
|
|
|
|
// No filtering.
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Exception("Unknown poll method!");
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($viewer_choices as $viewer_choice) {
|
|
|
|
$viewer_choice->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($votes as $vote) {
|
|
|
|
id(new PhabricatorSlowvoteChoice())
|
|
|
|
->setAuthorPHID($viewer_phid)
|
|
|
|
->setPollID($poll->getID())
|
|
|
|
->setOptionID($vote)
|
|
|
|
->save();
|
|
|
|
}
|
|
|
|
|
|
|
|
return id(new AphrontRedirectResponse())->setURI('/V'.$poll->getID());
|
|
|
|
}
|
|
|
|
|
|
|
|
require_celerity_resource('phabricator-slowvote-css');
|
|
|
|
|
|
|
|
$phids = array_merge(
|
|
|
|
mpull($choices, 'getAuthorPHID'),
|
|
|
|
mpull($comments, 'getAuthorPHID'),
|
|
|
|
array(
|
|
|
|
$poll->getAuthorPHID(),
|
|
|
|
));
|
|
|
|
|
2011-07-09 19:22:11 +02:00
|
|
|
$query = new PhabricatorObjectHandleData($phids);
|
|
|
|
$handles = $query->loadHandles();
|
|
|
|
$objects = $query->loadObjects();
|
2011-07-08 20:13:11 +02:00
|
|
|
|
|
|
|
if ($poll->getShuffle()) {
|
|
|
|
shuffle($options);
|
|
|
|
}
|
|
|
|
|
|
|
|
$option_markup = array();
|
|
|
|
foreach ($options as $option) {
|
2011-07-08 22:52:11 +02:00
|
|
|
$option_markup[] = $this->renderPollOption(
|
|
|
|
$poll,
|
|
|
|
$viewer_choices,
|
|
|
|
$option);
|
2011-07-08 20:13:11 +02:00
|
|
|
}
|
|
|
|
$option_markup = implode("\n", $option_markup);
|
|
|
|
|
|
|
|
$comments_by_option = array();
|
|
|
|
switch ($poll->getMethod()) {
|
|
|
|
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
|
|
|
|
$choice_ids = array();
|
2011-07-09 19:22:11 +02:00
|
|
|
foreach ($choices_by_user as $user_phid => $user_choices) {
|
|
|
|
$choice_ids[$user_phid] = head($user_choices)->getOptionID();
|
2011-07-08 20:13:11 +02:00
|
|
|
}
|
|
|
|
foreach ($comments as $comment) {
|
|
|
|
$choice = idx($choice_ids, $comment->getAuthorPHID());
|
|
|
|
if ($choice) {
|
|
|
|
$comments_by_option[$choice][] = $comment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
|
|
|
|
// All comments are grouped in approval voting.
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Exception("Unknown poll method!");
|
|
|
|
}
|
|
|
|
|
2011-07-08 22:52:11 +02:00
|
|
|
$result_markup = $this->renderResultMarkup(
|
|
|
|
$poll,
|
|
|
|
$options,
|
|
|
|
$choices,
|
|
|
|
$comments,
|
|
|
|
$viewer_choices,
|
|
|
|
$choices_by_option,
|
|
|
|
$comments_by_option,
|
2011-07-09 19:22:11 +02:00
|
|
|
$handles,
|
|
|
|
$objects);
|
2011-07-08 20:13:11 +02:00
|
|
|
|
|
|
|
if ($viewer_choices) {
|
|
|
|
$instructions =
|
|
|
|
'Your vote has been recorded... but there is still ample time to '.
|
|
|
|
'rethink your position. Have you thoroughly considered all possible '.
|
|
|
|
'eventualities?';
|
|
|
|
} else {
|
|
|
|
$instructions =
|
|
|
|
'This is a weighty matter indeed. Consider your choices with the '.
|
|
|
|
'greatest of care.';
|
|
|
|
}
|
|
|
|
|
|
|
|
$form = id(new AphrontFormView())
|
|
|
|
->setUser($user)
|
|
|
|
->appendChild(
|
|
|
|
'<p class="aphront-form-instructions">'.$instructions.'</p>')
|
|
|
|
->appendChild(
|
|
|
|
id(new AphrontFormMarkupControl())
|
|
|
|
->setLabel('Vote')
|
|
|
|
->setValue($option_markup))
|
|
|
|
->appendChild(
|
|
|
|
id(new AphrontFormTextAreaControl())
|
|
|
|
->setLabel('Comments')
|
|
|
|
->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT)
|
|
|
|
->setName('comments')
|
|
|
|
->setValue($comment_text))
|
|
|
|
->appendChild(
|
|
|
|
id(new AphrontFormSubmitControl())
|
|
|
|
->setValue('Cautiously Engage in Deliberations'));
|
|
|
|
|
|
|
|
|
|
|
|
$panel = new AphrontPanelView();
|
|
|
|
$panel->setHeader(phutil_escape_html($poll->getQuestion()));
|
|
|
|
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
|
|
|
|
|
|
|
|
$panel->appendChild($form);
|
|
|
|
$panel->appendChild('<br /><br />');
|
|
|
|
$panel->appendChild($result_markup);
|
|
|
|
|
|
|
|
return $this->buildStandardPageResponse(
|
|
|
|
$panel,
|
|
|
|
array(
|
|
|
|
'title' => 'V'.$poll->getID().' '.$poll->getQuestion(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
private function renderComments(array $comments, array $handles) {
|
|
|
|
$viewer = $this->getRequest()->getUser();
|
|
|
|
|
Generalize the markup engine factory
Summary:
This thing services every app but it lives inside Differential right now. Pull
it out, and separate the factory interfaces per-application.
This will let us accommodate changes we need to make for Phriction to support
wiki linking.
Test Plan: Tested remarkup in differential, diffusion, maniphest, people,
slowvote.
Reviewed By: hsb
Reviewers: hsb, codeblock, jungejason, tuomaspelkonen, aran
CC: aran, hsb
Differential Revision: 646
2011-07-12 00:58:32 +02:00
|
|
|
$engine = PhabricatorMarkupEngine::newSlowvoteMarkupEngine();
|
2011-07-08 20:13:11 +02:00
|
|
|
|
|
|
|
$comment_markup = array();
|
|
|
|
foreach ($comments as $comment) {
|
|
|
|
$handle = $handles[$comment->getAuthorPHID()];
|
|
|
|
|
|
|
|
$markup = $engine->markupText($comment->getCommentText());
|
|
|
|
|
|
|
|
require_celerity_resource('phabricator-remarkup-css');
|
|
|
|
|
|
|
|
$comment_markup[] =
|
|
|
|
'<tr>'.
|
|
|
|
'<th>'.
|
|
|
|
$handle->renderLink().
|
|
|
|
'<div class="phabricator-slowvote-datestamp">'.
|
|
|
|
phabricator_datetime($comment->getDateCreated(), $viewer).
|
|
|
|
'</div>'.
|
|
|
|
'<td>'.
|
|
|
|
'<div class="phabricator-remarkup">'.
|
|
|
|
$markup.
|
|
|
|
'</div>'.
|
|
|
|
'</td>'.
|
|
|
|
'</tr>';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($comment_markup) {
|
|
|
|
$comment_markup = phutil_render_tag(
|
|
|
|
'table',
|
|
|
|
array(
|
|
|
|
'class' => 'phabricator-slowvote-comments',
|
|
|
|
),
|
|
|
|
implode("\n", $comment_markup));
|
|
|
|
} else {
|
|
|
|
$comment_markup = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $comment_markup;
|
|
|
|
}
|
|
|
|
|
2011-07-08 22:52:11 +02:00
|
|
|
private function renderPollOption(
|
|
|
|
PhabricatorSlowvotePoll $poll,
|
|
|
|
array $viewer_choices,
|
|
|
|
PhabricatorSlowvoteOption $option) {
|
|
|
|
|
|
|
|
$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_render_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_render_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_render_tag(
|
|
|
|
'label',
|
|
|
|
array(
|
|
|
|
'class' => 'phabricator-slowvote-label '.$checked_class,
|
|
|
|
),
|
|
|
|
$input.phutil_escape_html($option->getName()));
|
|
|
|
}
|
|
|
|
|
|
|
|
private function renderVoteCount(
|
|
|
|
PhabricatorSlowvotePoll $poll,
|
|
|
|
array $choices,
|
|
|
|
array $chosen) {
|
|
|
|
|
|
|
|
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 $comments,
|
|
|
|
array $viewer_choices,
|
|
|
|
array $choices_by_option,
|
|
|
|
array $comments_by_option,
|
2011-07-09 19:22:11 +02:00
|
|
|
array $handles,
|
|
|
|
array $objects) {
|
2011-07-08 22:52:11 +02:00
|
|
|
|
|
|
|
$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('<h1>Ongoing Deliberation</h1>');
|
|
|
|
|
|
|
|
if (!$can_see_responses) {
|
|
|
|
if ($need_vote) {
|
|
|
|
$reason = "You must vote to see the results.";
|
|
|
|
} else {
|
|
|
|
$reason = "The results are not public.";
|
|
|
|
}
|
|
|
|
$result_markup
|
|
|
|
->appendChild(
|
|
|
|
'<p class="aphront-form-instructions"><em>'.$reason.'</em></p>');
|
|
|
|
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) {
|
2011-07-09 19:22:11 +02:00
|
|
|
$object = idx($objects, $handle->getPHID());
|
|
|
|
if (!$object) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$profile_image = PhabricatorFileURI::getViewURIForPHID(
|
|
|
|
$object->getProfileImagePHID());
|
|
|
|
|
|
|
|
$user_markup[] = phutil_render_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => $handle->getURI(),
|
|
|
|
'class' => 'phabricator-slowvote-facepile',
|
|
|
|
),
|
|
|
|
phutil_render_tag(
|
|
|
|
'img',
|
|
|
|
array(
|
|
|
|
'src' => $profile_image,
|
|
|
|
)));
|
2011-07-08 22:52:11 +02:00
|
|
|
}
|
|
|
|
$user_markup = implode('', $user_markup);
|
|
|
|
} else {
|
|
|
|
$user_markup = 'This option has failed to appeal to anyone.';
|
|
|
|
}
|
|
|
|
|
|
|
|
$comment_markup = $this->renderComments(
|
|
|
|
idx($comments_by_option, $id, array()),
|
|
|
|
$handles);
|
|
|
|
|
|
|
|
$vote_count = $this->renderVoteCount(
|
|
|
|
$poll,
|
|
|
|
$choices,
|
|
|
|
$chosen);
|
|
|
|
|
|
|
|
$result_markup->appendChild(
|
|
|
|
'<div>'.
|
|
|
|
'<div class="phabricator-slowvote-count">'.
|
|
|
|
$vote_count.
|
|
|
|
'</div>'.
|
|
|
|
'<h1>'.phutil_escape_html($option->getName()).'</h1>'.
|
|
|
|
'<hr class="phabricator-slowvote-hr" />'.
|
|
|
|
$user_markup.
|
2011-07-09 19:22:11 +02:00
|
|
|
'<div style="clear: both;">'.
|
2011-07-08 22:52:11 +02:00
|
|
|
'<hr class="phabricator-slowvote-hr" />'.
|
|
|
|
$comment_markup.
|
|
|
|
'</div>');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($poll->getMethod() == PhabricatorSlowvotePoll::METHOD_APPROVAL &&
|
|
|
|
$comments) {
|
|
|
|
$comment_markup = $this->renderComments(
|
|
|
|
$comments,
|
|
|
|
$handles);
|
|
|
|
$result_markup->appendChild(
|
|
|
|
'<h1>Motions Proposed for Consideration</h1>');
|
|
|
|
$result_markup->appendChild($comment_markup);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result_markup;
|
|
|
|
}
|
2011-07-08 20:13:11 +02:00
|
|
|
}
|