Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/aphront/AphrontController.php b/src/aphront/AphrontController.php
index 55c2f9f30e..df6cff6abc 100644
--- a/src/aphront/AphrontController.php
+++ b/src/aphront/AphrontController.php
@@ -1,103 +1,107 @@
<?php
abstract class AphrontController extends Phobject {
private $request;
private $currentApplication;
private $delegatingController;
public function setDelegatingController(
AphrontController $delegating_controller) {
$this->delegatingController = $delegating_controller;
return $this;
}
public function getDelegatingController() {
return $this->delegatingController;
}
public function willBeginExecution() {
return;
}
public function willProcessRequest(array $uri_data) {
return;
}
public function didProcessRequest($response) {
return $response;
}
public function handleRequest(AphrontRequest $request) {
if (method_exists($this, 'processRequest')) {
return $this->processRequest();
}
throw new PhutilMethodNotImplementedException(
pht(
- 'Controllers must implement either handleRequest() (recommended) '.
- 'or processRequest() (deprecated).'));
+ 'Controllers must implement either %s (recommended) '.
+ 'or %s (deprecated).',
+ 'handleRequest()',
+ 'processRequest()'));
}
final public function setRequest(AphrontRequest $request) {
$this->request = $request;
return $this;
}
final public function getRequest() {
if (!$this->request) {
- throw new Exception(pht('Call setRequest() before getRequest()!'));
+ throw new PhutilInvalidStateException('setRequest');
}
return $this->request;
}
final public function getViewer() {
return $this->getRequest()->getViewer();
}
final public function delegateToController(AphrontController $controller) {
$request = $this->getRequest();
$controller->setDelegatingController($this);
$controller->setRequest($request);
$application = $this->getCurrentApplication();
if ($application) {
$controller->setCurrentApplication($application);
}
return $controller->handleRequest($request);
}
final public function setCurrentApplication(
PhabricatorApplication $current_application) {
$this->currentApplication = $current_application;
return $this;
}
final public function getCurrentApplication() {
return $this->currentApplication;
}
public function getDefaultResourceSource() {
- throw new Exception(
+ throw new MethodNotImplementedException(
pht(
- 'A Controller must implement getDefaultResourceSource() before you '.
- 'can invoke requireResource() or initBehavior().'));
+ 'A Controller must implement %s before you can invoke %s or %s.',
+ 'getDefaultResourceSource()',
+ 'requireResource()',
+ 'initBehavior()'));
}
public function requireResource($symbol) {
$response = CelerityAPI::getStaticResourceResponse();
$response->requireResource($symbol, $this->getDefaultResourceSource());
return $this;
}
public function initBehavior($name, $config = array()) {
Javelin::initBehavior(
$name,
$config,
$this->getDefaultResourceSource());
}
}
diff --git a/src/applications/auth/engine/PhabricatorAuthInviteEngine.php b/src/applications/auth/engine/PhabricatorAuthInviteEngine.php
index bbdb6000f8..f1cb45483e 100644
--- a/src/applications/auth/engine/PhabricatorAuthInviteEngine.php
+++ b/src/applications/auth/engine/PhabricatorAuthInviteEngine.php
@@ -1,256 +1,256 @@
<?php
/**
* This class does an unusual amount of flow control via exceptions. The intent
* is to make the workflows highly testable, because this code is high-stakes
* and difficult to test.
*/
final class PhabricatorAuthInviteEngine extends Phobject {
private $viewer;
private $userHasConfirmedVerify;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
if (!$this->viewer) {
- throw new Exception(pht('Call setViewer() before getViewer()!'));
+ throw new PhutilInvalidStateException('setViewer');
}
return $this->viewer;
}
public function setUserHasConfirmedVerify($confirmed) {
$this->userHasConfirmedVerify = $confirmed;
return $this;
}
private function shouldVerify() {
return $this->userHasConfirmedVerify;
}
public function processInviteCode($code) {
$viewer = $this->getViewer();
$invite = id(new PhabricatorAuthInviteQuery())
->setViewer($viewer)
->withVerificationCodes(array($code))
->executeOne();
if (!$invite) {
throw id(new PhabricatorAuthInviteInvalidException(
pht('Bad Invite Code'),
pht(
'The invite code in the link you clicked is invalid. Check that '.
'you followed the link correctly.')))
->setCancelButtonURI('/')
->setCancelButtonText(pht('Curses!'));
}
$accepted_phid = $invite->getAcceptedByPHID();
if ($accepted_phid) {
if ($accepted_phid == $viewer->getPHID()) {
throw id(new PhabricatorAuthInviteInvalidException(
pht('Already Accepted'),
pht(
'You have already accepted this invitation.')))
->setCancelButtonURI('/')
->setCancelButtonText(pht('Awesome'));
} else {
throw id(new PhabricatorAuthInviteInvalidException(
pht('Already Accepted'),
pht(
'The invite code in the link you clicked has already '.
'been accepted.')))
->setCancelButtonURI('/')
->setCancelButtonText(pht('Continue'));
}
}
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$invite->getEmailAddress());
if ($viewer->isLoggedIn()) {
$this->handleLoggedInInvite($invite, $viewer, $email);
}
if ($email) {
$other_user = $this->loadUserForEmail($email);
if ($email->getIsVerified()) {
throw id(new PhabricatorAuthInviteLoginException(
pht('Already Registered'),
pht(
'The email address you just clicked a link from is already '.
'verified and associated with a registered account (%s). Log '.
'in to continue.',
phutil_tag('strong', array(), $other_user->getName()))))
->setCancelButtonText(pht('Log In'))
->setCancelButtonURI($this->getLoginURI());
} else if ($email->getIsPrimary()) {
throw id(new PhabricatorAuthInviteLoginException(
pht('Already Registered'),
pht(
'The email address you just clicked a link from is already '.
'the primary email address for a registered account (%s). Log '.
'in to continue.',
phutil_tag('strong', array(), $other_user->getName()))))
->setCancelButtonText(pht('Log In'))
->setCancelButtonURI($this->getLoginURI());
} else if (!$this->shouldVerify()) {
throw id(new PhabricatorAuthInviteVerifyException(
pht('Already Associated'),
pht(
'The email address you just clicked a link from is already '.
'associated with a registered account (%s), but is not '.
'verified. Log in to that account to continue. If you can not '.
'log in, you can register a new account.',
phutil_tag('strong', array(), $other_user->getName()))))
->setCancelButtonText(pht('Log In'))
->setCancelButtonURI($this->getLoginURI())
->setSubmitButtonText(pht('Register New Account'));
} else {
// NOTE: The address is not verified and not a primary address, so
// we will eventually steal it if the user completes registration.
}
}
// The invite and email address are OK, but the user needs to register.
return $invite;
}
private function handleLoggedInInvite(
PhabricatorAuthInvite $invite,
PhabricatorUser $viewer,
PhabricatorUserEmail $email = null) {
if ($email && ($email->getUserPHID() !== $viewer->getPHID())) {
$other_user = $this->loadUserForEmail($email);
if ($email->getIsVerified()) {
throw id(new PhabricatorAuthInviteAccountException(
pht('Wrong Account'),
pht(
'You are logged in as %s, but the email address you just '.
'clicked a link from is already verified and associated '.
'with another account (%s). Switch accounts, then try again.',
phutil_tag('strong', array(), $viewer->getUsername()),
phutil_tag('strong', array(), $other_user->getName()))))
->setSubmitButtonText(pht('Log Out'))
->setSubmitButtonURI($this->getLogoutURI())
->setCancelButtonURI('/');
} else if ($email->getIsPrimary()) {
// NOTE: We never steal primary addresses from other accounts, even
// if they are unverified. This would leave the other account with
// no address. Users can use password recovery to access the other
// account if they really control the address.
throw id(new PhabricatorAuthInviteAccountException(
pht('Wrong Acount'),
pht(
'You are logged in as %s, but the email address you just '.
'clicked a link from is already the primary email address '.
'for another account (%s). Switch accounts, then try again.',
phutil_tag('strong', array(), $viewer->getUsername()),
phutil_tag('strong', array(), $other_user->getName()))))
->setSubmitButtonText(pht('Log Out'))
->setSubmitButtonURI($this->getLogoutURI())
->setCancelButtonURI('/');
} else if (!$this->shouldVerify()) {
throw id(new PhabricatorAuthInviteVerifyException(
pht('Verify Email'),
pht(
'You are logged in as %s, but the email address (%s) you just '.
'clicked a link from is already associated with another '.
'account (%s). You can log out to switch accounts, or verify '.
'the address and attach it to your current account. Attach '.
'email address %s to user account %s?',
phutil_tag('strong', array(), $viewer->getUsername()),
phutil_tag('strong', array(), $invite->getEmailAddress()),
phutil_tag('strong', array(), $other_user->getName()),
phutil_tag('strong', array(), $invite->getEmailAddress()),
phutil_tag('strong', array(), $viewer->getUsername()))))
->setSubmitButtonText(
pht(
'Verify %s',
$invite->getEmailAddress()))
->setCancelButtonText(pht('Log Out'))
->setCancelButtonURI($this->getLogoutURI());
}
}
if (!$email) {
$email = id(new PhabricatorUserEmail())
->setAddress($invite->getEmailAddress())
->setIsVerified(0)
->setIsPrimary(0);
}
if (!$email->getIsVerified()) {
// We're doing this check here so that we can verify the address if
// it's already attached to the viewer's account, just not verified.
if (!$this->shouldVerify()) {
throw id(new PhabricatorAuthInviteVerifyException(
pht('Verify Email'),
pht(
'Verify this email address (%s) and attach it to your '.
'account (%s)?',
phutil_tag('strong', array(), $invite->getEmailAddress()),
phutil_tag('strong', array(), $viewer->getUsername()))))
->setSubmitButtonText(
pht(
'Verify %s',
$invite->getEmailAddress()))
->setCancelButtonURI('/');
}
$editor = id(new PhabricatorUserEditor())
->setActor($viewer);
// If this is a new email, add it to the user's account.
if (!$email->getUserPHID()) {
$editor->addEmail($viewer, $email);
}
// If another user added this email (but has not verified it),
// take it from them.
$editor->reassignEmail($viewer, $email);
$editor->verifyEmail($viewer, $email);
}
$invite->setAcceptedByPHID($viewer->getPHID());
$invite->save();
// If we make it here, the user was already logged in with the email
// address attached to their account and verified, or we attached it to
// their account (if it was not already attached) and verified it.
throw new PhabricatorAuthInviteRegisteredException();
}
private function loadUserForEmail(PhabricatorUserEmail $email) {
$user = id(new PhabricatorHandleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($email->getUserPHID()))
->executeOne();
if (!$user) {
throw new Exception(
pht(
'Email record ("%s") has bad associated user PHID ("%s").',
$email->getAddress(),
$email->getUserPHID()));
}
return $user;
}
private function getLoginURI() {
return '/auth/start/';
}
private function getLogoutURI() {
return '/logout/';
}
}
diff --git a/src/applications/conpherence/view/ConpherenceTransactionView.php b/src/applications/conpherence/view/ConpherenceTransactionView.php
index 57bec773fc..fa94b26ba6 100644
--- a/src/applications/conpherence/view/ConpherenceTransactionView.php
+++ b/src/applications/conpherence/view/ConpherenceTransactionView.php
@@ -1,280 +1,280 @@
<?php
final class ConpherenceTransactionView extends AphrontView {
private $conpherenceThread;
private $conpherenceTransaction;
private $handles;
private $markupEngine;
private $fullDisplay;
private $classes = array();
private $timeOnly;
public function setConpherenceThread(ConpherenceThread $t) {
$this->conpherenceThread = $t;
return $this;
}
private function getConpherenceThread() {
return $this->conpherenceThread;
}
public function setConpherenceTransaction(ConpherenceTransaction $tx) {
$this->conpherenceTransaction = $tx;
return $this;
}
private function getConpherenceTransaction() {
return $this->conpherenceTransaction;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function getHandles() {
return $this->handles;
}
public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) {
$this->markupEngine = $markup_engine;
return $this;
}
private function getMarkupEngine() {
return $this->markupEngine;
}
public function setFullDisplay($bool) {
$this->fullDisplay = $bool;
return $this;
}
private function getFullDisplay() {
return $this->fullDisplay;
}
public function addClass($class) {
$this->classes[] = $class;
return $this;
}
public function render() {
$viewer = $this->getUser();
if (!$viewer) {
- throw new Exception(pht('Call setUser() before render()!'));
+ throw new PhutilInvalidStateException('setUser');
}
require_celerity_resource('conpherence-transaction-css');
$transaction = $this->getConpherenceTransaction();
switch ($transaction->getTransactionType()) {
case ConpherenceTransaction::TYPE_DATE_MARKER:
return javelin_tag(
'div',
array(
'class' => 'conpherence-transaction-view date-marker',
'sigil' => 'conpherence-transaction-view',
'meta' => array(
'id' => $transaction->getID() + 0.5,
),
),
array(
phutil_tag(
'span',
array(
'class' => 'date',
),
phabricator_format_local_time(
$transaction->getDateCreated(),
$viewer,
'M jS, Y')),
));
break;
}
$info = $this->renderTransactionInfo();
$actions = $this->renderTransactionActions();
$image = $this->renderTransactionImage();
$content = $this->renderTransactionContent();
$classes = implode(' ', $this->classes);
$transaction_dom_id = null;
if ($this->getFullDisplay()) {
$transaction_dom_id = 'anchor-'.$transaction->getID();
}
$header = phutil_tag_div(
'conpherence-transaction-header grouped',
array($actions, $info));
return javelin_tag(
'div',
array(
'class' => 'conpherence-transaction-view '.$classes,
'id' => $transaction_dom_id,
'sigil' => 'conpherence-transaction-view',
'meta' => array(
'id' => $transaction->getID(),
),
),
array(
$image,
phutil_tag_div('conpherence-transaction-detail grouped',
array($header, $content)),
));
}
private function renderTransactionInfo() {
$viewer = $this->getUser();
$thread = $this->getConpherenceThread();
$transaction = $this->getConpherenceTransaction();
$info = array();
if ($this->getFullDisplay() && $transaction->getContentSource()) {
$content_source = id(new PhabricatorContentSourceView())
->setContentSource($transaction->getContentSource())
->setUser($viewer)
->render();
if ($content_source) {
$info[] = $content_source;
}
}
Javelin::initBehavior('phabricator-tooltips');
$tip = phabricator_datetime($transaction->getDateCreated(), $viewer);
$label = phabricator_time($transaction->getDateCreated(), $viewer);
$width = 360;
if ($this->getFullDisplay()) {
Javelin::initBehavior('phabricator-watch-anchor');
$anchor = id(new PhabricatorAnchorView())
->setAnchorName($transaction->getID())
->render();
$info[] = hsprintf(
'%s%s',
$anchor,
javelin_tag(
'a',
array(
'href' => '#'.$transaction->getID(),
'class' => 'anchor-link',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tip,
'size' => $width,
),
),
$label));
} else {
$href = '/'.$thread->getMonogram().'#'.$transaction->getID();
$info[] = javelin_tag(
'a',
array(
'href' => $href,
'class' => 'epoch-link',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tip,
'size' => $width,
),
),
$label);
}
$info = phutil_implode_html(" \xC2\xB7 ", $info);
return phutil_tag(
'span',
array(
'class' => 'conpherence-transaction-info',
),
$info);
}
private function renderTransactionActions() {
$transaction = $this->getConpherenceTransaction();
switch ($transaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$handles = $this->getHandles();
$author = $handles[$transaction->getAuthorPHID()];
$actions = array($author->renderLink());
break;
default:
$actions = null;
break;
}
return $actions;
}
private function renderTransactionImage() {
$image = null;
if ($this->getFullDisplay()) {
$transaction = $this->getConpherenceTransaction();
switch ($transaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$handles = $this->getHandles();
$author = $handles[$transaction->getAuthorPHID()];
$image_uri = $author->getImageURI();
$image = phutil_tag(
'span',
array(
'class' => 'conpherence-transaction-image',
'style' => 'background-image: url('.$image_uri.');',
));
break;
}
}
return $image;
}
private function renderTransactionContent() {
$transaction = $this->getConpherenceTransaction();
$content = null;
$content_class = null;
$content = null;
$handles = $this->getHandles();
switch ($transaction->getTransactionType()) {
case ConpherenceTransaction::TYPE_FILES:
$content = $transaction->getTitle();
break;
case ConpherenceTransaction::TYPE_TITLE:
case ConpherenceTransaction::TYPE_PICTURE:
case ConpherenceTransaction::TYPE_PICTURE_CROP:
case ConpherenceTransaction::TYPE_PARTICIPANTS:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
case PhabricatorTransactions::TYPE_EDGE:
$content = $transaction->getTitle();
$this->addClass('conpherence-edited');
break;
case PhabricatorTransactions::TYPE_COMMENT:
$this->addClass('conpherence-comment');
$author = $handles[$transaction->getAuthorPHID()];
$comment = $transaction->getComment();
$content = $this->getMarkupEngine()->getOutput(
$comment,
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
$content_class = 'conpherence-message';
break;
}
$this->appendChild(
phutil_tag(
'div',
array(
'class' => $content_class,
),
$content));
return phutil_tag_div(
'conpherence-transaction-content',
$this->renderChildren());
}
}
diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
index dfba37eaa5..fcf1e3d7c9 100644
--- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
+++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
@@ -1,305 +1,303 @@
<?php
final class PhabricatorDashboardPanelRenderingEngine extends Phobject {
const HEADER_MODE_NORMAL = 'normal';
const HEADER_MODE_NONE = 'none';
const HEADER_MODE_EDIT = 'edit';
private $panel;
private $viewer;
private $enableAsyncRendering;
private $parentPanelPHIDs;
private $headerMode = self::HEADER_MODE_NORMAL;
private $dashboardID;
public function setDashboardID($id) {
$this->dashboardID = $id;
return $this;
}
public function getDashboardID() {
return $this->dashboardID;
}
public function setHeaderMode($header_mode) {
$this->headerMode = $header_mode;
return $this;
}
public function getHeaderMode() {
return $this->headerMode;
}
/**
* Allow the engine to render the panel via Ajax.
*/
public function setEnableAsyncRendering($enable) {
$this->enableAsyncRendering = $enable;
return $this;
}
public function setParentPanelPHIDs(array $parents) {
$this->parentPanelPHIDs = $parents;
return $this;
}
public function getParentPanelPHIDs() {
return $this->parentPanelPHIDs;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setPanel(PhabricatorDashboardPanel $panel) {
$this->panel = $panel;
return $this;
}
public function getPanel() {
return $this->panel;
}
public function renderPanel() {
$panel = $this->getPanel();
$viewer = $this->getViewer();
if (!$panel) {
return $this->renderErrorPanel(
pht('Missing Panel'),
pht('This panel does not exist.'));
}
$panel_type = $panel->getImplementation();
if (!$panel_type) {
return $this->renderErrorPanel(
$panel->getName(),
pht(
'This panel has type "%s", but that panel type is not known to '.
'Phabricator.',
$panel->getPanelType()));
}
try {
$this->detectRenderingCycle($panel);
if ($this->enableAsyncRendering) {
if ($panel_type->shouldRenderAsync()) {
return $this->renderAsyncPanel();
}
}
return $this->renderNormalPanel($viewer, $panel, $this);
} catch (Exception $ex) {
return $this->renderErrorPanel(
$panel->getName(),
pht(
'%s: %s',
phutil_tag('strong', array(), get_class($ex)),
$ex->getMessage()));
}
}
private function renderNormalPanel() {
$panel = $this->getPanel();
$panel_type = $panel->getImplementation();
$content = $panel_type->renderPanelContent(
$this->getViewer(),
$panel,
$this);
$header = $this->renderPanelHeader();
return $this->renderPanelDiv(
$content,
$header);
}
private function renderAsyncPanel() {
$panel = $this->getPanel();
$panel_id = celerity_generate_unique_node_id();
$dashboard_id = $this->getDashboardID();
Javelin::initBehavior(
'dashboard-async-panel',
array(
'panelID' => $panel_id,
'parentPanelPHIDs' => $this->getParentPanelPHIDs(),
'headerMode' => $this->getHeaderMode(),
'dashboardID' => $dashboard_id,
'uri' => '/dashboard/panel/render/'.$panel->getID().'/',
));
$header = $this->renderPanelHeader();
$content = id(new PHUIPropertyListView())
->addTextContent(pht('Loading...'));
return $this->renderPanelDiv(
$content,
$header,
$panel_id);
}
private function renderErrorPanel($title, $body) {
switch ($this->getHeaderMode()) {
case self::HEADER_MODE_NONE:
$header = null;
break;
case self::HEADER_MODE_EDIT:
$header = id(new PHUIActionHeaderView())
->setHeaderTitle($title)
->setHeaderColor(PHUIActionHeaderView::HEADER_LIGHTBLUE);
$header = $this->addPanelHeaderActions($header);
break;
case self::HEADER_MODE_NORMAL:
default:
$header = id(new PHUIActionHeaderView())
->setHeaderTitle($title)
->setHeaderColor(PHUIActionHeaderView::HEADER_LIGHTBLUE);
break;
}
$icon = id(new PHUIIconView())
->setIconFont('fa-warning red msr');
$content = id(new PHUIBoxView())
->addClass('dashboard-box')
->appendChild($icon)
->appendChild($body);
return $this->renderPanelDiv(
$content,
$header);
}
private function renderPanelDiv(
$content,
$header = null,
$id = null) {
require_celerity_resource('phabricator-dashboard-css');
$panel = $this->getPanel();
if (!$id) {
$id = celerity_generate_unique_node_id();
}
return javelin_tag(
'div',
array(
'id' => $id,
'sigil' => 'dashboard-panel',
'meta' => array(
'objectPHID' => $panel->getPHID(),
),
'class' => 'dashboard-panel',
),
array(
$header,
$content,
));
}
private function renderPanelHeader() {
$panel = $this->getPanel();
switch ($this->getHeaderMode()) {
case self::HEADER_MODE_NONE:
$header = null;
break;
case self::HEADER_MODE_EDIT:
$header = id(new PHUIActionHeaderView())
->setHeaderTitle($panel->getName())
->setHeaderColor(PHUIActionHeaderView::HEADER_LIGHTBLUE);
$header = $this->addPanelHeaderActions($header);
break;
case self::HEADER_MODE_NORMAL:
default:
$header = id(new PHUIActionHeaderView())
->setHeaderTitle($panel->getName())
->setHeaderColor(PHUIActionHeaderView::HEADER_LIGHTBLUE);
$panel_type = $panel->getImplementation();
$header = $panel_type->adjustPanelHeader(
$this->getViewer(),
$panel,
$this,
$header);
break;
}
return $header;
}
private function addPanelHeaderActions(
PHUIActionHeaderView $header) {
$panel = $this->getPanel();
$dashboard_id = $this->getDashboardID();
$edit_uri = id(new PhutilURI(
'/dashboard/panel/edit/'.$panel->getID().'/'));
if ($dashboard_id) {
$edit_uri->setQueryParam('dashboardID', $dashboard_id);
}
$action_edit = id(new PHUIIconView())
->setIconFont('fa-pencil')
->setWorkflow(true)
->setHref((string)$edit_uri);
$header->addAction($action_edit);
if ($dashboard_id) {
$uri = id(new PhutilURI(
'/dashboard/removepanel/'.$dashboard_id.'/'))
->setQueryParam('panelPHID', $panel->getPHID());
$action_remove = id(new PHUIIconView())
->setIconFont('fa-trash-o')
->setHref((string)$uri)
->setWorkflow(true);
$header->addAction($action_remove);
}
return $header;
}
/**
* Detect graph cycles in panels, and deeply nested panels.
*
* This method throws if the current rendering stack is too deep or contains
* a cycle. This can happen if you embed layout panels inside each other,
* build a big stack of panels, or embed a panel in remarkup inside another
* panel. Generally, all of this stuff is ridiculous and we just want to
* shut it down.
*
* @param PhabricatorDashboardPanel Panel being rendered.
* @return void
*/
private function detectRenderingCycle(PhabricatorDashboardPanel $panel) {
if ($this->parentPanelPHIDs === null) {
- throw new Exception(
- pht(
- 'You must call setParentPanelPHIDs() before rendering panels.'));
+ throw new PhutilInvalidStateException('setParentPanelPHIDs');
}
$max_depth = 4;
if (count($this->parentPanelPHIDs) >= $max_depth) {
throw new Exception(
pht(
'To render more than %s levels of panels nested inside other '.
'panels, purchase a subscription to Phabricator Gold.',
new PhutilNumber($max_depth)));
}
if (in_array($panel->getPHID(), $this->parentPanelPHIDs)) {
throw new Exception(
pht(
'You awake in a twisting maze of mirrors, all alike. '.
'You are likely to be eaten by a graph cycle. '.
'Should you escape alive, you resolve to be more careful about '.
'putting dashboard panels inside themselves.'));
}
}
}
diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php
index 1694d42db0..f00c8a1ef5 100644
--- a/src/infrastructure/markup/PhabricatorMarkupEngine.php
+++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php
@@ -1,639 +1,636 @@
<?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 extends Phobject {
private $objects = array();
private $viewer;
private $contextObject;
private $version = 15;
private $engineCaches = array();
/* -( 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.
* @param object A context object for policy checks
* @return string Marked up output.
* @task markup
*/
public static function renderOneObject(
PhabricatorMarkupInterface $object,
$field,
PhabricatorUser $viewer,
$context_object = null) {
return id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->setContextObject($context_object)
->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);
$engines[$key]->setConfig('contextObject', $this->contextObject);
}
// Load or build the preprocessor caches.
$blocks = $this->loadPreprocessorCaches($engines, $objects);
$blocks = mpull($blocks, 'getCacheData');
$this->engineCaches = $blocks;
// Finalize the output.
foreach ($objects as $key => $info) {
$engine = $engines[$key];
$field = $info['field'];
$object = $info['object'];
$output = $engine->postprocessText($blocks[$key]);
$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);
$this->requireKeyProcessed($key);
return $this->objects[$key]['output'];
}
/**
* Retrieve engine metadata for a given field.
*
* @param PhabricatorMarkupInterface The object to retrieve.
* @param string The field to retrieve.
* @param string The engine metadata field to retrieve.
* @param wild Optional default value.
* @task markup
*/
public function getEngineMetadata(
PhabricatorMarkupInterface $object,
$field,
$metadata_key,
$default = null) {
$key = $this->getMarkupFieldKey($object, $field);
$this->requireKeyProcessed($key);
return idx($this->engineCaches[$key]['metadata'], $metadata_key, $default);
}
/**
* @task markup
*/
private function requireKeyProcessed($key) {
if (empty($this->objects[$key])) {
throw new Exception(
pht(
"Call %s before using results (key = '%s').",
'addObject()',
$key));
}
if (!isset($this->objects[$key]['output'])) {
- throw new Exception(
- pht(
- 'Call %s before using results.',
- 'process()'));
+ throw new PhutilInvalidStateException('process');
}
}
/**
* @task markup
*/
private function getMarkupFieldKey(
PhabricatorMarkupInterface $object,
$field) {
static $custom;
if ($custom === null) {
$custom = array_merge(
self::loadCustomInlineRules(),
self::loadCustomBlockRules());
$custom = mpull($custom, 'getRuleVersion', null);
ksort($custom);
$custom = PhabricatorHash::digestForIndex(serialize($custom));
}
return $object->getMarkupFieldKey($field).'@'.$this->version.'@'.$custom;
}
/**
* @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) {
// False check in case MySQL doesn't support unicode characters
// in the string (T1191), resulting in unserialize returning false.
if (isset($blocks[$key]) && $blocks[$key]->getCacheData() !== false) {
// 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;
}
/**
* Set the context object. Used to implement object permissions.
*
* @param The object in which context this remarkup is used.
* @return this
* @task markup
*/
public function setContextObject($object) {
$this->contextObject = $object;
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,
'uri.full' => true,
));
}
/**
* @task engine
*/
public static function newFeedMarkupEngine() {
return self::newMarkupEngine(
array(
'macros' => false,
'youtube' => false,
));
}
/**
* @task engine
*/
public static function newCalendarMarkupEngine() {
return self::newMarkupEngine(array(
));
}
/**
* @task engine
*/
public static function newDifferentialMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
'differential.diff' => idx($options, 'differential.diff'),
));
}
/**
* @task engine
*/
public static function newDiffusionMarkupEngine(array $options = array()) {
return self::newMarkupEngine(array(
'header.generate-toc' => true,
));
}
/**
* @task engine
*/
public static function getEngine($ruleset = 'default') {
static $engines = array();
if (isset($engines[$ruleset])) {
return $engines[$ruleset];
}
$engine = null;
switch ($ruleset) {
case 'default':
$engine = self::newMarkupEngine(array());
break;
case 'nolinebreaks':
$engine = self::newMarkupEngine(array());
$engine->setConfig('preserve-linebreaks', false);
break;
case 'diffusion-readme':
$engine = self::newMarkupEngine(array());
$engine->setConfig('preserve-linebreaks', false);
$engine->setConfig('header.generate-toc', true);
break;
case 'diviner':
$engine = self::newMarkupEngine(array());
$engine->setConfig('preserve-linebreaks', false);
// $engine->setConfig('diviner.renderer', new DivinerDefaultRenderer());
$engine->setConfig('header.generate-toc', true);
break;
case 'extract':
// Engine used for reference/edge extraction. Turn off anything which
// is slow and doesn't change reference extraction.
$engine = self::newMarkupEngine(array());
$engine->setConfig('pygments.enabled', false);
break;
default:
throw new Exception(pht('Unknown engine ruleset: %s!', $ruleset));
}
$engines[$ruleset] = $engine;
return $engine;
}
/**
* @task engine
*/
private static function getMarkupEngineDefaultConfiguration() {
return array(
'pygments' => PhabricatorEnv::getEnvConfig('pygments.enabled'),
'youtube' => PhabricatorEnv::getEnvConfig(
'remarkup.enable-embedded-youtube'),
'differential.diff' => null,
'header.generate-toc' => false,
'macros' => true,
'uri.allowed-protocols' => PhabricatorEnv::getEnvConfig(
'uri.allowed-protocols'),
'uri.full' => false,
'syntax-highlighter.engine' => PhabricatorEnv::getEnvConfig(
'syntax-highlighter.engine'),
'preserve-linebreaks' => true,
);
}
/**
* @task engine
*/
public static function newMarkupEngine(array $options) {
$options += self::getMarkupEngineDefaultConfiguration();
$engine = new PhutilRemarkupEngine();
$engine->setConfig('preserve-linebreaks', $options['preserve-linebreaks']);
$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']);
$engine->setConfig('uri.full', $options['uri.full']);
$rules = array();
$rules[] = new PhutilRemarkupEscapeRemarkupRule();
$rules[] = new PhutilRemarkupMonospaceRule();
$rules[] = new PhutilRemarkupDocumentLinkRule();
$rules[] = new PhabricatorNavigationRemarkupRule();
if ($options['youtube']) {
$rules[] = new PhabricatorYoutubeRemarkupRule();
}
$applications = PhabricatorApplication::getAllInstalledApplications();
foreach ($applications as $application) {
foreach ($application->getRemarkupRules() as $rule) {
$rules[] = $rule;
}
}
$rules[] = new PhutilRemarkupHyperlinkRule();
if ($options['macros']) {
$rules[] = new PhabricatorImageMacroRemarkupRule();
$rules[] = new PhabricatorMemeRemarkupRule();
}
$rules[] = new PhutilRemarkupBoldRule();
$rules[] = new PhutilRemarkupItalicRule();
$rules[] = new PhutilRemarkupDelRule();
$rules[] = new PhutilRemarkupUnderlineRule();
foreach (self::loadCustomInlineRules() as $rule) {
$rules[] = $rule;
}
$blocks = array();
$blocks[] = new PhutilRemarkupQuotesBlockRule();
$blocks[] = new PhutilRemarkupReplyBlockRule();
$blocks[] = new PhutilRemarkupLiteralBlockRule();
$blocks[] = new PhutilRemarkupHeaderBlockRule();
$blocks[] = new PhutilRemarkupHorizontalRuleBlockRule();
$blocks[] = new PhutilRemarkupListBlockRule();
$blocks[] = new PhutilRemarkupCodeBlockRule();
$blocks[] = new PhutilRemarkupNoteBlockRule();
$blocks[] = new PhutilRemarkupTableBlockRule();
$blocks[] = new PhutilRemarkupSimpleTableBlockRule();
$blocks[] = new PhutilRemarkupInterpreterBlockRule();
$blocks[] = new PhutilRemarkupDefaultBlockRule();
foreach (self::loadCustomBlockRules() as $rule) {
$blocks[] = $rule;
}
foreach ($blocks as $block) {
$block->setMarkupRules($rules);
}
$engine->setBlockRules($blocks);
return $engine;
}
public static function extractPHIDsFromMentions(
PhabricatorUser $viewer,
array $content_blocks) {
$mentions = array();
$engine = self::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $viewer);
foreach ($content_blocks as $content_block) {
$engine->markupText($content_block);
$phids = $engine->getTextMetadata(
PhabricatorMentionRemarkupRule::KEY_MENTIONED,
array());
$mentions += $phids;
}
return $mentions;
}
public static function extractFilePHIDsFromEmbeddedFiles(
PhabricatorUser $viewer,
array $content_blocks) {
$files = array();
$engine = self::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $viewer);
foreach ($content_blocks as $content_block) {
$engine->markupText($content_block);
$phids = $engine->getTextMetadata(
PhabricatorEmbedFileRemarkupRule::KEY_EMBED_FILE_PHIDS,
array());
foreach ($phids as $phid) {
$files[$phid] = $phid;
}
}
return array_values($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*/", $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;
}
private static function loadCustomInlineRules() {
return id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorRemarkupCustomInlineRule')
->loadObjects();
}
private static function loadCustomBlockRules() {
return id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorRemarkupCustomBlockRule')
->loadObjects();
}
}
diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php
index 7c4fefcac6..f062bb1b5a 100644
--- a/src/view/form/control/AphrontFormPolicyControl.php
+++ b/src/view/form/control/AphrontFormPolicyControl.php
@@ -1,327 +1,327 @@
<?php
final class AphrontFormPolicyControl extends AphrontFormControl {
private $object;
private $capability;
private $policies;
private $spacePHID;
private $templatePHIDType;
private $templateObject;
public function setPolicyObject(PhabricatorPolicyInterface $object) {
$this->object = $object;
return $this;
}
public function setPolicies(array $policies) {
assert_instances_of($policies, 'PhabricatorPolicy');
$this->policies = $policies;
return $this;
}
public function setSpacePHID($space_phid) {
$this->spacePHID = $space_phid;
return $this;
}
public function getSpacePHID() {
return $this->spacePHID;
}
public function setTemplatePHIDType($type) {
$this->templatePHIDType = $type;
return $this;
}
public function setTemplateObject($object) {
$this->templateObject = $object;
return $this;
}
public function setCapability($capability) {
$this->capability = $capability;
$labels = array(
PhabricatorPolicyCapability::CAN_VIEW => pht('Visible To'),
PhabricatorPolicyCapability::CAN_EDIT => pht('Editable By'),
PhabricatorPolicyCapability::CAN_JOIN => pht('Joinable By'),
);
if (isset($labels[$capability])) {
$label = $labels[$capability];
} else {
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
if ($capobj) {
$label = $capobj->getCapabilityName();
} else {
$label = pht('Capability "%s"', $capability);
}
}
$this->setLabel($label);
return $this;
}
protected function getCustomControlClass() {
return 'aphront-form-control-policy';
}
protected function getOptions() {
$capability = $this->capability;
$policies = $this->policies;
// Exclude object policies which don't make sense here. This primarily
// filters object policies associated from template capabilities (like
// "Default Task View Policy" being set to "Task Author") so they aren't
// made available on non-template capabilities (like "Can Bulk Edit").
foreach ($policies as $key => $policy) {
if ($policy->getType() != PhabricatorPolicyType::TYPE_OBJECT) {
continue;
}
$rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy->getPHID());
if (!$rule) {
continue;
}
$target = nonempty($this->templateObject, $this->object);
if (!$rule->canApplyToObject($target)) {
unset($policies[$key]);
continue;
}
}
$options = array();
foreach ($policies as $policy) {
if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
// Never expose "Public" for capabilities which don't support it.
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
continue;
}
}
$policy_short_name = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(28)
->truncateString($policy->getName());
$options[$policy->getType()][$policy->getPHID()] = array(
'name' => $policy_short_name,
'full' => $policy->getName(),
'icon' => $policy->getIcon(),
);
}
// If we were passed several custom policy options, throw away the ones
// which aren't the value for this capability. For example, an object might
// have a custom view pollicy and a custom edit policy. When we render
// the selector for "Can View", we don't want to show the "Can Edit"
// custom policy -- if we did, the menu would look like this:
//
// Custom
// Custom Policy
// Custom Policy
//
// ...where one is the "view" custom policy, and one is the "edit" custom
// policy.
$type_custom = PhabricatorPolicyType::TYPE_CUSTOM;
if (!empty($options[$type_custom])) {
$options[$type_custom] = array_select_keys(
$options[$type_custom],
array($this->getValue()));
}
// If there aren't any custom policies, add a placeholder policy so we
// render a menu item. This allows the user to switch to a custom policy.
if (empty($options[$type_custom])) {
$placeholder = new PhabricatorPolicy();
$placeholder->setName(pht('Custom Policy...'));
$options[$type_custom][$this->getCustomPolicyPlaceholder()] = array(
'name' => $placeholder->getName(),
'full' => $placeholder->getName(),
'icon' => $placeholder->getIcon(),
);
}
$options = array_select_keys(
$options,
array(
PhabricatorPolicyType::TYPE_GLOBAL,
PhabricatorPolicyType::TYPE_OBJECT,
PhabricatorPolicyType::TYPE_USER,
PhabricatorPolicyType::TYPE_CUSTOM,
PhabricatorPolicyType::TYPE_PROJECT,
));
return $options;
}
protected function renderInput() {
if (!$this->object) {
- throw new Exception(pht('Call setPolicyObject() before rendering!'));
+ throw new PhutilInvalidStateException('setPolicyObject');
}
if (!$this->capability) {
- throw new Exception(pht('Call setCapability() before rendering!'));
+ throw new PhutilInvalidStateException('setCapability');
}
$policy = $this->object->getPolicy($this->capability);
if (!$policy) {
// TODO: Make this configurable.
$policy = PhabricatorPolicies::POLICY_USER;
}
if (!$this->getValue()) {
$this->setValue($policy);
}
$control_id = celerity_generate_unique_node_id();
$input_id = celerity_generate_unique_node_id();
$caret = phutil_tag(
'span',
array(
'class' => 'caret',
));
$input = phutil_tag(
'input',
array(
'type' => 'hidden',
'id' => $input_id,
'name' => $this->getName(),
'value' => $this->getValue(),
));
$options = $this->getOptions();
$order = array();
$labels = array();
foreach ($options as $key => $values) {
$order[$key] = array_keys($values);
$labels[$key] = PhabricatorPolicyType::getPolicyTypeName($key);
}
$flat_options = array_mergev($options);
$icons = array();
foreach (igroup($flat_options, 'icon') as $icon => $ignored) {
$icons[$icon] = id(new PHUIIconView())
->setIconFont($icon);
}
if ($this->templatePHIDType) {
$context_path = 'template/'.$this->templatePHIDType.'/';
} else {
$object_phid = $this->object->getPHID();
if ($object_phid) {
$context_path = 'object/'.$object_phid.'/';
} else {
$object_type = phid_get_type($this->object->generatePHID());
$context_path = 'type/'.$object_type.'/';
}
}
Javelin::initBehavior(
'policy-control',
array(
'controlID' => $control_id,
'inputID' => $input_id,
'options' => $flat_options,
'groups' => array_keys($options),
'order' => $order,
'icons' => $icons,
'labels' => $labels,
'value' => $this->getValue(),
'capability' => $this->capability,
'editURI' => '/policy/edit/'.$context_path,
'customPlaceholder' => $this->getCustomPolicyPlaceholder(),
));
$selected = idx($flat_options, $this->getValue(), array());
$selected_icon = idx($selected, 'icon');
$selected_name = idx($selected, 'name');
$spaces_control = $this->buildSpacesControl();
return phutil_tag(
'div',
array(
),
array(
$spaces_control,
javelin_tag(
'a',
array(
'class' => 'grey button dropdown has-icon policy-control',
'href' => '#',
'mustcapture' => true,
'sigil' => 'policy-control',
'id' => $control_id,
),
array(
$caret,
javelin_tag(
'span',
array(
'sigil' => 'policy-label',
'class' => 'phui-button-text',
),
array(
idx($icons, $selected_icon),
$selected_name,
)),
)),
$input,
));
return AphrontFormSelectControl::renderSelectTag(
$this->getValue(),
$this->getOptions(),
array(
'name' => $this->getName(),
'disabled' => $this->getDisabled() ? 'disabled' : null,
'id' => $this->getID(),
));
}
private function getCustomPolicyPlaceholder() {
return 'custom:placeholder';
}
private function buildSpacesControl() {
if ($this->capability != PhabricatorPolicyCapability::CAN_VIEW) {
return null;
}
if (!($this->object instanceof PhabricatorSpacesInterface)) {
return null;
}
$viewer = $this->getUser();
if (!PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) {
return null;
}
$space_phid = $this->getSpacePHID();
if ($space_phid === null) {
$space_phid = $viewer->getDefaultSpacePHID();
}
$select = AphrontFormSelectControl::renderSelectTag(
$space_phid,
PhabricatorSpacesNamespaceQuery::getSpaceOptionsForViewer(
$viewer,
$space_phid),
array(
'name' => 'spacePHID',
));
return $select;
}
}
diff --git a/src/view/form/control/AphrontFormTokenizerControl.php b/src/view/form/control/AphrontFormTokenizerControl.php
index 841c1006e5..fd25f66a1f 100644
--- a/src/view/form/control/AphrontFormTokenizerControl.php
+++ b/src/view/form/control/AphrontFormTokenizerControl.php
@@ -1,131 +1,134 @@
<?php
final class AphrontFormTokenizerControl extends AphrontFormControl {
private $datasource;
private $disableBehavior;
private $limit;
private $placeholder;
private $handles;
public function setDatasource(PhabricatorTypeaheadDatasource $datasource) {
$this->datasource = $datasource;
return $this;
}
public function setDisableBehavior($disable) {
$this->disableBehavior = $disable;
return $this;
}
protected function getCustomControlClass() {
return 'aphront-form-control-tokenizer';
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function setPlaceholder($placeholder) {
$this->placeholder = $placeholder;
return $this;
}
public function willRender() {
// Load the handles now so we'll get a bulk load later on when we actually
// render them.
$this->loadHandles();
}
protected function renderInput() {
$name = $this->getName();
$handles = $this->loadHandles();
$handles = iterator_to_array($handles);
if ($this->getID()) {
$id = $this->getID();
} else {
$id = celerity_generate_unique_node_id();
}
$datasource = $this->datasource;
if (!$datasource) {
throw new Exception(
pht('You must set a datasource to use a TokenizerControl.'));
}
$datasource->setViewer($this->getUser());
$placeholder = null;
if (!strlen($this->placeholder)) {
$placeholder = $datasource->getPlaceholderText();
}
$values = nonempty($this->getValue(), array());
$tokens = $datasource->renderTokens($values);
foreach ($tokens as $token) {
$token->setInputName($this->getName());
}
$template = new AphrontTokenizerTemplateView();
$template->setName($name);
$template->setID($id);
$template->setValue($tokens);
$username = null;
if ($this->user) {
$username = $this->user->getUsername();
}
$datasource_uri = $datasource->getDatasourceURI();
$browse_uri = $datasource->getBrowseURI();
if ($browse_uri) {
$template->setBrowseURI($browse_uri);
}
if (!$this->disableBehavior) {
Javelin::initBehavior('aphront-basic-tokenizer', array(
'id' => $id,
'src' => $datasource_uri,
'value' => mpull($tokens, 'getValue', 'getKey'),
'icons' => mpull($tokens, 'getIcon', 'getKey'),
'types' => mpull($tokens, 'getTokenType', 'getKey'),
'colors' => mpull($tokens, 'getColor', 'getKey'),
'limit' => $this->limit,
'username' => $username,
'placeholder' => $placeholder,
'browseURI' => $browse_uri,
));
}
return $template->render();
}
private function loadHandles() {
if ($this->handles === null) {
$viewer = $this->getUser();
if (!$viewer) {
throw new Exception(
pht(
- 'Call setUser() before rendering tokenizers. Use appendControl() '.
- 'on AphrontFormView to do this easily.'));
+ 'Call %s before rendering tokenizers. '.
+ 'Use %s on %s to do this easily.',
+ 'setUser()',
+ 'appendControl()',
+ 'AphrontFormView'));
}
$values = nonempty($this->getValue(), array());
$phids = array();
foreach ($values as $value) {
if (!PhabricatorTypeaheadDatasource::isFunctionToken($value)) {
$phids[] = $value;
}
}
$this->handles = $viewer->loadHandles($phids);
}
return $this->handles;
}
}
diff --git a/src/view/layout/AphrontSideNavFilterView.php b/src/view/layout/AphrontSideNavFilterView.php
index fe8025026b..5e85f89254 100644
--- a/src/view/layout/AphrontSideNavFilterView.php
+++ b/src/view/layout/AphrontSideNavFilterView.php
@@ -1,326 +1,326 @@
<?php
/**
* Provides a navigation sidebar. For example:
*
* $nav = new AphrontSideNavFilterView();
* $nav
* ->setBaseURI($some_uri)
* ->addLabel('Cats')
* ->addFilter('meow', 'Meow')
* ->addFilter('purr', 'Purr')
* ->addLabel('Dogs')
* ->addFilter('woof', 'Woof')
* ->addFilter('bark', 'Bark');
* $valid_filter = $nav->selectFilter($user_selection, $default = 'meow');
*
*/
final class AphrontSideNavFilterView extends AphrontView {
private $items = array();
private $baseURI;
private $selectedFilter = false;
private $flexible;
private $collapsed = false;
private $active;
private $menu;
private $crumbs;
private $classes = array();
private $menuID;
private $iconNav;
public function setMenuID($menu_id) {
$this->menuID = $menu_id;
return $this;
}
public function getMenuID() {
return $this->menuID;
}
public function __construct() {
$this->menu = new PHUIListView();
}
public function addClass($class) {
$this->classes[] = $class;
return $this;
}
public static function newFromMenu(PHUIListView $menu) {
$object = new AphrontSideNavFilterView();
$object->setBaseURI(new PhutilURI('/'));
$object->menu = $menu;
return $object;
}
public function setCrumbs(PHUICrumbsView $crumbs) {
$this->crumbs = $crumbs;
return $this;
}
public function getCrumbs() {
return $this->crumbs;
}
public function setIconNav($nav) {
$this->iconNav = $nav;
return $this;
}
public function setActive($active) {
$this->active = $active;
return $this;
}
public function setFlexible($flexible) {
$this->flexible = $flexible;
return $this;
}
public function setCollapsed($collapsed) {
$this->collapsed = $collapsed;
return $this;
}
public function getMenuView() {
return $this->menu;
}
public function addMenuItem(PHUIListItemView $item) {
$this->menu->addMenuItem($item);
return $this;
}
public function getMenu() {
return $this->menu;
}
public function addFilter($key, $name, $uri = null) {
return $this->addThing(
$key, $name, $uri, PHUIListItemView::TYPE_LINK);
}
public function addIcon($key, $name, $icon, $image = null, $uri = null) {
if (!$uri) {
$href = clone $this->baseURI;
$href->setPath(rtrim($href->getPath().$key, '/').'/');
$href = (string)$href;
} else {
$href = $uri;
}
$item = id(new PHUIListItemView())
->setKey($key)
->setRenderNameAsTooltip(true)
->setType(PHUIListItemView::TYPE_ICON_NAV)
->setIcon($icon)
->setHref($href)
->setName($name)
->setProfileImage($image);
return $this->addMenuItem($item);
}
public function addButton($key, $name, $uri = null) {
return $this->addThing(
$key, $name, $uri, PHUIListItemView::TYPE_BUTTON);
}
private function addThing($key, $name, $uri, $type) {
$item = id(new PHUIListItemView())
->setName($name)
->setType($type);
if (strlen($key)) {
$item->setKey($key);
}
if ($uri) {
$item->setHref($uri);
} else {
$href = clone $this->baseURI;
$href->setPath(rtrim($href->getPath().$key, '/').'/');
$href = (string)$href;
$item->setHref($href);
}
return $this->addMenuItem($item);
}
public function addCustomBlock($block) {
$this->menu->addMenuItem(
id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_CUSTOM)
->appendChild($block));
return $this;
}
public function addLabel($name) {
return $this->addMenuItem(
id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_LABEL)
->setName($name));
}
public function setBaseURI(PhutilURI $uri) {
$this->baseURI = $uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function selectFilter($key, $default = null) {
$this->selectedFilter = $default;
if ($this->menu->getItem($key) && strlen($key)) {
$this->selectedFilter = $key;
}
return $this->selectedFilter;
}
public function getSelectedFilter() {
return $this->selectedFilter;
}
public function render() {
if ($this->menu->getItems()) {
if (!$this->baseURI) {
- throw new Exception(pht('Call setBaseURI() before render()!'));
+ throw new PhutilInvalidStateException('setBaseURI');
}
if ($this->selectedFilter === false) {
- throw new Exception(pht('Call selectFilter() before render()!'));
+ throw new PhutilInvalidStateException('selectFilter');
}
}
if ($this->selectedFilter !== null) {
$selected_item = $this->menu->getItem($this->selectedFilter);
if ($selected_item) {
$selected_item->addClass('phui-list-item-selected');
}
}
require_celerity_resource('phabricator-side-menu-view-css');
return $this->renderFlexNav();
}
private function renderFlexNav() {
$user = $this->user;
require_celerity_resource('phabricator-nav-view-css');
$nav_classes = array();
$nav_classes[] = 'phabricator-nav';
if ($this->iconNav) {
$nav_classes[] = 'phabricator-icon-nav';
}
$nav_id = null;
$drag_id = null;
$content_id = celerity_generate_unique_node_id();
$local_id = null;
$background_id = null;
$local_menu = null;
$main_id = celerity_generate_unique_node_id();
if ($this->flexible) {
$drag_id = celerity_generate_unique_node_id();
$flex_bar = phutil_tag(
'div',
array(
'class' => 'phabricator-nav-drag',
'id' => $drag_id,
),
'');
} else {
$flex_bar = null;
}
$nav_menu = null;
if ($this->menu->getItems()) {
$local_id = celerity_generate_unique_node_id();
$background_id = celerity_generate_unique_node_id();
if (!$this->collapsed) {
$nav_classes[] = 'has-local-nav';
}
$menu_background = phutil_tag(
'div',
array(
'class' => 'phabricator-nav-column-background',
'id' => $background_id,
),
'');
$local_menu = array(
$menu_background,
phutil_tag(
'div',
array(
'class' => 'phabricator-nav-local phabricator-side-menu',
'id' => $local_id,
),
$this->menu->setID($this->getMenuID())),
);
}
$crumbs = null;
if ($this->crumbs) {
$crumbs = $this->crumbs->render();
$nav_classes[] = 'has-crumbs';
}
if ($this->flexible) {
if (!$this->collapsed) {
$nav_classes[] = 'has-drag-nav';
}
Javelin::initBehavior(
'phabricator-nav',
array(
'mainID' => $main_id,
'localID' => $local_id,
'dragID' => $drag_id,
'contentID' => $content_id,
'backgroundID' => $background_id,
'collapsed' => $this->collapsed,
));
if ($this->active) {
Javelin::initBehavior(
'phabricator-active-nav',
array(
'localID' => $local_id,
));
}
}
$nav_classes = array_merge($nav_classes, $this->classes);
return phutil_tag(
'div',
array(
'class' => implode(' ', $nav_classes),
'id' => $main_id,
),
array(
$local_menu,
$flex_bar,
phutil_tag(
'div',
array(
'class' => 'phabricator-nav-content plb',
'id' => $content_id,
),
array(
$crumbs,
$this->renderChildren(),
)),
));
}
}
diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php
index 6db62c129e..82449951d7 100644
--- a/src/view/layout/PhabricatorActionListView.php
+++ b/src/view/layout/PhabricatorActionListView.php
@@ -1,66 +1,66 @@
<?php
final class PhabricatorActionListView extends AphrontView {
private $actions = array();
private $object;
private $objectURI;
private $id = null;
public function setObject(PhabricatorLiskDAO $object) {
$this->object = $object;
return $this;
}
public function setObjectURI($uri) {
$this->objectURI = $uri;
return $this;
}
public function addAction(PhabricatorActionView $view) {
$this->actions[] = $view;
return $this;
}
public function setID($id) {
$this->id = $id;
return $this;
}
public function render() {
if (!$this->user) {
- throw new Exception(pht('Call setUser() before render()!'));
+ throw new PhutilInvalidStateException('setUser');
}
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS,
array(
'object' => $this->object,
'actions' => $this->actions,
));
$event->setUser($this->user);
PhutilEventEngine::dispatchEvent($event);
$actions = $event->getValue('actions');
if (!$actions) {
return null;
}
foreach ($actions as $action) {
$action->setObjectURI($this->objectURI);
$action->setUser($this->user);
}
require_celerity_resource('phabricator-action-list-view-css');
return phutil_tag(
'ul',
array(
'class' => 'phabricator-action-list-view',
'id' => $this->id,
),
$actions);
}
}
diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php
index 8f49caa82a..439feb77c5 100644
--- a/src/view/phui/PHUITagView.php
+++ b/src/view/phui/PHUITagView.php
@@ -1,253 +1,253 @@
<?php
final class PHUITagView extends AphrontTagView {
const TYPE_PERSON = 'person';
const TYPE_OBJECT = 'object';
const TYPE_STATE = 'state';
const TYPE_SHADE = 'shade';
const COLOR_RED = 'red';
const COLOR_ORANGE = 'orange';
const COLOR_YELLOW = 'yellow';
const COLOR_BLUE = 'blue';
const COLOR_INDIGO = 'indigo';
const COLOR_VIOLET = 'violet';
const COLOR_GREEN = 'green';
const COLOR_BLACK = 'black';
const COLOR_GREY = 'grey';
const COLOR_WHITE = 'white';
const COLOR_PINK = 'pink';
const COLOR_BLUEGREY = 'bluegrey';
const COLOR_CHECKERED = 'checkered';
const COLOR_DISABLED = 'disabled';
const COLOR_OBJECT = 'object';
const COLOR_PERSON = 'person';
private $type;
private $href;
private $name;
private $phid;
private $backgroundColor;
private $dotColor;
private $closed;
private $external;
private $icon;
private $shade;
private $slimShady;
public function setType($type) {
$this->type = $type;
switch ($type) {
case self::TYPE_SHADE:
break;
case self::TYPE_OBJECT:
$this->setBackgroundColor(self::COLOR_OBJECT);
break;
case self::TYPE_PERSON:
$this->setBackgroundColor(self::COLOR_PERSON);
break;
}
return $this;
}
public function setShade($shade) {
$this->shade = $shade;
return $this;
}
public function setDotColor($dot_color) {
$this->dotColor = $dot_color;
return $this;
}
public function setBackgroundColor($background_color) {
$this->backgroundColor = $background_color;
return $this;
}
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function setHref($href) {
$this->href = $href;
return $this;
}
public function setClosed($closed) {
$this->closed = $closed;
return $this;
}
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function setSlimShady($mm) {
$this->slimShady = $mm;
return $this;
}
protected function getTagName() {
return strlen($this->href) ? 'a' : 'span';
}
protected function getTagAttributes() {
require_celerity_resource('phui-tag-view-css');
$classes = array(
'phui-tag-view',
'phui-tag-type-'.$this->type,
);
if ($this->shade) {
$classes[] = 'phui-tag-shade';
$classes[] = 'phui-tag-shade-'.$this->shade;
if ($this->slimShady) {
$classes[] = 'phui-tag-shade-slim';
}
}
if ($this->icon) {
$classes[] = 'phui-tag-icon-view';
}
if ($this->phid) {
Javelin::initBehavior('phabricator-hovercards');
$attributes = array(
'href' => $this->href,
'sigil' => 'hovercard',
'meta' => array(
'hoverPHID' => $this->phid,
),
'target' => $this->external ? '_blank' : null,
);
} else {
$attributes = array(
'href' => $this->href,
'target' => $this->external ? '_blank' : null,
);
}
return $attributes + array('class' => $classes);
}
protected function getTagContent() {
if (!$this->type) {
- throw new Exception(pht('You must call setType() before render()!'));
+ throw new PhutilInvalidStateException('setType', 'render');
}
$color = null;
if (!$this->shade && $this->backgroundColor) {
$color = 'phui-tag-color-'.$this->backgroundColor;
}
if ($this->dotColor) {
$dotcolor = 'phui-tag-color-'.$this->dotColor;
$dot = phutil_tag(
'span',
array(
'class' => 'phui-tag-dot '.$dotcolor,
),
'');
} else {
$dot = null;
}
if ($this->icon) {
$icon = id(new PHUIIconView())
->setIconFont($this->icon);
} else {
$icon = null;
}
$content = phutil_tag(
'span',
array(
'class' => 'phui-tag-core '.$color,
),
array($dot, $icon, $this->name));
if ($this->closed) {
$content = phutil_tag(
'span',
array(
'class' => 'phui-tag-core-closed',
),
array($icon, $content));
}
return $content;
}
public static function getTagTypes() {
return array(
self::TYPE_PERSON,
self::TYPE_OBJECT,
self::TYPE_STATE,
);
}
public static function getColors() {
return array(
self::COLOR_RED,
self::COLOR_ORANGE,
self::COLOR_YELLOW,
self::COLOR_BLUE,
self::COLOR_INDIGO,
self::COLOR_VIOLET,
self::COLOR_GREEN,
self::COLOR_BLACK,
self::COLOR_GREY,
self::COLOR_WHITE,
self::COLOR_OBJECT,
self::COLOR_PERSON,
);
}
public static function getShades() {
return array_keys(self::getShadeMap());
}
public static function getShadeMap() {
return array(
self::COLOR_RED => pht('Red'),
self::COLOR_ORANGE => pht('Orange'),
self::COLOR_YELLOW => pht('Yellow'),
self::COLOR_BLUE => pht('Blue'),
self::COLOR_INDIGO => pht('Indigo'),
self::COLOR_VIOLET => pht('Violet'),
self::COLOR_GREEN => pht('Green'),
self::COLOR_GREY => pht('Grey'),
self::COLOR_PINK => pht('Pink'),
self::COLOR_CHECKERED => pht('Checkered'),
self::COLOR_DISABLED => pht('Disabled'),
);
}
public static function getShadeName($shade) {
return idx(self::getShadeMap(), $shade, $shade);
}
public function setExternal($external) {
$this->external = $external;
return $this;
}
public function getExternal() {
return $this->external;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Apr 28, 5:34 PM (1 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1335456
Default Alt Text
(78 KB)

Event Timeline