Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/field/specification/DifferentialBlameRevisionFieldSpecification.php b/src/applications/differential/field/specification/DifferentialBlameRevisionFieldSpecification.php
index 27573da141..0c4bb8c366 100644
--- a/src/applications/differential/field/specification/DifferentialBlameRevisionFieldSpecification.php
+++ b/src/applications/differential/field/specification/DifferentialBlameRevisionFieldSpecification.php
@@ -1,98 +1,98 @@
<?php
final class DifferentialBlameRevisionFieldSpecification
extends DifferentialFieldSpecification {
private $value;
public function getStorageKey() {
return 'phabricator:blame-revision';
}
public function getValueForStorage() {
return $this->value;
}
public function setValueFromStorage($value) {
$this->value = $value;
return $this;
}
public function shouldAppearOnEdit() {
return true;
}
public function setValueFromRequest(AphrontRequest $request) {
$this->value = $request->getStr($this->getStorageKey());
return $this;
}
public function renderEditControl() {
return id(new AphrontFormTextControl())
->setLabel('Blame Revision')
->setCaption('Revision which broke the stuff which this change fixes.')
->setName($this->getStorageKey())
->setValue($this->value);
}
public function shouldAppearOnRevisionView() {
return true;
}
public function renderLabelForRevisionView() {
return 'Blame Revision:';
}
public function renderValueForRevisionView() {
if (!$this->value) {
return null;
}
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
- return $engine->markupText($this->value);
+ return phutil_safe_html($engine->markupText($this->value));
}
public function shouldAppearOnConduitView() {
return true;
}
public function getValueForConduit() {
return $this->value;
}
public function shouldAppearOnCommitMessage() {
return true;
}
public function getCommitMessageKey() {
return 'blameRevision';
}
public function setValueFromParsedCommitMessage($value) {
$this->value = $value;
return $this;
}
public function shouldOverwriteWhenCommitMessageIsEdited() {
return true;
}
public function renderLabelForCommitMessage() {
return 'Blame Revision';
}
public function renderValueForCommitMessage($is_edit) {
return $this->value;
}
public function getSupportedCommitMessageLabels() {
return array(
'Blame Revision',
'Blame Rev',
);
}
public function parseValueFromCommitMessage($value) {
return $value;
}
}
diff --git a/src/applications/differential/field/specification/DifferentialUnitFieldSpecification.php b/src/applications/differential/field/specification/DifferentialUnitFieldSpecification.php
index b4e89b3fa9..348964045c 100644
--- a/src/applications/differential/field/specification/DifferentialUnitFieldSpecification.php
+++ b/src/applications/differential/field/specification/DifferentialUnitFieldSpecification.php
@@ -1,223 +1,223 @@
<?php
final class DifferentialUnitFieldSpecification
extends DifferentialFieldSpecification {
public function shouldAppearOnDiffView() {
return true;
}
public function renderLabelForDiffView() {
return $this->renderLabelForRevisionView();
}
public function renderValueForDiffView() {
return $this->renderValueForRevisionView();
}
public function shouldAppearOnRevisionView() {
return true;
}
public function renderLabelForRevisionView() {
return 'Unit:';
}
private function getUnitExcuse() {
return $this->getDiffProperty('arc:unit-excuse');
}
public function renderValueForRevisionView() {
$diff = $this->getManualDiff();
$ustar = DifferentialRevisionUpdateHistoryView::renderDiffUnitStar($diff);
$umsg = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage($diff);
$rows = array();
$rows[] = array(
'style' => 'star',
'name' => $ustar,
'value' => $umsg,
'show' => true,
);
$excuse = $this->getUnitExcuse();
if ($excuse) {
$rows[] = array(
'style' => 'excuse',
'name' => 'Excuse',
'value' => nl2br(phutil_escape_html($excuse)),
'show' => true,
);
}
$show_limit = 10;
$hidden = array();
$udata = $this->getDiffProperty('arc:unit');
if ($udata) {
$sort_map = array(
ArcanistUnitTestResult::RESULT_BROKEN => 0,
ArcanistUnitTestResult::RESULT_FAIL => 1,
ArcanistUnitTestResult::RESULT_UNSOUND => 2,
ArcanistUnitTestResult::RESULT_SKIP => 3,
ArcanistUnitTestResult::RESULT_POSTPONED => 4,
ArcanistUnitTestResult::RESULT_PASS => 5,
);
foreach ($udata as $key => $test) {
$udata[$key]['sort'] = idx($sort_map, idx($test, 'result'));
}
$udata = isort($udata, 'sort');
foreach ($udata as $test) {
$result = idx($test, 'result');
$default_hide = false;
switch ($result) {
case ArcanistUnitTestResult::RESULT_POSTPONED:
case ArcanistUnitTestResult::RESULT_PASS:
$default_hide = true;
break;
}
if ($show_limit && !$default_hide) {
--$show_limit;
$show = true;
} else {
$show = false;
if (empty($hidden[$result])) {
$hidden[$result] = 0;
}
$hidden[$result]++;
}
$value = idx($test, 'name');
if (!empty($test['link'])) {
$value = phutil_tag(
'a',
array(
'href' => $test['link'],
'target' => '_blank',
),
$value);
}
$rows[] = array(
'style' => $this->getResultStyle($result),
'name' => phutil_escape_html(ucwords($result)),
'value' => $value,
'show' => $show,
);
$userdata = idx($test, 'userdata');
if ($userdata) {
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
- $userdata = $engine->markupText($userdata);
+ $userdata = phutil_safe_html($engine->markupText($userdata));
$rows[] = array(
'style' => 'details',
'value' => $userdata,
'show' => false,
);
if (empty($hidden['details'])) {
$hidden['details'] = 0;
}
$hidden['details']++;
}
}
}
$show_string = $this->renderShowString($hidden);
$view = new DifferentialResultsTableView();
$view->setRows($rows);
$view->setShowMoreString($show_string);
return $view->render();
}
private function getResultStyle($result) {
$map = array(
ArcanistUnitTestResult::RESULT_PASS => 'green',
ArcanistUnitTestResult::RESULT_FAIL => 'red',
ArcanistUnitTestResult::RESULT_SKIP => 'blue',
ArcanistUnitTestResult::RESULT_BROKEN => 'red',
ArcanistUnitTestResult::RESULT_UNSOUND => 'yellow',
ArcanistUnitTestResult::RESULT_POSTPONED => 'blue',
);
return idx($map, $result);
}
private function renderShowString(array $hidden) {
if (!$hidden) {
return null;
}
// Reorder hidden things by severity.
$hidden = array_select_keys(
$hidden,
array(
ArcanistUnitTestResult::RESULT_BROKEN,
ArcanistUnitTestResult::RESULT_FAIL,
ArcanistUnitTestResult::RESULT_UNSOUND,
ArcanistUnitTestResult::RESULT_SKIP,
ArcanistUnitTestResult::RESULT_POSTPONED,
ArcanistUnitTestResult::RESULT_PASS,
'details',
)) + $hidden;
$noun = array(
ArcanistUnitTestResult::RESULT_BROKEN => 'Broken',
ArcanistUnitTestResult::RESULT_FAIL => 'Failed',
ArcanistUnitTestResult::RESULT_UNSOUND => 'Unsound',
ArcanistUnitTestResult::RESULT_SKIP => 'Skipped',
ArcanistUnitTestResult::RESULT_POSTPONED => 'Postponed',
ArcanistUnitTestResult::RESULT_PASS => 'Passed',
);
$show = array();
foreach ($hidden as $key => $value) {
if ($key == 'details') {
$show[] = pht('%d Detail(s)', $value);
} else {
$show[] = $value.' '.idx($noun, $key);
}
}
return "Show Full Unit Results (".implode(', ', $show).")";
}
public function renderWarningBoxForRevisionAccept() {
$diff = $this->getDiff();
$unit_warning = null;
if ($diff->getUnitStatus() >= DifferentialUnitStatus::UNIT_WARN) {
$titles =
array(
DifferentialUnitStatus::UNIT_WARN => 'Unit Tests Warning',
DifferentialUnitStatus::UNIT_FAIL => 'Unit Tests Failure',
DifferentialUnitStatus::UNIT_SKIP => 'Unit Tests Skipped',
DifferentialUnitStatus::UNIT_POSTPONED => 'Unit Tests Postponed'
);
if ($diff->getUnitStatus() == DifferentialUnitStatus::UNIT_POSTPONED) {
$content =
"<p>This diff has postponed unit tests. The results should be ".
"coming in soon. You should probably wait for them before accepting ".
"this diff.</p>";
} else if ($diff->getUnitStatus() == DifferentialUnitStatus::UNIT_SKIP) {
$content =
"<p>Unit tests were skipped when this diff was created. Make sure ".
"you are OK with that before you accept this diff.</p>";
} else {
$content =
"<p>This diff has Unit Test Problems. Make sure you are OK with ".
"them before you accept this diff.</p>";
}
$unit_warning = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_ERROR)
->appendChild($content)
->setTitle(idx($titles, $diff->getUnitStatus(), 'Warning'));
}
return $unit_warning;
}
}
diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php
index 62d247a651..7d3ac8ed7c 100644
--- a/src/applications/diffusion/controller/DiffusionBrowseController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseController.php
@@ -1,121 +1,121 @@
<?php
final class DiffusionBrowseController extends DiffusionController {
public function processRequest() {
$drequest = $this->diffusionRequest;
if ($this->getRequest()->getStr('before')) {
$results = array();
$is_file = true;
} else {
$browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest);
$results = $browse_query->loadPaths();
$reason = $browse_query->getReasonForEmptyResultSet();
$is_file = ($reason == DiffusionBrowseQuery::REASON_IS_FILE);
}
$content = array();
if ($drequest->getTagContent()) {
$title = 'Tag: '.$drequest->getSymbolicCommit();
$tag_view = new AphrontPanelView();
$tag_view->setHeader(phutil_escape_html($title));
$tag_view->appendChild(
$this->markupText($drequest->getTagContent()));
$content[] = $tag_view;
}
if (!$results) {
if ($is_file) {
$controller = new DiffusionBrowseFileController($this->getRequest());
$controller->setDiffusionRequest($drequest);
$controller->setCurrentApplication($this->getCurrentApplication());
return $this->delegateToController($controller);
}
$empty_result = new DiffusionEmptyResultView();
$empty_result->setDiffusionRequest($drequest);
$empty_result->setBrowseQuery($browse_query);
$empty_result->setView($this->getRequest()->getStr('view'));
$content[] = $empty_result;
} else {
$phids = array();
foreach ($results as $result) {
$data = $result->getLastCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
}
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$browse_table = new DiffusionBrowseTableView();
$browse_table->setDiffusionRequest($drequest);
$browse_table->setHandles($handles);
$browse_table->setPaths($results);
$browse_table->setUser($this->getRequest()->getUser());
$browse_panel = new AphrontPanelView();
$browse_panel->appendChild($browse_table);
$browse_panel->setNoBackground();
$content[] = $browse_panel;
}
$content[] = $this->buildOpenRevisions();
$readme_content = $browse_query->renderReadme($results);
if ($readme_content) {
$readme_panel = new AphrontPanelView();
$readme_panel->setHeader('README');
$readme_panel->appendChild($readme_content);
$content[] = $readme_panel;
}
$nav = $this->buildSideNav('browse', false);
$nav->appendChild($content);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'browse',
));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => array(
nonempty(basename($drequest->getPath()), '/'),
$drequest->getRepository()->getCallsign().' Repository',
),
));
}
private function markupText($text) {
$engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine();
- $text = $engine->markupText($text);
+ $text = phutil_safe_html($engine->markupText($text));
$text = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$text);
return $text;
}
}
diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php
index 8afcc4b0e7..235be74c25 100644
--- a/src/applications/diffusion/controller/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitController.php
@@ -1,923 +1,924 @@
<?php
final class DiffusionCommitController extends DiffusionController {
const CHANGES_LIMIT = 100;
private $auditAuthorityPHIDs;
private $highlightedAudits;
public function willProcessRequest(array $data) {
// This controller doesn't use blob/path stuff, just pass the dictionary
// in directly instead of using the AphrontRequest parsing mechanism.
$drequest = DiffusionRequest::newFromDictionary($data);
$this->diffusionRequest = $drequest;
}
public function processRequest() {
$drequest = $this->getDiffusionRequest();
$request = $this->getRequest();
$user = $request->getUser();
if ($request->getStr('diff')) {
return $this->buildRawDiffResponse($drequest);
}
$callsign = $drequest->getRepository()->getCallsign();
$content = array();
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
if (!$commit) {
$query = DiffusionExistsQuery::newFromDiffusionRequest($drequest);
$exists = $query->loadExistentialData();
if (!$exists) {
return new Aphront404Response();
}
return $this->buildStandardPageResponse(
id(new AphrontErrorView())
->setTitle('Error displaying commit.')
->appendChild('Failed to load the commit because the commit has not '.
'been parsed yet.'),
array('title' => 'Commit Still Parsing')
);
}
$commit_data = $drequest->loadCommitData();
$commit->attachCommitData($commit_data);
$top_anchor = id(new PhabricatorAnchorView())
->setAnchorName('top')
->setNavigationMarker(true);
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
$changesets = null;
if ($is_foreign) {
$subpath = $commit_data->getCommitDetail('svn-subpath');
$error_panel = new AphrontErrorView();
$error_panel->setTitle('Commit Not Tracked');
$error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$error_panel->appendChild(
"This Diffusion repository is configured to track only one ".
"subdirectory of the entire Subversion repository, and this commit ".
"didn't affect the tracked subdirectory ('".
phutil_escape_html($subpath)."'), so no information is available.");
$content[] = $error_panel;
$content[] = $top_anchor;
} else {
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
require_celerity_resource('diffusion-commit-view-css');
require_celerity_resource('phabricator-remarkup-css');
$parent_query = DiffusionCommitParentsQuery::newFromDiffusionRequest(
$drequest);
$headsup_view = id(new PhabricatorHeaderView())
->setHeader('Commit Detail');
$headsup_actions = $this->renderHeadsupActionList($commit, $repository);
$commit_properties = $this->loadCommitProperties(
$commit,
$commit_data,
$parent_query->loadParents()
);
$property_list = id(new PhabricatorPropertyListView())
->setHasKeyboardShortcuts(true);
foreach ($commit_properties as $key => $value) {
$property_list->addProperty($key, $value);
}
$property_list->addTextContent(
'<div class="diffusion-commit-message phabricator-remarkup">'.
- $engine->markupText($commit_data->getCommitMessage()).
+ phutil_safe_html(
+ $engine->markupText($commit_data->getCommitMessage())).
'</div>'
);
$content[] = $top_anchor;
$content[] = $headsup_view;
$content[] = $headsup_actions;
$content[] = $property_list;
}
$query = new PhabricatorAuditQuery();
$query->withCommitPHIDs(array($commit->getPHID()));
$audit_requests = $query->execute();
$this->auditAuthorityPHIDs =
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$content[] = $this->buildAuditTable($commit, $audit_requests);
$content[] = $this->buildComments($commit);
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$changes = $change_query->loadChanges();
$content[] = $this->buildMergesTable($commit);
$owners_paths = array();
if ($this->highlightedAudits) {
$packages = id(new PhabricatorOwnersPackage())->loadAllWhere(
'phid IN (%Ls)',
mpull($this->highlightedAudits, 'getAuditorPHID'));
if ($packages) {
$owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere(
'repositoryPHID = %s AND packageID IN (%Ld)',
$repository->getPHID(),
mpull($packages, 'getID'));
}
}
$change_table = new DiffusionCommitChangeTableView();
$change_table->setDiffusionRequest($drequest);
$change_table->setPathChanges($changes);
$change_table->setOwnersPaths($owners_paths);
$count = count($changes);
$bad_commit = null;
if ($count == 0) {
$bad_commit = queryfx_one(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT * FROM %T WHERE fullCommitName = %s',
PhabricatorRepository::TABLE_BADCOMMIT,
'r'.$callsign.$commit->getCommitIdentifier());
}
$pane_id = null;
if ($bad_commit) {
$error_panel = new AphrontErrorView();
$error_panel->setTitle('Bad Commit');
$error_panel->appendChild(
phutil_escape_html($bad_commit['description']));
$content[] = $error_panel;
} else if ($is_foreign) {
// Don't render anything else.
} else if (!count($changes)) {
$no_changes = new AphrontErrorView();
$no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$no_changes->setTitle('Not Yet Parsed');
// TODO: This can also happen with weird SVN changes that don't do
// anything (or only alter properties?), although the real no-changes case
// is extremely rare and might be impossible to produce organically. We
// should probably write some kind of "Nothing Happened!" change into the
// DB once we parse these changes so we can distinguish between
// "not parsed yet" and "no changes".
$no_changes->appendChild(
"This commit hasn't been fully parsed yet (or doesn't affect any ".
"paths).");
$content[] = $no_changes;
} else {
$change_panel = new AphrontPanelView();
$change_panel->setHeader("Changes (".number_format($count).")");
$change_panel->setID('toc');
if ($count > self::CHANGES_LIMIT) {
$show_all_button = phutil_tag(
'a',
array(
'class' => 'button green',
'href' => '?show_all=true',
),
'Show All Changes');
$warning_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle('Very Large Commit')
->appendChild(
"<p>This commit is very large. Load each file individually.</p>");
$change_panel->appendChild($warning_view);
$change_panel->addButton($show_all_button);
}
$change_panel->appendChild($change_table);
$change_panel->setNoBackground();
$content[] = $change_panel;
$changesets = DiffusionPathChange::convertToDifferentialChangesets(
$changes);
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$vcs_supports_directory_changes = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$vcs_supports_directory_changes = false;
break;
default:
throw new Exception("Unknown VCS.");
}
$references = array();
foreach ($changesets as $key => $changeset) {
$file_type = $changeset->getFileType();
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
if (!$vcs_supports_directory_changes) {
unset($changesets[$key]);
continue;
}
}
$references[$key] = $drequest->generateURI(
array(
'action' => 'rendering-ref',
'path' => $changeset->getFilename(),
));
}
// TODO: Some parts of the views still rely on properties of the
// DifferentialChangeset. Make the objects ephemeral to make sure we don't
// accidentally save them, and then set their ID to the appropriate ID for
// this application (the path IDs).
$path_ids = array_flip(mpull($changes, 'getPath'));
foreach ($changesets as $changeset) {
$changeset->makeEphemeral();
$changeset->setID($path_ids[$changeset->getFilename()]);
}
if ($count <= self::CHANGES_LIMIT) {
$visible_changesets = $changesets;
} else {
$visible_changesets = array();
$inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
'commitPHID = %s AND (auditCommentID IS NOT NULL OR authorPHID = %s)',
$commit->getPHID(),
$user->getPHID());
$path_ids = mpull($inlines, null, 'getPathID');
foreach ($changesets as $key => $changeset) {
if (array_key_exists($changeset->getID(), $path_ids)) {
$visible_changesets[$key] = $changeset;
}
}
}
$change_list_title = DiffusionView::nameCommit(
$repository,
$commit->getCommitIdentifier()
);
$change_list = new DifferentialChangesetListView();
$change_list->setTitle($change_list_title);
$change_list->setChangesets($changesets);
$change_list->setVisibleChangesets($visible_changesets);
$change_list->setRenderingReferences($references);
$change_list->setRenderURI('/diffusion/'.$callsign.'/diff/');
$change_list->setRepository($repository);
$change_list->setUser($user);
// pick the first branch for "Browse in Diffusion" View Option
$branches = $commit_data->getCommitDetail('seenOnBranches', array());
$first_branch = reset($branches);
$change_list->setBranch($first_branch);
$change_list->setStandaloneURI(
'/diffusion/'.$callsign.'/diff/');
$change_list->setRawFileURIs(
// TODO: Implement this, somewhat tricky if there's an octopus merge
// or whatever?
null,
'/diffusion/'.$callsign.'/diff/?view=r');
$change_list->setInlineCommentControllerURI(
'/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/');
$change_references = array();
foreach ($changesets as $key => $changeset) {
$change_references[$changeset->getID()] = $references[$key];
}
$change_table->setRenderingReferences($change_references);
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
$pane_id = celerity_generate_unique_node_id();
$add_comment_view = $this->renderAddCommentPanel($commit,
$audit_requests,
$pane_id);
$main_pane = phutil_render_tag(
'div',
array(
'id' => $pane_id
),
$change_list->render().
id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true)
->render().
$add_comment_view);
$content[] = $main_pane;
}
$commit_id = 'r'.$callsign.$commit->getCommitIdentifier();
$short_name = DiffusionView::nameCommit(
$repository,
$commit->getCommitIdentifier()
);
$crumbs = $this->buildCrumbs(array(
'commit' => true,
));
if ($changesets) {
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setAnchorName('top')
->setTitle($short_name)
->setBaseURI(new PhutilURI('/'.$commit_id))
->build($changesets)
->setCrumbs($crumbs)
->appendChild($content);
$content = $nav;
} else {
$content = array($crumbs, $content);
}
return $this->buildApplicationPage(
$content,
array(
'title' => $commit_id
)
);
}
private function loadCommitProperties(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data,
array $parents) {
assert_instances_of($parents, 'PhabricatorRepositoryCommit');
$user = $this->getRequest()->getUser();
$commit_phid = $commit->getPHID();
$edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($commit_phid))
->withEdgeTypes(array(
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK,
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT
))
->execute();
$task_phids = array_keys(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK]
);
$proj_phids = array_keys(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT]
);
$phids = array_merge($task_phids, $proj_phids);
if ($data->getCommitDetail('authorPHID')) {
$phids[] = $data->getCommitDetail('authorPHID');
}
if ($data->getCommitDetail('reviewerPHID')) {
$phids[] = $data->getCommitDetail('reviewerPHID');
}
if ($data->getCommitDetail('committerPHID')) {
$phids[] = $data->getCommitDetail('committerPHID');
}
if ($data->getCommitDetail('differential.revisionPHID')) {
$phids[] = $data->getCommitDetail('differential.revisionPHID');
}
if ($parents) {
foreach ($parents as $parent) {
$phids[] = $parent->getPHID();
}
}
$handles = array();
if ($phids) {
$handles = $this->loadViewerHandles($phids);
}
$props = array();
if ($commit->getAuditStatus()) {
$status = PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus());
$props['Status'] = phutil_tag(
'strong',
array(),
$status);
}
$props['Committed'] = phabricator_datetime($commit->getEpoch(), $user);
$author_phid = $data->getCommitDetail('authorPHID');
if ($data->getCommitDetail('authorPHID')) {
$props['Author'] = $handles[$author_phid]->renderLink();
} else {
$props['Author'] = phutil_escape_html($data->getAuthorName());
}
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
if ($reviewer_phid) {
$props['Reviewer'] = $handles[$reviewer_phid]->renderLink();
}
$committer = $data->getCommitDetail('committer');
if ($committer) {
$committer_phid = $data->getCommitDetail('committerPHID');
if ($data->getCommitDetail('committerPHID')) {
$props['Committer'] = $handles[$committer_phid]->renderLink();
} else {
$props['Committer'] = phutil_escape_html($committer);
}
}
$revision_phid = $data->getCommitDetail('differential.revisionPHID');
if ($revision_phid) {
$props['Differential Revision'] = $handles[$revision_phid]->renderLink();
}
if ($parents) {
$parent_links = array();
foreach ($parents as $parent) {
$parent_links[] = $handles[$parent->getPHID()]->renderLink();
}
$props['Parents'] = implode(' &middot; ', $parent_links);
}
$request = $this->getDiffusionRequest();
$props['Branches'] = '<span id="commit-branches">Unknown</span>';
$props['Tags'] = '<span id="commit-tags">Unknown</span>';
$callsign = $request->getRepository()->getCallsign();
$root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
Javelin::initBehavior(
'diffusion-commit-branches',
array(
$root.'/branches/' => 'commit-branches',
$root.'/tags/' => 'commit-tags',
));
$refs = $this->buildRefs($request);
if ($refs) {
$props['References'] = $refs;
}
if ($task_phids) {
$task_list = array();
foreach ($task_phids as $phid) {
$task_list[] = $handles[$phid]->renderLink();
}
$task_list = implode('<br />', $task_list);
$props['Tasks'] = $task_list;
}
if ($proj_phids) {
$proj_list = array();
foreach ($proj_phids as $phid) {
$proj_list[] = $handles[$phid]->renderLink();
}
$proj_list = implode('<br />', $proj_list);
$props['Projects'] = $proj_list;
}
return $props;
}
private function buildAuditTable(
PhabricatorRepositoryCommit $commit,
array $audits) {
assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$view = new PhabricatorAuditListView();
$view->setAudits($audits);
$view->setCommits(array($commit));
$view->setUser($user);
$view->setShowDescriptions(false);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$view->setAuthorityPHIDs($this->auditAuthorityPHIDs);
$this->highlightedAudits = $view->getHighlightedAudits();
$panel = new AphrontPanelView();
$panel->setHeader('Audits');
$panel->setCaption('Audits you are responsible for are highlighted.');
$panel->appendChild($view);
$panel->setNoBackground();
return $panel;
}
private function buildComments(PhabricatorRepositoryCommit $commit) {
$user = $this->getRequest()->getUser();
$comments = id(new PhabricatorAuditComment())->loadAllWhere(
'targetPHID = %s ORDER BY dateCreated ASC',
$commit->getPHID());
$inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
'commitPHID = %s AND auditCommentID IS NOT NULL',
$commit->getPHID());
$path_ids = mpull($inlines, 'getPathID');
$path_map = array();
if ($path_ids) {
$path_map = id(new DiffusionPathQuery())
->withPathIDs($path_ids)
->execute();
$path_map = ipull($path_map, 'path', 'id');
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
foreach ($comments as $comment) {
$engine->addObject(
$comment,
PhabricatorAuditComment::MARKUP_FIELD_BODY);
}
foreach ($inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$view = new DiffusionCommentListView();
$view->setMarkupEngine($engine);
$view->setUser($user);
$view->setComments($comments);
$view->setInlineComments($inlines);
$view->setPathMap($path_map);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return $view;
}
private function renderAddCommentPanel(
PhabricatorRepositoryCommit $commit,
array $audit_requests,
$pane_id = null) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'diffusion-audit-'.$commit->getID());
if ($draft) {
$draft = $draft->getDraft();
} else {
$draft = null;
}
$actions = $this->getAuditActions($commit, $audit_requests);
$form = id(new AphrontFormView())
->setUser($user)
->setAction('/audit/addcomment/')
->addHiddenInput('commit', $commit->getPHID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Action')
->setName('action')
->setID('audit-action')
->setOptions($actions))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('Add Auditors')
->setName('auditors')
->setControlID('add-auditors')
->setControlStyle('display: none')
->setID('add-auditors-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('Add CCs')
->setName('ccs')
->setControlID('add-ccs')
->setControlStyle('display: none')
->setID('add-ccs-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel('Comments')
->setName('content')
->setValue($draft)
->setID('audit-content')
->setUser($user))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? 'Submit' : 'Cook the Books'));
$panel = new AphrontPanelView();
$panel->setHeader($is_serious ? 'Audit Commit' : 'Creative Accounting');
$panel->appendChild($form);
$panel->addClass('aphront-panel-accent');
$panel->addClass('aphront-panel-flush');
require_celerity_resource('phabricator-transaction-view-css');
Javelin::initBehavior(
'differential-add-reviewers-and-ccs',
array(
'dynamic' => array(
'add-auditors-tokenizer' => array(
'actions' => array('add_auditors' => 1),
'src' => '/typeahead/common/users/',
'row' => 'add-auditors',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => 'Type a user name...',
),
'add-ccs-tokenizer' => array(
'actions' => array('add_ccs' => 1),
'src' => '/typeahead/common/mailable/',
'row' => 'add-ccs',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => 'Type a user or mailing list...',
),
),
'select' => 'audit-action',
));
Javelin::initBehavior('differential-feedback-preview', array(
'uri' => '/audit/preview/'.$commit->getID().'/',
'preview' => 'audit-preview',
'content' => 'audit-content',
'action' => 'audit-action',
'previewTokenizers' => array(
'auditors' => 'add-auditors-tokenizer',
'ccs' => 'add-ccs-tokenizer',
),
'inline' => 'inline-comment-preview',
'inlineuri' => '/diffusion/inline/preview/'.$commit->getPHID().'/',
));
$preview_panel =
'<div class="aphront-panel-preview aphront-panel-flush">
<div id="audit-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
<div id="inline-comment-preview">
</div>
</div>';
return
phutil_render_tag(
'div',
array(
'class' => 'differential-add-comment-panel',
),
$panel->render().
$preview_panel);
}
/**
* Return a map of available audit actions for rendering into a <select />.
* This shows the user valid actions, and does not show nonsense/invalid
* actions (like closing an already-closed commit, or resigning from a commit
* you have no association with).
*/
private function getAuditActions(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$user_is_author = ($commit->getAuthorPHID() == $user->getPHID());
$user_request = null;
foreach ($audit_requests as $audit_request) {
if ($audit_request->getAuditorPHID() == $user->getPHID()) {
$user_request = $audit_request;
break;
}
}
$actions = array();
$actions[PhabricatorAuditActionConstants::COMMENT] = true;
$actions[PhabricatorAuditActionConstants::ADD_CCS] = true;
$actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
// We allow you to accept your own commits. A use case here is that you
// notice an issue with your own commit and "Raise Concern" as an indicator
// to other auditors that you're on top of the issue, then later resolve it
// and "Accept". You can not accept on behalf of projects or packages,
// however.
$actions[PhabricatorAuditActionConstants::ACCEPT] = true;
$actions[PhabricatorAuditActionConstants::CONCERN] = true;
// To resign, a user must have authority on some request and not be the
// commit's author.
if (!$user_is_author) {
$may_resign = false;
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
foreach ($audit_requests as $request) {
if (empty($authority_map[$request->getAuditorPHID()])) {
continue;
}
$may_resign = true;
break;
}
// If the user has already resigned, don't show "Resign...".
$status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
if ($user_request) {
if ($user_request->getAuditStatus() == $status_resigned) {
$may_resign = false;
}
}
if ($may_resign) {
$actions[PhabricatorAuditActionConstants::RESIGN] = true;
}
}
$status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
$concern_raised = ($commit->getAuditStatus() == $status_concern);
$can_close_option = PhabricatorEnv::getEnvConfig(
'audit.can-author-close-audit');
if ($can_close_option && $user_is_author && $concern_raised) {
$actions[PhabricatorAuditActionConstants::CLOSE] = true;
}
foreach ($actions as $constant => $ignored) {
$actions[$constant] =
PhabricatorAuditActionConstants::getActionName($constant);
}
return $actions;
}
private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
$drequest = $this->getDiffusionRequest();
$limit = 50;
$merge_query = DiffusionMergedCommitsQuery::newFromDiffusionRequest(
$drequest);
$merge_query->setLimit($limit + 1);
$merges = $merge_query->loadMergedCommits();
if (!$merges) {
return null;
}
$caption = null;
if (count($merges) > $limit) {
$merges = array_slice($merges, 0, $limit);
$caption =
"This commit merges more than {$limit} changes. Only the first ".
"{$limit} are shown.";
}
$history_table = new DiffusionHistoryTableView();
$history_table->setUser($this->getRequest()->getUser());
$history_table->setDiffusionRequest($drequest);
$history_table->setHistory($merges);
$history_table->loadRevisions();
$phids = $history_table->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$history_table->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader('Merged Changes');
$panel->setCaption($caption);
$panel->appendChild($history_table);
$panel->setNoBackground();
return $panel;
}
private function renderHeadsupActionList(
PhabricatorRepositoryCommit $commit,
PhabricatorRepository $repository) {
$request = $this->getRequest();
$user = $request->getUser();
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObject($commit);
// TODO -- integrate permissions into whether or not this action is shown
$uri = '/diffusion/'.$repository->getCallSign().'/commit/'.
$commit->getCommitIdentifier().'/edit/';
$action = id(new PhabricatorActionView())
->setName('Edit Commit')
->setHref($uri)
->setIcon('edit');
$actions->addAction($action);
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$action = id(new PhabricatorActionView())
->setName('Edit Maniphest Tasks')
->setIcon('attach')
->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/')
->setWorkflow(true);
$actions->addAction($action);
}
if ($user->getIsAdmin()) {
$action = id(new PhabricatorActionView())
->setName('MetaMTA Transcripts')
->setIcon('file')
->setHref('/mail/?phid='.$commit->getPHID());
$actions->addAction($action);
}
$action = id(new PhabricatorActionView())
->setName('Herald Transcripts')
->setIcon('file')
->setHref('/herald/transcript/?phid='.$commit->getPHID())
->setWorkflow(true);
$actions->addAction($action);
$action = id(new PhabricatorActionView())
->setName('Download Raw Diff')
->setHref($request->getRequestURI()->alter('diff', true))
->setIcon('download');
$actions->addAction($action);
return $actions;
}
private function buildRefs(DiffusionRequest $request) {
// Not turning this into a proper Query class since it's pretty simple,
// one-off, and Git-specific.
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$repository = $request->getRepository();
if ($repository->getVersionControlSystem() != $type_git) {
return null;
}
list($stdout) = $repository->execxLocalCommand(
'log --format=%s -n 1 %s --',
'%d',
$request->getCommit());
// %d, gives a weird output format
// similar to (remote/one, remote/two, remote/three)
$refs = trim($stdout, "() \n");
if (!$refs) {
return null;
}
$refs = explode(',', $refs);
$refs = array_map('trim', $refs);
$ref_links = array();
foreach ($refs as $ref) {
$ref_links[] = phutil_tag(
'a',
array(
'href' => $request->generateURI(
array(
'action' => 'browse',
'branch' => $ref,
)),
),
$ref);
}
$ref_links = implode(', ', $ref_links);
return $ref_links;
}
private function buildRawDiffResponse(DiffusionRequest $drequest) {
$raw_query = DiffusionRawDiffQuery::newFromDiffusionRequest($drequest);
$raw_diff = $raw_query->loadRawDiff();
$file = PhabricatorFile::buildFromFileDataOrHash(
$raw_diff,
array(
'name' => $drequest->getCommit().'.diff',
));
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
}
diff --git a/src/applications/diffusion/query/browse/DiffusionBrowseQuery.php b/src/applications/diffusion/query/browse/DiffusionBrowseQuery.php
index 832f4784d9..a4d2d44ceb 100644
--- a/src/applications/diffusion/query/browse/DiffusionBrowseQuery.php
+++ b/src/applications/diffusion/query/browse/DiffusionBrowseQuery.php
@@ -1,146 +1,146 @@
<?php
abstract class DiffusionBrowseQuery {
private $request;
protected $reason;
protected $existedAtCommit;
protected $deletedAtCommit;
protected $validityOnly;
const REASON_IS_FILE = 'is-file';
const REASON_IS_DELETED = 'is-deleted';
const REASON_IS_NONEXISTENT = 'nonexistent';
const REASON_BAD_COMMIT = 'bad-commit';
const REASON_IS_EMPTY = 'empty';
const REASON_IS_UNTRACKED_PARENT = 'untracked-parent';
final private function __construct() {
// <private>
}
final public static function newFromDiffusionRequest(
DiffusionRequest $request) {
$repository = $request->getRepository();
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
// TODO: Verify local-path?
$query = new DiffusionGitBrowseQuery();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$query = new DiffusionMercurialBrowseQuery();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$query = new DiffusionSvnBrowseQuery();
break;
default:
throw new Exception("Unsupported VCS!");
}
$query->request = $request;
return $query;
}
final protected function getRequest() {
return $this->request;
}
final public function getReasonForEmptyResultSet() {
return $this->reason;
}
final public function getExistedAtCommit() {
return $this->existedAtCommit;
}
final public function getDeletedAtCommit() {
return $this->deletedAtCommit;
}
final public function loadPaths() {
return $this->executeQuery();
}
final public function shouldOnlyTestValidity() {
return $this->validityOnly;
}
final public function needValidityOnly($need_validity_only) {
$this->validityOnly = $need_validity_only;
return $this;
}
final public function renderReadme(array $results) {
$drequest = $this->getRequest();
$readme = null;
foreach ($results as $result) {
$file_type = $result->getFileType();
if (($file_type != ArcanistDiffChangeType::FILE_NORMAL) &&
($file_type != ArcanistDiffChangeType::FILE_TEXT)) {
// Skip directories, etc.
continue;
}
$path = $result->getPath();
if (preg_match('/^readme(|\.txt|\.remarkup|\.rainbow)$/i', $path)) {
$readme = $result;
break;
}
}
if (!$readme) {
return null;
}
$readme_request = DiffusionRequest::newFromDictionary(
array(
'repository' => $drequest->getRepository(),
'commit' => $drequest->getStableCommitName(),
'path' => $readme->getFullPath(),
));
$content_query = DiffusionFileContentQuery::newFromDiffusionRequest(
$readme_request);
$content_query->loadFileContent();
$readme_content = $content_query->getRawData();
if (preg_match('/\\.txt$/', $readme->getPath())) {
$readme_content = phutil_escape_html_newlines($readme_content);
$class = null;
} else if (preg_match('/\\.rainbow$/', $readme->getPath())) {
$highlighter = new PhutilRainbowSyntaxHighlighter();
$readme_content = $highlighter
->getHighlightFuture($readme_content)
->resolve();
$readme_content = nl2br($readme_content);
$readme_content = phutil_safe_html($readme_content);
require_celerity_resource('syntax-highlighting-css');
$class = 'remarkup-code';
} else {
// Markup extensionless files as remarkup so we get links and such.
$engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine();
- $readme_content = $engine->markupText($readme_content);
+ $readme_content = phutil_safe_html($engine->markupText($readme_content));
$class = 'phabricator-remarkup';
}
$readme_content = phutil_tag(
'div',
array(
'class' => $class,
),
$readme_content);
return $readme_content;
}
abstract protected function executeQuery();
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php
index 88a62a3dab..576758f2e9 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php
@@ -1,230 +1,230 @@
<?php
final class PhabricatorPeopleProfileController
extends PhabricatorPeopleController {
private $username;
private $page;
private $profileUser;
public function willProcessRequest(array $data) {
$this->username = idx($data, 'username');
$this->page = idx($data, 'page');
}
public function getProfileUser() {
return $this->profileUser;
}
public function processRequest() {
$viewer = $this->getRequest()->getUser();
$user = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$this->username);
if (!$user) {
return new Aphront404Response();
}
$this->profileUser = $user;
require_celerity_resource('phabricator-profile-css');
$profile = id(new PhabricatorUserProfile())->loadOneWhere(
'userPHID = %s',
$user->getPHID());
if (!$profile) {
$profile = new PhabricatorUserProfile();
}
$username = phutil_escape_uri($user->getUserName());
$external_arrow = "\xE2\x86\x97";
$conpherence_uri =
new PhutilURI('/conpherence/new/?participant='.$user->getPHID());
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/p/'.$username.'/'));
$nav->addFilter('feed', 'Feed');
$nav->addMenuItem(
id(new PhabricatorMenuItemView())
->setName(pht('Conpherence').' '.$external_arrow)
->setHref($conpherence_uri)
);
$nav->addFilter('about', 'About');
$nav->addLabel('Activity');
$nav->addFilter(
null,
"Revisions {$external_arrow}",
'/differential/filter/revisions/'.$username.'/');
$nav->addFilter(
null,
"Tasks {$external_arrow}",
'/maniphest/view/action/?users='.$user->getPHID());
$nav->addFilter(
null,
"Commits {$external_arrow}",
'/audit/view/author/'.$username.'/');
$nav->addFilter(
null,
"Lint Messages {$external_arrow}",
'/diffusion/lint/?owner[0]='.$user->getPHID());
$oauths = id(new PhabricatorUserOAuthInfo())->loadAllWhere(
'userID = %d',
$user->getID());
$oauths = mpull($oauths, null, 'getOAuthProvider');
$providers = PhabricatorOAuthProvider::getAllProviders();
$added_label = false;
foreach ($providers as $provider) {
if (!$provider->isProviderEnabled()) {
continue;
}
$provider_key = $provider->getProviderKey();
if (!isset($oauths[$provider_key])) {
continue;
}
$name = $provider->getProviderName().' Profile';
$href = $oauths[$provider_key]->getAccountURI();
if ($href) {
if (!$added_label) {
$nav->addLabel('Linked Accounts');
$added_label = true;
}
$nav->addFilter(null, $name.' '.$external_arrow, $href);
}
}
$this->page = $nav->selectFilter($this->page, 'feed');
switch ($this->page) {
case 'feed':
$content = $this->renderUserFeed($user);
break;
case 'about':
$content = $this->renderBasicInformation($user, $profile);
break;
default:
throw new Exception("Unknown page '{$this->page}'!");
}
$picture = $user->loadProfileImageURI();
$header = new PhabricatorProfileHeaderView();
$header
->setProfilePicture($picture)
->setName($user->getUserName().' ('.$user->getRealName().')')
->setDescription($profile->getTitle());
if ($user->getIsDisabled()) {
$header->setStatus('Disabled');
} else {
$statuses = id(new PhabricatorUserStatus())->loadCurrentStatuses(
array($user->getPHID()));
if ($statuses) {
$header->setStatus(reset($statuses)->getTerseSummary($viewer));
}
}
$nav->appendChild($header);
$content = '<div style="padding: 1em;">'.$content.'</div>';
$header->appendChild($content);
if ($user->getPHID() == $viewer->getPHID()) {
$nav->addFilter(null, 'Edit Profile...', '/settings/panel/profile/');
}
if ($viewer->getIsAdmin()) {
$nav->addFilter(
null,
'Administrate User...',
'/people/edit/'.$user->getID().'/');
}
return $this->buildApplicationPage(
$nav,
array(
'title' => $user->getUsername(),
));
}
private function renderBasicInformation($user, $profile) {
$blurb = nonempty(
$profile->getBlurb(),
'//Nothing is known about this rare specimen.//');
$engine = PhabricatorMarkupEngine::newProfileMarkupEngine();
- $blurb = $engine->markupText($blurb);
+ $blurb = phutil_safe_html($engine->markupText($blurb));
$viewer = $this->getRequest()->getUser();
$content =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">Basic Information</h1>
<div class="phabricator-profile-info-pane">
<table class="phabricator-profile-info-table">
<tr>
<th>PHID</th>
<td>'.phutil_escape_html($user->getPHID()).'</td>
</tr>
<tr>
<th>User Since</th>
<td>'.phabricator_datetime($user->getDateCreated(),
$viewer).
'</td>
</tr>
</table>
</div>
</div>';
$content .=
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">Flavor Text</h1>
<div class="phabricator-profile-info-pane">
<table class="phabricator-profile-info-table">
<tr>
<th>Blurb</th>
<td>'.$blurb.'</td>
</tr>
</table>
</div>
</div>';
return $content;
}
private function renderUserFeed(PhabricatorUser $user) {
$viewer = $this->getRequest()->getUser();
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$user->getPHID(),
));
$query->setLimit(100);
$query->setViewer($viewer);
$stories = $query->execute();
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($viewer);
$view = $builder->buildView();
return
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">Activity Feed</h1>
<div class="phabricator-profile-info-pane">
'.$view->render().'
</div>
</div>';
}
}
diff --git a/src/applications/phame/skins/PhameBasicBlogSkin.php b/src/applications/phame/skins/PhameBasicBlogSkin.php
index 74d70f4f52..569bb6b3b3 100644
--- a/src/applications/phame/skins/PhameBasicBlogSkin.php
+++ b/src/applications/phame/skins/PhameBasicBlogSkin.php
@@ -1,315 +1,323 @@
<?php
/**
* @task paging Paging
* @task internal Internals
* @group phame
*/
abstract class PhameBasicBlogSkin extends PhameBlogSkin {
private $pager;
private $title;
private $description;
private $oGType;
private $uriPath;
public function setURIPath($uri_path) {
$this->uriPath = $uri_path;
return $this;
}
public function getURIPath() {
return $this->uriPath;
}
protected function setOGType($og_type) {
$this->oGType = $og_type;
return $this;
}
protected function getOGType() {
return $this->oGType;
}
protected function setDescription($description) {
$this->description = $description;
return $this;
}
protected function getDescription() {
return $this->description;
}
protected function setTitle($title) {
$this->title = $title;
return $this;
}
protected function getTitle() {
return $this->title;
}
public function processRequest() {
$request = $this->getRequest();
$content = $this->renderContent($request);
if (!$content) {
$content = $this->render404Page();
}
$content = array(
$this->renderHeader(),
$content,
$this->renderFooter(),
);
$view = id(new PhabricatorBarePageView())
->setRequest($request)
->setController($this)
->setDeviceReady(true)
->setTitle($this->getBlog()->getName());
if ($this->getPreview()) {
$view->setFrameable(true);
}
+
$view->appendChild($content);
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
}
public function getSkinName() {
return get_class($this);
}
abstract protected function renderHeader();
abstract protected function renderFooter();
protected function renderPostDetail(PhamePostView $post) {
return $post;
}
protected function renderPostList(array $posts) {
$summaries = array();
foreach ($posts as $post) {
$summaries[] = $post->renderWithSummary();
}
- $list = phutil_render_tag(
+ $list = phutil_tag(
'div',
array(
'class' => 'phame-post-list',
),
id(new AphrontNullView())->appendChild($summaries)->render());
- $pager = $this->renderOlderPageLink().$this->renderNewerPageLink();
- if ($pager) {
+ $pager = null;
+ if ($this->renderOlderPageLink() || $this->renderNewerPageLink()) {
$pager = phutil_tag(
'div',
array(
'class' => 'phame-pager',
+ ),
+ array(
+ $this->renderOlderPageLink(),
+ $this->renderNewerPageLink(),
));
}
- return $list.$pager;
+ return array(
+ $list,
+ $pager,
+ );
}
protected function render404Page() {
return '<h2>404 Not Found</h2>';
}
final public function getResourceURI($resource) {
$root = $this->getSpecification()->getRootDirectory();
$path = $root.DIRECTORY_SEPARATOR.$resource;
$data = Filesystem::readFile($path);
$hash = PhabricatorHash::digest($data);
$hash = substr($hash, 0, 6);
$id = $this->getBlog()->getID();
$uri = '/phame/r/'.$id.'/'.$hash.'/'.$resource;
$uri = PhabricatorEnv::getCDNURI($uri);
return $uri;
}
/* -( Paging )------------------------------------------------------------- */
/**
* @task paging
*/
public function getPageSize() {
return 100;
}
/**
* @task paging
*/
protected function getOlderPageURI() {
if ($this->pager) {
$next = $this->pager->getNextPageID();
if ($next) {
return $this->getURI('older/'.$next.'/');
}
}
return null;
}
/**
* @task paging
*/
protected function renderOlderPageLink() {
$uri = $this->getOlderPageURI();
if (!$uri) {
return null;
}
return phutil_tag(
'a',
array(
'class' => 'phame-page-link phame-page-older',
'href' => $uri,
),
pht("\xE2\x80\xB9 Older"));
}
/**
* @task paging
*/
protected function getNewerPageURI() {
if ($this->pager) {
$next = $this->pager->getPrevPageID();
if ($next) {
return $this->getURI('newer/'.$next.'/');
}
}
return null;
}
/**
* @task paging
*/
protected function renderNewerPageLink() {
$uri = $this->getNewerPageURI();
if (!$uri) {
return null;
}
return phutil_tag(
'a',
array(
'class' => 'phame-page-link phame-page-newer',
'href' => $uri,
),
pht("Newer \xE2\x80\xBA"));
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
protected function renderContent(AphrontRequest $request) {
$user = $request->getUser();
$matches = null;
$path = $request->getPath();
// default to the blog-wide values
$this->setTitle($this->getBlog()->getName());
$this->setDescription($this->getBlog()->getDescription());
$this->setOGType('website');
$this->setURIPath('');
if (preg_match('@^/post/(?P<name>.*)$@', $path, $matches)) {
$post = id(new PhamePostQuery())
->setViewer($user)
->withBlogPHIDs(array($this->getBlog()->getPHID()))
->withPhameTitles(array($matches['name']))
->executeOne();
if ($post) {
$description = $post->getMarkupText(PhamePost::MARKUP_FIELD_SUMMARY);
$this->setTitle($post->getTitle());
$this->setDescription($description);
$this->setOGType('article');
$this->setURIPath('post/'.$post->getPhameTitle());
$view = head($this->buildPostViews(array($post)));
return $this->renderPostDetail($view);
}
} else {
$pager = new AphrontCursorPagerView();
if (preg_match('@^/older/(?P<before>\d+)/$@', $path, $matches)) {
$pager->setAfterID($matches['before']);
} else if (preg_match('@^/newer/(?P<after>\d)/$@', $path, $matches)) {
$pager->setBeforeID($matches['after']);
} else if (preg_match('@^/$@', $path, $matches)) {
// Just show the first page.
} else {
return null;
}
$pager->setPageSize($this->getPageSize());
$posts = id(new PhamePostQuery())
->setViewer($user)
->withBlogPHIDs(array($this->getBlog()->getPHID()))
->executeWithCursorPager($pager);
$this->pager = $pager;
if ($posts) {
$views = $this->buildPostViews($posts);
return $this->renderPostList($views);
}
}
return null;
}
private function buildPostViews(array $posts) {
assert_instances_of($posts, 'PhamePost');
$user = $this->getRequest()->getUser();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$phids = array();
foreach ($posts as $post) {
$engine->addObject($post, PhamePost::MARKUP_FIELD_BODY);
$engine->addObject($post, PhamePost::MARKUP_FIELD_SUMMARY);
$phids[] = $post->getBloggerPHID();
}
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$engine->process();
$views = array();
foreach ($posts as $post) {
$view = id(new PhamePostView())
->setUser($user)
->setSkin($this)
->setPost($post)
->setBody($engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY))
->setSummary($engine->getOutput($post, PhamePost::MARKUP_FIELD_SUMMARY))
->setAuthor($handles[$post->getBloggerPHID()]);
$post->makeEphemeral();
if (!$post->getDatePublished()) {
$post->setDatePublished(time());
}
$views[] = $view;
}
return $views;
}
}
diff --git a/src/applications/phame/view/PhamePostView.php b/src/applications/phame/view/PhamePostView.php
index 533d4402a5..8c5a47c452 100644
--- a/src/applications/phame/view/PhamePostView.php
+++ b/src/applications/phame/view/PhamePostView.php
@@ -1,242 +1,244 @@
<?php
/**
* @group phame
*/
final class PhamePostView extends AphrontView {
private $post;
private $author;
private $body;
private $skin;
private $summary;
public function setSkin(PhameBlogSkin $skin) {
$this->skin = $skin;
return $this;
}
public function getSkin() {
return $this->skin;
}
public function setAuthor(PhabricatorObjectHandle $author) {
$this->author = $author;
return $this;
}
public function getAuthor() {
return $this->author;
}
public function setPost(PhamePost $post) {
$this->post = $post;
return $this;
}
public function getPost() {
return $this->post;
}
public function setBody($body) {
$this->body = $body;
return $this;
}
public function getBody() {
return $this->body;
}
public function setSummary($summary) {
$this->summary = $summary;
return $this;
}
public function getSummary() {
return $this->summary;
}
public function renderTitle() {
$href = $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle());
return phutil_tag(
'h2',
array(
'class' => 'phame-post-title',
),
phutil_tag(
'a',
array(
'href' => $href,
),
$this->getPost()->getTitle()));
}
public function renderDatePublished() {
return phutil_tag(
'div',
array(
'class' => 'phame-post-date',
),
pht(
'Published on %s by %s',
phabricator_datetime(
$this->getPost()->getDatePublished(),
$this->getUser()),
$this->getAuthor()->getName()));
}
public function renderBody() {
- return phutil_render_tag(
+ return phutil_tag(
'div',
array(
'class' => 'phame-post-body',
),
$this->getBody());
}
public function renderSummary() {
- return phutil_render_tag(
+ return phutil_tag(
'div',
array(
'class' => 'phame-post-body',
),
$this->getSummary());
}
public function renderComments() {
$post = $this->getPost();
switch ($post->getCommentsWidget()) {
case 'facebook':
$comments = $this->renderFacebookComments();
break;
case 'disqus':
$comments = $this->renderDisqusComments();
break;
case 'none':
default:
$comments = null;
break;
}
return $comments;
}
public function render() {
return phutil_tag(
'div',
array(
'class' => 'phame-post',
),
array(
$this->renderTitle(),
$this->renderDatePublished(),
$this->renderBody(),
$this->renderComments(),
));
}
public function renderWithSummary() {
return phutil_tag(
'div',
array(
'class' => 'phame-post',
),
array(
$this->renderTitle(),
$this->renderDatePublished(),
$this->renderSummary(),
));
}
private function renderFacebookComments() {
$fb_id = PhabricatorEnv::getEnvConfig('facebook.application-id');
if (!$fb_id) {
return null;
}
$fb_root = phutil_tag('div',
array(
'id' => 'fb-root',
),
- ''
- );
+ '');
$c_uri = '//connect.facebook.net/en_US/all.js#xfbml=1&appId='.$fb_id;
- $fb_js = jsprintf(
- '<script>(function(d, s, id) {'.
- ' var js, fjs = d.getElementsByTagName(s)[0];'.
- ' if (d.getElementById(id)) return;'.
- ' js = d.createElement(s); js.id = id;'.
- ' js.src = %s;'.
- ' fjs.parentNode.insertBefore(js, fjs);'.
- '}(document, \'script\', \'facebook-jssdk\'));</script>',
- $c_uri
- );
+ $fb_js = phutil_safe_html(
+ jsprintf(
+ '<script>(function(d, s, id) {'.
+ ' var js, fjs = d.getElementsByTagName(s)[0];'.
+ ' if (d.getElementById(id)) return;'.
+ ' js = d.createElement(s); js.id = id;'.
+ ' js.src = %s;'.
+ ' fjs.parentNode.insertBefore(js, fjs);'.
+ '}(document, \'script\', \'facebook-jssdk\'));</script>',
+ $c_uri));
$uri = $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle());
$fb_comments = phutil_tag('div',
array(
'class' => 'fb-comments',
'data-href' => $uri,
'data-num-posts' => 5,
),
- ''
- );
+ '');
- return phutil_render_tag(
+ return phutil_tag(
'div',
array(
'class' => 'phame-comments-facebook',
),
- $fb_root.
- $fb_js.
- $fb_comments);
+ array(
+ $fb_root,
+ $fb_js,
+ $fb_comments,
+ ));
}
private function renderDisqusComments() {
$disqus_shortname = PhabricatorEnv::getEnvConfig('disqus.shortname');
if (!$disqus_shortname) {
return null;
}
$post = $this->getPost();
$disqus_thread = phutil_tag('div',
array(
'id' => 'disqus_thread'
)
);
// protip - try some var disqus_developer = 1; action to test locally
- $disqus_js = jsprintf(
- '<script>'.
- ' var disqus_shortname = "phabricator";'.
- ' var disqus_identifier = %s;'.
- ' var disqus_url = %s;'.
- ' var disqus_title = %s;'.
- '(function() {'.
- ' var dsq = document.createElement("script");'.
- ' dsq.type = "text/javascript";'.
- ' dsq.async = true;'.
- ' dsq.src = "http://" + disqus_shortname + ".disqus.com/embed.js";'.
- '(document.getElementsByTagName("head")[0] ||'.
- ' document.getElementsByTagName("body")[0]).appendChild(dsq);'.
- '})(); </script>',
- $post->getPHID(),
- $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle()),
- $post->getTitle()
- );
+ $disqus_js = phutil_safe_html(
+ jsprintf(
+ '<script>'.
+ ' var disqus_shortname = "phabricator";'.
+ ' var disqus_identifier = %s;'.
+ ' var disqus_url = %s;'.
+ ' var disqus_title = %s;'.
+ '(function() {'.
+ ' var dsq = document.createElement("script");'.
+ ' dsq.type = "text/javascript";'.
+ ' dsq.async = true;'.
+ ' dsq.src = "http://" + disqus_shortname + ".disqus.com/embed.js";'.
+ '(document.getElementsByTagName("head")[0] ||'.
+ ' document.getElementsByTagName("body")[0]).appendChild(dsq);'.
+ '})(); </script>',
+ $post->getPHID(),
+ $this->getSkin()->getURI('post/'.$this->getPost()->getPhameTitle()),
+ $post->getTitle()));
- return phutil_render_tag(
+ return phutil_tag(
'div',
array(
'class' => 'phame-comments-disqus',
),
- $disqus_thread.
- $disqus_js);
+ array(
+ $disqus_thread,
+ $disqus_js,
+ ));
}
}
diff --git a/src/applications/remarkup/conduit/ConduitAPI_remarkup_process_Method.php b/src/applications/remarkup/conduit/ConduitAPI_remarkup_process_Method.php
index 6986488342..7d51f23d26 100644
--- a/src/applications/remarkup/conduit/ConduitAPI_remarkup_process_Method.php
+++ b/src/applications/remarkup/conduit/ConduitAPI_remarkup_process_Method.php
@@ -1,60 +1,67 @@
<?php
final class ConduitAPI_remarkup_process_Method extends ConduitAPIMethod {
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function getMethodDescription() {
return 'Process text through remarkup in phabricator context.';
}
public function defineReturnType() {
return 'nonempty dict';
}
public function defineErrorTypes() {
return array(
'ERR-NO-CONTENT' => 'Content may not be empty.',
'ERR-INVALID-ENGINE' => 'Invalid markup engine.',
);
}
public function defineParamTypes() {
$available_contexts = array_keys($this->getEngineContexts());
$available_contexts = implode(', ', $available_contexts);
return array(
'context' => 'required enum<'.$available_contexts.'>',
'content' => 'required string',
);
}
protected function execute(ConduitAPIRequest $request) {
$content = $request->getValue('content');
$context = $request->getValue('context');
$engine_class = idx($this->getEngineContexts(), $context);
if (!$engine_class) {
throw new ConduitException('ERR-INVALID_ENGINE');
}
$engine = PhabricatorMarkupEngine::$engine_class();
$engine->setConfig('viewer', $request->getUser());
+ $text = $engine->markupText($content);
+ if ($text) {
+ $content = phutil_safe_html($text)->getHTMLContent();
+ } else {
+ $content = '';
+ }
+
$result = array(
- 'content' => $engine->markupText($content),
+ 'content' => $content,
);
return $result;
}
private function getEngineContexts() {
return array(
'phriction' => 'newPhrictionMarkupEngine',
'maniphest' => 'newManiphestMarkupEngine',
'differential' => 'newDifferentialMarkupEngine',
);
}
}
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
index 531ddaba61..2bc75bc744 100644
--- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
@@ -1,456 +1,457 @@
<?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 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(),
));
$query = new PhabricatorObjectHandleData($phids);
$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);
}
$option_markup = implode("\n", $option_markup);
$comments_by_option = array();
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();
}
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!");
}
$result_markup = $this->renderResultMarkup(
$poll,
$options,
$choices,
$comments,
$viewer_choices,
$choices_by_option,
$comments_by_option,
$handles,
$objects);
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) {
assert_instances_of($comments, 'PhabricatorSlowvoteComment');
assert_instances_of($handles, 'PhabricatorObjectHandle');
$viewer = $this->getRequest()->getUser();
$engine = PhabricatorMarkupEngine::newSlowvoteMarkupEngine();
$comment_markup = array();
foreach ($comments as $comment) {
$handle = $handles[$comment->getAuthorPHID()];
- $markup = $engine->markupText($comment->getCommentText());
+ $markup = phutil_safe_html(
+ $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_tag(
'table',
array(
'class' => 'phabricator-slowvote-comments',
),
new PhutilSafeHTML(implode("\n", $comment_markup)));
} else {
$comment_markup = null;
}
return $comment_markup;
}
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 $comments,
array $viewer_choices,
array $choices_by_option,
array $comments_by_option,
array $handles,
array $objects) {
assert_instances_of($options, 'PhabricatorSlowvoteOption');
assert_instances_of($choices, 'PhabricatorSlowvoteChoice');
assert_instances_of($comments, 'PhabricatorSlowvoteComment');
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('<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) {
$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,
)));
}
$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.
'<div style="clear: both;">'.
'<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;
}
}
diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php
index 9a82942558..5e9a96d31d 100644
--- a/src/infrastructure/markup/PhabricatorMarkupEngine.php
+++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php
@@ -1,549 +1,549 @@
<?php
/**
* Manages markup engine selection, configuration, application, caching and
* pipelining.
*
* @{class:PhabricatorMarkupEngine} can be used to render objects which
* implement @{interface:PhabricatorMarkupInterface} in a batched, cache-aware
* way. For example, if you have a list of comments written in remarkup (and
* the objects implement the correct interface) you can render them by first
* building an engine and adding the fields with @{method:addObject}.
*
* $field = 'field:body'; // Field you want to render. Each object exposes
* // one or more fields of markup.
*
* $engine = new PhabricatorMarkupEngine();
* foreach ($comments as $comment) {
* $engine->addObject($comment, $field);
* }
*
* Now, call @{method:process} to perform the actual cache/rendering
* step. This is a heavyweight call which does batched data access and
* transforms the markup into output.
*
* $engine->process();
*
* Finally, do something with the results:
*
* $results = array();
* foreach ($comments as $comment) {
* $results[] = $engine->getOutput($comment, $field);
* }
*
* If you have a single object to render, you can use the convenience method
* @{method:renderOneObject}.
*
* @task markup Markup Pipeline
* @task engine Engine Construction
*/
final class PhabricatorMarkupEngine {
private $objects = array();
private $viewer;
private $version = 2;
/* -( Markup Pipeline )---------------------------------------------------- */
/**
* Convenience method for pushing a single object through the markup
* pipeline.
*
* @param PhabricatorMarkupInterface The object to render.
* @param string The field to render.
* @param PhabricatorUser User viewing the markup.
* @return string Marked up output.
* @task markup
*/
public static function renderOneObject(
PhabricatorMarkupInterface $object,
$field,
PhabricatorUser $viewer) {
return id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->addObject($object, $field)
->process()
->getOutput($object, $field);
}
/**
* Queue an object for markup generation when @{method:process} is
* called. You can retrieve the output later with @{method:getOutput}.
*
* @param PhabricatorMarkupInterface The object to render.
* @param string The field to render.
* @return this
* @task markup
*/
public function addObject(PhabricatorMarkupInterface $object, $field) {
$key = $this->getMarkupFieldKey($object, $field);
$this->objects[$key] = array(
'object' => $object,
'field' => $field,
);
return $this;
}
/**
* Process objects queued with @{method:addObject}. You can then retrieve
* the output with @{method:getOutput}.
*
* @return this
* @task markup
*/
public function process() {
$keys = array();
foreach ($this->objects as $key => $info) {
if (!isset($info['markup'])) {
$keys[] = $key;
}
}
if (!$keys) {
return;
}
$objects = array_select_keys($this->objects, $keys);
// Build all the markup engines. We need an engine for each field whether
// we have a cache or not, since we still need to postprocess the cache.
$engines = array();
foreach ($objects as $key => $info) {
$engines[$key] = $info['object']->newMarkupEngine($info['field']);
$engines[$key]->setConfig('viewer', $this->viewer);
}
// Load or build the preprocessor caches.
$blocks = $this->loadPreprocessorCaches($engines, $objects);
// Finalize the output.
foreach ($objects as $key => $info) {
$data = $blocks[$key]->getCacheData();
$engine = $engines[$key];
$field = $info['field'];
$object = $info['object'];
$output = $engine->postprocessText($data);
$output = $object->didMarkupText($field, $output, $engine);
$this->objects[$key]['output'] = $output;
}
return $this;
}
/**
* Get the output of markup processing for a field queued with
* @{method:addObject}. Before you can call this method, you must call
* @{method:process}.
*
* @param PhabricatorMarkupInterface The object to retrieve.
* @param string The field to retrieve.
* @return string Processed output.
* @task markup
*/
public function getOutput(PhabricatorMarkupInterface $object, $field) {
$key = $this->getMarkupFieldKey($object, $field);
if (empty($this->objects[$key])) {
throw new Exception(
"Call addObject() before getOutput() (key = '{$key}').");
}
if (!isset($this->objects[$key]['output'])) {
throw new Exception(
"Call process() before getOutput().");
}
- return $this->objects[$key]['output'];
+ return new PhutilSafeHTML($this->objects[$key]['output']);
}
/**
* @task markup
*/
private function getMarkupFieldKey(
PhabricatorMarkupInterface $object,
$field) {
return $object->getMarkupFieldKey($field).'@'.$this->version;
}
/**
* @task markup
*/
private function loadPreprocessorCaches(array $engines, array $objects) {
$blocks = array();
$use_cache = array();
foreach ($objects as $key => $info) {
if ($info['object']->shouldUseMarkupCache($info['field'])) {
$use_cache[$key] = true;
}
}
if ($use_cache) {
try {
$blocks = id(new PhabricatorMarkupCache())->loadAllWhere(
'cacheKey IN (%Ls)',
array_keys($use_cache));
$blocks = mpull($blocks, null, 'getCacheKey');
} catch (Exception $ex) {
phlog($ex);
}
}
foreach ($objects as $key => $info) {
if (isset($blocks[$key])) {
// If we already have a preprocessing cache, we don't need to rebuild
// it.
continue;
}
$text = $info['object']->getMarkupText($info['field']);
$data = $engines[$key]->preprocessText($text);
// NOTE: This is just debugging information to help sort out cache issues.
// If one machine is misconfigured and poisoning caches you can use this
// field to hunt it down.
$metadata = array(
'host' => php_uname('n'),
);
$blocks[$key] = id(new PhabricatorMarkupCache())
->setCacheKey($key)
->setCacheData($data)
->setMetadata($metadata);
if (isset($use_cache[$key])) {
// This is just filling a cache and always safe, even on a read pathway.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$blocks[$key]->replace();
unset($unguarded);
}
}
return $blocks;
}
/**
* Set the viewing user. Used to implement object permissions.
*
* @param PhabricatorUser The viewing user.
* @return this
* @task markup
*/
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
/* -( Engine Construction )------------------------------------------------ */
/**
* @task engine
*/
public static function newManiphestMarkupEngine() {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newPhrictionMarkupEngine() {
return self::newMarkupEngine(array(
'header.generate-toc' => true,
));
}
/**
* @task engine
*/
public static function newPhameMarkupEngine() {
return self::newMarkupEngine(array(
'macros' => false,
));
}
/**
* @task engine
*/
public static function newFeedMarkupEngine() {
return self::newMarkupEngine(
array(
'macros' => false,
'youtube' => false,
));
}
/**
* @task engine
*/
public static function newDifferentialMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
'custom-inline' => PhabricatorEnv::getEnvConfig(
'differential.custom-remarkup-rules'),
'custom-block' => PhabricatorEnv::getEnvConfig(
'differential.custom-remarkup-block-rules'),
'differential.diff' => idx($options, 'differential.diff'),
));
}
/**
* @task engine
*/
public static function newDiffusionMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newProfileMarkupEngine() {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newSlowvoteMarkupEngine() {
return self::newMarkupEngine(array(
));
}
public static function newPonderMarkupEngine(array $options = array()) {
return self::newMarkupEngine($options);
}
/**
* @task engine
*/
private static function getMarkupEngineDefaultConfiguration() {
return array(
'pygments' => PhabricatorEnv::getEnvConfig('pygments.enabled'),
'youtube' => PhabricatorEnv::getEnvConfig(
'remarkup.enable-embedded-youtube'),
'custom-inline' => array(),
'custom-block' => array(),
'differential.diff' => null,
'header.generate-toc' => false,
'macros' => true,
'uri.allowed-protocols' => PhabricatorEnv::getEnvConfig(
'uri.allowed-protocols'),
'syntax-highlighter.engine' => PhabricatorEnv::getEnvConfig(
'syntax-highlighter.engine'),
);
}
/**
* @task engine
*/
public static function newMarkupEngine(array $options) {
$options += self::getMarkupEngineDefaultConfiguration();
$engine = new PhutilRemarkupEngine();
$engine->setConfig('preserve-linebreaks', true);
$engine->setConfig('pygments.enabled', $options['pygments']);
$engine->setConfig(
'uri.allowed-protocols',
$options['uri.allowed-protocols']);
$engine->setConfig('differential.diff', $options['differential.diff']);
$engine->setConfig('header.generate-toc', $options['header.generate-toc']);
$engine->setConfig(
'syntax-highlighter.engine',
$options['syntax-highlighter.engine']);
$rules = array();
$rules[] = new PhutilRemarkupRuleEscapeRemarkup();
$rules[] = new PhutilRemarkupRuleMonospace();
$custom_rule_classes = $options['custom-inline'];
if ($custom_rule_classes) {
foreach ($custom_rule_classes as $custom_rule_class) {
$rules[] = newv($custom_rule_class, array());
}
}
$rules[] = new PhutilRemarkupRuleDocumentLink();
if ($options['youtube']) {
$rules[] = new PhabricatorRemarkupRuleYoutube();
}
$rules[] = new PhutilRemarkupRuleHyperlink();
$rules[] = new PhabricatorRemarkupRulePhriction();
$rules[] = new PhabricatorRemarkupRuleDifferentialHandle();
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$rules[] = new PhabricatorRemarkupRuleManiphestHandle();
}
$rules[] = new PhabricatorRemarkupRuleEmbedFile();
$rules[] = new PhabricatorRemarkupRuleDifferential();
$rules[] = new PhabricatorRemarkupRuleDiffusion();
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$rules[] = new PhabricatorRemarkupRuleManiphest();
}
$rules[] = new PhabricatorRemarkupRulePaste();
$rules[] = new PhabricatorRemarkupRuleCountdown();
$rules[] = new PonderRuleQuestion();
if ($options['macros']) {
$rules[] = new PhabricatorRemarkupRuleImageMacro();
$rules[] = new PhabricatorRemarkupRuleMeme();
}
$rules[] = new PhabricatorRemarkupRuleMention();
$rules[] = new PhutilRemarkupRuleEscapeHTML();
$rules[] = new PhutilRemarkupRuleBold();
$rules[] = new PhutilRemarkupRuleItalic();
$rules[] = new PhutilRemarkupRuleDel();
$blocks = array();
$blocks[] = new PhutilRemarkupEngineRemarkupQuotesBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupLiteralBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupHeaderBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupListBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupCodeBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupNoteBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupTableBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupSimpleTableBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupDefaultBlockRule();
$custom_block_rule_classes = $options['custom-block'];
if ($custom_block_rule_classes) {
foreach ($custom_block_rule_classes as $custom_block_rule_class) {
$blocks[] = newv($custom_block_rule_class, array());
}
}
foreach ($blocks as $block) {
if ($block instanceof PhutilRemarkupEngineRemarkupLiteralBlockRule) {
$literal_rules = array();
$literal_rules[] = new PhutilRemarkupRuleEscapeHTML();
$literal_rules[] = new PhutilRemarkupRuleLinebreaks();
$block->setMarkupRules($literal_rules);
} else if (
!($block instanceof PhutilRemarkupEngineRemarkupCodeBlockRule)) {
$block->setMarkupRules($rules);
}
}
$engine->setBlockRules($blocks);
return $engine;
}
public static function extractPHIDsFromMentions(array $content_blocks) {
$mentions = array();
$engine = self::newDifferentialMarkupEngine();
foreach ($content_blocks as $content_block) {
$engine->markupText($content_block);
$phids = $engine->getTextMetadata(
PhabricatorRemarkupRuleMention::KEY_MENTIONED,
array());
$mentions += $phids;
}
return $mentions;
}
public static function extractFilePHIDsFromEmbeddedFiles(
array $content_blocks) {
$files = array();
$engine = self::newDifferentialMarkupEngine();
foreach ($content_blocks as $content_block) {
$engine->markupText($content_block);
$ids = $engine->getTextMetadata(
PhabricatorRemarkupRuleEmbedFile::KEY_EMBED_FILE_PHIDS,
array());
$files += $ids;
}
return $files;
}
/**
* Produce a corpus summary, in a way that shortens the underlying text
* without truncating it somewhere awkward.
*
* TODO: We could do a better job of this.
*
* @param string Remarkup corpus to summarize.
* @return string Summarized corpus.
*/
public static function summarize($corpus) {
// Major goals here are:
// - Don't split in the middle of a character (utf-8).
// - Don't split in the middle of, e.g., **bold** text, since
// we end up with hanging '**' in the summary.
// - Try not to pick an image macro, header, embedded file, etc.
// - Hopefully don't return too much text. We don't explicitly limit
// this right now.
$blocks = preg_split("/\n *\n\s*/", trim($corpus));
$best = null;
foreach ($blocks as $block) {
// This is a test for normal spaces in the block, i.e. a heuristic to
// distinguish standard paragraphs from things like image macros. It may
// not work well for non-latin text. We prefer to summarize with a
// paragraph of normal words over an image macro, if possible.
$has_space = preg_match('/\w\s\w/', $block);
// This is a test to find embedded images and headers. We prefer to
// summarize with a normal paragraph over a header or an embedded object,
// if possible.
$has_embed = preg_match('/^[{=]/', $block);
if ($has_space && !$has_embed) {
// This seems like a good summary, so return it.
return $block;
}
if (!$best) {
// This is the first block we found; if everything is garbage just
// use the first block.
$best = $block;
}
}
return $best;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 16, 4:50 AM (1 d, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
272432
Default Alt Text
(103 KB)

Event Timeline