diff --git a/src/applications/auth/controller/PhabricatorEmailTokenController.php b/src/applications/auth/controller/PhabricatorEmailTokenController.php
index e907a7dcb..0964a7a96 100644
--- a/src/applications/auth/controller/PhabricatorEmailTokenController.php
+++ b/src/applications/auth/controller/PhabricatorEmailTokenController.php
@@ -1,92 +1,93 @@
token = $data['token'];
}
public function processRequest() {
$request = $this->getRequest();
$token = $this->token;
$email = $request->getStr('email');
// NOTE: We need to bind verification to **addresses**, not **users**,
// because we verify addresses when they're used to login this way, and if
// we have a user-based verification you can:
//
// - Add some address you do not own;
// - request a password reset;
// - change the URI in the email to the address you don't own;
// - login via the email link; and
// - get a "verified" address you don't control.
$target_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$email);
$target_user = null;
if ($target_email) {
$target_user = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$target_email->getUserPHID());
}
if (!$target_email ||
!$target_user ||
!$target_user->validateEmailToken($target_email, $token)) {
$view = new AphrontRequestFailureView();
$view->setHeader(pht('Unable to Login'));
$view->appendChild(phutil_tag('p', array(), pht(
'The authentication information in the link you clicked is '.
'invalid or out of date. Make sure you are copy-and-pasting the '.
'entire link into your browser. You can try again, or request '.
'a new email.')));
- $view->appendChild(hsprintf(
- '
',
- pht('Send Another Email')));
+ $view->appendChild(phutil_tag_div(
+ 'aphront-failure-continue',
+ phutil_tag(
+ 'a',
+ array('class' => 'button', 'href' => '/login/email/'),
+ pht('Send Another Email'))));
return $this->buildStandardPageResponse(
$view,
array(
'title' => pht('Login Failure'),
));
}
// Verify email so that clicking the link in the "Welcome" email is good
// enough, without requiring users to go through a second round of email
// verification.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$target_email->setIsVerified(1);
$target_email->save();
unset($unguarded);
$next = '/';
if (!PhabricatorAuthProviderPassword::getPasswordProvider()) {
$next = '/settings/panel/external/';
} else if (PhabricatorEnv::getEnvConfig('account.editable')) {
$next = (string)id(new PhutilURI('/settings/panel/password/'))
->setQueryParams(
array(
'token' => $token,
'email' => $email,
));
}
$request->setCookie('next_uri', $next);
return $this->loginUser($target_user);
}
}
diff --git a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php
index 67a253b40..7b580a1ef 100644
--- a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php
+++ b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php
@@ -1,77 +1,77 @@
getRequest();
$user = $request->getUser();
$email = $user->loadPrimaryEmail();
if ($email->getIsVerified()) {
return id(new AphrontRedirectResponse())->setURI('/');
}
$email_address = $email->getAddress();
$sent = null;
if ($request->isFormPost()) {
$email->sendVerificationEmail($user);
$sent = new AphrontErrorView();
$sent->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$sent->setTitle(pht('Email Sent'));
$sent->appendChild(phutil_tag(
'p',
array(),
pht(
'Another verification email was sent to %s.',
phutil_tag('strong', array(), $email_address))));
}
$error_view = new AphrontRequestFailureView();
$error_view->setHeader(pht('Check Your Email'));
$error_view->appendChild(phutil_tag('p', array(), pht(
'You must verify your email address to login. You should have a new '.
'email message from Phabricator with verification instructions in your '.
'inbox (%s).', phutil_tag('strong', array(), $email_address))));
$error_view->appendChild(phutil_tag('p', array(), pht(
'If you did not receive an email, you can click the button below '.
'to try sending another one.')));
- $error_view->appendChild(hsprintf(
- '
%s
',
+ $error_view->appendChild(phutil_tag_div(
+ 'aphront-failure-continue',
phabricator_form(
$user,
array(
'action' => '/login/mustverify/',
'method' => 'POST',
),
phutil_tag(
'button',
array(
),
pht('Send Another Email')))));
return $this->buildApplicationPage(
array(
$sent,
$error_view,
),
array(
'title' => pht('Must Verify Email'),
'device' => true
));
}
}
diff --git a/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php b/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php
index 6b68b4249..9c4a365fc 100644
--- a/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php
+++ b/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php
@@ -1,393 +1,393 @@
setProperty(self::KEY_PORT, 389)
->setProperty(self::KEY_VERSION, 3);
}
public function getAdapter() {
if (!$this->adapter) {
$conf = $this->getProviderConfig();
$realname_attributes = $conf->getProperty(self::KEY_REALNAME_ATTRIBUTES);
if (!is_array($realname_attributes)) {
$realname_attributes = array();
}
$adapter = id(new PhutilAuthAdapterLDAP())
->setHostname(
$conf->getProperty(self::KEY_HOSTNAME))
->setPort(
$conf->getProperty(self::KEY_PORT))
->setBaseDistinguishedName(
$conf->getProperty(self::KEY_DISTINGUISHED_NAME))
->setSearchAttribute(
$conf->getProperty(self::KEY_SEARCH_ATTRIBUTE))
->setUsernameAttribute(
$conf->getProperty(self::KEY_USERNAME_ATTRIBUTE))
->setRealNameAttributes($realname_attributes)
->setLDAPVersion(
$conf->getProperty(self::KEY_VERSION))
->setLDAPReferrals(
$conf->getProperty(self::KEY_REFERRALS))
->setLDAPStartTLS(
$conf->getProperty(self::KEY_START_TLS))
->setAnonymousUsername(
$conf->getProperty(self::KEY_ANONYMOUS_USERNAME))
->setAnonymousPassword(
new PhutilOpaqueEnvelope(
$conf->getProperty(self::KEY_ANONYMOUS_PASSWORD)))
->setSearchFirst(
$conf->getProperty(self::KEY_SEARCH_FIRST))
->setActiveDirectoryDomain(
$conf->getProperty(self::KEY_ACTIVEDIRECTORY_DOMAIN));
$this->adapter = $adapter;
}
return $this->adapter;
}
protected function renderLoginForm(AphrontRequest $request, $mode) {
$viewer = $request->getUser();
$dialog = id(new AphrontDialogView())
->setSubmitURI($this->getLoginURI())
->setUser($viewer);
if ($mode == 'link') {
$dialog->setTitle(pht('Link LDAP Account'));
$dialog->addSubmitButton(pht('Link Accounts'));
$dialog->addCancelButton($this->getSettingsURI());
} else if ($mode == 'refresh') {
$dialog->setTitle(pht('Refresh LDAP Account'));
$dialog->addSubmitButton(pht('Refresh Account'));
$dialog->addCancelButton($this->getSettingsURI());
} else {
if ($this->shouldAllowRegistration()) {
$dialog->setTitle(pht('Login or Register with LDAP'));
$dialog->addSubmitButton(pht('Login or Register'));
} else {
$dialog->setTitle(pht('Login with LDAP'));
$dialog->addSubmitButton(pht('Login'));
}
if ($mode == 'login') {
$dialog->addCancelButton($this->getStartURI());
}
}
$v_user = $request->getStr('ldap_username');
$e_user = null;
$e_pass = null;
$errors = array();
if ($request->isHTTPPost()) {
// NOTE: This is intentionally vague so as not to disclose whether a
// given username exists.
$e_user = pht('Invalid');
$e_pass = pht('Invalid');
$errors[] = pht('Username or password are incorrect.');
}
$form = id(new PHUIFormLayoutView())
->setUser($viewer)
->setFullWidth(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel('LDAP Username')
->setName('ldap_username')
->setValue($v_user)
->setError($e_user))
->appendChild(
id(new AphrontFormPasswordControl())
->setLabel('LDAP Password')
->setName('ldap_password')
->setError($e_pass));
if ($errors) {
$errors = id(new AphrontErrorView())->setErrors($errors);
}
$dialog->appendChild($errors);
$dialog->appendChild($form);
return $dialog;
}
public function processLoginRequest(
PhabricatorAuthLoginController $controller) {
$request = $controller->getRequest();
$viewer = $request->getUser();
$response = null;
$account = null;
$username = $request->getStr('ldap_username');
$password = $request->getStr('ldap_password');
$has_password = strlen($password);
$password = new PhutilOpaqueEnvelope($password);
if (!strlen($username) || !$has_password) {
$response = $controller->buildProviderPageResponse(
$this,
$this->renderLoginForm($request, 'login'));
return array($account, $response);
}
try {
if (strlen($username) && $has_password) {
$adapter = $this->getAdapter();
$adapter->setLoginUsername($username);
$adapter->setLoginPassword($password);
// TODO: This calls ldap_bind() eventually, which dumps cleartext
// passwords to the error log. See note in PhutilAuthAdapterLDAP.
// See T3351.
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
$account_id = $adapter->getAccountID();
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
} else {
throw new Exception("Username and password are required!");
}
} catch (Exception $ex) {
// TODO: Make this cleaner.
throw $ex;
}
return array($this->loadOrCreateAccount($account_id), $response);
}
const KEY_HOSTNAME = 'ldap:host';
const KEY_PORT = 'ldap:port';
const KEY_DISTINGUISHED_NAME = 'ldap:dn';
const KEY_SEARCH_ATTRIBUTE = 'ldap:search-attribute';
const KEY_USERNAME_ATTRIBUTE = 'ldap:username-attribute';
const KEY_REALNAME_ATTRIBUTES = 'ldap:realname-attributes';
const KEY_VERSION = 'ldap:version';
const KEY_REFERRALS = 'ldap:referrals';
const KEY_START_TLS = 'ldap:start-tls';
const KEY_ANONYMOUS_USERNAME = 'ldap:anoynmous-username';
const KEY_ANONYMOUS_PASSWORD = 'ldap:anonymous-password';
const KEY_SEARCH_FIRST = 'ldap:search-first';
const KEY_ACTIVEDIRECTORY_DOMAIN = 'ldap:activedirectory-domain';
private function getPropertyKeys() {
return array_keys($this->getPropertyLabels());
}
private function getPropertyLabels() {
return array(
self::KEY_HOSTNAME => pht('LDAP Hostname'),
self::KEY_PORT => pht('LDAP Port'),
self::KEY_DISTINGUISHED_NAME => pht('Base Distinguished Name'),
self::KEY_SEARCH_ATTRIBUTE => pht('Search Attribute'),
self::KEY_USERNAME_ATTRIBUTE => pht('Username Attribute'),
self::KEY_REALNAME_ATTRIBUTES => pht('Realname Attributes'),
self::KEY_VERSION => pht('LDAP Version'),
self::KEY_REFERRALS => pht('Enable Referrals'),
self::KEY_START_TLS => pht('Use TLS'),
self::KEY_SEARCH_FIRST => pht('Search First'),
self::KEY_ANONYMOUS_USERNAME => pht('Anonymous Username'),
self::KEY_ANONYMOUS_PASSWORD => pht('Anonymous Password'),
self::KEY_ACTIVEDIRECTORY_DOMAIN => pht('ActiveDirectory Domain'),
);
}
public function readFormValuesFromProvider() {
$properties = array();
foreach ($this->getPropertyLabels() as $key => $ignored) {
$properties[$key] = $this->getProviderConfig()->getProperty($key);
}
return $properties;
}
public function readFormValuesFromRequest(AphrontRequest $request) {
$values = array();
foreach ($this->getPropertyKeys() as $key) {
switch ($key) {
case self::KEY_REALNAME_ATTRIBUTES:
$values[$key] = $request->getStrList($key, array());
break;
default:
$values[$key] = $request->getStr($key);
break;
}
}
return $values;
}
public function processEditForm(
AphrontRequest $request,
array $values) {
$errors = array();
$issues = array();
return array($errors, $issues, $values);
}
public function extendEditForm(
AphrontRequest $request,
AphrontFormView $form,
array $values,
array $issues) {
$labels = $this->getPropertyLabels();
$captions = array(
self::KEY_HOSTNAME =>
pht('Example: %s',
- hsprintf('%s', pht('ldap.example.com'))),
+ phutil_tag('tt', array(), pht('ldap.example.com'))),
self::KEY_DISTINGUISHED_NAME =>
pht('Example: %s',
- hsprintf('%s', pht('ou=People, dc=example, dc=com'))),
+ phutil_tag('tt', array(), pht('ou=People, dc=example, dc=com'))),
self::KEY_SEARCH_ATTRIBUTE =>
pht('Example: %s',
- hsprintf('%s', pht('sn'))),
+ phutil_tag('tt', array(), pht('sn'))),
self::KEY_USERNAME_ATTRIBUTE =>
pht('Optional, if different from search attribute.'),
self::KEY_REALNAME_ATTRIBUTES =>
pht('Optional. Example: %s',
- hsprintf('%s', pht('firstname, lastname'))),
+ phutil_tag('tt', array(), pht('firstname, lastname'))),
self::KEY_REFERRALS =>
pht('Follow referrals. Disable this for Windows AD 2003.'),
self::KEY_START_TLS =>
pht('Start TLS after binding to the LDAP server.'),
self::KEY_SEARCH_FIRST =>
pht(
'When the user enters their username, search for a matching '.
'record using the "Search Attribute", then try to bind using '.
'the DN for the record. This is useful if usernames are not '.
'part of the record DN.'),
self::KEY_ANONYMOUS_USERNAME =>
pht('Username to bind with before searching.'),
self::KEY_ANONYMOUS_PASSWORD =>
pht('Password to bind with before searching.'),
);
$types = array(
self::KEY_REFERRALS => 'checkbox',
self::KEY_START_TLS => 'checkbox',
self::KEY_SEARCH_FIRST => 'checkbox',
self::KEY_REALNAME_ATTRIBUTES => 'list',
self::KEY_ANONYMOUS_PASSWORD => 'password',
);
foreach ($labels as $key => $label) {
$caption = idx($captions, $key);
$type = idx($types, $key);
$value = idx($values, $key);
$control = null;
switch ($type) {
case 'checkbox':
$control = id(new AphrontFormCheckboxControl())
->addCheckbox(
$key,
1,
hsprintf('%s: %s', $label, $caption),
$value);
break;
case 'list':
$control = id(new AphrontFormTextControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setValue($value ? implode(', ', $value) : null);
break;
case 'password':
$control = id(new AphrontFormPasswordControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setValue($value);
break;
default:
$control = id(new AphrontFormTextControl())
->setName($key)
->setLabel($label)
->setCaption($caption)
->setValue($value);
break;
}
$form->appendChild($control);
}
}
public function renderConfigPropertyTransactionTitle(
PhabricatorAuthProviderConfigTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$key = $xaction->getMetadataValue(
PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY);
$labels = $this->getPropertyLabels();
if (isset($labels[$key])) {
$label = $labels[$key];
$mask = false;
switch ($key) {
case self::KEY_ANONYMOUS_PASSWORD:
$mask = true;
break;
}
if ($mask) {
return pht(
'%s updated the "%s" value.',
$xaction->renderHandleLink($author_phid),
$label);
}
if (!strlen($old)) {
return pht(
'%s set the "%s" value to "%s".',
$xaction->renderHandleLink($author_phid),
$label,
$new);
} else {
return pht(
'%s changed the "%s" value from "%s" to "%s".',
$xaction->renderHandleLink($author_phid),
$label,
$old,
$new);
}
}
return parent::renderConfigPropertyTransactionTitle($xaction);
}
public static function getLDAPProvider() {
$providers = self::getAllEnabledProviders();
foreach ($providers as $provider) {
if ($provider instanceof PhabricatorAuthProviderLDAP) {
return $provider;
}
}
return null;
}
}
diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php
index 0fe516968..10f7a5921 100644
--- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php
+++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php
@@ -1,128 +1,126 @@
getDomain());
}
public function getDefaultProviderConfig() {
return parent::getDefaultProviderConfig()
->setProperty(self::KEY_REQUIRE_SECURE, 1);
}
protected function newOAuthAdapter() {
$require_secure = $this->getProviderConfig()->getProperty(
self::KEY_REQUIRE_SECURE);
return id(new PhutilAuthAdapterOAuthFacebook())
->setRequireSecureBrowsing($require_secure);
}
protected function getLoginIcon() {
return 'Facebook';
}
public function readFormValuesFromProvider() {
$require_secure = $this->getProviderConfig()->getProperty(
self::KEY_REQUIRE_SECURE);
return parent::readFormValuesFromProvider() + array(
self::KEY_REQUIRE_SECURE => $require_secure,
);
}
public function readFormValuesFromRequest(AphrontRequest $request) {
return parent::readFormValuesFromRequest($request) + array(
self::KEY_REQUIRE_SECURE => $request->getBool(self::KEY_REQUIRE_SECURE),
);
}
public function extendEditForm(
AphrontRequest $request,
AphrontFormView $form,
array $values,
array $issues) {
parent::extendEditForm($request, $form, $values, $issues);
$key_require = self::KEY_REQUIRE_SECURE;
$v_require = idx($values, $key_require);
$form
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
$key_require,
$v_require,
pht(
"%s ".
"Require users to enable 'secure browsing' on Facebook in order ".
"to use Facebook to authenticate with Phabricator. This ".
"improves security by preventing an attacker from capturing ".
"an insecure Facebook session and escalating it into a ".
"Phabricator session. Enabling it is recommended.",
- hsprintf(
- '%s',
- pht('Require Secure Browsing:')))));
+ phutil_tag('strong', array(), pht('Require Secure Browsing:')))));
}
public function renderConfigPropertyTransactionTitle(
PhabricatorAuthProviderConfigTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$key = $xaction->getMetadataValue(
PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY);
switch ($key) {
case self::KEY_REQUIRE_SECURE:
if ($new) {
return pht(
'%s turned "Require Secure Browsing" on.',
$xaction->renderHandleLink($author_phid));
} else {
return pht(
'%s turned "Require Secure Browsing" off.',
$xaction->renderHandleLink($author_phid));
}
}
return parent::renderConfigPropertyTransactionTitle($xaction);
}
public static function getFacebookApplicationID() {
$providers = PhabricatorAuthProvider::getAllProviders();
$fb_provider = idx($providers, 'facebook:facebook.com');
if (!$fb_provider) {
return null;
}
return $fb_provider->getProviderConfig()->getProperty(
PhabricatorAuthProviderOAuth::PROPERTY_APP_ID);
}
}
diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php
index 5449546b0..d4cbd60de 100644
--- a/src/applications/base/controller/PhabricatorController.php
+++ b/src/applications/base/controller/PhabricatorController.php
@@ -1,410 +1,411 @@
getRequest();
if ($request->getUser()) {
// NOTE: Unit tests can set a user explicitly. Normal requests are not
// permitted to do this.
PhabricatorTestCase::assertExecutingUnitTests();
$user = $request->getUser();
} else {
$user = new PhabricatorUser();
$phusr = $request->getCookie('phusr');
$phsid = $request->getCookie('phsid');
if (strlen($phusr) && $phsid) {
$info = queryfx_one(
$user->establishConnection('r'),
'SELECT u.* FROM %T u JOIN %T s ON u.phid = s.userPHID
AND s.type LIKE %> AND s.sessionKey = %s',
$user->getTableName(),
'phabricator_session',
'web-',
PhabricatorHash::digest($phsid));
if ($info) {
$user->loadFromArray($info);
}
}
$request->setUser($user);
}
$translation = $user->getTranslation();
if ($translation &&
$translation != PhabricatorEnv::getEnvConfig('translation.provider')) {
$translation = newv($translation, array());
PhutilTranslator::getInstance()
->setLanguage($translation->getLanguage())
->addTranslations($translation->getTranslations());
}
$preferences = $user->loadPreferences();
if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
$dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE;
if ($preferences->getPreference($dark_console) ||
PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
$console = new DarkConsoleCore();
$request->getApplicationConfiguration()->setConsole($console);
}
}
if ($user->getIsDisabled() && $this->shouldRequireEnabledUser()) {
$disabled_user_controller = new PhabricatorDisabledUserController(
$request);
return $this->delegateToController($disabled_user_controller);
}
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST,
array(
'request' => $request,
'controller' => $this,
));
$event->setUser($user);
PhutilEventEngine::dispatchEvent($event);
$checker_controller = $event->getValue('controller');
if ($checker_controller != $this) {
return $this->delegateToController($checker_controller);
}
if ($this->shouldRequireLogin()) {
// This actually means we need either:
// - a valid user, or a public controller; and
// - permission to see the application.
$auth_class = 'PhabricatorApplicationAuth';
$auth_application = PhabricatorApplication::getByClass($auth_class);
$allow_public = $this->shouldAllowPublic() &&
PhabricatorEnv::getEnvConfig('policy.allow-public');
// If this controller isn't public, and the user isn't logged in, require
// login.
if (!$allow_public && !$user->isLoggedIn()) {
$login_controller = new PhabricatorAuthStartController($request);
$this->setCurrentApplication($auth_application);
return $this->delegateToController($login_controller);
}
if ($user->isLoggedIn()) {
if ($this->shouldRequireEmailVerification()) {
$email = $user->loadPrimaryEmail();
if (!$email) {
throw new Exception(
"No primary email address associated with this account!");
}
if (!$email->getIsVerified()) {
$controller = new PhabricatorMustVerifyEmailController($request);
$this->setCurrentApplication($auth_application);
return $this->delegateToController($controller);
}
}
}
// If the user doesn't have access to the application, don't let them use
// any of its controllers. We query the application in order to generate
// a policy exception if the viewer doesn't have permission.
$application = $this->getCurrentApplication();
if ($application) {
id(new PhabricatorApplicationQuery())
->setViewer($user)
->withPHIDs(array($application->getPHID()))
->executeOne();
}
}
// NOTE: We do this last so that users get a login page instead of a 403
// if they need to login.
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
return new Aphront403Response();
}
}
public function buildStandardPageView() {
$view = new PhabricatorStandardPageView();
$view->setRequest($this->getRequest());
$view->setController($this);
return $view;
}
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->appendChild($view);
$response = new AphrontWebpageResponse();
$response->setContent($page->render());
return $response;
}
public function getApplicationURI($path = '') {
if (!$this->getCurrentApplication()) {
throw new Exception("No application!");
}
return $this->getCurrentApplication()->getApplicationURI($path);
}
public function buildApplicationPage($view, array $options) {
$page = $this->buildStandardPageView();
$title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ?
'Phabricator' :
pht('Bacon Ice Cream for Breakfast');
$application = $this->getCurrentApplication();
$page->setTitle(idx($options, 'title', $title));
if ($application) {
$page->setApplicationName($application->getName());
if ($application->getTitleGlyph()) {
$page->setGlyph($application->getTitleGlyph());
}
}
if (!($view instanceof AphrontSideNavFilterView)) {
$nav = new AphrontSideNavFilterView();
$nav->appendChild($view);
$view = $nav;
}
$user = $this->getRequest()->getUser();
$view->setUser($user);
$page->appendChild($view);
$object_phids = idx($options, 'pageObjects', array());
if ($object_phids) {
$page->appendPageObjects($object_phids);
foreach ($object_phids as $object_phid) {
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
$user,
$object_phid);
}
}
if (idx($options, 'device')) {
$page->setDeviceReady(true);
}
$page->setShowChrome(idx($options, 'chrome', true));
$application_menu = $this->buildApplicationMenu();
if ($application_menu) {
$page->setApplicationMenu($application_menu);
}
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
}
public function didProcessRequest($response) {
$request = $this->getRequest();
$response->setRequest($request);
$seen = array();
while ($response instanceof AphrontProxyResponse) {
$hash = spl_object_hash($response);
if (isset($seen[$hash])) {
$seen[] = get_class($response);
throw new Exception(
"Cycle while reducing proxy responses: ".
implode(' -> ', $seen));
}
$seen[$hash] = get_class($response);
$response = $response->reduceProxyResponse();
}
if ($response instanceof AphrontDialogResponse) {
if (!$request->isAjax()) {
$view = new PhabricatorStandardPageView();
$view->setRequest($request);
$view->setController($this);
- $view->appendChild(hsprintf(
- '
%s
',
+ $view->appendChild(phutil_tag(
+ 'div',
+ array('style' => 'padding: 2em 0;'),
$response->buildResponseString()));
$page_response = new AphrontWebpageResponse();
$page_response->setContent($view->render());
$page_response->setHTTPResponseCode($response->getHTTPResponseCode());
return $page_response;
} else {
$response->getDialog()->setIsStandalone(true);
return id(new AphrontAjaxResponse())
->setContent(array(
'dialog' => $response->buildResponseString(),
));
}
} else if ($response instanceof AphrontRedirectResponse) {
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'redirect' => $response->getURI(),
));
}
}
return $response;
}
protected function getHandle($phid) {
if (empty($this->handles[$phid])) {
throw new Exception(
"Attempting to access handle which wasn't loaded: {$phid}");
}
return $this->handles[$phid];
}
protected function loadHandles(array $phids) {
$phids = array_filter($phids);
$this->handles = $this->loadViewerHandles($phids);
return $this;
}
protected function getLoadedHandles() {
return $this->handles;
}
protected function loadViewerHandles(array $phids) {
return id(new PhabricatorHandleQuery())
->setViewer($this->getRequest()->getUser())
->withPHIDs($phids)
->execute();
}
/**
* Render a list of links to handles, identified by PHIDs. The handles must
* already be loaded.
*
* @param list List of PHIDs to render links to.
* @param string Style, one of "\n" (to put each item on its own line)
* or "," (to list items inline, separated by commas).
* @return string Rendered list of handle links.
*/
protected function renderHandlesForPHIDs(array $phids, $style = "\n") {
$style_map = array(
"\n" => phutil_tag('br'),
',' => ', ',
);
if (empty($style_map[$style])) {
throw new Exception("Unknown handle list style '{$style}'!");
}
return implode_selected_handle_links($style_map[$style],
$this->getLoadedHandles(),
array_filter($phids));
}
protected function buildApplicationMenu() {
return null;
}
protected function buildApplicationCrumbs() {
$crumbs = array();
$application = $this->getCurrentApplication();
if ($application) {
$sprite = $application->getIconName();
if (!$sprite) {
$sprite = 'application';
}
$crumbs[] = id(new PhabricatorCrumbView())
->setHref($this->getApplicationURI())
->setIcon($sprite);
}
$view = new PhabricatorCrumbsView();
foreach ($crumbs as $crumb) {
$view->addCrumb($crumb);
}
return $view;
}
protected function hasApplicationCapability($capability) {
return PhabricatorPolicyFilter::hasCapability(
$this->getRequest()->getUser(),
$this->getCurrentApplication(),
$capability);
}
protected function requireApplicationCapability($capability) {
PhabricatorPolicyFilter::requireCapability(
$this->getRequest()->getUser(),
$this->getCurrentApplication(),
$capability);
}
protected function explainApplicationCapability(
$capability,
$positive_message,
$negative_message) {
$can_act = $this->hasApplicationCapability($capability);
if ($can_act) {
$message = $positive_message;
$icon_name = 'enable-grey';
} else {
$message = $negative_message;
$icon_name = 'lock';
}
$icon = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
->setSpriteIcon($icon_name);
require_celerity_resource('policy-css');
$phid = $this->getCurrentApplication()->getPHID();
$explain_uri = "/policy/explain/{$phid}/{$capability}/";
$message = phutil_tag(
'div',
array(
'class' => 'policy-capability-explanation',
),
array(
$icon,
javelin_tag(
'a',
array(
'href' => $explain_uri,
'sigil' => 'workflow',
),
$message),
));
return array($can_act, $message);
}
}
diff --git a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php b/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php
index 5ab092868..88e2dfeb0 100644
--- a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php
@@ -1,96 +1,96 @@
getRequest();
$user = $request->getUser();
$year_d = phabricator_format_local_time($now, $user, 'Y');
$year = $request->getInt('year', $year_d);
$month_d = phabricator_format_local_time($now, $user, 'm');
$month = $request->getInt('month', $month_d);
$day = phabricator_format_local_time($now, $user, 'j');
$holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere(
'day BETWEEN %s AND %s',
"{$year}-{$month}-01",
"{$year}-{$month}-31");
$statuses = id(new PhabricatorUserStatus())
->loadAllWhere(
'dateTo >= %d AND dateFrom <= %d',
strtotime("{$year}-{$month}-01"),
strtotime("{$year}-{$month}-01 next month"));
if ($month == $month_d && $year == $year_d) {
$month_view = new AphrontCalendarMonthView($month, $year, $day);
} else {
$month_view = new AphrontCalendarMonthView($month, $year);
}
$month_view->setBrowseURI($request->getRequestURI());
$month_view->setUser($user);
$month_view->setHolidays($holidays);
$phids = mpull($statuses, 'getUserPHID');
$handles = $this->loadViewerHandles($phids);
foreach ($statuses as $status) {
$event = new AphrontCalendarEventView();
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
$name_text = $handles[$status->getUserPHID()]->getName();
$status_text = $status->getHumanStatus();
$event->setUserPHID($status->getUserPHID());
$event->setName("{$name_text} ({$status_text})");
$details = '';
if ($status->getDescription()) {
$details = "\n\n".rtrim($status->getDescription());
}
$event->setDescription(
$status->getTerseSummary($user).$details);
$event->setEventID($status->getID());
$month_view->addEvent($event);
}
$nav = $this->buildSideNavView();
$nav->selectFilter('/');
$nav->appendChild(
array(
$this->getNoticeView(),
- hsprintf('
',
- $left_th,
- phutil_tag('th', array('colspan' => $colspan), $date->format('F Y')),
- $right_th);
+ return phutil_tag(
+ 'tr',
+ array('class' => 'aphront-calendar-month-year-header'),
+ array(
+ $left_th,
+ phutil_tag('th', array('colspan' => $colspan), $date->format('F Y')),
+ $right_th,
+ ));
}
private function getNextYearAndMonth() {
$month = $this->month;
$year = $this->year;
$next_year = $year;
$next_month = $month + 1;
if ($next_month == 13) {
$next_year = $year + 1;
$next_month = 1;
}
return array($next_year, $next_month);
}
private function getPrevYearAndMonth() {
$month = $this->month;
$year = $this->year;
$prev_year = $year;
$prev_month = $month - 1;
if ($prev_month == 0) {
$prev_year = $year - 1;
$prev_month = 12;
}
return array($prev_year, $prev_month);
}
/**
* Return a DateTime object representing the first moment in each day in the
* month, according to the user's locale.
*
* @return list List of DateTimes, one for each day.
*/
private function getDatesInMonth() {
$user = $this->user;
$timezone = new DateTimeZone($user->getTimezoneIdentifier());
$month = $this->month;
$year = $this->year;
// Get the year and month numbers of the following month, so we can
// determine when this month ends.
list($next_year, $next_month) = $this->getNextYearAndMonth();
$end_date = new DateTime("{$next_year}-{$next_month}-01", $timezone);
$end_epoch = $end_date->format('U');
$days = array();
for ($day = 1; $day <= 31; $day++) {
$day_date = new DateTime("{$year}-{$month}-{$day}", $timezone);
$day_epoch = $day_date->format('U');
if ($day_epoch >= $end_epoch) {
break;
} else {
$days[] = $day_date;
}
}
return $days;
}
private function renderEvent(
AphrontCalendarEventView $event,
$epoch_start,
$epoch_end) {
$user = $this->user;
$event_start = $event->getEpochStart();
$event_end = $event->getEpochEnd();
$classes = array();
$when = array();
$classes[] = 'aphront-calendar-event';
if ($event_start < $epoch_start) {
$classes[] = 'aphront-calendar-event-continues-before';
$when[] = 'Started '.phabricator_datetime($event_start, $user);
} else {
$when[] = 'Starts at '.phabricator_time($event_start, $user);
}
if ($event_end > $epoch_end) {
$classes[] = 'aphront-calendar-event-continues-after';
$when[] = 'Ends '.phabricator_datetime($event_end, $user);
} else {
$when[] = 'Ends at '.phabricator_time($event_end, $user);
}
Javelin::initBehavior('phabricator-tooltips');
$info = $event->getName();
if ($event->getDescription()) {
$info .= "\n\n".$event->getDescription();
}
if ($user->getPHID() == $event->getUserPHID()) {
$tag = 'a';
$href = '/calendar/status/edit/'.$event->getEventID().'/';
} else {
$tag = 'div';
$href = null;
}
$text_div = javelin_tag(
$tag,
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $info."\n\n".implode("\n", $when),
'size' => 240,
),
'class' => 'aphront-calendar-event-text',
'href' => $href,
),
phutil_utf8_shorten($event->getName(), 32));
return javelin_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$text_div);
}
}
diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
index 837674996..ce6b6a02c 100644
--- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
@@ -1,502 +1,505 @@
method = $data['method'];
return $this;
}
public function processRequest() {
$time_start = microtime(true);
$request = $this->getRequest();
$method = $this->method;
$api_request = null;
$log = new PhabricatorConduitMethodCallLog();
$log->setMethod($method);
$metadata = array();
try {
$params = $this->decodeConduitParams($request, $method);
$metadata = idx($params, '__conduit__', array());
unset($params['__conduit__']);
$call = new ConduitCall(
$method, $params, idx($metadata, 'isProxied', false));
$result = null;
// TODO: Straighten out the auth pathway here. We shouldn't be creating
// a ConduitAPIRequest at this level, but some of the auth code expects
// it. Landing a halfway version of this to unblock T945.
$api_request = new ConduitAPIRequest($params);
$allow_unguarded_writes = false;
$auth_error = null;
$conduit_username = '-';
if ($call->shouldRequireAuthentication()) {
$metadata['scope'] = $call->getRequiredScope();
$auth_error = $this->authenticateUser($api_request, $metadata);
// If we've explicitly authenticated the user here and either done
// CSRF validation or are using a non-web authentication mechanism.
$allow_unguarded_writes = true;
if (isset($metadata['actAsUser'])) {
$this->actAsUser($api_request, $metadata['actAsUser']);
}
if ($auth_error === null) {
$conduit_user = $api_request->getUser();
if ($conduit_user && $conduit_user->getPHID()) {
$conduit_username = $conduit_user->getUsername();
}
$call->setUser($api_request->getUser());
}
}
$access_log = PhabricatorAccessLog::getLog();
if ($access_log) {
$access_log->setData(
array(
'u' => $conduit_username,
'm' => $method,
));
}
if ($call->shouldAllowUnguardedWrites()) {
$allow_unguarded_writes = true;
}
if ($auth_error === null) {
if ($allow_unguarded_writes) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
}
try {
$result = $call->execute();
$error_code = null;
$error_info = null;
} catch (ConduitException $ex) {
$result = null;
$error_code = $ex->getMessage();
if ($ex->getErrorDescription()) {
$error_info = $ex->getErrorDescription();
} else {
$error_info = $call->getErrorDescription($error_code);
}
}
if ($allow_unguarded_writes) {
unset($unguarded);
}
} else {
list($error_code, $error_info) = $auth_error;
}
} catch (Exception $ex) {
phlog($ex);
$result = null;
$error_code = ($ex instanceof ConduitException
? 'ERR-CONDUIT-CALL'
: 'ERR-CONDUIT-CORE');
$error_info = $ex->getMessage();
}
$time_end = microtime(true);
$connection_id = null;
if (idx($metadata, 'connectionID')) {
$connection_id = $metadata['connectionID'];
} else if (($method == 'conduit.connect') && $result) {
$connection_id = idx($result, 'connectionID');
}
$log
->setCallerPHID(
isset($conduit_user)
? $conduit_user->getPHID()
: null)
->setConnectionID($connection_id)
->setError((string)$error_code)
->setDuration(1000000 * ($time_end - $time_start));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$log->save();
unset($unguarded);
$response = id(new ConduitAPIResponse())
->setResult($result)
->setErrorCode($error_code)
->setErrorInfo($error_info);
switch ($request->getStr('output')) {
case 'human':
return $this->buildHumanReadableResponse(
$method,
$api_request,
$response->toDictionary());
case 'json':
default:
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
}
}
/**
* Change the api request user to the user that we want to act as.
* Only admins can use actAsUser
*
* @param ConduitAPIRequest Request being executed.
* @param string The username of the user we want to act as
*/
private function actAsUser(
ConduitAPIRequest $api_request,
$user_name) {
if (!$api_request->getUser()->getIsAdmin()) {
throw new Exception("Only administrators can use actAsUser");
}
$user = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$user_name);
if (!$user) {
throw new Exception(
"The actAsUser username '{$user_name}' is not a valid user."
);
}
$api_request->setUser($user);
}
/**
* Authenticate the client making the request to a Phabricator user account.
*
* @param ConduitAPIRequest Request being executed.
* @param dict Request metadata.
* @return null|pair Null to indicate successful authentication, or
* an error code and error message pair.
*/
private function authenticateUser(
ConduitAPIRequest $api_request,
array $metadata) {
$request = $this->getRequest();
if ($request->getUser()->getPHID()) {
$request->validateCSRF();
return $this->validateAuthenticatedUser(
$api_request,
$request->getUser());
}
// handle oauth
$access_token = $request->getStr('access_token');
$method_scope = $metadata['scope'];
if ($access_token &&
$method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) {
$token = id(new PhabricatorOAuthServerAccessToken())
->loadOneWhere('token = %s',
$access_token);
if (!$token) {
return array(
'ERR-INVALID-AUTH',
'Access token does not exist.',
);
}
$oauth_server = new PhabricatorOAuthServer();
$valid = $oauth_server->validateAccessToken($token,
$method_scope);
if (!$valid) {
return array(
'ERR-INVALID-AUTH',
'Access token is invalid.',
);
}
// valid token, so let's log in the user!
$user_phid = $token->getUserPHID();
$user = id(new PhabricatorUser())
->loadOneWhere('phid = %s',
$user_phid);
if (!$user) {
return array(
'ERR-INVALID-AUTH',
'Access token is for invalid user.',
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
// Handle sessionless auth. TOOD: This is super messy.
if (isset($metadata['authUser'])) {
$user = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$metadata['authUser']);
if (!$user) {
return array(
'ERR-INVALID-AUTH',
'Authentication is invalid.',
);
}
$token = idx($metadata, 'authToken');
$signature = idx($metadata, 'authSignature');
$certificate = $user->getConduitCertificate();
if (sha1($token.$certificate) !== $signature) {
return array(
'ERR-INVALID-AUTH',
'Authentication is invalid.',
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
$session_key = idx($metadata, 'sessionKey');
if (!$session_key) {
return array(
'ERR-INVALID-SESSION',
'Session key is not present.'
);
}
$session = queryfx_one(
id(new PhabricatorUser())->establishConnection('r'),
'SELECT * FROM %T WHERE sessionKey = %s',
PhabricatorUser::SESSION_TABLE,
PhabricatorHash::digest($session_key));
if (!$session) {
return array(
'ERR-INVALID-SESSION',
'Session key is invalid.',
);
}
// TODO: Make sessions timeout.
// TODO: When we pull a session, read connectionID from the session table.
$user = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$session['userPHID']);
if (!$user) {
return array(
'ERR-INVALID-SESSION',
'Session is for nonexistent user.',
);
}
return $this->validateAuthenticatedUser(
$api_request,
$user);
}
private function validateAuthenticatedUser(
ConduitAPIRequest $request,
PhabricatorUser $user) {
if ($user->getIsDisabled()) {
return array(
'ERR-USER-DISABLED',
'User is disabled.');
}
if (PhabricatorUserEmail::isEmailVerificationRequired()) {
$email = $user->loadPrimaryEmail();
if (!$email) {
return array(
'ERR-USER-NOEMAIL',
'User has no primary email address.');
}
if (!$email->getIsVerified()) {
return array(
'ERR-USER-UNVERIFIED',
'User has unverified email address.');
}
}
$request->setUser($user);
return null;
}
private function buildHumanReadableResponse(
$method,
ConduitAPIRequest $request = null,
$result = null) {
$param_rows = array();
$param_rows[] = array('Method', $this->renderAPIValue($method));
if ($request) {
foreach ($request->getAllParameters() as $key => $value) {
$param_rows[] = array(
$key,
$this->renderAPIValue($value),
);
}
}
$param_table = new AphrontTableView($param_rows);
$param_table->setDeviceReadyTable(true);
$param_table->setColumnClasses(
array(
'header',
'wide',
));
$result_rows = array();
foreach ($result as $key => $value) {
$result_rows[] = array(
$key,
$this->renderAPIValue($value),
);
}
$result_table = new AphrontTableView($result_rows);
$result_table->setDeviceReadyTable(true);
$result_table->setColumnClasses(
array(
'header',
'wide',
));
$param_panel = new AphrontPanelView();
$param_panel->setHeader('Method Parameters');
$param_panel->appendChild($param_table);
$result_panel = new AphrontPanelView();
$result_panel->setHeader('Method Result');
$result_panel->appendChild($result_table);
$param_head = id(new PHUIHeaderView())
->setHeader(pht('Method Parameters'));
$result_head = id(new PHUIHeaderView())
->setHeader(pht('Method Result'));
$method_uri = $this->getApplicationURI('method/'.$method.'/');
$crumbs = $this->buildApplicationCrumbs();
$crumbs
->addCrumb(
id(new PhabricatorCrumbView())
->setName($method)
->setHref($method_uri))
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Call')));
return $this->buildApplicationPage(
array(
$crumbs,
$param_head,
$param_table,
$result_head,
$result_table,
),
array(
'title' => 'Method Call Result',
'device' => true,
));
}
private function renderAPIValue($value) {
$json = new PhutilJSON();
if (is_array($value)) {
$value = $json->encodeFormatted($value);
}
- $value = hsprintf('
%s
', $value);
+ $value = phutil_tag(
+ 'pre',
+ array('style' => 'white-space: pre-wrap;'),
+ $value);
return $value;
}
private function decodeConduitParams(
AphrontRequest $request,
$method) {
// Look for parameters from the Conduit API Console, which are encoded
// as HTTP POST parameters in an array, e.g.:
//
// params[name]=value¶ms[name2]=value2
//
// The fields are individually JSON encoded, since we require users to
// enter JSON so that we avoid type ambiguity.
$params = $request->getArr('params', null);
if ($params !== null) {
foreach ($params as $key => $value) {
if ($value == '') {
// Interpret empty string null (e.g., the user didn't type anything
// into the box).
$value = 'null';
}
$decoded_value = json_decode($value, true);
if ($decoded_value === null && strtolower($value) != 'null') {
// When json_decode() fails, it returns null. This almost certainly
// indicates that a user was using the web UI and didn't put quotes
// around a string value. We can either do what we think they meant
// (treat it as a string) or fail. For now, err on the side of
// caution and fail. In the future, if we make the Conduit API
// actually do type checking, it might be reasonable to treat it as
// a string if the parameter type is string.
throw new Exception(
"The value for parameter '{$key}' is not valid JSON. All ".
"parameters must be encoded as JSON values, including strings ".
"(which means you need to surround them in double quotes). ".
"Check your syntax. Value was: {$value}");
}
$params[$key] = $decoded_value;
}
return $params;
}
// Otherwise, look for a single parameter called 'params' which has the
// entire param dictionary JSON encoded. This is the usual case for remote
// requests.
$params_json = $request->getStr('params');
if (!strlen($params_json)) {
if ($request->getBool('allowEmptyParams')) {
// TODO: This is a bit messy, but otherwise you can't call
// "conduit.ping" from the web console.
$params = array();
} else {
throw new Exception(
"Request has no 'params' key. This may mean that an extension like ".
"Suhosin has dropped data from the request. Check the PHP ".
"configuration on your server. If you are developing a Conduit ".
"client, you MUST provide a 'params' parameter when making a ".
"Conduit request, even if the value is empty (e.g., provide '{}').");
}
} else {
$params = json_decode($params_json, true);
if (!is_array($params)) {
throw new Exception(
"Invalid parameter information was passed to method ".
"'{$method}', could not decode JSON serialization. Data: ".
$params_json);
}
}
return $params;
}
}
diff --git a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
index 91a0ad7e4..43bf22bd3 100644
--- a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
+++ b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
@@ -1,137 +1,138 @@
setParameter('isStable', $request->getStr('isStable'));
$saved->setParameter('isUnstable', $request->getStr('isUnstable'));
$saved->setParameter('isDeprecated', $request->getStr('isDeprecated'));
$saved->setParameter(
'applicationNames',
$request->getStrList('applicationNames'));
$saved->setParameter('nameContains', $request->getStr('nameContains'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new PhabricatorConduitMethodQuery());
$query->withIsStable($saved->getParameter('isStable'));
$query->withIsUnstable($saved->getParameter('isUnstable'));
$query->withIsDeprecated($saved->getParameter('isDeprecated'));
$names = $saved->getParameter('applicationNames', array());
if ($names) {
$query->withApplicationNames($names);
}
$contains = $saved->getParameter('nameContains');
if (strlen($contains)) {
$query->withNameContains($contains);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name Contains')
->setName('nameContains')
->setValue($saved->getParameter('nameContains')));
$names = $saved->getParameter('applicationNames', array());
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Applications')
->setName('applicationNames')
->setValue(implode(', ', $names))
- ->setCaption(
- pht('Example: %s', hsprintf('differential, paste'))));
+ ->setCaption(pht(
+ 'Example: %s',
+ phutil_tag('tt', array(), 'differential, paste'))));
$is_stable = $saved->getParameter('isStable');
$is_unstable = $saved->getParameter('isUnstable');
$is_deprecated = $saved->getParameter('isDeprecated');
$form
->appendChild(
id(new AphrontFormCheckboxControl())
->setLabel('Stability')
->addCheckbox(
'isStable',
1,
hsprintf(
'%s: %s',
pht('Stable Methods'),
pht('Show established API methods with stable interfaces.')),
$is_stable)
->addCheckbox(
'isUnstable',
1,
hsprintf(
'%s: %s',
pht('Unstable Methods'),
pht('Show new methods which are subject to change.')),
$is_unstable)
->addCheckbox(
'isDeprecated',
1,
hsprintf(
'%s: %s',
pht('Deprecated Methods'),
pht(
'Show old methods which will be deleted in a future '.
'version of Phabricator.')),
$is_deprecated));
}
protected function getURI($path) {
return '/conduit/'.$path;
}
public function getBuiltinQueryNames() {
$names = array(
'modern' => pht('Modern Methods'),
'all' => pht('All Methods'),
);
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'modern':
return $query
->setParameter('isStable', true)
->setParameter('isUnstable', true);
case 'all':
return $query
->setParameter('isStable', true)
->setParameter('isUnstable', true)
->setParameter('isDeprecated', true);
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php
index e2da019e9..7ec6e41c7 100644
--- a/src/applications/config/controller/PhabricatorConfigEditController.php
+++ b/src/applications/config/controller/PhabricatorConfigEditController.php
@@ -1,545 +1,545 @@
key = $data['key'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
if (empty($options[$this->key])) {
$ancient = PhabricatorSetupCheckExtraConfig::getAncientConfig();
if (isset($ancient[$this->key])) {
$desc = pht(
"This configuration has been removed. You can safely delete ".
"it.\n\n%s",
$ancient[$this->key]);
} else {
$desc = pht(
"This configuration option is unknown. It may be misspelled, ".
"or have existed in a previous version of Phabricator.");
}
// This may be a dead config entry, which existed in the past but no
// longer exists. Allow it to be edited so it can be reviewed and
// deleted.
$option = id(new PhabricatorConfigOption())
->setKey($this->key)
->setType('wild')
->setDefault(null)
->setDescription($desc);
$group = null;
$group_uri = $this->getApplicationURI();
} else {
$option = $options[$this->key];
$group = $option->getGroup();
$group_uri = $this->getApplicationURI('group/'.$group->getKey().'/');
}
$issue = $request->getStr('issue');
if ($issue) {
// If the user came here from an open setup issue, send them back.
$done_uri = $this->getApplicationURI('issue/'.$issue.'/');
} else {
$done_uri = $group_uri;
}
// Check if the config key is already stored in the database.
// Grab the value if it is.
$config_entry = id(new PhabricatorConfigEntry())
->loadOneWhere(
'configKey = %s AND namespace = %s',
$this->key,
'default');
if (!$config_entry) {
$config_entry = id(new PhabricatorConfigEntry())
->setConfigKey($this->key)
->setNamespace('default')
->setIsDeleted(true);
$config_entry->setPHID($config_entry->generatePHID());
}
$e_value = null;
$errors = array();
if ($request->isFormPost() && !$option->getLocked()) {
$result = $this->readRequest(
$option,
$request);
list($e_value, $value_errors, $display_value, $xaction) = $result;
$errors = array_merge($errors, $value_errors);
if (!$errors) {
$editor = id(new PhabricatorConfigEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
try {
$editor->applyTransactions($config_entry, array($xaction));
return id(new AphrontRedirectResponse())->setURI($done_uri);
} catch (PhabricatorConfigValidationException $ex) {
$e_value = pht('Invalid');
$errors[] = $ex->getMessage();
}
}
} else {
$display_value = $this->getDisplayValue($option, $config_entry);
}
$form = new AphrontFormView();
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('You broke everything!'))
->setErrors($errors);
} else if ($option->getHidden()) {
$msg = pht(
"This configuration is hidden and can not be edited or viewed from ".
"the web interface.");
$error_view = id(new AphrontErrorView())
->setTitle(pht('Configuration Hidden'))
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->appendChild(phutil_tag('p', array(), $msg));
} else if ($option->getLocked()) {
$msg = pht(
"This configuration is locked and can not be edited from the web ".
"interface. Use `./bin/config` in `phabricator/` to edit it.");
$error_view = id(new AphrontErrorView())
->setTitle(pht('Configuration Locked'))
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->appendChild(phutil_tag('p', array(), $msg));
}
if ($option->getHidden()) {
$control = null;
} else {
$control = $this->renderControl(
$option,
$display_value,
$e_value);
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
$engine->addObject($option, 'description');
$engine->process();
$description = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($option, 'description'));
$form
->setUser($user)
->addHiddenInput('issue', $request->getStr('issue'))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Description'))
->setValue($description));
if ($group) {
$extra = $group->renderContextualDescription(
$option,
$request);
if ($extra !== null) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setValue($extra));
}
}
$form
->appendChild($control);
$submit_control = id(new AphrontFormSubmitControl())
->addCancelButton($done_uri);
if (!$option->getLocked()) {
$submit_control->setValue(pht('Save Config Entry'));
}
$form->appendChild($submit_control);
$examples = $this->renderExamples($option);
if ($examples) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Examples'))
->setValue($examples));
}
if (!$option->getHidden()) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Default'))
->setValue($this->renderDefaults($option)));
}
$title = pht('Edit %s', $this->key);
$short = pht('Edit');
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormError($error_view)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Config'))
->setHref($this->getApplicationURI()));
if ($group) {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($group->getName())
->setHref($group_uri));
}
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($this->key)
->setHref('/config/edit/'.$this->key));
$xactions = id(new PhabricatorConfigTransactionQuery())
->withObjectPHIDs(array($config_entry->getPHID()))
->setViewer($user)
->execute();
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setObjectPHID($config_entry->getPHID())
->setTransactions($xactions);
return $this->buildApplicationPage(
array(
$crumbs,
$form_box,
$xaction_view,
),
array(
'title' => $title,
'device' => true,
));
}
private function readRequest(
PhabricatorConfigOption $option,
AphrontRequest $request) {
$xaction = new PhabricatorConfigTransaction();
$xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT);
$e_value = null;
$errors = array();
$value = $request->getStr('value');
if (!strlen($value)) {
$value = null;
$xaction->setNewValue(
array(
'deleted' => true,
'value' => null,
));
return array($e_value, $errors, $value, $xaction);
}
if ($option->isCustomType()) {
$info = $option->getCustomObject()->readRequest($option, $request);
list($e_value, $errors, $set_value, $value) = $info;
} else {
$type = $option->getType();
$set_value = null;
switch ($type) {
case 'int':
if (preg_match('/^-?[0-9]+$/', trim($value))) {
$set_value = (int)$value;
} else {
$e_value = pht('Invalid');
$errors[] = pht('Value must be an integer.');
}
break;
case 'string':
case 'enum':
$set_value = (string)$value;
break;
case 'list':
case 'list':
$set_value = phutil_split_lines(
$request->getStr('value'),
$retain_endings = false);
foreach ($set_value as $key => $v) {
if (!strlen($v)) {
unset($set_value[$key]);
}
}
$set_value = array_values($set_value);
break;
case 'set':
$set_value = array_fill_keys($request->getStrList('value'), true);
break;
case 'bool':
switch ($value) {
case 'true':
$set_value = true;
break;
case 'false':
$set_value = false;
break;
default:
$e_value = pht('Invalid');
$errors[] = pht('Value must be boolean, "true" or "false".');
break;
}
break;
case 'class':
if (!class_exists($value)) {
$e_value = pht('Invalid');
$errors[] = pht('Class does not exist.');
} else {
$base = $option->getBaseClass();
if (!is_subclass_of($value, $base)) {
$e_value = pht('Invalid');
$errors[] = pht('Class is not of valid type.');
} else {
$set_value = $value;
}
}
break;
default:
$json = json_decode($value, true);
if ($json === null && strtolower($value) != 'null') {
$e_value = pht('Invalid');
$errors[] = pht(
'The given value must be valid JSON. This means, among '.
'other things, that you must wrap strings in double-quotes.');
} else {
$set_value = $json;
}
break;
}
}
if (!$errors) {
$xaction->setNewValue(
array(
'deleted' => false,
'value' => $set_value,
));
} else {
$xaction = null;
}
return array($e_value, $errors, $value, $xaction);
}
private function getDisplayValue(
PhabricatorConfigOption $option,
PhabricatorConfigEntry $entry) {
if ($entry->getIsDeleted()) {
return null;
}
if ($option->isCustomType()) {
return $option->getCustomObject()->getDisplayValue($option, $entry);
} else {
$type = $option->getType();
$value = $entry->getValue();
switch ($type) {
case 'int':
case 'string':
case 'enum':
case 'class':
return $value;
case 'bool':
return $value ? 'true' : 'false';
case 'list':
case 'list':
return implode("\n", nonempty($value, array()));
case 'set':
return implode("\n", nonempty(array_keys($value), array()));
default:
return PhabricatorConfigJSON::prettyPrintJSON($value);
}
}
}
private function renderControl(
PhabricatorConfigOption $option,
$display_value,
$e_value) {
if ($option->isCustomType()) {
$control = $option->getCustomObject()->renderControl(
$option,
$display_value,
$e_value);
} else {
$type = $option->getType();
switch ($type) {
case 'int':
case 'string':
$control = id(new AphrontFormTextControl());
break;
case 'bool':
$control = id(new AphrontFormSelectControl())
->setOptions(
array(
'' => pht('(Use Default)'),
'true' => idx($option->getBoolOptions(), 0),
'false' => idx($option->getBoolOptions(), 1),
));
break;
case 'enum':
$options = array_mergev(
array(
array('' => pht('(Use Default)')),
$option->getEnumOptions(),
));
$control = id(new AphrontFormSelectControl())
->setOptions($options);
break;
case 'class':
$symbols = id(new PhutilSymbolLoader())
->setType('class')
->setAncestorClass($option->getBaseClass())
->setConcreteOnly(true)
->selectSymbolsWithoutLoading();
$names = ipull($symbols, 'name', 'name');
asort($names);
$names = array(
'' => pht('(Use Default)'),
) + $names;
$control = id(new AphrontFormSelectControl())
->setOptions($names);
break;
case 'list':
case 'list':
$control = id(new AphrontFormTextAreaControl())
->setCaption(pht('Separate values with newlines.'));
break;
case 'set':
$control = id(new AphrontFormTextAreaControl())
->setCaption(pht('Separate values with newlines or commas.'));
break;
default:
$control = id(new AphrontFormTextAreaControl())
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setCustomClass('PhabricatorMonospaced')
->setCaption(pht('Enter value in JSON.'));
break;
}
$control
->setLabel(pht('Value'))
->setError($e_value)
->setValue($display_value)
->setName('value');
}
if ($option->getLocked()) {
$control->setDisabled(true);
}
return $control;
}
private function renderExamples(PhabricatorConfigOption $option) {
$examples = $option->getExamples();
if (!$examples) {
return null;
}
$table = array();
- $table[] = hsprintf(
- '
%s',
pht(
'This will squash and rebase revision %s, and push it to '.
'the default / master branch.',
$revision_id),
pht('It is an experimental feature and may not work.'));
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht("Land Revision %s?", $revision_id))
->appendChild($prompt)
->setSubmitURI($request->getRequestURI())
->addSubmitButton(pht('Land it!'))
->addCancelButton('/D'.$revision_id);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function attemptLand($revision, $request) {
$status = $revision->getStatus();
if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) {
throw new Exception("Only Accepted revisions can be landed.");
}
$repository = $revision->getRepository();
if ($repository === null) {
throw new Exception("revision is not attached to a repository.");
}
$can_push = PhabricatorPolicyFilter::hasCapability(
$request->getUser(),
$repository,
DiffusionCapabilityPush::CAPABILITY);
if (!$can_push) {
throw new Exception(
pht('You do not have permission to push to this repository.'));
}
$lock = $this->lockRepository($repository);
try {
$this->pushStrategy->processLandRequest(
$request,
$revision,
$repository);
} catch (Exception $e) {
$lock->unlock();
throw $e;
}
$lock->unlock();
}
private function lockRepository($repository) {
$lock_name = __CLASS__.':'.($repository->getCallsign());
$lock = PhabricatorGlobalLock::newLock($lock_name);
$lock->lock();
return $lock;
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
index b965b78fc..bf2aeadde 100644
--- a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
@@ -1,409 +1,401 @@
getChangeset();
$change = $changeset->getChangeType();
$file = $changeset->getFileType();
$message = null;
if ($change == DifferentialChangeType::TYPE_CHANGE &&
$file == DifferentialChangeType::FILE_TEXT) {
if ($force) {
// We have to force something to render because there were no changes
// of other kinds.
$message = pht('This file was not modified.');
} else {
// Default case of changes to a text file, no metadata.
return null;
}
} else {
$none = hsprintf('');
switch ($change) {
case DifferentialChangeType::TYPE_ADD:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was added.', $none);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was added.', $none);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht(
'This directory was added.',
$none);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht(
'This binary file was added.',
$none);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was added.', $none);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht(
'This submodule was added.',
$none);
break;
}
break;
case DifferentialChangeType::TYPE_DELETE:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was deleted.', $none);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was deleted.', $none);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht(
'This directory was deleted.',
$none);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht(
'This binary file was deleted.',
$none);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht(
'This symlink was deleted.',
$none);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht(
'This submodule was deleted.',
$none);
break;
}
break;
case DifferentialChangeType::TYPE_MOVE_HERE:
$from = phutil_tag('strong', array(), $changeset->getOldFile());
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was moved from %s.', $from);
break;
}
break;
case DifferentialChangeType::TYPE_COPY_HERE:
$from = phutil_tag('strong', array(), $changeset->getOldFile());
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was copied from %s.', $from);
break;
}
break;
case DifferentialChangeType::TYPE_MOVE_AWAY:
$paths = phutil_tag(
'strong',
array(),
implode(', ', $changeset->getAwayPaths()));
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was moved to %s.', $paths);
break;
}
break;
case DifferentialChangeType::TYPE_COPY_AWAY:
$paths = phutil_tag(
'strong',
array(),
implode(', ', $changeset->getAwayPaths()));
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This directory was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This binary file was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This submodule was copied to %s.', $paths);
break;
}
break;
case DifferentialChangeType::TYPE_MULTICOPY:
$paths = phutil_tag(
'strong',
array(),
implode(', ', $changeset->getAwayPaths()));
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht(
'This file was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht(
'This image was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht(
'This directory was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht(
'This binary file was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht(
'This symlink was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht(
'This submodule was deleted after being copied to %s.',
$paths);
break;
}
break;
default:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This is a file.');
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This is an image.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht('This is a directory.');
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht('This is a binary file.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This is a symlink.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht('This is a submodule.');
break;
}
break;
}
}
- return hsprintf(
- '
', $n_class);
+ $n_copy = phutil_tag('td', array('class' => "copy {$n_class}"));
} else {
list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num];
$title = ($orig_type == '-' ? 'Moved' : 'Copied').' from ';
if ($orig_file == '') {
$title .= "line {$orig_line}";
} else {
$title .=
basename($orig_file).
":{$orig_line} in dir ".
dirname('/'.$orig_file);
}
$class = ($orig_type == '-' ? 'new-move' : 'new-copy');
$n_copy = javelin_tag(
'td',
array(
'meta' => array(
'msg' => $title,
),
'class' => 'copy '.$class,
),
'');
}
}
}
$n_classes .= ' right'.$n_colspan;
if (isset($hunk_starts[$o_num])) {
$html[] = $context_not_available;
}
if ($o_num && $left_id) {
$o_id = 'C'.$left_id.$left_char.'L'.$o_num;
} else {
$o_id = null;
}
if ($n_num && $right_id) {
$n_id = 'C'.$right_id.$right_char.'L'.$n_num;
} else {
$n_id = null;
}
+ // NOTE: This is a unicode zero-width space, which we use as a hint
+ // when intercepting 'copy' events to make sure sensible text ends
+ // up on the clipboard. See the 'phabricator-oncopy' behavior.
+ $zero_space = "\xE2\x80\x8B";
+
// NOTE: The Javascript is sensitive to whitespace changes in this
// block!
- $html[] = hsprintf(
- '
'.
- '%s'.
- '
%s
'.
- '%s'.
- '%s'.
- // NOTE: This is a unicode zero-width space, which we use as a hint
- // when intercepting 'copy' events to make sure sensible text ends
- // up on the clipboard. See the 'phabricator-oncopy' behavior.
- '
', $inst));
+ ->appendChild(
+ phutil_tag('p', array('class' => 'aphront-form-instructions'), $inst));
if ($user->getIsSystemAgent()) {
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Username'))
->setValue($user->getUsername()))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Certificate'))
->setValue($user->getConduitCertificate()));
} else {
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Certificate'))
->setValue(
pht('You may only view the certificates of System Agents.')));
}
$title = pht('Conduit Certificate');
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
return array($form_box);
}
private function processRenameRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$e_username = true;
$username = $user->getUsername();
$errors = array();
if ($request->isFormPost()) {
$username = $request->getStr('username');
if (!strlen($username)) {
$e_username = pht('Required');
$errors[] = pht('New username is required.');
} else if ($username == $user->getUsername()) {
$e_username = pht('Invalid');
$errors[] = pht('New username must be different from old username.');
} else if (!PhabricatorUser::validateUsername($username)) {
$e_username = pht('Invalid');
$errors[] = PhabricatorUser::describeValidUsername();
}
if (!$errors) {
try {
id(new PhabricatorUserEditor())
->setActor($admin)
->changeUsername($user, $username);
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('saved', true));
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_username = pht('Not Unique');
$errors[] = pht('Another user already has that username.');
}
}
}
if ($errors) {
$errors = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
} else {
$errors = null;
}
$inst1 = pht('Be careful when renaming users!');
$inst2 = pht('The old username will no longer be tied to the user, so '.
'anything which uses it (like old commit messages) will no longer '.
'associate correctly. And if you give a user a username which some '.
'other user used to have, username lookups will begin returning '.
'the wrong user.');
$inst3 = pht('It is generally safe to rename newly created users (and '.
'test users and so on), but less safe to rename established users '.
'and unsafe to reissue a username.');
$inst4 = pht('Users who rely on password auth will need to reset their '.
'passwordafter their username is changed (their username is part '.
'of the salt in the password hash). They will receive an email '.
'with instructions on how to do this.');
$form = new AphrontFormView();
$form
->setUser($admin)
->setAction($request->getRequestURI())
->appendChild(hsprintf(
'
'.
'%s '.
'%s'.
'
'.
'
'.
'%s'.
'
'.
'
'.
'%s'.
'
', $inst1, $inst2, $inst3, $inst4))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Old Username'))
->setValue($user->getUsername()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('New Username'))
->setValue($username)
->setName('username')
->setError($e_username))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Change Username')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Change Username'))
->setFormError($errors)
->setForm($form);
return array($form_box);
}
private function processDeleteRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$far1 = pht('As you stare into the gaping maw of the abyss, something '.
'hold you back.');
$far2 = pht('You can not delete your own account.');
if ($user->getPHID() == $admin->getPHID()) {
$error = new AphrontErrorView();
$error->setTitle(pht('You Shall Journey No Farther'));
$error->appendChild(hsprintf(
'
%s
%s
', $far1, $far2));
return $error;
}
$e_username = true;
$username = null;
$errors = array();
if ($request->isFormPost()) {
$username = $request->getStr('username');
if (!strlen($username)) {
$e_username = pht('Required');
$errors[] = pht('You must type the username to confirm deletion.');
} else if ($username != $user->getUsername()) {
$e_username = pht('Invalid');
$errors[] = pht('You must type the username correctly.');
}
if (!$errors) {
id(new PhabricatorUserEditor())
->setActor($admin)
->deleteUser($user);
return id(new AphrontRedirectResponse())->setURI('/people/');
}
}
if ($errors) {
$errors = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
} else {
$errors = null;
}
$str1 = pht('Be careful when deleting users!');
$str2 = pht('If this user interacted with anything, it is generally '.
'better to disable them, not delete them. If you delete them, it will '.
'no longer be possible to search for their objects, for example, '.
'and you will lose other information about their history. Disabling '.
'them instead will prevent them from logging in but not destroy '.
'any of their data.');
$str3 = pht('It is generally safe to delete newly created users (and '.
'test users and so on), but less safe to delete established users. '.
'If possible, disable them instead.');
$form = new AphrontFormView();
$form
->setUser($admin)
->setAction($request->getRequestURI())
->appendChild(hsprintf(
'
', pht(
- 'No one is affiliated with this project.'));
+ $affiliated = phutil_tag('p', array(),
+ phutil_tag('em', array(),
+ pht('No one is affiliated with this project.')));
}
- return hsprintf(
- '
'.
- '
%s
'.
- '
%s
'.
- '
',
- pht('People'),
- $affiliated);
+ return phutil_tag_div(
+ 'phabricator-profile-info-group profile-wrap-responsive',
+ array(
+ phutil_tag(
+ 'h1',
+ array('class' => 'phabricator-profile-info-header'),
+ pht('People')),
+ phutil_tag_div('phabricator-profile-info-pane', $affiliated),
+ ));
}
private function renderFeedPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(array($project->getPHID()));
$query->setViewer($this->getRequest()->getUser());
$query->setLimit(100);
$stories = $query->execute();
if (!$stories) {
return pht('There are no stories about this project.');
}
return $this->renderStories($stories);
}
private function renderStories(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($this->getRequest()->getUser());
$builder->setShowHovercards(true);
$view = $builder->buildView();
- return hsprintf(
- '
');
+ $results = phutil_tag_div(
+ 'phabricator-search-result-list',
+ phutil_tag(
+ 'p',
+ array('class' => 'phabricator-search-no-results'),
+ pht('No search results.')));
}
$results = id(new PHUIBoxView())
->addMargin(PHUI::MARGIN_LARGE)
->addPadding(PHUI::PADDING_LARGE)
->setShadow(true)
->appendChild($results)
->addClass('phabricator-search-result-box');
} else {
$results = null;
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Search')));
return $this->buildApplicationPage(
array(
$crumbs,
$search_panel,
$results,
),
array(
'title' => pht('Search Results'),
'device' => true,
));
}
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelConduit.php b/src/applications/settings/panel/PhabricatorSettingsPanelConduit.php
index eb79fa2c5..e44d86e8f 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelConduit.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelConduit.php
@@ -1,109 +1,112 @@
getUser();
if ($request->isFormPost()) {
if (!$request->isDialogFormPost()) {
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle(pht('Really regenerate session?'));
$dialog->setSubmitURI($this->getPanelURI());
$dialog->addSubmitButton(pht('Regenerate'));
$dialog->addCancelbutton($this->getPanelURI());
$dialog->appendChild(phutil_tag('p', array(), pht(
'Really destroy the old certificate? Any established '.
'sessions will be terminated.')));
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
$conn = $user->establishConnection('w');
queryfx(
$conn,
'DELETE FROM %T WHERE userPHID = %s AND type LIKE %>',
PhabricatorUser::SESSION_TABLE,
$user->getPHID(),
'conduit');
// This implicitly regenerates the certificate.
$user->setConduitCertificate(null);
$user->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?regenerated=true'));
}
if ($request->getStr('regenerated')) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Certificate Regenerated'));
$notice->appendChild(phutil_tag(
'p',
array(),
pht('Your old certificate has been destroyed and you have been issued '.
'a new certificate. Sessions established under the old certificate '.
'are no longer valid.')));
$notice = $notice->render();
} else {
$notice = null;
}
$cert_form = new AphrontFormView();
$cert_form
->setUser($user)
- ->appendChild(hsprintf(
- '
%s
',
+ ->appendChild(phutil_tag(
+ 'p',
+ array('class' => 'aphront-form-instructions'),
pht('This certificate allows you to authenticate over Conduit, '.
'the Phabricator API. Normally, you just run %s to install it.',
- hsprintf('%s', 'arc install-certificate'))))
+ phutil_tag('tt', array(), 'arc install-certificate'))))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Certificate'))
->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT)
->setValue($user->getConduitCertificate()));
$cert_form = id(new PHUIObjectBoxView())
->setHeaderText(pht('Arcanist Certificate'))
->setForm($cert_form);
$regen_instruction = pht('You can regenerate this certificate, which '.
'will invalidate the old certificate and create a new one.');
$regen_form = new AphrontFormView();
$regen_form
->setUser($user)
->setAction($this->getPanelURI())
->setWorkflow(true)
- ->appendChild(hsprintf(
- '
%s
', $regen_instruction))
+ ->appendChild(phutil_tag(
+ 'p',
+ array('class' => 'aphront-form-instructions'),
+ $regen_instruction))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Regenerate Certificate')));
$regen_form = id(new PHUIObjectBoxView())
->setHeaderText(pht('Regenerate Certificate'))
->setForm($regen_form);
return array(
$notice,
$cert_form,
$regen_form,
);
}
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php b/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php
index 9a6100f46..79d6d3d3b 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php
@@ -1,158 +1,158 @@
getUser();
$preferences = $user->loadPreferences();
$pref_monospaced = PhabricatorUserPreferences::PREFERENCE_MONOSPACED;
$pref_editor = PhabricatorUserPreferences::PREFERENCE_EDITOR;
$pref_multiedit = PhabricatorUserPreferences::PREFERENCE_MULTIEDIT;
$pref_titles = PhabricatorUserPreferences::PREFERENCE_TITLES;
$pref_monospaced_textareas =
PhabricatorUserPreferences::PREFERENCE_MONOSPACED_TEXTAREAS;
if ($request->isFormPost()) {
$monospaced = $request->getStr($pref_monospaced);
// Prevent the user from doing stupid things.
$monospaced = preg_replace('/[^a-z0-9 ,"]+/i', '', $monospaced);
$preferences->setPreference($pref_titles, $request->getStr($pref_titles));
$preferences->setPreference($pref_editor, $request->getStr($pref_editor));
$preferences->setPreference(
$pref_multiedit,
$request->getStr($pref_multiedit));
$preferences->setPreference($pref_monospaced, $monospaced);
$preferences->setPreference(
$pref_monospaced_textareas,
$request->getStr($pref_monospaced_textareas));
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$example_string = << PhabricatorEnv::getDoclink(
'article/User_Guide_Configuring_an_External_Editor.html'),
),
pht('User Guide: Configuring an External Editor'));
$font_default = PhabricatorEnv::getEnvConfig('style.monospace');
$pref_monospaced_textareas_value = $preferences
->getPreference($pref_monospaced_textareas);
if (!$pref_monospaced_textareas_value) {
$pref_monospaced_textareas_value = 'disabled';
}
$editor_instructions = pht('Link to edit files in external editor. '.
'%%f is replaced by filename, %%l by line number, %%r by repository '.
'callsign, %%%% by literal %%. For documentation, see: %s',
- hsprintf('%s', $editor_doc_link));
+ $editor_doc_link);
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Page Titles'))
->setName($pref_titles)
->setValue($preferences->getPreference($pref_titles))
->setOptions(
array(
'glyph' =>
pht("In page titles, show Tool names as unicode glyphs: " .
"\xE2\x9A\x99"),
'text' =>
pht('In page titles, show Tool names as plain text: ' .
'[Differential]'),
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Editor Link'))
->setName($pref_editor)
// How to pht()
->setCaption($editor_instructions)
->setValue($preferences->getPreference($pref_editor)))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Edit Multiple Files'))
->setName($pref_multiedit)
->setOptions(array(
'' => pht('Supported (paths separated by spaces)'),
'disable' => pht('Not Supported'),
))
->setValue($preferences->getPreference($pref_multiedit)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Monospaced Font'))
->setName($pref_monospaced)
// Check plz
->setCaption(hsprintf(
'%s (%s: %s)',
pht('Overrides default fonts in tools like Differential.'),
pht('Default'),
$font_default))
->setValue($preferences->getPreference($pref_monospaced)))
->appendChild(
id(new AphrontFormMarkupControl())
->setValue(phutil_tag(
'pre',
array('class' => 'PhabricatorMonospaced'),
$example_string)))
->appendChild(
id(new AphrontFormRadioButtonControl())
->setLabel(pht('Monospaced Textareas'))
->setName($pref_monospaced_textareas)
->setValue($pref_monospaced_textareas_value)
->addButton('enabled', pht('Enabled'),
pht('Show all textareas using the monospaced font defined above.'))
->addButton('disabled', pht('Disabled'), null));
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preferences')));
$error_view = null;
if ($request->getStr('saved') === 'true') {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Preferences Saved'))
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setErrors(array(pht('Your preferences have been saved.')));
}
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Display Preferences'))
->setFormError($error_view)
->setForm($form);
return array(
$form_box,
);
}
}
diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php b/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
index 02495e88b..b7bacccdf 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
@@ -1,355 +1,355 @@
getUser();
$editable = PhabricatorEnv::getEnvConfig('account.editable');
$uri = $request->getRequestURI();
$uri->setQueryParams(array());
if ($editable) {
$new = $request->getStr('new');
if ($new) {
return $this->returnNewAddressResponse($request, $uri, $new);
}
$delete = $request->getInt('delete');
if ($delete) {
return $this->returnDeleteAddressResponse($request, $uri, $delete);
}
}
$verify = $request->getInt('verify');
if ($verify) {
return $this->returnVerifyAddressResponse($request, $uri, $verify);
}
$primary = $request->getInt('primary');
if ($primary) {
return $this->returnPrimaryAddressResponse($request, $uri, $primary);
}
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
'userPHID = %s ORDER BY address',
$user->getPHID());
$rowc = array();
$rows = array();
foreach ($emails as $email) {
$button_verify = javelin_tag(
'a',
array(
'class' => 'button small grey',
'href' => $uri->alter('verify', $email->getID()),
'sigil' => 'workflow',
),
pht('Verify'));
$button_make_primary = javelin_tag(
'a',
array(
'class' => 'button small grey',
'href' => $uri->alter('primary', $email->getID()),
'sigil' => 'workflow',
),
pht('Make Primary'));
$button_remove = javelin_tag(
'a',
array(
'class' => 'button small grey',
'href' => $uri->alter('delete', $email->getID()),
'sigil' => 'workflow'
),
pht('Remove'));
$button_primary = phutil_tag(
'a',
array(
'class' => 'button small disabled',
),
pht('Primary'));
if (!$email->getIsVerified()) {
$action = $button_verify;
} else if ($email->getIsPrimary()) {
$action = $button_primary;
} else {
$action = $button_make_primary;
}
if ($email->getIsPrimary()) {
$remove = $button_primary;
$rowc[] = 'highlighted';
} else {
$remove = $button_remove;
$rowc[] = null;
}
$rows[] = array(
$email->getAddress(),
$action,
$remove,
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Email'),
pht('Status'),
pht('Remove'),
));
$table->setColumnClasses(
array(
'wide',
'action',
'action',
));
$table->setRowClasses($rowc);
$table->setColumnVisibility(
array(
true,
true,
$editable,
));
$view = new AphrontPanelView();
if ($editable) {
$view->addButton(
javelin_tag(
'a',
array(
'href' => $uri->alter('new', 'true'),
'class' => 'green button',
'sigil' => 'workflow',
),
pht('Add New Address')));
}
$view->setHeader(pht('Email Addresses'));
$view->appendChild($table);
$view->setNoBackground();
return $view;
}
private function returnNewAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$new) {
$user = $request->getUser();
$e_email = true;
$email = trim($request->getStr('email'));
$errors = array();
if ($request->isDialogFormPost()) {
if ($new == 'verify') {
// The user clicked "Done" from the "an email has been sent" dialog.
return id(new AphrontReloadResponse())->setURI($uri);
}
if (!strlen($email)) {
$e_email = pht('Required');
$errors[] = pht('Email is required.');
} else if (!PhabricatorUserEmail::isAllowedAddress($email)) {
$e_email = pht('Invalid');
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
}
if (!$errors) {
$object = id(new PhabricatorUserEmail())
->setAddress($email)
->setIsVerified(0);
try {
id(new PhabricatorUserEditor())
->setActor($user)
->addEmail($user, $object);
$object->sendVerificationEmail($user);
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('new', 'verify')
->setTitle(pht('Verification Email Sent'))
->appendChild(phutil_tag('p', array(), pht(
'A verification email has been sent. Click the link in the '.
'email to verify your address.')))
->setSubmitURI($uri)
->addSubmitButton(pht('Done'));
return id(new AphrontDialogResponse())->setDialog($dialog);
} catch (AphrontQueryDuplicateKeyException $ex) {
$email = pht('Duplicate');
$errors[] = pht('Another user already has this email.');
}
}
}
if ($errors) {
$errors = id(new AphrontErrorView())
->setErrors($errors);
}
$form = id(new PHUIFormLayoutView())
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($request->getStr('email'))
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
->setError($e_email));
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('new', 'true')
->setTitle(pht('New Address'))
->appendChild($errors)
->appendChild($form)
->addSubmitButton(pht('Save'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnDeleteAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $request->getUser();
// NOTE: You can only delete your own email addresses, and you can not
// delete your primary address.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isPrimary = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($user)
->removeEmail($user, $email);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('delete', $email_id)
->setTitle(pht("Really delete address '%s'?", $address))
->appendChild(phutil_tag('p', array(), pht(
'Are you sure you want to delete this address? You will no '.
'longer be able to use it to login.')))
->addSubmitButton(pht('Delete'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnVerifyAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $request->getUser();
// NOTE: You can only send more email for your unverified addresses.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isVerified = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$email->sendVerificationEmail($user);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('verify', $email_id)
->setTitle(pht("Send Another Verification Email?"))
- ->appendChild(hsprintf(
- '
%s
',
- pht('Send another copy of the verification email to %s?', $address)))
+ ->appendChild(phutil_tag('p', array(), pht(
+ 'Send another copy of the verification email to %s?',
+ $address)))
->addSubmitButton(pht('Send Email'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnPrimaryAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $request->getUser();
// NOTE: You can only make your own verified addresses primary.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isVerified = 1 AND isPrimary = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($user)
->changePrimaryEmail($user, $email);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($user)
->addHiddenInput('primary', $email_id)
->setTitle(pht("Change primary email address?"))
- ->appendChild(hsprintf(
- '
If you change your primary address, Phabricator will send'.
- ' all email to %s.
',
- $address))
+ ->appendChild(phutil_tag('p', array(), pht(
+ 'If you change your primary address, Phabricator will send'.
+ ' all email to %s.',
+ $address)))
->addSubmitButton(pht('Change Primary Address'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/system/PhabricatorDebugController.php b/src/applications/system/PhabricatorDebugController.php
index e26dd0e6c..7a6690668 100644
--- a/src/applications/system/PhabricatorDebugController.php
+++ b/src/applications/system/PhabricatorDebugController.php
@@ -1,39 +1,39 @@
getDebugFilePath())) {
return new Aphront404Response();
}
$request = $this->getRequest();
$user = $request->getUser();
ob_start();
require_once $this->getDebugFilePath();
$out = ob_get_clean();
$response = new AphrontWebpageResponse();
- $response->setContent(hsprintf('
%s
', $out));
+ $response->setContent(phutil_tag('pre', array(), $out));
return $response;
}
private function getDebugFilePath() {
$root = dirname(phutil_get_library_root('phabricator'));
return $root.'/support/debug.php';
}
}
diff --git a/webroot/index.php b/webroot/index.php
index 92a41bce8..1557b499e 100644
--- a/webroot/index.php
+++ b/webroot/index.php
@@ -1,137 +1,142 @@
setData(
array(
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
'r' => idx($_SERVER, 'REMOTE_ADDR', '-'),
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
));
DarkConsoleXHProfPluginAPI::hookProfiler();
DarkConsoleErrorLogPluginAPI::registerErrorHandler();
$sink = new AphrontPHPHTTPSink();
$response = PhabricatorSetupCheck::willProcessRequest();
if ($response) {
PhabricatorStartup::endOutputCapture();
$sink->writeResponse($response);
return;
}
$host = AphrontRequest::getHTTPHeader('Host');
$path = $_REQUEST['__path__'];
switch ($host) {
default:
$config_key = 'aphront.default-application-configuration-class';
$application = PhabricatorEnv::newObjectFromConfig($config_key);
break;
}
$application->setHost($host);
$application->setPath($path);
$application->willBuildRequest();
$request = $application->buildRequest();
// Until an administrator sets "phabricator.base-uri", assume it is the same
// as the request URI. This will work fine in most cases, it just breaks down
// when daemons need to do things.
$request_protocol = ($request->isHTTPS() ? 'https' : 'http');
$request_base_uri = "{$request_protocol}://{$host}/";
PhabricatorEnv::setRequestBaseURI($request_base_uri);
$write_guard = new AphrontWriteGuard(array($request, 'validateCSRF'));
$application->setRequest($request);
list($controller, $uri_data) = $application->buildController();
$access_log->setData(
array(
'U' => (string)$request->getRequestURI()->getPath(),
'C' => get_class($controller),
));
// If execution throws an exception and then trying to render that exception
// throws another exception, we want to show the original exception, as it is
// likely the root cause of the rendering exception.
$original_exception = null;
try {
$response = $controller->willBeginExecution();
if ($request->getUser() && $request->getUser()->getPHID()) {
$access_log->setData(
array(
'u' => $request->getUser()->getUserName(),
'P' => $request->getUser()->getPHID(),
));
}
if (!$response) {
$controller->willProcessRequest($uri_data);
$response = $controller->processRequest();
}
} catch (Exception $ex) {
$original_exception = $ex;
$response = $application->handleException($ex);
}
try {
$response = $controller->didProcessRequest($response);
$response = $application->willSendResponse($response, $controller);
$response->setRequest($request);
$unexpected_output = PhabricatorStartup::endOutputCapture();
if ($unexpected_output) {
$unexpected_output = "Unexpected output:\n\n{$unexpected_output}";
phlog($unexpected_output);
if ($response instanceof AphrontWebpageResponse) {
- echo hsprintf(
- '