Page MenuHomestyx hydra

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/applications/auth/controller/PhabricatorEmailTokenController.php b/src/applications/auth/controller/PhabricatorEmailTokenController.php
index e907a7dcb4..0964a7a966 100644
--- a/src/applications/auth/controller/PhabricatorEmailTokenController.php
+++ b/src/applications/auth/controller/PhabricatorEmailTokenController.php
@@ -1,92 +1,93 @@
<?php
final class PhabricatorEmailTokenController
extends PhabricatorAuthController {
private $token;
public function shouldRequireLogin() {
return false;
}
public function willProcessRequest(array $data) {
$this->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(
- '<div class="aphront-failure-continue">'.
- '<a class="button" href="/login/email/">%s</a>'.
- '</div>',
- 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 67a253b406..7b580a1ef7 100644
--- a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php
+++ b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php
@@ -1,77 +1,77 @@
<?php
final class PhabricatorMustVerifyEmailController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function shouldRequireEmailVerification() {
// NOTE: We don't technically need this since PhabricatorController forces
// us here in either case, but it's more consistent with intent.
return false;
}
public function processRequest() {
$request = $this->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(
- '<div class="aphront-failure-continue">%s</div>',
+ $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 6b68b42493..9c4a365fcb 100644
--- a/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php
+++ b/src/applications/auth/provider/PhabricatorAuthProviderLDAP.php
@@ -1,393 +1,393 @@
<?php
final class PhabricatorAuthProviderLDAP
extends PhabricatorAuthProvider {
private $adapter;
public function getProviderName() {
return pht('LDAP');
}
public function getDescriptionForCreate() {
return pht(
'Configure a connection to an LDAP server so that users can use their '.
'LDAP credentials to log in to Phabricator.');
}
public function getDefaultProviderConfig() {
return parent::getDefaultProviderConfig()
->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('<tt>%s</tt>', pht('ldap.example.com'))),
+ phutil_tag('tt', array(), pht('ldap.example.com'))),
self::KEY_DISTINGUISHED_NAME =>
pht('Example: %s',
- hsprintf('<tt>%s</tt>', 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('<tt>%s</tt>', 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('<tt>%s</tt>', 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('<strong>%s:</strong> %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 0fe5169683..10f7a5921e 100644
--- a/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php
+++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php
@@ -1,128 +1,126 @@
<?php
final class PhabricatorAuthProviderOAuthFacebook
extends PhabricatorAuthProviderOAuth {
const KEY_REQUIRE_SECURE = 'oauth:facebook:require-secure';
public function getProviderName() {
return pht('Facebook');
}
public function getConfigurationHelp() {
$uri = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
return pht(
'To configure Facebook OAuth, create a new Facebook Application here:'.
"\n\n".
'https://developers.facebook.com/apps'.
"\n\n".
'You should use these settings in your application:'.
"\n\n".
" - **Site URL**: Set this to your full domain with protocol. For ".
" this Phabricator install, the correct value is: `%s`\n".
" - **Site Domain**: Set this to the full domain without a protocol. ".
" For this Phabricator install, the correct value is: `%s`\n\n".
"After creating your new application, copy the **App ID** and ".
"**App Secret** to the fields above.",
(string)$uri,
$uri->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(
- '<strong>%s</strong>',
- 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 5449546b0a..d4cbd60de7 100644
--- a/src/applications/base/controller/PhabricatorController.php
+++ b/src/applications/base/controller/PhabricatorController.php
@@ -1,410 +1,411 @@
<?php
abstract class PhabricatorController extends AphrontController {
private $handles;
public function shouldRequireLogin() {
return true;
}
public function shouldRequireAdmin() {
return false;
}
public function shouldRequireEnabledUser() {
return true;
}
public function shouldAllowPublic() {
return false;
}
public function shouldRequireEmailVerification() {
return PhabricatorUserEmail::isEmailVerificationRequired();
}
public function willBeginExecution() {
$request = $this->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(
- '<div style="padding: 2em 0;">%s</div>',
+ $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<phid> 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 5ab0928687..88e2dfeb05 100644
--- a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php
@@ -1,96 +1,96 @@
<?php
final class PhabricatorCalendarBrowseController
extends PhabricatorCalendarController {
public function processRequest() {
$now = time();
$request = $this->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('<div style="padding: 20px;">%s</div>', $month_view),
+ phutil_tag('div', array('style' => 'padding: 20px;'), $month_view),
));
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Calendar'),
'device' => true,
));
}
private function getNoticeView() {
$request = $this->getRequest();
$view = null;
if ($request->getExists('created')) {
$view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Successfully created your status.'));
} else if ($request->getExists('updated')) {
$view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Successfully updated your status.'));
} else if ($request->getExists('deleted')) {
$view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Successfully deleted your status.'));
}
return $view;
}
}
diff --git a/src/applications/calendar/view/AphrontCalendarMonthView.php b/src/applications/calendar/view/AphrontCalendarMonthView.php
index 3597857a8d..0859e0acf9 100644
--- a/src/applications/calendar/view/AphrontCalendarMonthView.php
+++ b/src/applications/calendar/view/AphrontCalendarMonthView.php
@@ -1,327 +1,333 @@
<?php
final class AphrontCalendarMonthView extends AphrontView {
private $day;
private $month;
private $year;
private $holidays = array();
private $events = array();
private $browseURI;
public function setBrowseURI($browse_uri) {
$this->browseURI = $browse_uri;
return $this;
}
private function getBrowseURI() {
return $this->browseURI;
}
public function addEvent(AphrontCalendarEventView $event) {
$this->events[] = $event;
return $this;
}
public function setHolidays(array $holidays) {
assert_instances_of($holidays, 'PhabricatorCalendarHoliday');
$this->holidays = mpull($holidays, null, 'getDay');
return $this;
}
public function __construct($month, $year, $day = null) {
$this->day = $day;
$this->month = $month;
$this->year = $year;
}
public function render() {
if (empty($this->user)) {
throw new Exception("Call setUser() before render()!");
}
$events = msort($this->events, 'getEpochStart');
$days = $this->getDatesInMonth();
require_celerity_resource('aphront-calendar-view-css');
$first = reset($days);
$empty = $first->format('w');
$markup = array();
$empty_box = phutil_tag(
'div',
array('class' => 'aphront-calendar-day aphront-calendar-empty'),
'');
for ($ii = 0; $ii < $empty; $ii++) {
$markup[] = $empty_box;
}
$show_events = array();
foreach ($days as $day) {
$day_number = $day->format('j');
$holiday = idx($this->holidays, $day->format('Y-m-d'));
$class = 'aphront-calendar-day';
$weekday = $day->format('w');
if ($day_number == $this->day) {
$class .= ' aphront-calendar-today';
}
if ($holiday || $weekday == 0 || $weekday == 6) {
$class .= ' aphront-calendar-not-work-day';
}
$day->setTime(0, 0, 0);
$epoch_start = $day->format('U');
$day->modify('+1 day');
$epoch_end = $day->format('U');
if ($weekday == 0) {
$show_events = array();
} else {
$show_events = array_fill_keys(
array_keys($show_events),
- hsprintf(
- '<div class="aphront-calendar-event aphront-calendar-event-empty">'.
- '&nbsp;'.
- '</div>'));
+ phutil_tag_div(
+ 'aphront-calendar-event aphront-calendar-event-empty',
+ "\xC2\xA0")); // &nbsp;
}
foreach ($events as $event) {
if ($event->getEpochStart() >= $epoch_end) {
// This list is sorted, so we can stop looking.
break;
}
if ($event->getEpochStart() < $epoch_end &&
$event->getEpochEnd() > $epoch_start) {
$show_events[$event->getUserPHID()] = $this->renderEvent(
$event,
$epoch_start,
$epoch_end);
}
}
$holiday_markup = null;
if ($holiday) {
$name = $holiday->getName();
$holiday_markup = phutil_tag(
'div',
array(
'class' => 'aphront-calendar-holiday',
'title' => $name,
),
$name);
}
- $markup[] = hsprintf(
- '<div class="%s">'.
- '<div class="aphront-calendar-date-number">%s</div>'.
- '%s%s'.
- '</div>',
+ $markup[] = phutil_tag_div(
$class,
- $day_number,
- $holiday_markup,
- phutil_implode_html("\n", $show_events));
+ array(
+ phutil_tag_div('aphront-calendar-date-number', $day_number),
+ $holiday_markup,
+ phutil_implode_html("\n", $show_events),
+ ));
}
$table = array();
$rows = array_chunk($markup, 7);
foreach ($rows as $row) {
- $table[] = hsprintf('<tr>');
+ $cells = array();
while (count($row) < 7) {
$row[] = $empty_box;
}
foreach ($row as $cell) {
- $table[] = phutil_tag('td', array(), $cell);
+ $cells[] = phutil_tag('td', array(), $cell);
}
- $table[] = hsprintf('</tr>');
+ $table[] = phutil_tag('tr', array(), $cells);
}
- $table = hsprintf(
- '<table class="aphront-calendar-view">'.
- '%s'.
- '<tr class="aphront-calendar-day-of-week-header">'.
- '<th>Sun</th>'.
- '<th>Mon</th>'.
- '<th>Tue</th>'.
- '<th>Wed</th>'.
- '<th>Thu</th>'.
- '<th>Fri</th>'.
- '<th>Sat</th>'.
- '</tr>'.
- '%s'.
- '</table>',
- $this->renderCalendarHeader($first),
- phutil_implode_html("\n", $table));
+
+ $header = phutil_tag(
+ 'tr',
+ array('class' => 'aphront-calendar-day-of-week-header'),
+ array(
+ phutil_tag('th', array(), pht('Sun')),
+ phutil_tag('th', array(), pht('Mon')),
+ phutil_tag('th', array(), pht('Tue')),
+ phutil_tag('th', array(), pht('Wed')),
+ phutil_tag('th', array(), pht('Thu')),
+ phutil_tag('th', array(), pht('Fri')),
+ phutil_tag('th', array(), pht('Sat')),
+ ));
+
+ $table = phutil_tag(
+ 'table',
+ array('class' => 'aphront-calendar-view'),
+ array(
+ $this->renderCalendarHeader($first),
+ $header,
+ phutil_implode_html("\n", $table),
+ ));
return $table;
}
private function renderCalendarHeader(DateTime $date) {
$colspan = 7;
$left_th = '';
$right_th = '';
// check for a browseURI, which means we need "fancy" prev / next UI
$uri = $this->getBrowseURI();
if ($uri) {
$colspan = 5;
$uri = new PhutilURI($uri);
list($prev_year, $prev_month) = $this->getPrevYearAndMonth();
$query = array('year' => $prev_year, 'month' => $prev_month);
$prev_link = phutil_tag(
'a',
array('href' => (string) $uri->setQueryParams($query)),
"\xE2\x86\x90");
list($next_year, $next_month) = $this->getNextYearAndMonth();
$query = array('year' => $next_year, 'month' => $next_month);
$next_link = phutil_tag(
'a',
array('href' => (string) $uri->setQueryParams($query)),
"\xE2\x86\x92");
$left_th = phutil_tag('th', array(), $prev_link);
$right_th = phutil_tag('th', array(), $next_link);
}
- return hsprintf(
- '<tr class="aphront-calendar-month-year-header">%s%s%s</tr>',
- $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 837674996e..ce6b6a02c9 100644
--- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
@@ -1,502 +1,505 @@
<?php
/**
* @group conduit
*/
final class PhabricatorConduitAPIController
extends PhabricatorConduitController {
public function shouldRequireLogin() {
return false;
}
private $method;
public function willProcessRequest(array $data) {
$this->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('<pre style="white-space: pre-wrap;">%s</pre>', $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&params[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 91a0ad7e49..43bf22bd3a 100644
--- a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
+++ b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
@@ -1,137 +1,138 @@
<?php
final class PhabricatorConduitSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getPageSize(PhabricatorSavedQuery $saved) {
return PHP_INT_MAX - 1;
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->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('<tt>differential, paste</tt>'))));
+ ->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(
'<strong>%s</strong>: %s',
pht('Stable Methods'),
pht('Show established API methods with stable interfaces.')),
$is_stable)
->addCheckbox(
'isUnstable',
1,
hsprintf(
'<strong>%s</strong>: %s',
pht('Unstable Methods'),
pht('Show new methods which are subject to change.')),
$is_unstable)
->addCheckbox(
'isDeprecated',
1,
hsprintf(
'<strong>%s</strong>: %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 e2da019e96..7ec6e41c77 100644
--- a/src/applications/config/controller/PhabricatorConfigEditController.php
+++ b/src/applications/config/controller/PhabricatorConfigEditController.php
@@ -1,545 +1,545 @@
<?php
final class PhabricatorConfigEditController
extends PhabricatorConfigController {
private $key;
public function willProcessRequest(array $data) {
$this->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<string>':
case 'list<regex>':
$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<string>':
case 'list<regex>':
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<string>':
case 'list<regex>':
$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(
- '<tr class="column-labels"><th>%s</th><th>%s</th></tr>',
- pht('Example'),
- pht('Value'));
+ $table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
+ phutil_tag('th', array(), pht('Example')),
+ phutil_tag('th', array(), pht('Value')),
+ ));
foreach ($examples as $example) {
list($value, $description) = $example;
if ($value === null) {
$value = phutil_tag('em', array(), pht('(empty)'));
} else {
if (is_array($value)) {
$value = implode("\n", $value);
}
}
- $table[] = hsprintf(
- '<tr><th>%s</th><td>%s</td></tr>',
- $description,
- $value);
+ $table[] = phutil_tag('tr', array(), array(
+ phutil_tag('th', array(), $description),
+ phutil_tag('th', array(), $value),
+ ));
}
require_celerity_resource('config-options-css');
return phutil_tag(
'table',
array(
'class' => 'config-option-table',
),
$table);
}
private function renderDefaults(PhabricatorConfigOption $option) {
$stack = PhabricatorEnv::getConfigSourceStack();
$stack = $stack->getStack();
$table = array();
- $table[] = hsprintf(
- '<tr class="column-labels"><th>%s</th><th>%s</th></tr>',
- pht('Source'),
- pht('Value'));
+ $table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
+ phutil_tag('th', array(), pht('Source')),
+ phutil_tag('th', array(), pht('Value')),
+ ));
foreach ($stack as $key => $source) {
$value = $source->getKeys(
array(
$option->getKey(),
));
if (!array_key_exists($option->getKey(), $value)) {
$value = phutil_tag('em', array(), pht('(empty)'));
} else {
$value = PhabricatorConfigJSON::prettyPrintJSON(
$value[$option->getKey()]);
}
- $table[] = hsprintf(
- '<tr><th>%s</th><td>%s</td></tr>',
- $source->getName(),
- $value);
+ $table[] = phutil_tag('tr', array('class' => 'column-labels'), array(
+ phutil_tag('th', array(), $source->getName()),
+ phutil_tag('td', array(), $value),
+ ));
}
require_celerity_resource('config-options-css');
return phutil_tag(
'table',
array(
'class' => 'config-option-table',
),
$table);
}
}
diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php
index 76c5897e62..1da1521bd2 100644
--- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php
+++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php
@@ -1,106 +1,106 @@
<?php
/**
* @group conpherence
*/
final class ConpherenceNotificationPanelController
extends ConpherenceController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$conpherences = array();
$unread_status = ConpherenceParticipationStatus::BEHIND;
$participant_data = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()))
->setLimit(5)
->execute();
if ($participant_data) {
$conpherences = id(new ConpherenceThreadQuery())
->setViewer($user)
->withPHIDs(array_keys($participant_data))
->needParticipantCache(true)
->execute();
}
if ($conpherences) {
require_celerity_resource('conpherence-notification-css');
// re-order the conpherences based on participation data
$conpherences = array_select_keys(
$conpherences, array_keys($participant_data));
$view = new AphrontNullView();
foreach ($conpherences as $conpherence) {
$p_data = $participant_data[$conpherence->getPHID()];
$d_data = $conpherence->getDisplayData($user);
$classes = array(
'phabricator-notification',
'conpherence-notification',
);
if ($p_data->getParticipationStatus() == $unread_status) {
$classes[] = 'phabricator-notification-unread';
}
$uri = $this->getApplicationURI($conpherence->getID().'/');
$title = $d_data['title'];
$subtitle = $d_data['subtitle'];
$unread_count = $d_data['unread_count'];
$epoch = $d_data['epoch'];
$image = $d_data['image'];
$msg_view = id(new ConpherenceMenuItemView())
->setUser($user)
->setTitle($title)
->setSubtitle($subtitle)
->setHref($uri)
->setEpoch($epoch)
->setImageURI($image)
->setUnreadCount($unread_count);
$view->appendChild(javelin_tag(
'div',
array(
'class' => implode(' ', $classes),
'sigil' => 'notification',
'meta' => array(
'href' => $uri,
),
),
$msg_view));
}
$content = $view->render();
} else {
- $content = hsprintf(
- '<div class="phabricator-notification no-notifications">%s</div>',
+ $content = phutil_tag_div(
+ 'phabricator-notification no-notifications',
pht('You have no messages.'));
}
$content = hsprintf(
'<div class="phabricator-notification-header">%s</div>'.
'%s'.
'<div class="phabricator-notification-view-all">%s</div>',
pht('Messages'),
$content,
phutil_tag(
'a',
array(
'href' => '/conpherence/',
),
'View All Conpherences'));
$unread = id(new ConpherenceParticipantCountQuery())
->withParticipantPHIDs(array($user->getPHID()))
->withParticipationStatus($unread_status)
->execute();
$unread_count = idx($unread, $user->getPHID(), 0);
$json = array(
'content' => $content,
'number' => (int)$unread_count,
);
return id(new AphrontAjaxResponse())->setContent($json);
}
}
diff --git a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php b/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php
index e241aac4f1..d82cebaa5d 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php
@@ -1,54 +1,54 @@
<?php
/**
* @group countdown
*/
final class PhabricatorCountdownDeleteController
extends PhabricatorCountdownController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$countdown = id(new PhabricatorCountdownQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$countdown) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$countdown->delete();
return id(new AphrontRedirectResponse())
->setURI('/countdown/');
}
$inst = pht('Are you sure you want to delete the countdown %s?',
$countdown->getTitle());
$dialog = new AphrontDialogView();
$dialog->setUser($request->getUser());
$dialog->setTitle(pht('Really delete this countdown?'));
- $dialog->appendChild(hsprintf('<p>%s</p>', $inst));
+ $dialog->appendChild(phutil_tag('p', array(), $inst));
$dialog->addSubmitButton(pht('Delete'));
$dialog->addCancelButton('/countdown/');
$dialog->setSubmitURI($request->getPath());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/countdown/view/PhabricatorCountdownView.php b/src/applications/countdown/view/PhabricatorCountdownView.php
index 9c09144b51..1f4b961144 100644
--- a/src/applications/countdown/view/PhabricatorCountdownView.php
+++ b/src/applications/countdown/view/PhabricatorCountdownView.php
@@ -1,78 +1,79 @@
<?php
final class PhabricatorCountdownView extends AphrontTagView {
private $countdown;
private $headless;
public function setHeadless($headless) {
$this->headless = $headless;
return $this;
}
public function setCountdown(PhabricatorCountdown $countdown) {
$this->countdown = $countdown;
return $this;
}
public function getTagContent() {
$countdown = $this->countdown;
require_celerity_resource('phabricator-countdown-css');
$header = null;
if (!$this->headless) {
$header = phutil_tag(
'div',
array(
'class' => 'phabricator-timer-header',
),
array(
"C".$countdown->getID(),
' ',
phutil_tag(
'a',
array(
'href' => '/countdown/'.$countdown->getID(),
),
$countdown->getTitle()),
));
}
- $container = celerity_generate_unique_node_id();
- $content = hsprintf(
- '<div class="phabricator-timer" id="%s">
- %s
- <table class="phabricator-timer-table">
- <tr>
- <th>%s</th>
- <th>%s</th>
- <th>%s</th>
- <th>%s</th>
- </tr>
- <tr>%s%s%s%s</tr>
- </table>
- </div>',
- $container,
- $header,
- pht('Days'),
- pht('Hours'),
- pht('Minutes'),
- pht('Seconds'),
+ $ths = array(
+ phutil_tag('th', array(), pht('Days')),
+ phutil_tag('th', array(), pht('Hours')),
+ phutil_tag('th', array(), pht('Minutes')),
+ phutil_tag('th', array(), pht('Seconds')),
+ );
+
+ $dashes = array(
javelin_tag('td', array('sigil' => 'phabricator-timer-days'), '-'),
javelin_tag('td', array('sigil' => 'phabricator-timer-hours'), '-'),
javelin_tag('td', array('sigil' => 'phabricator-timer-minutes'), '-'),
- javelin_tag('td', array('sigil' => 'phabricator-timer-seconds'), '-'));
+ javelin_tag('td', array('sigil' => 'phabricator-timer-seconds'), '-'),
+ );
+
+ $container = celerity_generate_unique_node_id();
+ $content = phutil_tag(
+ 'div',
+ array('class' => 'phabricator-timer', 'id' => $container),
+ array(
+ $header,
+ phutil_tag('table', array('class' => 'phabricator-timer-table'), array(
+ phutil_tag('tr', array(), $ths),
+ phutil_tag('tr', array(), $dashes),
+ )),
+ ));
Javelin::initBehavior('countdown-timer', array(
'timestamp' => $countdown->getEpoch(),
'container' => $container,
));
return $content;
}
}
diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php
index e55cb30afd..f6543a93d5 100644
--- a/src/applications/differential/controller/DifferentialDiffViewController.php
+++ b/src/applications/differential/controller/DifferentialDiffViewController.php
@@ -1,166 +1,167 @@
<?php
final class DifferentialDiffViewController extends DifferentialController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$diff = id(new DifferentialDiffQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$diff) {
return new Aphront404Response();
}
if ($diff->getRevisionID()) {
$top_part = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->appendChild(
pht(
'This diff belongs to revision %s.',
phutil_tag(
'a',
array(
'href' => '/D'.$diff->getRevisionID(),
),
'D'.$diff->getRevisionID())));
} else {
// TODO: implement optgroup support in AphrontFormSelectControl?
$select = array();
$select[] = hsprintf('<optgroup label="%s">', pht('Create New Revision'));
- $select[] = hsprintf(
- '<option value="">%s</option>',
+ $select[] = phutil_tag(
+ 'option',
+ array('value' => ''),
pht('Create a new Revision...'));
$select[] = hsprintf('</optgroup>');
$revisions = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withAuthors(array($viewer->getPHID()))
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->execute();
if ($revisions) {
$select[] = hsprintf(
'<optgroup label="%s">',
pht('Update Existing Revision'));
foreach ($revisions as $revision) {
$select[] = phutil_tag(
'option',
array(
'value' => $revision->getID(),
),
'D'.$revision->getID().' '.$revision->getTitle());
}
$select[] = hsprintf('</optgroup>');
}
$select = phutil_tag(
'select',
array('name' => 'revisionID'),
$select);
$form = id(new AphrontFormView())
->setUser($request->getUser())
->setAction('/differential/revision/edit/')
->addHiddenInput('diffID', $diff->getID())
->addHiddenInput('viaDiffView', 1)
->appendRemarkupInstructions(
pht(
'Review the diff for correctness. When you are satisfied, either '.
'**create a new revision** or **update an existing revision**.'))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Attach To'))
->setValue($select))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Continue')));
$top_part = $form;
}
$props = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d',
$diff->getID());
$props = mpull($props, 'getData', 'getName');
$aux_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($aux_fields as $key => $aux_field) {
if (!$aux_field->shouldAppearOnDiffView()) {
unset($aux_fields[$key]);
} else {
$aux_field->setUser($this->getRequest()->getUser());
}
}
$dict = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setDiff($diff);
$aux_field->setManualDiff($diff);
$aux_field->setDiffProperties($props);
$value = $aux_field->renderValueForDiffView();
if (strlen($value)) {
$label = rtrim($aux_field->renderLabelForDiffView(), ':');
$dict[$label] = $value;
}
}
$property_head = id(new PHUIHeaderView())
->setHeader(pht('Properties'));
$property_view = new PHUIPropertyListView();
foreach ($dict as $key => $value) {
$property_view->addProperty($key, $value);
}
$changesets = $diff->loadChangesets();
$changesets = msort($changesets, 'getSortKey');
$table_of_contents = id(new DifferentialDiffTableOfContentsView())
->setChangesets($changesets)
->setVisibleChangesets($changesets)
->setUnitTestData(idx($props, 'arc:unit', array()));
$refs = array();
foreach ($changesets as $changeset) {
$refs[$changeset->getID()] = $changeset->getID();
}
$details = id(new DifferentialChangesetListView())
->setChangesets($changesets)
->setVisibleChangesets($changesets)
->setRenderingReferences($refs)
->setStandaloneURI('/differential/changeset/')
->setDiff($diff)
->setTitle(pht('Diff %d', $diff->getID()))
->setUser($request->getUser());
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Diff %d', $diff->getID())));
return $this->buildApplicationPage(
array(
$crumbs,
$top_part,
$property_head,
$property_view,
$table_of_contents,
$details,
),
array(
'title' => pht('Diff View'),
));
}
}
diff --git a/src/applications/differential/controller/DifferentialRevisionLandController.php b/src/applications/differential/controller/DifferentialRevisionLandController.php
index fad8b48e07..b42c50c8cd 100644
--- a/src/applications/differential/controller/DifferentialRevisionLandController.php
+++ b/src/applications/differential/controller/DifferentialRevisionLandController.php
@@ -1,130 +1,130 @@
<?php
final class DifferentialRevisionLandController extends DifferentialController {
private $revisionID;
private $strategyClass;
private $pushStrategy;
public function willProcessRequest(array $data) {
$this->revisionID = $data['id'];
$this->strategyClass = $data['strategy'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$revision_id = $this->revisionID;
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($revision_id))
->setViewer($viewer)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
if (is_subclass_of($this->strategyClass, 'DifferentialLandingStrategy')) {
$this->pushStrategy = newv($this->strategyClass, array());
} else {
throw new Exception(
"Strategy type must be a valid class name and must subclass ".
"DifferentialLandingStrategy. ".
"'{$this->strategyClass}' is not a subclass of ".
"DifferentialLandingStrategy.");
}
if ($request->isDialogFormPost()) {
try {
$this->attemptLand($revision, $request);
$title = pht("Success!");
$text = pht("Revision was successfully landed.");
} catch (Exception $ex) {
$title = pht("Failed to land revision");
$text = 'moo';
if ($ex instanceof PhutilProxyException) {
$text = hsprintf(
'%s:<br><pre>%s</pre>',
$ex->getMessage(),
$ex->getPreviousException()->getMessage());
} else {
- $text = hsprintf('<pre>%s</pre>', $ex->getMessage());
+ $text = phutil_tag('pre', array(), $ex->getMessage());
}
$text = id(new AphrontErrorView())
->appendChild($text);
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle($title)
->appendChild(phutil_tag('p', array(), $text))
->setSubmitURI('/D'.$revision_id)
->addSubmitButton(pht('Done'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$prompt = hsprintf('%s<br><br>%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 b965b78fcd..bf2aeadded 100644
--- a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
@@ -1,409 +1,401 @@
<?php
abstract class DifferentialChangesetHTMLRenderer
extends DifferentialChangesetRenderer {
protected function renderChangeTypeHeader($force) {
$changeset = $this->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 <strong>added</strong>.', $none);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was <strong>added</strong>.', $none);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht(
'This directory was <strong>added</strong>.',
$none);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht(
'This binary file was <strong>added</strong>.',
$none);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht('This symlink was <strong>added</strong>.', $none);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht(
'This submodule was <strong>added</strong>.',
$none);
break;
}
break;
case DifferentialChangeType::TYPE_DELETE:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$message = pht('This file was <strong>deleted</strong>.', $none);
break;
case DifferentialChangeType::FILE_IMAGE:
$message = pht('This image was <strong>deleted</strong>.', $none);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$message = pht(
'This directory was <strong>deleted</strong>.',
$none);
break;
case DifferentialChangeType::FILE_BINARY:
$message = pht(
'This binary file was <strong>deleted</strong>.',
$none);
break;
case DifferentialChangeType::FILE_SYMLINK:
$message = pht(
'This symlink was <strong>deleted</strong>.',
$none);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$message = pht(
'This submodule was <strong>deleted</strong>.',
$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(
- '<div class="differential-meta-notice">%s</div>',
- $message);
+ return phutil_tag_div('differential-meta-notice', $message);
}
protected function renderPropertyChangeHeader() {
$changeset = $this->getChangeset();
$old = $changeset->getOldProperties();
$new = $changeset->getNewProperties();
$keys = array_keys($old + $new);
sort($keys);
$rows = array();
foreach ($keys as $key) {
$oval = idx($old, $key);
$nval = idx($new, $key);
if ($oval !== $nval) {
if ($oval === null) {
$oval = phutil_tag('em', array(), 'null');
} else {
$oval = phutil_escape_html_newlines($oval);
}
if ($nval === null) {
$nval = phutil_tag('em', array(), 'null');
} else {
$nval = phutil_escape_html_newlines($nval);
}
- $rows[] = hsprintf(
- '<tr>'.
- '<th>%s</th>'.
- '<td class="oval">%s</td>'.
- '<td class="nval">%s</td>'.
- '</tr>',
- $key,
- $oval,
- $nval);
+ $rows[] = phutil_tag('tr', array(), array(
+ phutil_tag('th', array(), $key),
+ phutil_tag('td', array('class' => 'oval'), $oval),
+ phutil_tag('td', array('class' => 'nval'), $nval),
+ ));
}
}
- array_unshift($rows, hsprintf(
- '<tr class="property-table-header">'.
- '<th>%s</th>'.
- '<td class="oval">%s</td>'.
- '<td class="nval">%s</td>'.
- '</tr>',
- pht('Property Changes'),
- pht('Old Value'),
- pht('New Value')));
+ array_unshift(
+ $rows,
+ phutil_tag('tr', array('class' => 'property-table-header'), array(
+ phutil_tag('th', array(), pht('Property Changes')),
+ phutil_tag('td', array('class' => 'oval'), pht('Old Value')),
+ phutil_tag('td', array('class' => 'nval'), pht('New Value')),
+ )));
return phutil_tag(
'table',
array('class' => 'differential-property-table'),
$rows);
}
public function renderShield($message, $force = 'default') {
$end = count($this->getOldLines());
$reference = $this->getRenderingReference();
if ($force !== 'text' &&
$force !== 'whitespace' &&
$force !== 'none' &&
$force !== 'default') {
throw new Exception("Invalid 'force' parameter '{$force}'!");
}
$range = "0-{$end}";
if ($force == 'text') {
// If we're forcing text, force the whole file to be rendered.
$range = "{$range}/0-{$end}";
}
$meta = array(
'ref' => $reference,
'range' => $range,
);
if ($force == 'whitespace') {
$meta['whitespace'] = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
}
$content = array();
$content[] = $message;
if ($force !== 'none') {
$content[] = ' ';
$content[] = javelin_tag(
'a',
array(
'mustcapture' => true,
'sigil' => 'show-more',
'class' => 'complete',
'href' => '#',
'meta' => $meta,
),
pht('Show File Contents'));
}
return $this->wrapChangeInTable(
javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
phutil_tag(
'td',
array(
'class' => 'differential-shield',
'colspan' => 6,
),
$content)));
}
protected function wrapChangeInTable($content) {
if (!$content) {
return null;
}
return javelin_tag(
'table',
array(
'class' => 'differential-diff remarkup-code PhabricatorMonospaced',
'sigil' => 'differential-diff',
),
$content);
}
protected function renderInlineComment(
PhabricatorInlineCommentInterface $comment,
$on_right = false) {
return $this->buildInlineComment($comment, $on_right)->render();
}
protected function buildInlineComment(
PhabricatorInlineCommentInterface $comment,
$on_right = false) {
$user = $this->getUser();
$edit = $user &&
($comment->getAuthorPHID() == $user->getPHID()) &&
($comment->isDraft());
$allow_reply = (bool)$user;
return id(new DifferentialInlineCommentView())
->setInlineComment($comment)
->setOnRight($on_right)
->setHandles($this->getHandles())
->setMarkupEngine($this->getMarkupEngine())
->setEditable($edit)
->setAllowReply($allow_reply);
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php
index 567116db05..25d8aeee58 100644
--- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php
@@ -1,77 +1,77 @@
<?php
final class DifferentialChangesetOneUpRenderer
extends DifferentialChangesetHTMLRenderer {
public function isOneUpRenderer() {
return true;
}
public function renderTextChange(
$range_start,
$range_len,
$rows) {
$primitives = $this->buildPrimitives($range_start, $range_len);
$out = array();
foreach ($primitives as $p) {
$type = $p['type'];
switch ($type) {
case 'old':
case 'new':
$out[] = hsprintf('<tr>');
if ($type == 'old') {
if ($p['htype']) {
$class = 'left old';
} else {
$class = 'left';
}
- $out[] = hsprintf('<th>%s</th>', $p['line']);
- $out[] = hsprintf('<th></th>');
- $out[] = hsprintf('<td class="%s">%s</td>', $class, $p['render']);
+ $out[] = phutil_tag('th', array(), $p['line']);
+ $out[] = phutil_tag('th', array());
+ $out[] = phutil_tag('td', array('class' => $class), $p['render']);
} else if ($type == 'new') {
if ($p['htype']) {
$class = 'right new';
- $out[] = hsprintf('<th />');
+ $out[] = phutil_tag('th', array());
} else {
$class = 'right';
- $out[] = hsprintf('<th>%s</th>', $p['oline']);
+ $out[] = phutil_tag('th', array(), $p['oline']);
}
- $out[] = hsprintf('<th>%s</th>', $p['line']);
- $out[] = hsprintf('<td class="%s">%s</td>', $class, $p['render']);
+ $out[] = phutil_tag('th', array(), $p['line']);
+ $out[] = phutil_tag('td', array('class' => $class), $p['render']);
}
$out[] = hsprintf('</tr>');
break;
case 'inline':
$out[] = hsprintf('<tr><th /><th />');
$out[] = hsprintf('<td>');
$inline = $this->buildInlineComment(
$p['comment'],
$p['right']);
$inline->setBuildScaffolding(false);
$out[] = $inline->render();
$out[] = hsprintf('</td></tr>');
break;
default:
$out[] = hsprintf('<tr><th /><th /><td>%s</td></tr>', $type);
break;
}
}
if ($out) {
return $this->wrapChangeInTable(phutil_implode_html('', $out));
}
return null;
}
public function renderFileChange($old_file = null,
$new_file = null,
$id = 0,
$vs = 0) {
throw new Exception("Not implemented!");
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
index 378d4bdcc5..f44889ce5e 100644
--- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
@@ -1,455 +1,448 @@
<?php
final class DifferentialChangesetTwoUpRenderer
extends DifferentialChangesetHTMLRenderer {
public function isOneUpRenderer() {
return false;
}
public function renderTextChange(
$range_start,
$range_len,
$rows) {
$hunk_starts = $this->getHunkStartLines();
$context_not_available = null;
if ($hunk_starts) {
$context_not_available = javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
phutil_tag(
'td',
array(
'colspan' => 6,
'class' => 'show-more'
),
pht('Context not available.')));
}
$html = array();
$old_lines = $this->getOldLines();
$new_lines = $this->getNewLines();
$gaps = $this->getGaps();
$reference = $this->getRenderingReference();
$left_id = $this->getOldChangesetID();
$right_id = $this->getNewChangesetID();
// "N" stands for 'new' and means the comment should attach to the new file
// when stored, i.e. DifferentialInlineComment->setIsNewFile().
// "O" stands for 'old' and means the comment should attach to the old file.
$left_char = $this->getOldAttachesToNewFile()
? 'N'
: 'O';
$right_char = $this->getNewAttachesToNewFile()
? 'N'
: 'O';
$changeset = $this->getChangeset();
$copy_lines = idx($changeset->getMetadata(), 'copy:lines', array());
$highlight_old = $this->getHighlightOld();
$highlight_new = $this->getHighlightNew();
$old_render = $this->getOldRender();
$new_render = $this->getNewRender();
$original_left = $this->getOriginalOld();
$original_right = $this->getOriginalNew();
$depths = $this->getDepths();
$mask = $this->getMask();
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
if (empty($mask[$ii])) {
// If we aren't going to show this line, we've just entered a gap.
// Pop information about the next gap off the $gaps stack and render
// an appropriate "Show more context" element. This branch eventually
// increments $ii by the entire size of the gap and then continues
// the loop.
$gap = array_pop($gaps);
$top = $gap[0];
$len = $gap[1];
$end = $top + $len - 20;
$contents = array();
if ($len > 40) {
$is_first_block = false;
if ($ii == 0) {
$is_first_block = true;
}
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-20",
),
),
$is_first_block
? pht("Show First 20 Lines")
: pht("\xE2\x96\xB2 Show 20 Lines"));
}
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'type' => 'all',
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-{$len}",
),
),
pht('Show All %d Lines', $len));
$is_last_block = false;
if ($ii + $len >= $rows) {
$is_last_block = true;
}
if ($len > 40) {
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$end}-20",
),
),
$is_last_block
? pht("Show Last 20 Lines")
: pht("\xE2\x96\xBC Show 20 Lines"));
}
$context = null;
$context_line = null;
if (!$is_last_block && $depths[$ii + $len]) {
for ($l = $ii + $len - 1; $l >= $ii; $l--) {
$line = $new_lines[$l]['text'];
if ($depths[$l] < $depths[$ii + $len] && trim($line) != '') {
$context = $new_render[$l];
$context_line = $new_lines[$l]['line'];
break;
}
}
}
$container = javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
array(
phutil_tag(
'td',
array(
'colspan' => 2,
'class' => 'show-more',
),
phutil_implode_html(
" \xE2\x80\xA2 ", // Bullet
$contents)),
phutil_tag(
'th',
array(
'class' => 'show-context-line',
),
$context_line ? (int)$context_line : null),
phutil_tag(
'td',
array(
'colspan' => 3,
'class' => 'show-context',
),
// TODO: [HTML] Escaping model here isn't ideal.
phutil_safe_html($context)),
));
$html[] = $container;
$ii += ($len - 1);
continue;
}
$o_num = null;
$o_classes = 'left';
$o_text = null;
if (isset($old_lines[$ii])) {
$o_num = $old_lines[$ii]['line'];
$o_text = isset($old_render[$ii]) ? $old_render[$ii] : null;
if ($old_lines[$ii]['type']) {
if ($old_lines[$ii]['type'] == '\\') {
$o_text = $old_lines[$ii]['text'];
$o_classes .= ' comment';
} else if ($original_left && !isset($highlight_old[$o_num])) {
$o_classes .= ' old-rebase';
} else if (empty($new_lines[$ii])) {
$o_classes .= ' old old-full';
} else {
$o_classes .= ' old';
}
}
}
$n_copy = hsprintf('<td class="copy" />');
$n_cov = null;
$n_colspan = 2;
$n_classes = '';
$n_num = null;
$n_text = null;
if (isset($new_lines[$ii])) {
$n_num = $new_lines[$ii]['line'];
$n_text = isset($new_render[$ii]) ? $new_render[$ii] : null;
$coverage = $this->getCodeCoverage();
if ($coverage !== null) {
if (empty($coverage[$n_num - 1])) {
$cov_class = 'N';
} else {
$cov_class = $coverage[$n_num - 1];
}
$cov_class = 'cov-'.$cov_class;
- $n_cov = hsprintf('<td class="cov %s"></td>', $cov_class);
+ $n_cov = phutil_tag('td', array('class' => "cov {$cov_class}"));
$n_colspan--;
}
if ($new_lines[$ii]['type']) {
if ($new_lines[$ii]['type'] == '\\') {
$n_text = $new_lines[$ii]['text'];
$n_class = 'comment';
} else if ($original_right && !isset($highlight_new[$n_num])) {
$n_class = 'new-rebase';
} else if (empty($old_lines[$ii])) {
$n_class = 'new new-full';
} else {
$n_class = 'new';
}
$n_classes = $n_class;
if ($new_lines[$ii]['type'] == '\\' || !isset($copy_lines[$n_num])) {
- $n_copy = hsprintf('<td class="copy %s"></td>', $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(
- '<tr>'.
- '%s'.
- '<td class="%s">%s</td>'.
- '%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.
- '<td class="%s" colspan="%s">'.
- "\xE2\x80\x8B%s".
- '</td>'.
- '%s'.
- '</tr>',
+ $html[] = phutil_tag('tr', array(), array(
phutil_tag('th', array('id' => $o_id), $o_num),
- $o_classes, $o_text,
+ phutil_tag('td', array('class' => $o_classes), $o_text),
phutil_tag('th', array('id' => $n_id), $n_num),
$n_copy,
- $n_classes, $n_colspan, $n_text,
- $n_cov);
+ phutil_tag(
+ 'td',
+ array('class' => $n_classes, 'colspan' => $n_colspan),
+ array($zero_space, $n_text)),
+ $n_cov,
+ ));
if ($context_not_available && ($ii == $rows - 1)) {
$html[] = $context_not_available;
}
$old_comments = $this->getOldComments();
$new_comments = $this->getNewComments();
if ($o_num && isset($old_comments[$o_num])) {
foreach ($old_comments[$o_num] as $comment) {
$comment_html = $this->renderInlineComment($comment,
$on_right = false);
$new = '';
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $key => $new_comment) {
if ($comment->isCompatible($new_comment)) {
$new = $this->renderInlineComment($new_comment,
$on_right = true);
unset($new_comments[$n_num][$key]);
}
}
}
- $html[] = hsprintf(
- '<tr class="inline">'.
- '<th />'.
- '<td class="left">%s</td>'.
- '<th />'.
- '<td colspan="3" class="right3">%s</td>'.
- '</tr>',
- $comment_html,
- $new);
+ $html[] = phutil_tag('tr', array('class' => 'inline'), array(
+ phutil_tag('th', array()),
+ phutil_tag('td', array('class' => 'left'), $comment_html),
+ phutil_tag('th', array()),
+ phutil_tag('td', array('colspan' => 3, 'class' => 'right3'), $new),
+ ));
}
}
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $comment) {
$comment_html = $this->renderInlineComment($comment,
$on_right = true);
- $html[] = hsprintf(
- '<tr class="inline">'.
- '<th />'.
- '<td class="left" />'.
- '<th />'.
- '<td colspan="3" class="right3">%s</td>'.
- '</tr>',
- $comment_html);
+ $html[] = phutil_tag('tr', array('class' => 'inline'), array(
+ phutil_tag('th', array()),
+ phutil_tag('td', array('class' => 'left')),
+ phutil_tag('th', array()),
+ phutil_tag(
+ 'td',
+ array('colspan' => 3, 'class' => 'right3'),
+ $comment_html),
+ ));
}
}
}
return $this->wrapChangeInTable(phutil_implode_html('', $html));
}
public function renderFileChange($old_file = null,
$new_file = null,
$id = 0,
$vs = 0) {
$old = null;
if ($old_file) {
$old = phutil_tag(
'div',
array(
'class' => 'differential-image-stage'
),
phutil_tag(
'img',
array(
'src' => $old_file->getBestURI(),
)));
}
$new = null;
if ($new_file) {
$new = phutil_tag(
'div',
array(
'class' => 'differential-image-stage'
),
phutil_tag(
'img',
array(
'src' => $new_file->getBestURI(),
)));
}
$html_old = array();
$html_new = array();
foreach ($this->getOldComments() as $on_line => $comment_group) {
foreach ($comment_group as $comment) {
$comment_html = $this->renderInlineComment($comment, $on_right = false);
- $html_old[] = hsprintf(
- '<tr class="inline">'.
- '<th />'.
- '<td class="left">%s</td>'.
- '<th />'.
- '<td class="right3" colspan="3" />'.
- '</tr>',
- $comment_html);
+ $html_old[] = phutil_tag('tr', array('class' => 'inline'), array(
+ phutil_tag('th', array()),
+ phutil_tag('td', array('class' => 'left'), $comment_html),
+ phutil_tag('th', array()),
+ phutil_tag('td', array('colspan' => 3, 'class' => 'right3')),
+ ));
}
}
foreach ($this->getNewComments() as $lin_line => $comment_group) {
foreach ($comment_group as $comment) {
$comment_html = $this->renderInlineComment($comment, $on_right = true);
- $html_new[] = hsprintf(
- '<tr class="inline">'.
- '<th />'.
- '<td class="left" />'.
- '<th />'.
- '<td class="right3" colspan="3">%s</td>'.
- '</tr>',
- $comment_html);
+ $html_new[] = phutil_tag('tr', array('class' => 'inline'), array(
+ phutil_tag('th', array()),
+ phutil_tag('td', array('class' => 'left')),
+ phutil_tag('th', array()),
+ phutil_tag(
+ 'td',
+ array('colspan' => 3, 'class' => 'right3'),
+ $comment_html),
+ ));
}
}
if (!$old) {
- $th_old = hsprintf('<th></th>');
+ $th_old = phutil_tag('th', array());
} else {
- $th_old = hsprintf('<th id="C%sOL1">1</th>', $vs);
+ $th_old = phutil_tag('th', array('id' => "C{$vs}OL1"), 1);
}
if (!$new) {
- $th_new = hsprintf('<th></th>');
+ $th_new = phutil_tag('th', array());
} else {
- $th_new = hsprintf('<th id="C%sNL1">1</th>', $id);
+ $th_new = phutil_tag('th', array('id' => "C{$id}OL1"), 1);
}
$output = hsprintf(
'<tr class="differential-image-diff">'.
'%s'.
'<td class="left differential-old-image">%s</td>'.
'%s'.
'<td class="right3 differential-new-image" colspan="3">%s</td>'.
'</tr>'.
'%s'.
'%s',
$th_old,
$old,
$th_new,
$new,
phutil_implode_html('', $html_old),
phutil_implode_html('', $html_new));
$output = $this->wrapChangeInTable($output);
return $this->renderChangesetTable($output);
}
}
diff --git a/src/applications/differential/view/DifferentialAddCommentView.php b/src/applications/differential/view/DifferentialAddCommentView.php
index ceba6a2197..822a85d6ac 100644
--- a/src/applications/differential/view/DifferentialAddCommentView.php
+++ b/src/applications/differential/view/DifferentialAddCommentView.php
@@ -1,204 +1,205 @@
<?php
final class DifferentialAddCommentView extends AphrontView {
private $revision;
private $actions;
private $actionURI;
private $draft;
private $auxFields;
private $reviewers = array();
private $ccs = array();
public function setRevision($revision) {
$this->revision = $revision;
return $this;
}
public function setAuxFields(array $aux_fields) {
assert_instances_of($aux_fields, 'DifferentialFieldSpecification');
$this->auxFields = $aux_fields;
return $this;
}
public function setActions(array $actions) {
$this->actions = $actions;
return $this;
}
public function setActionURI($uri) {
$this->actionURI = $uri;
return $this;
}
public function setDraft(PhabricatorDraft $draft = null) {
$this->draft = $draft;
return $this;
}
public function setReviewers(array $names) {
$this->reviewers = $names;
return $this;
}
public function setCCs(array $names) {
$this->ccs = $names;
return $this;
}
public function render() {
require_celerity_resource('differential-revision-add-comment-css');
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$revision = $this->revision;
$action = null;
if ($this->draft) {
$action = idx($this->draft->getMetadata(), 'action');
}
$enable_reviewers = DifferentialAction::allowReviewers($action);
$enable_ccs = ($action == DifferentialAction::ACTION_ADDCCS);
$add_reviewers_labels = array(
'add_reviewers' => pht('Add Reviewers'),
'request_review' => pht('Add Reviewers'),
'resign' => pht('Suggest Reviewers'),
);
$form = new AphrontFormView();
$form
->setWorkflow(true)
->setUser($this->user)
->setAction($this->actionURI)
->addHiddenInput('revision_id', $revision->getID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Action'))
->setName('action')
->setValue($action)
->setID('comment-action')
->setOptions($this->actions))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel($enable_reviewers ? $add_reviewers_labels[$action] :
$add_reviewers_labels['add_reviewers'])
->setName('reviewers')
->setControlID('add-reviewers')
->setControlStyle($enable_reviewers ? null : 'display: none')
->setID('add-reviewers-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Add CCs'))
->setName('ccs')
->setControlID('add-ccs')
->setControlStyle($enable_ccs ? null : 'display: none')
->setID('add-ccs-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new PhabricatorRemarkupControl())
->setName('comment')
->setID('comment-content')
->setLabel(pht('Comment'))
->setValue($this->draft ? $this->draft->getDraft() : null)
->setUser($this->user))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? pht('Submit') : pht('Clowncopterize')));
Javelin::initBehavior(
'differential-add-reviewers-and-ccs',
array(
'dynamic' => array(
'add-reviewers-tokenizer' => array(
'actions' => array(
'request_review' => 1,
'add_reviewers' => 1,
'resign' => 1,
),
'src' => '/typeahead/common/usersorprojects/',
'value' => $this->reviewers,
'row' => 'add-reviewers',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'labels' => $add_reviewers_labels,
'placeholder' => pht('Type a user or project name...'),
),
'add-ccs-tokenizer' => array(
'actions' => array('add_ccs' => 1),
'src' => '/typeahead/common/mailable/',
'value' => $this->ccs,
'row' => 'add-ccs',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user or mailing list...'),
),
),
'select' => 'comment-action',
));
$diff = $revision->loadActiveDiff();
$warnings = mpull($this->auxFields, 'renderWarningBoxForRevisionAccept');
Javelin::initBehavior(
'differential-accept-with-errors',
array(
'select' => 'comment-action',
'warnings' => 'warnings',
));
$rev_id = $revision->getID();
Javelin::initBehavior(
'differential-feedback-preview',
array(
'uri' => '/differential/comment/preview/'.$rev_id.'/',
'preview' => 'comment-preview',
'action' => 'comment-action',
'content' => 'comment-content',
'previewTokenizers' => array(
'reviewers' => 'add-reviewers-tokenizer',
'ccs' => 'add-ccs-tokenizer',
),
'inlineuri' => '/differential/comment/inline/preview/'.$rev_id.'/',
'inline' => 'inline-comment-preview',
));
$warning_container = array();
foreach ($warnings as $warning) {
if ($warning) {
$warning_container[] = $warning->render();
}
}
$header = id(new PHUIHeaderView())
->setHeader($is_serious ? pht('Add Comment') : pht('Leap Into Action'));
$anchor = id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true);
$warn = phutil_tag('div', array('id' => 'warnings'), $warning_container);
- $preview = hsprintf(
- '<div class="aphront-panel-preview aphront-panel-flush">'.
- '<div id="comment-preview">'.
- '<span class="aphront-panel-preview-loading-text">%s</span>'.
- '</div>'.
- '<div id="inline-comment-preview">'.
- '</div>'.
- '</div>',
+ $loading = phutil_tag(
+ 'span',
+ array('class' => 'aphront-panel-preview-loading-text'),
pht('Loading comment preview...'));
+ $preview = phutil_tag_div(
+ 'aphront-panel-preview aphront-panel-flush',
+ array(
+ phutil_tag('div', array('id' => 'comment-preview'), $loading),
+ phutil_tag('div', array('id' => 'inline-comment-preview')),
+ ));
$comment_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($anchor)
->appendChild($warn)
->appendChild($form);
return array($comment_box, $preview);
}
}
diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php
index ca05bf97b0..17ed42eac9 100644
--- a/src/applications/differential/view/DifferentialChangesetListView.php
+++ b/src/applications/differential/view/DifferentialChangesetListView.php
@@ -1,346 +1,348 @@
<?php
final class DifferentialChangesetListView extends AphrontView {
private $changesets = array();
private $visibleChangesets = array();
private $references = array();
private $inlineURI;
private $renderURI = '/differential/changeset/';
private $whitespace;
private $standaloneURI;
private $leftRawFileURI;
private $rightRawFileURI;
private $symbolIndexes = array();
private $repository;
private $branch;
private $diff;
private $vsMap = array();
private $title;
public function setTitle($title) {
$this->title = $title;
return $this;
}
private function getTitle() {
return $this->title;
}
public function setBranch($branch) {
$this->branch = $branch;
return $this;
}
private function getBranch() {
return $this->branch;
}
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setVisibleChangesets($visible_changesets) {
$this->visibleChangesets = $visible_changesets;
return $this;
}
public function setInlineCommentControllerURI($uri) {
$this->inlineURI = $uri;
return $this;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function setRenderingReferences(array $references) {
$this->references = $references;
return $this;
}
public function setSymbolIndexes(array $indexes) {
$this->symbolIndexes = $indexes;
return $this;
}
public function setRenderURI($render_uri) {
$this->renderURI = $render_uri;
return $this;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function setVsMap(array $vs_map) {
$this->vsMap = $vs_map;
return $this;
}
public function getVsMap() {
return $this->vsMap;
}
public function setStandaloneURI($uri) {
$this->standaloneURI = $uri;
return $this;
}
public function setRawFileURIs($l, $r) {
$this->leftRawFileURI = $l;
$this->rightRawFileURI = $r;
return $this;
}
public function render() {
require_celerity_resource('differential-changeset-view-css');
$changesets = $this->changesets;
Javelin::initBehavior('differential-toggle-files', array(
'pht' => array(
'undo' => pht('Undo'),
'collapsed' => pht('This file content has been collapsed.'))
));
Javelin::initBehavior(
'differential-dropdown-menus',
array(
'pht' => array(
'Open in Editor' => pht('Open in Editor'),
'Show Entire File' => pht('Show Entire File'),
'Entire File Shown' => pht('Entire File Shown'),
"Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"),
'Expand File' => pht('Expand File'),
'Collapse File' => pht('Collapse File'),
'Browse in Diffusion' => pht('Browse in Diffusion'),
'View Standalone' => pht('View Standalone'),
'Show Raw File (Left)' => pht('Show Raw File (Left)'),
'Show Raw File (Right)' => pht('Show Raw File (Right)'),
'Configure Editor' => pht('Configure Editor'),
),
));
$output = array();
$mapping = array();
foreach ($changesets as $key => $changeset) {
$file = $changeset->getFilename();
$class = 'differential-changeset';
if (!$this->inlineURI) {
$class .= ' differential-changeset-noneditable';
}
$ref = $this->references[$key];
$detail = new DifferentialChangesetDetailView();
$view_options = $this->renderViewOptionsDropdown(
$detail,
$ref,
$changeset);
$detail->setChangeset($changeset);
$detail->addButton($view_options);
$detail->setSymbolIndex(idx($this->symbolIndexes, $key));
$detail->setVsChangesetID(idx($this->vsMap, $changeset->getID()));
$detail->setEditable(true);
$uniq_id = 'diff-'.$changeset->getAnchorName();
if (isset($this->visibleChangesets[$key])) {
$load = 'Loading...';
$mapping[$uniq_id] = $ref;
} else {
$load = javelin_tag(
'a',
array(
'href' => '#'.$uniq_id,
'meta' => array(
'id' => $uniq_id,
'ref' => $ref,
'kill' => true,
),
'sigil' => 'differential-load',
'mustcapture' => true,
),
pht('Load'));
}
$detail->appendChild(
phutil_tag(
'div',
array(
'id' => $uniq_id,
),
phutil_tag('div', array('class' => 'differential-loading'), $load)));
$output[] = $detail->render();
}
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('differential-populate', array(
'registry' => $mapping,
'whitespace' => $this->whitespace,
'uri' => $this->renderURI,
));
Javelin::initBehavior('differential-show-more', array(
'uri' => $this->renderURI,
'whitespace' => $this->whitespace,
));
Javelin::initBehavior('differential-comment-jump', array());
if ($this->inlineURI) {
$undo_templates = $this->renderUndoTemplates();
Javelin::initBehavior('differential-edit-inline-comments', array(
'uri' => $this->inlineURI,
'undo_templates' => $undo_templates,
'stage' => 'differential-review-stage',
));
}
$header = id(new PHUIHeaderView())
->setHeader($this->getTitle());
$content = phutil_tag(
'div',
array(
'class' => 'differential-review-stage',
'id' => 'differential-review-stage',
),
$output);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($content);
return $object_box;
}
/**
* Render the "Undo" markup for the inline comment undo feature.
*/
private function renderUndoTemplates() {
$link = javelin_tag(
'a',
array(
'href' => '#',
'sigil' => 'differential-inline-comment-undo',
),
pht('Undo'));
$div = phutil_tag(
'div',
array(
'class' => 'differential-inline-undo',
),
array('Changes discarded. ', $link));
return array(
- 'l' => hsprintf(
- '<table><tr>'.
- '<th></th><td>%s</td>'.
- '<th></th><td colspan="3"></td>'.
- '</tr></table>',
- $div),
-
- 'r' => hsprintf(
- '<table><tr>'.
- '<th></th><td></td>'.
- '<th></th><td colspan="3">%s</td>'.
- '</tr></table>',
- $div),
+ 'l' => phutil_tag('table', array(),
+ phutil_tag('tr', array(), array(
+ phutil_tag('th', array()),
+ phutil_tag('td', array(), $div),
+ phutil_tag('th', array()),
+ phutil_tag('td', array('colspan' => 3)),
+ ))),
+
+ 'r' => phutil_tag('table', array(),
+ phutil_tag('tr', array(), array(
+ phutil_tag('th', array()),
+ phutil_tag('td', array()),
+ phutil_tag('th', array()),
+ phutil_tag('td', array('colspan' => 3), $div),
+ ))),
);
}
private function renderViewOptionsDropdown(
DifferentialChangesetDetailView $detail,
$ref,
DifferentialChangeset $changeset) {
$meta = array();
$qparams = array(
'ref' => $ref,
'whitespace' => $this->whitespace,
);
if ($this->standaloneURI) {
$uri = new PhutilURI($this->standaloneURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['standaloneURI'] = (string)$uri;
}
$repository = $this->repository;
if ($repository) {
try {
$meta['diffusionURI'] =
(string)$repository->getDiffusionBrowseURIForPath(
$this->user,
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
idx($changeset->getMetadata(), 'line:first'),
$this->getBranch());
} catch (DiffusionSetupException $e) {
// Ignore
}
}
$change = $changeset->getChangeType();
if ($this->leftRawFileURI) {
if ($change != DifferentialChangeType::TYPE_ADD) {
$uri = new PhutilURI($this->leftRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['leftURI'] = (string)$uri;
}
}
if ($this->rightRawFileURI) {
if ($change != DifferentialChangeType::TYPE_DELETE &&
$change != DifferentialChangeType::TYPE_MULTICOPY) {
$uri = new PhutilURI($this->rightRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['rightURI'] = (string)$uri;
}
}
$user = $this->user;
if ($user && $repository) {
$path = ltrim(
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
'/');
$line = idx($changeset->getMetadata(), 'line:first', 1);
$callsign = $repository->getCallsign();
$editor_link = $user->loadEditorLink($path, $line, $callsign);
if ($editor_link) {
$meta['editor'] = $editor_link;
} else {
$meta['editorConfigure'] = '/settings/panel/display/';
}
}
$meta['containerID'] = $detail->getID();
$caret = phutil_tag('span', array('class' => 'caret'), '');
return javelin_tag(
'a',
array(
'class' => 'button grey small dropdown',
'meta' => $meta,
'href' => idx($meta, 'detailURI', '#'),
'target' => '_blank',
'sigil' => 'differential-view-options',
),
array(pht('View Options'), $caret));
}
}
diff --git a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
index 1ba389839a..84fae42243 100644
--- a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
+++ b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php
@@ -1,312 +1,309 @@
<?php
final class DifferentialDiffTableOfContentsView extends AphrontView {
private $changesets = array();
private $visibleChangesets = array();
private $references = array();
private $repository;
private $diff;
private $renderURI = '/differential/changeset/';
private $revisionID;
private $whitespace;
private $unitTestData;
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setVisibleChangesets($visible_changesets) {
$this->visibleChangesets = $visible_changesets;
return $this;
}
public function setRenderingReferences(array $references) {
$this->references = $references;
return $this;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function setUnitTestData($unit_test_data) {
$this->unitTestData = $unit_test_data;
return $this;
}
public function setRevisionID($revision_id) {
$this->revisionID = $revision_id;
return $this;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function render() {
require_celerity_resource('differential-core-view-css');
require_celerity_resource('differential-table-of-contents-css');
$rows = array();
$coverage = array();
if ($this->unitTestData) {
$coverage_by_file = array();
foreach ($this->unitTestData as $result) {
$test_coverage = idx($result, 'coverage');
if (!$test_coverage) {
continue;
}
foreach ($test_coverage as $file => $results) {
$coverage_by_file[$file][] = $results;
}
}
foreach ($coverage_by_file as $file => $coverages) {
$coverage[$file] = ArcanistUnitTestResult::mergeCoverage($coverages);
}
}
$changesets = $this->changesets;
$paths = array();
foreach ($changesets as $id => $changeset) {
$type = $changeset->getChangeType();
$ftype = $changeset->getFileType();
$ref = idx($this->references, $id);
$display_file = $changeset->getDisplayFilename();
$meta = null;
if (DifferentialChangeType::isOldLocationChangeType($type)) {
$away = $changeset->getAwayPaths();
if (count($away) > 1) {
$meta = array();
if ($type == DifferentialChangeType::TYPE_MULTICOPY) {
$meta[] = pht('Deleted after being copied to multiple locations:');
} else {
$meta[] = pht('Copied to multiple locations:');
}
foreach ($away as $path) {
$meta[] = $path;
}
$meta = phutil_implode_html(phutil_tag('br'), $meta);
} else {
if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) {
$display_file = $this->renderRename(
$display_file,
reset($away),
"\xE2\x86\x92");
} else {
$meta = pht('Copied to %s', reset($away));
}
}
} else if ($type == DifferentialChangeType::TYPE_MOVE_HERE) {
$old_file = $changeset->getOldFile();
$display_file = $this->renderRename(
$display_file,
$old_file,
"\xE2\x86\x90");
} else if ($type == DifferentialChangeType::TYPE_COPY_HERE) {
$meta = pht('Copied from %s', $changeset->getOldFile());
}
$link = $this->renderChangesetLink($changeset, $ref, $display_file);
$line_count = $changeset->getAffectedLineCount();
if ($line_count == 0) {
$lines = null;
} else {
$lines = ' '.pht('(%d line(s))', $line_count);
}
$char = DifferentialChangeType::getSummaryCharacterForChangeType($type);
$chartitle = DifferentialChangeType::getFullNameForChangeType($type);
$desc = DifferentialChangeType::getShortNameForFileType($ftype);
if ($desc) {
$desc = '('.$desc.')';
}
$pchar =
($changeset->getOldProperties() === $changeset->getNewProperties())
? null
- : hsprintf('<span title="%s">M</span>', pht('Properties Changed'));
+ : phutil_tag('span', array('title' => pht('Properties Changed')), 'M')
+ ;
$fname = $changeset->getFilename();
$cov = $this->renderCoverage($coverage, $fname);
if ($cov === null) {
$mcov = $cov = phutil_tag('em', array(), '-');
} else {
$mcov = phutil_tag(
'div',
array(
'id' => 'differential-mcoverage-'.md5($fname),
'class' => 'differential-mcoverage-loading',
),
(isset($this->visibleChangesets[$id]) ? 'Loading...' : '?'));
}
- $rows[] = hsprintf(
- '<tr>'.
- '<td class="differential-toc-char" title="%s">%s</td>'.
- '<td class="differential-toc-prop">%s</td>'.
- '<td class="differential-toc-ftype">%s</td>'.
- '<td class="differential-toc-file">%s%s</td>'.
- '<td class="differential-toc-cov">%s</td>'.
- '<td class="differential-toc-mcov">%s</td>'.
- '</tr>',
- $chartitle, $char,
- $pchar,
- $desc,
- $link, $lines,
- $cov,
- $mcov);
+ $rows[] = phutil_tag('tr', array(), array(
+ phutil_tag(
+ 'td',
+ array('class' => 'differential-toc-char', 'title' => $chartitle),
+ $char),
+ phutil_tag('td', array('class' => 'differential-toc-prop'), $pchar),
+ phutil_tag('td', array('class' => 'differential-toc-ftype'), $desc),
+ phutil_tag(
+ 'td',
+ array('class' => 'differential-toc-file'),
+ array($link, $lines)),
+ phutil_tag('td', array('class' => 'differential-toc-cov'), $cov),
+ phutil_tag('td', array('class' => 'differential-toc-mcov'), $mcov),
+ ));
if ($meta) {
- $rows[] = hsprintf(
- '<tr>'.
- '<td colspan="3"></td>'.
- '<td class="differential-toc-meta">%s</td>'.
- '</tr>',
- $meta);
+ $rows[] = phutil_tag('tr', array(), array(
+ phutil_tag('td', array('colspan' => 3)),
+ phutil_tag('td', array('class' => 'differential-toc-meta'), $meta),
+ ));
}
if ($this->diff && $this->repository) {
$paths[] =
$changeset->getAbsoluteRepositoryPath($this->repository, $this->diff);
}
}
$editor_link = null;
if ($paths && $this->user) {
$editor_link = $this->user->loadEditorLink(
$paths,
1, // line number
$this->repository->getCallsign());
if ($editor_link) {
$editor_link =
phutil_tag(
'a',
array(
'href' => $editor_link,
'class' => 'button differential-toc-edit-all',
),
pht('Open All in Editor'));
}
}
$reveal_link = javelin_tag(
'a',
array(
'sigil' => 'differential-reveal-all',
'mustcapture' => true,
'class' => 'button differential-toc-reveal-all',
),
pht('Show All Context'));
- $buttons = hsprintf(
- '<tr><td colspan="7">%s%s</td></tr>',
- $editor_link,
- $reveal_link);
+ $buttons = phutil_tag('tr', array(),
+ phutil_tag('td', array('colspan' => 7),
+ array($editor_link, $reveal_link)));
$content = hsprintf(
'%s'.
'<div class="differential-toc differential-panel">'.
'<table>'.
'<tr>'.
'<th></th>'.
'<th></th>'.
'<th></th>'.
'<th>Path</th>'.
'<th class="differential-toc-cov">%s</th>'.
'<th class="differential-toc-mcov">%s</th>'.
'</tr>'.
'%s%s'.
'</table>'.
'</div>',
id(new PhabricatorAnchorView())
->setAnchorName('toc')
->setNavigationMarker(true)
->render(),
pht('Coverage (All)'),
pht('Coverage (Touched)'),
phutil_implode_html("\n", $rows),
$buttons);
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Table of Contents'))
->appendChild($content);
}
private function renderRename($display_file, $other_file, $arrow) {
$old = explode('/', $display_file);
$new = explode('/', $other_file);
$start = count($old);
foreach ($old as $index => $part) {
if (!isset($new[$index]) || $part != $new[$index]) {
$start = $index;
break;
}
}
$end = count($old);
foreach (array_reverse($old) as $from_end => $part) {
$index = count($new) - $from_end - 1;
if (!isset($new[$index]) || $part != $new[$index]) {
$end = $from_end;
break;
}
}
$rename =
'{'.
implode('/', array_slice($old, $start, count($old) - $end - $start)).
' '.$arrow.' '.
implode('/', array_slice($new, $start, count($new) - $end - $start)).
'}';
array_splice($new, $start, count($new) - $end - $start, $rename);
return implode('/', $new);
}
private function renderCoverage(array $coverage, $file) {
$info = idx($coverage, $file);
if (!$info) {
return null;
}
$not_covered = substr_count($info, 'U');
$covered = substr_count($info, 'C');
if (!$not_covered && !$covered) {
return null;
}
return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
}
private function renderChangesetLink(
DifferentialChangeset $changeset,
$ref,
$display_file) {
return javelin_tag(
'a',
array(
'href' => '#'.$changeset->getAnchorName(),
'meta' => array(
'id' => 'diff-'.$changeset->getAnchorName(),
'ref' => $ref,
),
'sigil' => 'differential-load',
),
$display_file);
}
}
diff --git a/src/applications/differential/view/DifferentialInlineCommentEditView.php b/src/applications/differential/view/DifferentialInlineCommentEditView.php
index 672d945e4e..a2318eb069 100644
--- a/src/applications/differential/view/DifferentialInlineCommentEditView.php
+++ b/src/applications/differential/view/DifferentialInlineCommentEditView.php
@@ -1,156 +1,160 @@
<?php
final class DifferentialInlineCommentEditView extends AphrontView {
private $inputs = array();
private $uri;
private $title;
private $onRight;
private $number;
private $length;
public function addHiddenInput($key, $value) {
$this->inputs[] = array($key, $value);
return $this;
}
public function setSubmitURI($uri) {
$this->uri = $uri;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function setOnRight($on_right) {
$this->onRight = $on_right;
$this->addHiddenInput('on_right', $on_right);
return $this;
}
public function setNumber($number) {
$this->number = $number;
return $this;
}
public function setLength($length) {
$this->length = $length;
return $this;
}
public function render() {
if (!$this->uri) {
throw new Exception("Call setSubmitURI() before render()!");
}
if (!$this->user) {
throw new Exception("Call setUser() before render()!");
}
$content = phabricator_form(
$this->user,
array(
'action' => $this->uri,
'method' => 'POST',
'sigil' => 'inline-edit-form',
),
array(
$this->renderInputs(),
$this->renderBody(),
));
- return hsprintf(
- '<table>'.
- '<tr class="inline-comment-splint">'.
- '<th></th>'.
- '<td class="left">%s</td>'.
- '<th></th>'.
- '<td colspan="3" class="right3">%s</td>'.
- '</tr>'.
- '</table>',
- $this->onRight ? null : $content,
- $this->onRight ? $content : null);
+ return phutil_tag('table', array(), phutil_tag(
+ 'tr',
+ array('class' => 'inline-comment-splint'),
+ array(
+ phutil_tag('th', array()),
+ phutil_tag(
+ 'td',
+ array('class' => 'left'),
+ $this->onRight ? null : $content),
+ phutil_tag('th', array()),
+ phutil_tag(
+ 'td',
+ array('colspan' => 3, 'class' => 'right3'),
+ $this->onRight ? $content : null),
+ )));
}
private function renderInputs() {
$out = array();
foreach ($this->inputs as $input) {
list($name, $value) = $input;
$out[] = phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => $name,
'value' => $value,
));
}
return $out;
}
private function renderBody() {
$buttons = array();
$buttons[] = phutil_tag('button', array(), 'Ready');
$buttons[] = javelin_tag(
'button',
array(
'sigil' => 'inline-edit-cancel',
'class' => 'grey',
),
pht('Cancel'));
$formatting = phutil_tag(
'a',
array(
'href' => PhabricatorEnv::getDoclink(
'article/Remarkup_Reference.html'),
'tabindex' => '-1',
'target' => '_blank',
),
pht('Formatting Reference'));
$title = phutil_tag(
'div',
array(
'class' => 'differential-inline-comment-edit-title',
),
$this->title);
$body = phutil_tag(
'div',
array(
'class' => 'differential-inline-comment-edit-body',
),
$this->renderChildren());
$edit = phutil_tag(
'div',
array(
'class' => 'differential-inline-comment-edit-buttons',
),
array(
$formatting,
$buttons,
phutil_tag('div', array('style' => 'clear: both'), ''),
));
return javelin_tag(
'div',
array(
'class' => 'differential-inline-comment-edit',
'sigil' => 'differential-inline-comment',
'meta' => array(
'on_right' => $this->onRight,
'number' => $this->number,
'length' => $this->length,
),
),
array(
$title,
$body,
$edit,
));
}
}
diff --git a/src/applications/differential/view/DifferentialInlineCommentView.php b/src/applications/differential/view/DifferentialInlineCommentView.php
index f9ff40e2b7..0b491b64e0 100644
--- a/src/applications/differential/view/DifferentialInlineCommentView.php
+++ b/src/applications/differential/view/DifferentialInlineCommentView.php
@@ -1,264 +1,269 @@
<?php
final class DifferentialInlineCommentView extends AphrontView {
private $inlineComment;
private $onRight;
private $buildScaffolding;
private $handles;
private $markupEngine;
private $editable;
private $preview;
private $allowReply;
public function setInlineComment(PhabricatorInlineCommentInterface $comment) {
$this->inlineComment = $comment;
return $this;
}
public function setOnRight($on_right) {
$this->onRight = $on_right;
return $this;
}
public function setBuildScaffolding($scaffold) {
$this->buildScaffolding = $scaffold;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setEditable($editable) {
$this->editable = $editable;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setAllowReply($allow_reply) {
$this->allowReply = $allow_reply;
return $this;
}
public function render() {
$inline = $this->inlineComment;
$start = $inline->getLineNumber();
$length = $inline->getLineLength();
if ($length) {
$end = $start + $length;
$line = 'Lines '.number_format($start).'-'.number_format($end);
} else {
$line = 'Line '.number_format($start);
}
$metadata = array(
'id' => $inline->getID(),
'number' => $inline->getLineNumber(),
'length' => $inline->getLineLength(),
'on_right' => $this->onRight,
'original' => $inline->getContent(),
);
$sigil = 'differential-inline-comment';
if ($this->preview) {
$sigil = $sigil . ' differential-inline-comment-preview';
}
$content = $inline->getContent();
$handles = $this->handles;
$links = array();
$is_synthetic = false;
if ($inline->getSyntheticAuthor()) {
$is_synthetic = true;
}
$is_draft = false;
if ($inline->isDraft() && !$is_synthetic) {
$links[] = pht('Not Submitted Yet');
$is_draft = true;
}
if (!$this->preview) {
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-prev',
),
pht('Previous'));
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-next',
),
pht('Next'));
if ($this->allowReply) {
if (!$is_synthetic) {
// NOTE: No product reason why you can't reply to these, but the reply
// mechanism currently sends the inline comment ID to the server, not
// file/line information, and synthetic comments don't have an inline
// comment ID.
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-reply',
),
pht('Reply'));
}
}
}
$anchor_name = 'inline-'.$inline->getID();
if ($this->editable && !$this->preview) {
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-edit',
),
pht('Edit'));
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-delete',
),
pht('Delete'));
} else if ($this->preview) {
$links[] = javelin_tag(
'a',
array(
'meta' => array(
'anchor' => $anchor_name,
),
'sigil' => 'differential-inline-preview-jump',
),
pht('Not Visible'));
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-delete',
),
pht('Delete'));
}
if ($links) {
$links = phutil_tag(
'span',
array('class' => 'differential-inline-comment-links'),
phutil_implode_html(" \xC2\xB7 ", $links));
} else {
$links = null;
}
$content = $this->markupEngine->getOutput(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
if ($this->preview) {
$anchor = null;
} else {
$anchor = phutil_tag(
'a',
array(
'name' => $anchor_name,
'id' => $anchor_name,
'class' => 'differential-inline-comment-anchor',
),
'');
}
$classes = array(
'differential-inline-comment',
);
if ($is_draft) {
$classes[] = 'differential-inline-comment-unsaved-draft';
}
if ($is_synthetic) {
$classes[] = 'differential-inline-comment-synthetic';
}
$classes = implode(' ', $classes);
if ($is_synthetic) {
$author = $inline->getSyntheticAuthor();
} else {
$author = $handles[$inline->getAuthorPHID()]->getName();
}
+ $line = phutil_tag(
+ 'span',
+ array('class' => 'differential-inline-comment-line'),
+ $line);
+
$markup = javelin_tag(
'div',
array(
'class' => $classes,
'sigil' => $sigil,
'meta' => $metadata,
),
- hsprintf(
- '<div class="differential-inline-comment-head">'.
- '%s%s <span class="differential-inline-comment-line">%s</span> %s'.
- '</div>'.
- '<div class="differential-inline-comment-content">'.
- '<div class="phabricator-remarkup">%s</div>'.
- '</div>',
- $anchor,
- $links,
- $line,
- $author,
- $content));
+ array(
+ phutil_tag_div('differential-inline-comment-head', array(
+ $anchor,
+ $links,
+ ' ',
+ $line,
+ ' ',
+ $author,
+ )),
+ phutil_tag_div(
+ 'differential-inline-comment-content',
+ phutil_tag_div('phabricator-remarkup', $content)),
+ ));
return $this->scaffoldMarkup($markup);
}
private function scaffoldMarkup($markup) {
if (!$this->buildScaffolding) {
return $markup;
}
$left_markup = !$this->onRight ? $markup : '';
$right_markup = $this->onRight ? $markup : '';
- return hsprintf(
- '<table>'.
- '<tr class="inline">'.
- '<th></th>'.
- '<td class="left">%s</td>'.
- '<th></th>'.
- '<td class="right3" colspan="3">%s</td>'.
- '</tr>'.
- '</table>',
- $left_markup,
- $right_markup);
+ return phutil_tag('table', array(),
+ phutil_tag('tr', array(), array(
+ phutil_tag('th', array()),
+ phutil_tag('td', array('class' => 'left'), $left_markup),
+ phutil_tag('th', array()),
+ phutil_tag(
+ 'td',
+ array('colspan' => 3, 'class' => 'right3'),
+ $right_markup),
+ )));
}
}
diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php
index aec57bfe81..4321807139 100644
--- a/src/applications/differential/view/DifferentialLocalCommitsView.php
+++ b/src/applications/differential/view/DifferentialLocalCommitsView.php
@@ -1,147 +1,145 @@
<?php
final class DifferentialLocalCommitsView extends AphrontView {
private $localCommits;
public function setLocalCommits($local_commits) {
$this->localCommits = $local_commits;
return $this;
}
public function render() {
$user = $this->user;
if (!$user) {
throw new Exception("Call setUser() before render()-ing this view.");
}
$local = $this->localCommits;
if (!$local) {
return null;
}
require_celerity_resource('differential-local-commits-view-css');
$has_tree = false;
$has_local = false;
foreach ($local as $commit) {
if (idx($commit, 'tree')) {
$has_tree = true;
}
if (idx($commit, 'local')) {
$has_local = true;
}
}
$rows = array();
$highlight = true;
foreach ($local as $commit) {
if ($highlight) {
$class = 'alt';
$highlight = false;
} else {
$class = '';
$highlight = true;
}
$row = array();
if (idx($commit, 'commit')) {
$commit_hash = self::formatCommit($commit['commit']);
} else if (isset($commit['rev'])) {
$commit_hash = self::formatCommit($commit['rev']);
} else {
$commit_hash = null;
}
$row[] = phutil_tag('td', array(), $commit_hash);
if ($has_tree) {
$tree = idx($commit, 'tree');
$tree = self::formatCommit($tree);
$row[] = phutil_tag('td', array(), $tree);
}
if ($has_local) {
$local_rev = idx($commit, 'local', null);
$row[] = phutil_tag('td', array(), $local_rev);
}
$parents = idx($commit, 'parents', array());
foreach ($parents as $k => $parent) {
if (is_array($parent)) {
$parent = idx($parent, 'rev');
}
$parents[$k] = self::formatCommit($parent);
}
$parents = phutil_implode_html(phutil_tag('br'), $parents);
$row[] = phutil_tag('td', array(), $parents);
$author = nonempty(
idx($commit, 'user'),
idx($commit, 'author'));
$row[] = phutil_tag('td', array(), $author);
$message = idx($commit, 'message');
$summary = idx($commit, 'summary');
$summary = phutil_utf8_shorten($summary, 80);
$view = new AphrontMoreView();
$view->setSome($summary);
if ($message && (trim($summary) != trim($message))) {
$view->setMore(phutil_escape_html_newlines($message));
}
$row[] = phutil_tag(
'td',
array(
'class' => 'summary',
),
$view->render());
$date = nonempty(
idx($commit, 'date'),
idx($commit, 'time'));
if ($date) {
$date = phabricator_datetime($date, $user);
}
$row[] = phutil_tag('td', array(), $date);
$rows[] = phutil_tag('tr', array('class' => $class), $row);
}
$headers = array();
$headers[] = phutil_tag('th', array(), pht('Commit'));
if ($has_tree) {
$headers[] = phutil_tag('th', array(), pht('Tree'));
}
if ($has_local) {
$headers[] = phutil_tag('th', array(), pht('Local'));
}
$headers[] = phutil_tag('th', array(), pht('Parents'));
$headers[] = phutil_tag('th', array(), pht('Author'));
$headers[] = phutil_tag('th', array(), pht('Summary'));
$headers[] = phutil_tag('th', array(), pht('Date'));
$headers = phutil_tag('tr', array(), $headers);
- $content = hsprintf(
- '<div class="differential-panel">'.
- '<table class="differential-local-commits-table">%s%s</table>'.
- '</div>',
- $headers,
- phutil_implode_html("\n", $rows));
+ $content = phutil_tag_div('differential-panel', phutil_tag(
+ 'table',
+ array('class' => 'differential-local-commits-table'),
+ array($headers, phutil_implode_html("\n", $rows))));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Local Commits'))
->appendChild($content);
}
private static function formatCommit($commit) {
return substr($commit, 0, 12);
}
}
diff --git a/src/applications/differential/view/DifferentialRevisionCommentView.php b/src/applications/differential/view/DifferentialRevisionCommentView.php
index c7499ddcca..820954dfff 100644
--- a/src/applications/differential/view/DifferentialRevisionCommentView.php
+++ b/src/applications/differential/view/DifferentialRevisionCommentView.php
@@ -1,305 +1,302 @@
<?php
final class DifferentialRevisionCommentView extends AphrontView {
private $comment;
private $handles;
private $markupEngine;
private $preview;
private $inlines;
private $changesets;
private $target;
private $anchorName;
private $versusDiffID;
public function setComment($comment) {
$this->comment = $comment;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) {
$this->markupEngine = $markup_engine;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setInlineComments(array $inline_comments) {
assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
$this->inlines = $inline_comments;
return $this;
}
public function setChangesets(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
// Ship these in sorted by getSortKey() and keyed by ID... or else!
$this->changesets = $changesets;
return $this;
}
public function setTargetDiff($target) {
$this->target = $target;
return $this;
}
public function setVersusDiffID($diff_vs) {
$this->versusDiffID = $diff_vs;
return $this;
}
public function setAnchorName($anchor_name) {
$this->anchorName = $anchor_name;
return $this;
}
public function render() {
if (!$this->user) {
throw new Exception("Call setUser() before rendering!");
}
require_celerity_resource('phabricator-remarkup-css');
require_celerity_resource('differential-revision-comment-css');
$comment = $this->comment;
$action = $comment->getAction();
$action_class = 'differential-comment-action-'.$action;
$info = array();
$content = $comment->getContent();
$hide_comments = true;
if (strlen(rtrim($content))) {
$hide_comments = false;
$content = $this->markupEngine->getOutput(
$comment,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
- $content = hsprintf(
- '<div class="phabricator-remarkup">%s</div>',
- $content);
+ $content = phutil_tag_div('phabricator-remarkup', $content);
}
$inline_render = $this->renderInlineComments();
if ($inline_render) {
$hide_comments = false;
}
$author = $this->handles[$comment->getAuthorPHID()];
$author_link = $author->renderLink();
$metadata = $comment->getMetadata();
$added_reviewers = idx(
$metadata,
DifferentialComment::METADATA_ADDED_REVIEWERS,
array());
$removed_reviewers = idx(
$metadata,
DifferentialComment::METADATA_REMOVED_REVIEWERS,
array());
$added_ccs = idx(
$metadata,
DifferentialComment::METADATA_ADDED_CCS,
array());
$verb = DifferentialAction::getActionPastTenseVerb($comment->getAction());
$actions = array();
// TODO: i18n
switch ($comment->getAction()) {
case DifferentialAction::ACTION_ADDCCS:
$actions[] = hsprintf(
"%s added CCs: %s.",
$author_link,
$this->renderHandleList($added_ccs));
$added_ccs = null;
break;
case DifferentialAction::ACTION_ADDREVIEWERS:
$actions[] = hsprintf(
"%s added reviewers: %s.",
$author_link,
$this->renderHandleList($added_reviewers));
$added_reviewers = null;
break;
case DifferentialAction::ACTION_UPDATE:
$diff_id = idx($metadata, DifferentialComment::METADATA_DIFF_ID);
if ($diff_id) {
$diff_link = phutil_tag(
'a',
array(
'href' => '/D'.$comment->getRevisionID().'?id='.$diff_id,
),
'Diff #'.$diff_id);
$actions[] = hsprintf(
"%s updated this revision to %s.",
$author_link,
$diff_link);
} else {
$actions[] = hsprintf(
"%s %s this revision.",
$author_link,
$verb);
}
break;
default:
$actions[] = hsprintf(
"%s %s this revision.",
$author_link,
$verb);
break;
}
if ($added_reviewers) {
$actions[] = hsprintf(
"%s added reviewers: %s.",
$author_link,
$this->renderHandleList($added_reviewers));
}
if ($removed_reviewers) {
$actions[] = hsprintf(
"%s removed reviewers: %s.",
$author_link,
$this->renderHandleList($removed_reviewers));
}
if ($added_ccs) {
$actions[] = hsprintf(
"%s added CCs: %s.",
$author_link,
$this->renderHandleList($added_ccs));
}
foreach ($actions as $key => $action) {
$actions[$key] = phutil_tag('div', array(), $action);
}
$xaction_view = id(new PhabricatorTransactionView())
->setUser($this->user)
->setImageURI($author->getImageURI())
->setContentSource($comment->getContentSource())
->addClass($action_class)
->setActions($actions);
if ($this->preview) {
$xaction_view->setIsPreview($this->preview);
} else {
$xaction_view->setEpoch($comment->getDateCreated());
if ($this->anchorName) {
$anchor_text =
'D'.$comment->getRevisionID().
'#'.preg_replace('/^comment-/', '', $this->anchorName);
$xaction_view->setAnchor($this->anchorName, $anchor_text);
}
}
if (!$hide_comments) {
- $xaction_view->appendChild(hsprintf(
- '<div class="differential-comment-core">%s%s</div>',
- $content,
- $inline_render));
+ $xaction_view->appendChild(phutil_tag_div(
+ 'differential-comment-core',
+ array($content, $inline_render)));
}
return $xaction_view->render();
}
private function renderHandleList(array $phids) {
$result = array();
foreach ($phids as $phid) {
$result[] = $this->handles[$phid]->renderLink();
}
return phutil_implode_html(', ', $result);
}
private function renderInlineComments() {
if (!$this->inlines) {
return null;
}
$inlines = $this->inlines;
$changesets = $this->changesets;
$inlines_by_changeset = mgroup($inlines, 'getChangesetID');
$inlines_by_changeset = array_select_keys(
$inlines_by_changeset,
array_keys($this->changesets));
$view = new PhabricatorInlineSummaryView();
foreach ($inlines_by_changeset as $changeset_id => $inlines) {
$changeset = $changesets[$changeset_id];
$items = array();
foreach ($inlines as $inline) {
$on_target = ($this->target) &&
($this->target->getID() == $changeset->getDiffID());
$is_visible = false;
if ($inline->getIsNewFile()) {
// This comment is on the right side of the versus diff, and visible
// on the left side of the page.
if ($this->versusDiffID) {
if ($changeset->getDiffID() == $this->versusDiffID) {
$is_visible = true;
}
}
// This comment is on the right side of the target diff, and visible
// on the right side of the page.
if ($on_target) {
$is_visible = true;
}
} else {
// Ths comment is on the left side of the target diff, and visible
// on the left side of the page.
if (!$this->versusDiffID) {
if ($on_target) {
$is_visible = true;
}
}
// TODO: We still get one edge case wrong here, when we have a
// versus diff and the file didn't exist in the old version. The
// comment is visible because we show the left side of the target
// diff when there's no corresponding file in the versus diff, but
// we incorrectly link it off-page.
}
$item = array(
'id' => $inline->getID(),
'line' => $inline->getLineNumber(),
'length' => $inline->getLineLength(),
'content' => $this->markupEngine->getOutput(
$inline,
DifferentialInlineComment::MARKUP_FIELD_BODY),
);
if (!$is_visible) {
$diff_id = $changeset->getDiffID();
$item['where'] = '(On Diff #'.$diff_id.')';
$item['href'] =
'D'.$this->comment->getRevisionID().
'?id='.$diff_id.
'#inline-'.$inline->getID();
}
$items[] = $item;
}
$view->addCommentGroup($changeset->getFilename(), $items);
}
return $view;
}
}
diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
index c10f985b1d..e4a78860df 100644
--- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
+++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
@@ -1,327 +1,329 @@
<?php
final class DifferentialRevisionUpdateHistoryView extends AphrontView {
private $diffs = array();
private $selectedVersusDiffID;
private $selectedDiffID;
private $selectedWhitespace;
public function setDiffs(array $diffs) {
assert_instances_of($diffs, 'DifferentialDiff');
$this->diffs = $diffs;
return $this;
}
public function setSelectedVersusDiffID($id) {
$this->selectedVersusDiffID = $id;
return $this;
}
public function setSelectedDiffID($id) {
$this->selectedDiffID = $id;
return $this;
}
public function setSelectedWhitespace($whitespace) {
$this->selectedWhitespace = $whitespace;
return $this;
}
public function render() {
require_celerity_resource('differential-core-view-css');
require_celerity_resource('differential-revision-history-css');
$data = array(
array(
'name' => 'Base',
'id' => null,
'desc' => 'Base',
'age' => null,
'obj' => null,
),
);
$seq = 0;
foreach ($this->diffs as $diff) {
$data[] = array(
'name' => 'Diff '.(++$seq),
'id' => $diff->getID(),
'desc' => $diff->getDescription(),
'age' => $diff->getDateCreated(),
'obj' => $diff,
);
}
$max_id = $diff->getID();
$idx = 0;
$rows = array();
$disable = false;
$radios = array();
$last_base = null;
foreach ($data as $row) {
$diff = $row['obj'];
$name = $row['name'];
$id = $row['id'];
$old_class = null;
$new_class = null;
if ($id) {
$new_checked = ($this->selectedDiffID == $id);
$new = javelin_tag(
'input',
array(
'type' => 'radio',
'name' => 'id',
'value' => $id,
'checked' => $new_checked ? 'checked' : null,
'sigil' => 'differential-new-radio',
));
if ($new_checked) {
$new_class = " revhistory-new-now";
$disable = true;
}
} else {
$new = null;
}
if ($max_id != $id) {
$uniq = celerity_generate_unique_node_id();
$old_checked = ($this->selectedVersusDiffID == $id);
$old = phutil_tag(
'input',
array(
'type' => 'radio',
'name' => 'vs',
'value' => $id,
'id' => $uniq,
'checked' => $old_checked ? 'checked' : null,
'disabled' => $disable ? 'disabled' : null,
));
$radios[] = $uniq;
if ($old_checked) {
$old_class = " revhistory-old-now";
}
} else {
$old = null;
}
$desc = $row['desc'];
if ($row['age']) {
$age = phabricator_datetime($row['age'], $this->getUser());
} else {
$age = null;
}
if (++$idx % 2) {
$class = 'alt';
} else {
$class = null;
}
$lint_attrs = array('class' => 'revhistory-star');
$unit_attrs = array('class' => 'revhistory-star');
if ($diff) {
$lint = self::renderDiffLintStar($row['obj']);
$unit = self::renderDiffUnitStar($row['obj']);
$lint_attrs['title'] = self::getDiffLintMessage($diff);
$unit_attrs['title'] = self::getDiffUnitMessage($diff);
$base = $this->renderBaseRevision($diff);
} else {
$lint = null;
$unit = null;
$base = null;
}
if ($last_base !== null && $base !== $last_base) {
// TODO: Render some kind of notice about rebases.
}
$last_base = $base;
$id_link = phutil_tag(
'a',
array('href' => '/differential/diff/'.$id.'/'),
$id);
$rows[] = phutil_tag(
'tr',
array('class' => $class),
array(
phutil_tag('td', array('class' => 'revhistory-name'), $name),
phutil_tag('td', array('class' => 'revhistory-id'), $id_link),
phutil_tag('td', array('class' => 'revhistory-base'), $base),
phutil_tag('td', array('class' => 'revhistory-desc'), $desc),
phutil_tag('td', array('class' => 'revhistory-age'), $age),
phutil_tag('td', $lint_attrs, $lint),
phutil_tag('td', $unit_attrs, $unit),
phutil_tag('td', array('class' => 'revhistory-old'.$old_class), $old),
phutil_tag('td', array('class' => 'revhistory-new'.$new_class), $new),
));
}
Javelin::initBehavior(
'differential-diff-radios',
array(
'radios' => $radios,
));
$options = array(
DifferentialChangesetParser::WHITESPACE_IGNORE_FORCE => 'Ignore All',
DifferentialChangesetParser::WHITESPACE_IGNORE_ALL => 'Ignore Most',
DifferentialChangesetParser::WHITESPACE_IGNORE_TRAILING =>
'Ignore Trailing',
DifferentialChangesetParser::WHITESPACE_SHOW_ALL => 'Show All',
);
foreach ($options as $value => $label) {
$options[$value] = phutil_tag(
'option',
array(
'value' => $value,
'selected' => ($value == $this->selectedWhitespace)
? 'selected'
: null,
),
$label);
}
$select = phutil_tag('select', array('name' => 'whitespace'), $options);
array_unshift($rows, phutil_tag('tr', array(), array(
phutil_tag('th', array(), pht('Diff')),
phutil_tag('th', array(), pht('ID')),
phutil_tag('th', array(), pht('Base')),
phutil_tag('th', array(), pht('Description')),
phutil_tag('th', array(), pht('Created')),
phutil_tag('th', array(), pht('Lint')),
phutil_tag('th', array(), pht('Unit')),
)));
- $content = hsprintf(
- '<div class="differential-revision-history differential-panel">'.
- '<form action="#toc">'.
- '<table class="differential-revision-history-table">'.
- '%s'.
- '<tr>'.
- '<td colspan="9" class="diff-differ-submit">'.
- '<label>%s</label>'.
- '<button>%s</button>'.
- '</td>'.
- '</tr>'.
- '</table>'.
- '</form>'.
- '</div>',
- phutil_implode_html("\n", $rows),
- pht('Whitespace Changes: %s', $select),
- pht('Show Diff'));
+ $label = pht('Whitespace Changes: %s', $select);
+
+ $content = phutil_tag_div(
+ 'differential-revision-history differential-panel',
+ phutil_tag(
+ 'form',
+ array('action' => '#toc'),
+ phutil_tag(
+ 'table',
+ array('class' => 'differential-revision-history-table'), array(
+ phutil_implode_html("\n", $rows),
+ phutil_tag('tr', array(), phutil_tag(
+ 'td',
+ array('colspan' => 9, 'class' => 'diff-differ-submit'),
+ array(
+ phutil_tag('label', array(), $label),
+ phutil_tag('button', array(), pht('Show Diff')),
+ )))
+ ))));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Revision Update History'))
->appendChild($content);
}
const STAR_NONE = 'none';
const STAR_OKAY = 'okay';
const STAR_WARN = 'warn';
const STAR_FAIL = 'fail';
const STAR_SKIP = 'skip';
public static function renderDiffLintStar(DifferentialDiff $diff) {
static $map = array(
DifferentialLintStatus::LINT_NONE => self::STAR_NONE,
DifferentialLintStatus::LINT_OKAY => self::STAR_OKAY,
DifferentialLintStatus::LINT_WARN => self::STAR_WARN,
DifferentialLintStatus::LINT_FAIL => self::STAR_FAIL,
DifferentialLintStatus::LINT_SKIP => self::STAR_SKIP,
DifferentialLintStatus::LINT_POSTPONED => self::STAR_SKIP
);
$star = idx($map, $diff->getLintStatus(), self::STAR_FAIL);
return self::renderDiffStar($star);
}
public static function renderDiffUnitStar(DifferentialDiff $diff) {
static $map = array(
DifferentialUnitStatus::UNIT_NONE => self::STAR_NONE,
DifferentialUnitStatus::UNIT_OKAY => self::STAR_OKAY,
DifferentialUnitStatus::UNIT_WARN => self::STAR_WARN,
DifferentialUnitStatus::UNIT_FAIL => self::STAR_FAIL,
DifferentialUnitStatus::UNIT_SKIP => self::STAR_SKIP,
DifferentialUnitStatus::UNIT_POSTPONED => self::STAR_SKIP,
);
$star = idx($map, $diff->getUnitStatus(), self::STAR_FAIL);
return self::renderDiffStar($star);
}
public static function getDiffLintMessage(DifferentialDiff $diff) {
switch ($diff->getLintStatus()) {
case DifferentialLintStatus::LINT_NONE:
return 'No Linters Available';
case DifferentialLintStatus::LINT_OKAY:
return 'Lint OK';
case DifferentialLintStatus::LINT_WARN:
return 'Lint Warnings';
case DifferentialLintStatus::LINT_FAIL:
return 'Lint Errors';
case DifferentialLintStatus::LINT_SKIP:
return 'Lint Skipped';
case DifferentialLintStatus::LINT_POSTPONED:
return 'Lint Postponed';
}
return '???';
}
public static function getDiffUnitMessage(DifferentialDiff $diff) {
switch ($diff->getUnitStatus()) {
case DifferentialUnitStatus::UNIT_NONE:
return 'No Unit Test Coverage';
case DifferentialUnitStatus::UNIT_OKAY:
return 'Unit Tests OK';
case DifferentialUnitStatus::UNIT_WARN:
return 'Unit Test Warnings';
case DifferentialUnitStatus::UNIT_FAIL:
return 'Unit Test Errors';
case DifferentialUnitStatus::UNIT_SKIP:
return 'Unit Tests Skipped';
case DifferentialUnitStatus::UNIT_POSTPONED:
return 'Unit Tests Postponed';
}
return '???';
}
private static function renderDiffStar($star) {
$class = 'diff-star-'.$star;
return phutil_tag(
'span',
array('class' => $class),
"\xE2\x98\x85");
}
private function renderBaseRevision(DifferentialDiff $diff) {
switch ($diff->getSourceControlSystem()) {
case 'git':
$base = $diff->getSourceControlBaseRevision();
if (strpos($base, '@') === false) {
return substr($base, 0, 7);
} else {
// The diff is from git-svn
$base = explode('@', $base);
$base = last($base);
return $base;
}
case 'svn':
$base = $diff->getSourceControlBaseRevision();
$base = explode('@', $base);
$base = last($base);
return $base;
default:
return null;
}
}
}
diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php
index d52bda931b..067fe72a37 100644
--- a/src/applications/diffusion/controller/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitController.php
@@ -1,1084 +1,1084 @@
<?php
final class DiffusionCommitController extends DiffusionController {
const CHANGES_LIMIT = 100;
private $auditAuthorityPHIDs;
private $highlightedAudits;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
// This controller doesn't use blob/path stuff, just pass the dictionary
// in directly instead of using the AphrontRequest parsing mechanism.
$data['user'] = $this->getRequest()->getUser();
$drequest = DiffusionRequest::newFromDictionary($data);
$this->diffusionRequest = $drequest;
}
public function processRequest() {
$drequest = $this->getDiffusionRequest();
$request = $this->getRequest();
$user = $request->getUser();
if ($request->getStr('diff')) {
return $this->buildRawDiffResponse($drequest);
}
$callsign = $drequest->getRepository()->getCallsign();
$content = array();
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
$crumbs = $this->buildCrumbs(array(
'commit' => true,
));
if (!$commit) {
$exists = $this->callConduitWithDiffusionRequest(
'diffusion.existsquery',
array('commit' => $drequest->getCommit()));
if (!$exists) {
return new Aphront404Response();
}
$error = id(new AphrontErrorView())
->setTitle(pht('Commit Still Parsing'))
->appendChild(
pht(
'Failed to load the commit because the commit has not been '.
'parsed yet.'));
return $this->buildApplicationPage(
array(
$crumbs,
$error,
),
array(
'title' => pht('Commit Still Parsing'),
));
}
$commit_data = $drequest->loadCommitData();
$commit->attachCommitData($commit_data);
$top_anchor = id(new PhabricatorAnchorView())
->setAnchorName('top')
->setNavigationMarker(true);
$audit_requests = id(new PhabricatorAuditQuery())
->withCommitPHIDs(array($commit->getPHID()))
->execute();
$this->auditAuthorityPHIDs =
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
$changesets = null;
if ($is_foreign) {
$subpath = $commit_data->getCommitDetail('svn-subpath');
$error_panel = new AphrontErrorView();
$error_panel->setTitle(pht('Commit Not Tracked'));
$error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$error_panel->appendChild(
pht("This Diffusion repository is configured to track only one ".
"subdirectory of the entire Subversion repository, and this commit ".
"didn't affect the tracked subdirectory ('%s'), so no ".
"information is available.", $subpath));
$content[] = $error_panel;
$content[] = $top_anchor;
} else {
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $user);
require_celerity_resource('diffusion-commit-view-css');
require_celerity_resource('phabricator-remarkup-css');
$parents = $this->callConduitWithDiffusionRequest(
'diffusion.commitparentsquery',
array('commit' => $drequest->getCommit()));
$headsup_view = id(new PHUIHeaderView())
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')));
$headsup_actions = $this->renderHeadsupActionList($commit, $repository);
$commit_properties = $this->loadCommitProperties(
$commit,
$commit_data,
$parents,
$audit_requests);
$property_list = id(new PHUIPropertyListView())
->setHasKeyboardShortcuts(true)
->setUser($user)
->setObject($commit);
foreach ($commit_properties as $key => $value) {
$property_list->addProperty($key, $value);
}
$message = $commit_data->getCommitMessage();
$revision = $commit->getCommitIdentifier();
$message = $this->linkBugtraq($message);
$message = $engine->markupText($message);
$property_list->invokeWillRenderEvent();
$property_list->setActionList($headsup_actions);
$detail_list = new PHUIPropertyListView();
$detail_list->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$detail_list->addTextContent(
phutil_tag(
'div',
array(
'class' => 'diffusion-commit-message phabricator-remarkup',
),
$message));
$content[] = $top_anchor;
$object_box = id(new PHUIObjectBoxView())
->setHeader($headsup_view)
->addPropertyList($property_list)
->addPropertyList($detail_list);
$content[] = $object_box;
}
$content[] = $this->buildComments($commit);
$hard_limit = 1000;
if ($commit->isImported()) {
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$change_query->setLimit($hard_limit + 1);
$changes = $change_query->loadChanges();
} else {
$changes = array();
}
$was_limited = (count($changes) > $hard_limit);
if ($was_limited) {
$changes = array_slice($changes, 0, $hard_limit);
}
$content[] = $this->buildMergesTable($commit);
// TODO: This is silly, but the logic to figure out which audits are
// highlighted currently lives in PhabricatorAuditListView. Refactor this
// to be less goofy.
$highlighted_audits = id(new PhabricatorAuditListView())
->setAudits($audit_requests)
->setAuthorityPHIDs($this->auditAuthorityPHIDs)
->setUser($user)
->setCommits(array($commit->getPHID() => $commit))
->getHighlightedAudits();
$owners_paths = array();
if ($highlighted_audits) {
$packages = id(new PhabricatorOwnersPackage())->loadAllWhere(
'phid IN (%Ls)',
mpull($highlighted_audits, 'getAuditorPHID'));
if ($packages) {
$owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere(
'repositoryPHID = %s AND packageID IN (%Ld)',
$repository->getPHID(),
mpull($packages, 'getID'));
}
}
$change_table = new DiffusionCommitChangeTableView();
$change_table->setDiffusionRequest($drequest);
$change_table->setPathChanges($changes);
$change_table->setOwnersPaths($owners_paths);
$count = count($changes);
$bad_commit = null;
if ($count == 0) {
$bad_commit = queryfx_one(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT * FROM %T WHERE fullCommitName = %s',
PhabricatorRepository::TABLE_BADCOMMIT,
'r'.$callsign.$commit->getCommitIdentifier());
}
if ($bad_commit) {
$content[] = $this->renderStatusMessage(
pht('Bad Commit'),
$bad_commit['description']);
} else if ($is_foreign) {
// Don't render anything else.
} else if (!$commit->isImported()) {
$content[] = $this->renderStatusMessage(
pht('Still Importing...'),
pht(
'This commit is still importing. Changes will be visible once '.
'the import finishes.'));
} else if (!count($changes)) {
$content[] = $this->renderStatusMessage(
pht('Empty Commit'),
pht(
'This commit is empty and does not affect any paths.'));
} else if ($was_limited) {
$content[] = $this->renderStatusMessage(
pht('Enormous Commit'),
pht(
'This commit is enormous, and affects more than %d files. '.
'Changes are not shown.',
$hard_limit));
} else {
// The user has clicked "Show All Changes", and we should show all the
// changes inline even if there are more than the soft limit.
$show_all_details = $request->getBool('show_all');
$change_panel = new AphrontPanelView();
$change_panel->setHeader("Changes (".number_format($count).")");
$change_panel->setID('toc');
if ($count > self::CHANGES_LIMIT && !$show_all_details) {
$show_all_button = phutil_tag(
'a',
array(
'class' => 'button green',
'href' => '?show_all=true',
),
pht('Show All Changes'));
$warning_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle('Very Large Commit')
->appendChild(
pht("This commit is very large. Load each file individually."));
$change_panel->appendChild($warning_view);
$change_panel->addButton($show_all_button);
}
$change_panel->appendChild($change_table);
$change_panel->setNoBackground();
$content[] = $change_panel;
$changesets = DiffusionPathChange::convertToDifferentialChangesets(
$changes);
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$vcs_supports_directory_changes = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$vcs_supports_directory_changes = false;
break;
default:
throw new Exception("Unknown VCS.");
}
$references = array();
foreach ($changesets as $key => $changeset) {
$file_type = $changeset->getFileType();
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
if (!$vcs_supports_directory_changes) {
unset($changesets[$key]);
continue;
}
}
$references[$key] = $drequest->generateURI(
array(
'action' => 'rendering-ref',
'path' => $changeset->getFilename(),
));
}
// TODO: Some parts of the views still rely on properties of the
// DifferentialChangeset. Make the objects ephemeral to make sure we don't
// accidentally save them, and then set their ID to the appropriate ID for
// this application (the path IDs).
$path_ids = array_flip(mpull($changes, 'getPath'));
foreach ($changesets as $changeset) {
$changeset->makeEphemeral();
$changeset->setID($path_ids[$changeset->getFilename()]);
}
if ($count <= self::CHANGES_LIMIT || $show_all_details) {
$visible_changesets = $changesets;
} else {
$visible_changesets = array();
$inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
'commitPHID = %s AND (auditCommentID IS NOT NULL OR authorPHID = %s)',
$commit->getPHID(),
$user->getPHID());
$path_ids = mpull($inlines, null, 'getPathID');
foreach ($changesets as $key => $changeset) {
if (array_key_exists($changeset->getID(), $path_ids)) {
$visible_changesets[$key] = $changeset;
}
}
}
$change_list_title = DiffusionView::nameCommit(
$repository,
$commit->getCommitIdentifier());
$change_list = new DifferentialChangesetListView();
$change_list->setTitle($change_list_title);
$change_list->setChangesets($changesets);
$change_list->setVisibleChangesets($visible_changesets);
$change_list->setRenderingReferences($references);
$change_list->setRenderURI('/diffusion/'.$callsign.'/diff/');
$change_list->setRepository($repository);
$change_list->setUser($user);
// pick the first branch for "Browse in Diffusion" View Option
$branches = $commit_data->getCommitDetail('seenOnBranches', array());
$first_branch = reset($branches);
$change_list->setBranch($first_branch);
$change_list->setStandaloneURI(
'/diffusion/'.$callsign.'/diff/');
$change_list->setRawFileURIs(
// TODO: Implement this, somewhat tricky if there's an octopus merge
// or whatever?
null,
'/diffusion/'.$callsign.'/diff/?view=r');
$change_list->setInlineCommentControllerURI(
'/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/');
$change_references = array();
foreach ($changesets as $key => $changeset) {
$change_references[$changeset->getID()] = $references[$key];
}
$change_table->setRenderingReferences($change_references);
$content[] = $change_list->render();
}
$content[] = $this->renderAddCommentPanel($commit, $audit_requests);
$commit_id = 'r'.$callsign.$commit->getCommitIdentifier();
$short_name = DiffusionView::nameCommit(
$repository,
$commit->getCommitIdentifier());
$prefs = $user->loadPreferences();
$pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
$pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED;
$show_filetree = $prefs->getPreference($pref_filetree);
$collapsed = $prefs->getPreference($pref_collapse);
if ($changesets && $show_filetree) {
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setAnchorName('top')
->setTitle($short_name)
->setBaseURI(new PhutilURI('/'.$commit_id))
->build($changesets)
->setCrumbs($crumbs)
->setCollapsed((bool)$collapsed)
->appendChild($content);
$content = $nav;
} else {
$content = array($crumbs, $content);
}
return $this->buildApplicationPage(
$content,
array(
'title' => $commit_id,
'pageObjects' => array($commit->getPHID()),
));
}
private function loadCommitProperties(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data,
array $parents,
array $audit_requests) {
assert_instances_of($parents, 'PhabricatorRepositoryCommit');
$user = $this->getRequest()->getUser();
$commit_phid = $commit->getPHID();
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($commit_phid))
->withEdgeTypes(array(
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK,
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT,
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV,
));
$edges = $edge_query->execute();
$task_phids = array_keys(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK]);
$proj_phids = array_keys(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT]);
$revision_phid = key(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV]);
$phids = $edge_query->getDestinationPHIDs(array($commit_phid));
if ($data->getCommitDetail('authorPHID')) {
$phids[] = $data->getCommitDetail('authorPHID');
}
if ($data->getCommitDetail('reviewerPHID')) {
$phids[] = $data->getCommitDetail('reviewerPHID');
}
if ($data->getCommitDetail('committerPHID')) {
$phids[] = $data->getCommitDetail('committerPHID');
}
if ($parents) {
foreach ($parents as $parent) {
$phids[] = $parent->getPHID();
}
}
$handles = array();
if ($phids) {
$handles = $this->loadViewerHandles($phids);
}
$props = array();
if ($commit->getAuditStatus()) {
$status = PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus());
$tag = id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setName($status);
switch ($commit->getAuditStatus()) {
case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT:
$tag->setBackgroundColor(PhabricatorTagView::COLOR_ORANGE);
break;
case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED:
$tag->setBackgroundColor(PhabricatorTagView::COLOR_RED);
break;
case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED:
$tag->setBackgroundColor(PhabricatorTagView::COLOR_BLUE);
break;
case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED:
$tag->setBackgroundColor(PhabricatorTagView::COLOR_GREEN);
break;
}
$props['Status'] = $tag;
}
if ($audit_requests) {
$user_requests = array();
$other_requests = array();
foreach ($audit_requests as $audit_request) {
if ($audit_request->isUser()) {
$user_requests[] = $audit_request;
} else {
$other_requests[] = $audit_request;
}
}
if ($user_requests) {
$props['Auditors'] = $this->renderAuditStatusView(
$user_requests);
}
if ($other_requests) {
$props['Project/Package Auditors'] = $this->renderAuditStatusView(
$other_requests);
}
}
$props['Committed'] = phabricator_datetime($commit->getEpoch(), $user);
$author_phid = $data->getCommitDetail('authorPHID');
if ($data->getCommitDetail('authorPHID')) {
$props['Author'] = $handles[$author_phid]->renderLink();
} else {
$props['Author'] = $data->getAuthorName();
}
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
if ($reviewer_phid) {
$props['Reviewer'] = $handles[$reviewer_phid]->renderLink();
}
$committer = $data->getCommitDetail('committer');
if ($committer) {
$committer_phid = $data->getCommitDetail('committerPHID');
if ($data->getCommitDetail('committerPHID')) {
$props['Committer'] = $handles[$committer_phid]->renderLink();
} else {
$props['Committer'] = $committer;
}
}
if ($revision_phid) {
$props['Differential Revision'] = $handles[$revision_phid]->renderLink();
}
if ($parents) {
$parent_links = array();
foreach ($parents as $parent) {
$parent_links[] = $handles[$parent->getPHID()]->renderLink();
}
$props['Parents'] = phutil_implode_html(" \xC2\xB7 ", $parent_links);
}
$request = $this->getDiffusionRequest();
$props['Branches'] = phutil_tag(
'span',
array(
'id' => 'commit-branches',
),
pht('Unknown'));
$props['Tags'] = phutil_tag(
'span',
array(
'id' => 'commit-tags',
),
pht('Unknown'));
$callsign = $request->getRepository()->getCallsign();
$root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
Javelin::initBehavior(
'diffusion-commit-branches',
array(
$root.'/branches/' => 'commit-branches',
$root.'/tags/' => 'commit-tags',
));
$refs = $this->buildRefs($request);
if ($refs) {
$props['References'] = $refs;
}
if ($task_phids) {
$task_list = array();
foreach ($task_phids as $phid) {
$task_list[] = $handles[$phid]->renderLink();
}
$task_list = phutil_implode_html(phutil_tag('br'), $task_list);
$props['Tasks'] = $task_list;
}
if ($proj_phids) {
$proj_list = array();
foreach ($proj_phids as $phid) {
$proj_list[] = $handles[$phid]->renderLink();
}
$proj_list = phutil_implode_html(phutil_tag('br'), $proj_list);
$props['Projects'] = $proj_list;
}
return $props;
}
private function buildComments(PhabricatorRepositoryCommit $commit) {
$user = $this->getRequest()->getUser();
$comments = id(new PhabricatorAuditComment())->loadAllWhere(
'targetPHID = %s ORDER BY dateCreated ASC',
$commit->getPHID());
$inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
'commitPHID = %s AND auditCommentID IS NOT NULL',
$commit->getPHID());
$path_ids = mpull($inlines, 'getPathID');
$path_map = array();
if ($path_ids) {
$path_map = id(new DiffusionPathQuery())
->withPathIDs($path_ids)
->execute();
$path_map = ipull($path_map, 'path', 'id');
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
foreach ($comments as $comment) {
$engine->addObject(
$comment,
PhabricatorAuditComment::MARKUP_FIELD_BODY);
}
foreach ($inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$view = new DiffusionCommentListView();
$view->setMarkupEngine($engine);
$view->setUser($user);
$view->setComments($comments);
$view->setInlineComments($inlines);
$view->setPathMap($path_map);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return $view;
}
private function renderAddCommentPanel(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$request = $this->getRequest();
$user = $request->getUser();
if (!$user->isLoggedIn()) {
return id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setRequestURI($request->getRequestURI());
}
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'diffusion-audit-'.$commit->getID());
if ($draft) {
$draft = $draft->getDraft();
} else {
$draft = null;
}
$actions = $this->getAuditActions($commit, $audit_requests);
$form = id(new AphrontFormView())
->setUser($user)
->setAction('/audit/addcomment/')
->addHiddenInput('commit', $commit->getPHID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Action'))
->setName('action')
->setID('audit-action')
->setOptions($actions))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Add Auditors'))
->setName('auditors')
->setControlID('add-auditors')
->setControlStyle('display: none')
->setID('add-auditors-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Add CCs'))
->setName('ccs')
->setControlID('add-ccs')
->setControlStyle('display: none')
->setID('add-ccs-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Comments'))
->setName('content')
->setValue($draft)
->setID('audit-content')
->setUser($user))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? pht('Submit') : pht('Cook the Books')));
$header = new PHUIHeaderView();
$header->setHeader(
$is_serious ? pht('Audit Commit') : pht('Creative Accounting'));
require_celerity_resource('phabricator-transaction-view-css');
Javelin::initBehavior(
'differential-add-reviewers-and-ccs',
array(
'dynamic' => array(
'add-auditors-tokenizer' => array(
'actions' => array('add_auditors' => 1),
'src' => '/typeahead/common/users/',
'row' => 'add-auditors',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user name...'),
),
'add-ccs-tokenizer' => array(
'actions' => array('add_ccs' => 1),
'src' => '/typeahead/common/mailable/',
'row' => 'add-ccs',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user or mailing list...'),
),
),
'select' => 'audit-action',
));
Javelin::initBehavior('differential-feedback-preview', array(
'uri' => '/audit/preview/'.$commit->getID().'/',
'preview' => 'audit-preview',
'content' => 'audit-content',
'action' => 'audit-action',
'previewTokenizers' => array(
'auditors' => 'add-auditors-tokenizer',
'ccs' => 'add-ccs-tokenizer',
),
'inline' => 'inline-comment-preview',
'inlineuri' => '/diffusion/inline/preview/'.$commit->getPHID().'/',
));
- $preview_panel = hsprintf(
- '<div class="aphront-panel-preview aphront-panel-flush">
- <div id="audit-preview">
- <div class="aphront-panel-preview-loading-text">
- Loading preview...
- </div>
- </div>
- <div id="inline-comment-preview">
- </div>
- </div>');
+ $loading = phutil_tag_div(
+ 'aphront-panel-preview-loading-text',
+ pht('Loading preview...'));
+
+ $preview_panel = phutil_tag_div(
+ 'aphront-panel-preview aphront-panel-flush',
+ array(
+ phutil_tag('div', array('id' => 'audit-preview'), $loading),
+ phutil_tag('div', array('id' => 'inline-comment-preview'))
+ ));
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
+ $anchor = id(new PhabricatorAnchorView())
+ ->setAnchorName('comment')
+ ->setNavigationMarker(true)
+ ->render();
+
$comment_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($form);
return phutil_tag(
'div',
array(
'id' => $pane_id,
),
- hsprintf(
- '<div class="differential-add-comment-panel">%s%s%s</div>',
- id(new PhabricatorAnchorView())
- ->setAnchorName('comment')
- ->setNavigationMarker(true)
- ->render(),
- $comment_box,
- $preview_panel));
+ phutil_tag_div(
+ 'differential-add-comment-panel',
+ array($anchor, $comment_box, $preview_panel)));
}
/**
* Return a map of available audit actions for rendering into a <select />.
* This shows the user valid actions, and does not show nonsense/invalid
* actions (like closing an already-closed commit, or resigning from a commit
* you have no association with).
*/
private function getAuditActions(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$user_is_author = ($commit->getAuthorPHID() == $user->getPHID());
$user_request = null;
foreach ($audit_requests as $audit_request) {
if ($audit_request->getAuditorPHID() == $user->getPHID()) {
$user_request = $audit_request;
break;
}
}
$actions = array();
$actions[PhabricatorAuditActionConstants::COMMENT] = true;
$actions[PhabricatorAuditActionConstants::ADD_CCS] = true;
$actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
// We allow you to accept your own commits. A use case here is that you
// notice an issue with your own commit and "Raise Concern" as an indicator
// to other auditors that you're on top of the issue, then later resolve it
// and "Accept". You can not accept on behalf of projects or packages,
// however.
$actions[PhabricatorAuditActionConstants::ACCEPT] = true;
$actions[PhabricatorAuditActionConstants::CONCERN] = true;
// To resign, a user must have authority on some request and not be the
// commit's author.
if (!$user_is_author) {
$may_resign = false;
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
foreach ($audit_requests as $request) {
if (empty($authority_map[$request->getAuditorPHID()])) {
continue;
}
$may_resign = true;
break;
}
// If the user has already resigned, don't show "Resign...".
$status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
if ($user_request) {
if ($user_request->getAuditStatus() == $status_resigned) {
$may_resign = false;
}
}
if ($may_resign) {
$actions[PhabricatorAuditActionConstants::RESIGN] = true;
}
}
$status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
$concern_raised = ($commit->getAuditStatus() == $status_concern);
$can_close_option = PhabricatorEnv::getEnvConfig(
'audit.can-author-close-audit');
if ($can_close_option && $user_is_author && $concern_raised) {
$actions[PhabricatorAuditActionConstants::CLOSE] = true;
}
foreach ($actions as $constant => $ignored) {
$actions[$constant] =
PhabricatorAuditActionConstants::getActionName($constant);
}
return $actions;
}
private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
$drequest = $this->getDiffusionRequest();
$limit = 50;
$merges = array();
try {
$merges = $this->callConduitWithDiffusionRequest(
'diffusion.mergedcommitsquery',
array(
'commit' => $drequest->getCommit(),
'limit' => $limit + 1));
} catch (ConduitException $ex) {
if ($ex->getMessage() != 'ERR-UNSUPPORTED-VCS') {
throw $ex;
}
}
if (!$merges) {
return null;
}
$caption = null;
if (count($merges) > $limit) {
$merges = array_slice($merges, 0, $limit);
$caption =
"This commit merges more than {$limit} changes. Only the first ".
"{$limit} are shown.";
}
$history_table = new DiffusionHistoryTableView();
$history_table->setUser($this->getRequest()->getUser());
$history_table->setDiffusionRequest($drequest);
$history_table->setHistory($merges);
$history_table->loadRevisions();
$phids = $history_table->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$history_table->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader(pht('Merged Changes'));
$panel->setCaption($caption);
$panel->appendChild($history_table);
$panel->setNoBackground();
return $panel;
}
private function renderHeadsupActionList(
PhabricatorRepositoryCommit $commit,
PhabricatorRepository $repository) {
$request = $this->getRequest();
$user = $request->getUser();
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObject($commit)
->setObjectURI($request->getRequestURI());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$commit,
PhabricatorPolicyCapability::CAN_EDIT);
$uri = '/diffusion/'.$repository->getCallsign().'/commit/'.
$commit->getCommitIdentifier().'/edit/';
$action = id(new PhabricatorActionView())
->setName(pht('Edit Commit'))
->setHref($uri)
->setIcon('edit')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
$actions->addAction($action);
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$maniphest = 'PhabricatorApplicationManiphest';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$action = id(new PhabricatorActionView())
->setName(pht('Edit Maniphest Tasks'))
->setIcon('attach')
->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/')
->setWorkflow(true)
->setDisabled(!$can_edit);
$actions->addAction($action);
}
$action = id(new PhabricatorActionView())
->setName(pht('Download Raw Diff'))
->setHref($request->getRequestURI()->alter('diff', true))
->setIcon('download');
$actions->addAction($action);
return $actions;
}
private function buildRefs(DiffusionRequest $request) {
// this is git-only, so save a conduit round trip and just get out of
// here if the repository isn't git
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$repository = $request->getRepository();
if ($repository->getVersionControlSystem() != $type_git) {
return null;
}
$results = $this->callConduitWithDiffusionRequest(
'diffusion.refsquery',
array('commit' => $request->getCommit()));
$ref_links = array();
foreach ($results as $ref_data) {
$ref_links[] = phutil_tag('a',
array('href' => $ref_data['href']),
$ref_data['ref']);
}
return phutil_implode_html(', ', $ref_links);
}
private function buildRawDiffResponse(DiffusionRequest $drequest) {
$raw_diff = $this->callConduitWithDiffusionRequest(
'diffusion.rawdiffquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath()));
$file = PhabricatorFile::buildFromFileDataOrHash(
$raw_diff,
array(
'name' => $drequest->getCommit().'.diff',
));
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
private function renderAuditStatusView(array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$phids = mpull($audit_requests, 'getAuditorPHID');
$this->loadHandles($phids);
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
$view = new PHUIStatusListView();
foreach ($audit_requests as $request) {
$item = new PHUIStatusItemView();
switch ($request->getAuditStatus()) {
case PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED:
$item->setIcon('open-blue', pht('Commented'));
break;
case PhabricatorAuditStatusConstants::AUDIT_REQUIRED:
$item->setIcon('warning-blue', pht('Audit Required'));
break;
case PhabricatorAuditStatusConstants::CONCERNED:
$item->setIcon('reject-red', pht('Concern Raised'));
break;
case PhabricatorAuditStatusConstants::ACCEPTED:
$item->setIcon('accept-green', pht('Accepted'));
break;
case PhabricatorAuditStatusConstants::AUDIT_REQUESTED:
$item->setIcon('warning-dark', pht('Audit Requested'));
break;
case PhabricatorAuditStatusConstants::RESIGNED:
$item->setIcon('open-dark', pht('Resigned'));
break;
case PhabricatorAuditStatusConstants::CLOSED:
$item->setIcon('accept-blue', pht('Closed'));
break;
case PhabricatorAuditStatusConstants::CC:
$item->setIcon('info-dark', pht('Subscribed'));
break;
default:
$item->setIcon(
'question-dark',
pht('%s?', $request->getAuditStatus()));
break;
}
$note = array();
foreach ($request->getAuditReasons() as $reason) {
$note[] = phutil_tag('div', array(), $reason);
}
$item->setNote($note);
$auditor_phid = $request->getAuditorPHID();
$target = $this->getHandle($auditor_phid)->renderLink();
$item->setTarget($target);
if (isset($authority_map[$auditor_phid])) {
$item->setHighlighted(true);
}
$view->addItem($item);
}
return $view;
}
private function linkBugtraq($corpus) {
$url = PhabricatorEnv::getEnvConfig('bugtraq.url');
if (!strlen($url)) {
return $corpus;
}
$regexes = PhabricatorEnv::getEnvConfig('bugtraq.logregex');
if (!$regexes) {
return $corpus;
}
$parser = id(new PhutilBugtraqParser())
->setBugtraqPattern("[[ {$url} | %BUGID% ]]")
->setBugtraqCaptureExpression(array_shift($regexes));
$select = array_shift($regexes);
if ($select) {
$parser->setBugtraqSelectExpression($select);
}
return $parser->processCorpus($corpus);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php
index 3280f60c26..061258b24e 100644
--- a/src/applications/diffusion/controller/DiffusionLintController.php
+++ b/src/applications/diffusion/controller/DiffusionLintController.php
@@ -1,352 +1,347 @@
<?php
final class DiffusionLintController extends DiffusionController {
public function shouldAllowPublic() {
return true;
}
public function processRequest() {
$request = $this->getRequest();
$user = $this->getRequest()->getUser();
$drequest = $this->diffusionRequest;
if ($request->getStr('lint') !== null) {
$controller = new DiffusionLintDetailsController($request);
$controller->setDiffusionRequest($drequest);
$controller->setCurrentApplication($this->getCurrentApplication());
return $this->delegateToController($controller);
}
$owners = array();
if (!$drequest) {
if (!$request->getArr('owner')) {
$owners = array($user->getPHID());
} else {
$owners = array(head($request->getArr('owner')));
}
$owner_handles = $this->loadViewerHandles($owners);
}
$codes = $this->loadLintCodes($owners);
if ($codes && !$drequest) {
// TODO: Build some real Query classes for this stuff.
$branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
'id IN (%Ld)',
array_unique(ipull($codes, 'branchID')));
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($user)
->withIDs(mpull($branches, 'getRepositoryID'))
->execute();
$drequests = array();
foreach ($branches as $id => $branch) {
if (empty($repositories[$branch->getRepositoryID()])) {
continue;
}
$drequests[$id] = DiffusionRequest::newFromDictionary(array(
'user' => $user,
'repository' => $repositories[$branch->getRepositoryID()],
'branch' => $branch->getName(),
));
}
}
$rows = array();
$total = 0;
foreach ($codes as $code) {
if (!$this->diffusionRequest) {
$drequest = idx($drequests, $code['branchID']);
}
if (!$drequest) {
continue;
}
$total += $code['n'];
+ $href_lint = $drequest->generateURI(array(
+ 'action' => 'lint',
+ 'lint' => $code['code'],
+ ));
+ $href_browse = $drequest->generateURI(array(
+ 'action' => 'browse',
+ 'lint' => $code['code'],
+ ));
+ $href_repo = $drequest->generateURI(array('action' => 'lint'));
+
$rows[] = array(
- hsprintf(
- '<a href="%s">%s</a>',
- $drequest->generateURI(array(
- 'action' => 'lint',
- 'lint' => $code['code'],
- )),
- $code['n']),
- hsprintf(
- '<a href="%s">%s</a>',
- $drequest->generateURI(array(
- 'action' => 'browse',
- 'lint' => $code['code'],
- )),
- $code['files']),
- hsprintf(
- '<a href="%s">%s</a>',
- $drequest->generateURI(array('action' => 'lint')),
- $drequest->getCallsign()),
+ phutil_tag('a', array('href' => $href_lint), $code['n']),
+ phutil_tag('a', array('href' => $href_browse), $code['files']),
+ phutil_tag('a', array('href' => $href_repo), $drequest->getCallsign()),
ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']),
$code['code'],
$code['maxName'],
$code['maxDescription'],
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(array(
pht('Problems'),
pht('Files'),
pht('Repository'),
pht('Severity'),
pht('Code'),
pht('Name'),
pht('Example'),
))
->setColumnVisibility(array(true, true, !$this->diffusionRequest))
->setColumnClasses(array('n', 'n', '', '', 'pri', '', ''));
$content = array();
$link = null;
if (!$this->diffusionRequest) {
$form = id(new AphrontFormView())
->setUser($user)
->setMethod('GET')
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setLimit(1)
->setName('owner')
->setLabel(pht('Owner'))
->setValue($owner_handles))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Filter'));
$content[] = id(new AphrontListFilterView())->appendChild($form);
}
$content[] = id(new AphrontPanelView())
->setNoBackground(true)
->setCaption($link)
->appendChild($table);
$title = array('Lint');
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'lint',
));
if ($this->diffusionRequest) {
$title[] = $drequest->getCallsign();
} else {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('All Lint')));
}
if ($this->diffusionRequest) {
$branch = $drequest->loadBranch();
$header = id(new PHUIHeaderView())
->setHeader($this->renderPathLinks($drequest, 'lint'))
->setUser($user)
->setPolicyObject($drequest->getRepository());
$actions = $this->buildActionView($drequest);
$properties = $this->buildPropertyView(
$drequest,
$branch,
$total,
$actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
} else {
$object_box = null;
}
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$content,
),
array(
'title' => $title,
));
}
private function loadLintCodes(array $owner_phids) {
$drequest = $this->diffusionRequest;
$conn = id(new PhabricatorRepository())->establishConnection('r');
$where = array('1 = 1');
if ($drequest) {
$branch = $drequest->loadBranch();
if (!$branch) {
return array();
}
$where[] = qsprintf($conn, 'branchID = %d', $branch->getID());
if ($drequest->getPath() != '') {
$path = '/'.$drequest->getPath();
$is_dir = (substr($path, -1) == '/');
$where[] = ($is_dir
? qsprintf($conn, 'path LIKE %>', $path)
: qsprintf($conn, 'path = %s', $path));
}
}
if ($owner_phids) {
$or = array();
$or[] = qsprintf($conn, 'authorPHID IN (%Ls)', $owner_phids);
$paths = array();
$packages = id(new PhabricatorOwnersOwner())
->loadAllWhere('userPHID IN (%Ls)', $owner_phids);
if ($packages) {
$paths = id(new PhabricatorOwnersPath())->loadAllWhere(
'packageID IN (%Ld)',
mpull($packages, 'getPackageID'));
}
if ($paths) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getRequest()->getUser())
->withPHIDs(mpull($paths, 'getRepositoryPHID'))
->execute();
$repositories = mpull($repositories, 'getID', 'getPHID');
$branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
'repositoryID IN (%Ld)',
$repositories);
$branches = mgroup($branches, 'getRepositoryID');
}
foreach ($paths as $path) {
$branch = idx(
$branches,
idx(
$repositories,
$path->getRepositoryPHID()));
if ($branch) {
$condition = qsprintf(
$conn,
'(branchID IN (%Ld) AND path LIKE %>)',
array_keys($branch),
$path->getPath());
if ($path->getExcluded()) {
$where[] = 'NOT '.$condition;
} else {
$or[] = $condition;
}
}
}
$where[] = '('.implode(' OR ', $or).')';
}
return queryfx_all(
$conn,
'SELECT
branchID,
code,
MAX(severity) AS maxSeverity,
MAX(name) AS maxName,
MAX(description) AS maxDescription,
COUNT(DISTINCT path) AS files,
COUNT(*) AS n
FROM %T
WHERE %Q
GROUP BY branchID, code
ORDER BY n DESC',
PhabricatorRepository::TABLE_LINTMESSAGE,
implode(' AND ', $where));
}
protected function buildActionView(DiffusionRequest $drequest) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$list_uri = $drequest->generateURI(
array(
'action' => 'lint',
'lint' => '',
));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('View As List'))
->setHref($list_uri)
->setIcon('transcript'));
$history_uri = $drequest->generateURI(
array(
'action' => 'history',
));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('View History'))
->setHref($history_uri)
->setIcon('history'));
$browse_uri = $drequest->generateURI(
array(
'action' => 'browse',
));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Browse Content'))
->setHref($browse_uri)
->setIcon('file'));
return $view;
}
protected function buildPropertyView(
DiffusionRequest $drequest,
PhabricatorRepositoryBranch $branch,
$total,
PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActions($actions);
$callsign = $drequest->getRepository()->getCallsign();
$lint_commit = $branch->getLintCommit();
$view->addProperty(
pht('Lint Commit'),
phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'commit',
'commit' => $lint_commit,
)),
),
$drequest->getRepository()->formatCommitName($lint_commit)));
$view->addProperty(
pht('Total Messages'),
pht('%s', new PhutilNumber($total)));
return $view;
}
}
diff --git a/src/applications/diffusion/controller/DiffusionLintDetailsController.php b/src/applications/diffusion/controller/DiffusionLintDetailsController.php
index d9437e6f12..6225fd68bb 100644
--- a/src/applications/diffusion/controller/DiffusionLintDetailsController.php
+++ b/src/applications/diffusion/controller/DiffusionLintDetailsController.php
@@ -1,140 +1,140 @@
<?php
final class DiffusionLintDetailsController extends DiffusionController {
public function processRequest() {
$limit = 500;
$offset = $this->getRequest()->getInt('offset', 0);
$drequest = $this->getDiffusionRequest();
$branch = $drequest->loadBranch();
$messages = $this->loadLintMessages($branch, $limit, $offset);
$is_dir = (substr('/'.$drequest->getPath(), -1) == '/');
$authors = $this->loadViewerHandles(ipull($messages, 'authorPHID'));
$rows = array();
foreach ($messages as $message) {
- $path = hsprintf(
- '<a href="%s">%s</a>',
- $drequest->generateURI(array(
+ $path = phutil_tag(
+ 'a',
+ array('href' => $drequest->generateURI(array(
'action' => 'lint',
'path' => $message['path'],
- )),
+ ))),
substr($message['path'], strlen($drequest->getPath()) + 1));
- $line = hsprintf(
- '<a href="%s">%s</a>',
- $drequest->generateURI(array(
+ $line = phutil_tag(
+ 'a',
+ array('href' => $drequest->generateURI(array(
'action' => 'browse',
'path' => $message['path'],
'line' => $message['line'],
'commit' => $branch->getLintCommit(),
- )),
+ ))),
$message['line']);
$author = $message['authorPHID'];
if ($author && $authors[$author]) {
$author = $authors[$author]->renderLink();
}
$rows[] = array(
$path,
$line,
$author,
ArcanistLintSeverity::getStringForSeverity($message['severity']),
$message['name'],
$message['description'],
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(array(
pht('Path'),
pht('Line'),
pht('Author'),
pht('Severity'),
pht('Name'),
pht('Description'),
))
->setColumnClasses(array('', 'n'))
->setColumnVisibility(array($is_dir));
$content = array();
$pager = id(new AphrontPagerView())
->setPageSize($limit)
->setOffset($offset)
->setHasMorePages(count($messages) >= $limit)
->setURI($this->getRequest()->getRequestURI(), 'offset');
$content[] = id(new AphrontPanelView())
->setNoBackground(true)
->appendChild($table)
->appendChild($pager);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'lint',
));
return $this->buildApplicationPage(
array(
$crumbs,
$content,
),
array(
'device' => true,
'title' =>
array(
pht('Lint'),
$drequest->getRepository()->getCallsign(),
)));
}
private function loadLintMessages(
PhabricatorRepositoryBranch $branch,
$limit,
$offset) {
$drequest = $this->getDiffusionRequest();
if (!$branch) {
return array();
}
$conn = $branch->establishConnection('r');
$where = array(
qsprintf($conn, 'branchID = %d', $branch->getID()),
);
if ($drequest->getPath() != '') {
$path = '/'.$drequest->getPath();
$is_dir = (substr($path, -1) == '/');
$where[] = ($is_dir
? qsprintf($conn, 'path LIKE %>', $path)
: qsprintf($conn, 'path = %s', $path));
}
if ($drequest->getLint() != '') {
$where[] = qsprintf(
$conn,
'code = %s',
$drequest->getLint());
}
return queryfx_all(
$conn,
'SELECT *
FROM %T
WHERE %Q
ORDER BY path, code, line LIMIT %d OFFSET %d',
PhabricatorRepository::TABLE_LINTMESSAGE,
implode(' AND ', $where),
$limit,
$offset);
}
}
diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php
index b0efba9c1b..cf64a18d7c 100644
--- a/src/applications/diffusion/view/DiffusionBrowseTableView.php
+++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php
@@ -1,280 +1,280 @@
<?php
final class DiffusionBrowseTableView extends DiffusionView {
private $paths;
private $handles = array();
public function setPaths(array $paths) {
assert_instances_of($paths, 'DiffusionRepositoryPath');
$this->paths = $paths;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function renderLastModifiedColumns(
DiffusionRequest $drequest,
array $handles,
PhabricatorRepositoryCommit $commit = null,
PhabricatorRepositoryCommitData $data = null) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
if ($commit) {
$epoch = $commit->getEpoch();
$modified = DiffusionView::linkCommit(
$drequest->getRepository(),
$commit->getCommitIdentifier());
$date = phabricator_date($epoch, $this->user);
$time = phabricator_time($epoch, $this->user);
} else {
$modified = '';
$date = '';
$time = '';
}
if ($data) {
$author_phid = $data->getCommitDetail('authorPHID');
if ($author_phid && isset($handles[$author_phid])) {
$author = $handles[$author_phid]->renderLink();
} else {
$author = self::renderName($data->getAuthorName());
}
$committer = $data->getCommitDetail('committer');
if ($committer) {
$committer_phid = $data->getCommitDetail('committerPHID');
if ($committer_phid && isset($handles[$committer_phid])) {
$committer = $handles[$committer_phid]->renderLink();
} else {
$committer = self::renderName($committer);
}
if ($author != $committer) {
$author = hsprintf('%s/%s', $author, $committer);
}
}
$details = AphrontTableView::renderSingleDisplayLine($data->getSummary());
} else {
$author = '';
$details = '';
}
$return = array(
'commit' => $modified,
'date' => $date,
'time' => $time,
'author' => $author,
'details' => $details,
);
$lint = self::loadLintMessagesCount($drequest);
if ($lint !== null) {
- $return['lint'] = hsprintf(
- '<a href="%s">%s</a>',
- $drequest->generateURI(array(
+ $return['lint'] = phutil_tag(
+ 'a',
+ array('href' => $drequest->generateURI(array(
'action' => 'lint',
'lint' => null,
- )),
+ ))),
number_format($lint));
}
return $return;
}
private static function loadLintMessagesCount(DiffusionRequest $drequest) {
$branch = $drequest->loadBranch();
if (!$branch) {
return null;
}
$conn = $drequest->getRepository()->establishConnection('r');
$path = '/'.$drequest->getPath();
$where = (substr($path, -1) == '/'
? qsprintf($conn, 'AND path LIKE %>', $path)
: qsprintf($conn, 'AND path = %s', $path));
if ($drequest->getLint()) {
$where .= qsprintf($conn, ' AND code = %s', $drequest->getLint());
}
return head(queryfx_one(
$conn,
'SELECT COUNT(*) FROM %T WHERE branchID = %d %Q',
PhabricatorRepository::TABLE_LINTMESSAGE,
$branch->getID(),
$where));
}
public function render() {
$request = $this->getDiffusionRequest();
$repository = $request->getRepository();
$base_path = trim($request->getPath(), '/');
if ($base_path) {
$base_path = $base_path.'/';
}
$need_pull = array();
$rows = array();
$show_edit = false;
foreach ($this->paths as $path) {
$dir_slash = null;
$file_type = $path->getFileType();
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
$browse_text = $path->getPath().'/';
$dir_slash = '/';
$browse_link = phutil_tag('strong', array(), $this->linkBrowse(
$base_path.$path->getPath().$dir_slash,
array(
'text' => $this->renderPathIcon('dir', $browse_text),
)));
} else if ($file_type == DifferentialChangeType::FILE_SUBMODULE) {
$browse_text = $path->getPath().'/';
$browse_link = phutil_tag('strong', array(), $this->linkExternal(
$path->getHash(),
$path->getExternalURI(),
$this->renderPathIcon('ext', $browse_text)));
} else {
if ($file_type == DifferentialChangeType::FILE_SYMLINK) {
$type = 'link';
} else {
$type = 'file';
}
$browse_text = $path->getPath();
$browse_link = $this->linkBrowse(
$base_path.$path->getPath(),
array(
'text' => $this->renderPathIcon($type, $browse_text),
));
}
$commit = $path->getLastModifiedCommit();
if ($commit) {
$drequest = clone $request;
$drequest->setPath($request->getPath().$path->getPath().$dir_slash);
$dict = $this->renderLastModifiedColumns(
$drequest,
$this->handles,
$commit,
$path->getLastCommitData());
} else {
$dict = array(
'lint' => celerity_generate_unique_node_id(),
'commit' => celerity_generate_unique_node_id(),
'date' => celerity_generate_unique_node_id(),
'time' => celerity_generate_unique_node_id(),
'author' => celerity_generate_unique_node_id(),
'details' => celerity_generate_unique_node_id(),
);
$uri = (string)$request->generateURI(
array(
'action' => 'lastmodified',
'path' => $base_path.$path->getPath().$dir_slash,
));
$need_pull[$uri] = $dict;
foreach ($dict as $k => $uniq) {
$dict[$k] = phutil_tag('span', array('id' => $uniq), '');
}
}
$editor_button = '';
if ($this->user) {
$editor_link = $this->user->loadEditorLink(
$base_path.$path->getPath(),
1,
$request->getRepository()->getCallsign());
if ($editor_link) {
$show_edit = true;
$editor_button = phutil_tag(
'a',
array(
'href' => $editor_link,
),
pht('Edit'));
}
}
$rows[] = array(
$this->linkHistory($base_path.$path->getPath().$dir_slash),
$editor_button,
$browse_link,
idx($dict, 'lint'),
$dict['commit'],
$dict['date'],
$dict['time'],
$dict['author'],
$dict['details'],
);
}
if ($need_pull) {
Javelin::initBehavior('diffusion-pull-lastmodified', $need_pull);
}
$branch = $this->getDiffusionRequest()->loadBranch();
$show_lint = ($branch && $branch->getLintCommit());
$lint = $request->getLint();
$view = new AphrontTableView($rows);
$view->setHeaders(
array(
pht('History'),
pht('Edit'),
pht('Path'),
($lint ? $lint : pht('Lint')),
pht('Modified'),
pht('Date'),
pht('Time'),
pht('Author/Committer'),
pht('Details'),
));
$view->setColumnClasses(
array(
'',
'',
'',
'n',
'',
'',
'right',
'',
'wide',
));
$view->setColumnVisibility(
array(
true,
$show_edit,
true,
$show_lint,
true,
true,
true,
true,
true,
));
return $view->render();
}
private function renderPathIcon($type, $text) {
require_celerity_resource('diffusion-icons-css');
return phutil_tag(
'span',
array(
'class' => 'diffusion-path-icon diffusion-path-icon-'.$type,
),
$text);
}
}
diff --git a/src/applications/diffusion/view/DiffusionCommentView.php b/src/applications/diffusion/view/DiffusionCommentView.php
index a5dc0c59bb..defaf49a07 100644
--- a/src/applications/diffusion/view/DiffusionCommentView.php
+++ b/src/applications/diffusion/view/DiffusionCommentView.php
@@ -1,209 +1,209 @@
<?php
final class DiffusionCommentView extends AphrontView {
private $comment;
private $commentNumber;
private $handles;
private $isPreview;
private $pathMap;
private $inlineComments;
private $markupEngine;
public function setComment(PhabricatorAuditComment $comment) {
$this->comment = $comment;
return $this;
}
public function setCommentNumber($comment_number) {
$this->commentNumber = $comment_number;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setIsPreview($is_preview) {
$this->isPreview = $is_preview;
return $this;
}
public function setInlineComments(array $inline_comments) {
assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
$this->inlineComments = $inline_comments;
return $this;
}
public function setPathMap(array $path_map) {
$this->pathMap = $path_map;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) {
$this->markupEngine = $markup_engine;
return $this;
}
public function getMarkupEngine() {
return $this->markupEngine;
}
public function getRequiredHandlePHIDs() {
return array($this->comment->getActorPHID());
}
private function getHandle($phid) {
if (empty($this->handles[$phid])) {
throw new Exception("Unloaded handle '{$phid}'!");
}
return $this->handles[$phid];
}
public function render() {
$comment = $this->comment;
$author = $this->getHandle($comment->getActorPHID());
$author_link = $author->renderLink();
$actions = $this->renderActions();
$content = $this->renderContent();
$classes = $this->renderClasses();
$xaction_view = id(new PhabricatorTransactionView())
->setUser($this->user)
->setImageURI($author->getImageURI())
->setActions($actions)
->appendChild($content);
if ($this->isPreview) {
$xaction_view->setIsPreview(true);
} else {
$xaction_view
->setAnchor('comment-'.$this->commentNumber, '#'.$this->commentNumber)
->setEpoch($comment->getDateCreated());
}
foreach ($classes as $class) {
$xaction_view->addClass($class);
}
return $xaction_view->render();
}
private function renderActions() {
$comment = $this->comment;
$author = $this->getHandle($comment->getActorPHID());
$author_link = $author->renderLink();
$action = $comment->getAction();
$verb = PhabricatorAuditActionConstants::getActionPastTenseVerb($action);
$metadata = $comment->getMetadata();
$added_auditors = idx(
$metadata,
PhabricatorAuditComment::METADATA_ADDED_AUDITORS,
array());
$added_ccs = idx(
$metadata,
PhabricatorAuditComment::METADATA_ADDED_CCS,
array());
$actions = array();
if ($action == PhabricatorAuditActionConstants::ADD_CCS) {
$rendered_ccs = $this->renderHandleList($added_ccs);
$actions[] = pht("%s added CCs: %s.", $author_link, $rendered_ccs);
} else if ($action == PhabricatorAuditActionConstants::ADD_AUDITORS) {
$rendered_auditors = $this->renderHandleList($added_auditors);
$actions[] = pht(
"%s added auditors: %s.",
$author_link,
$rendered_auditors);
} else {
$actions[] = hsprintf("%s %s this commit.", $author_link, $verb);
}
foreach ($actions as $key => $action) {
$actions[$key] = phutil_tag('div', array(), $action);
}
return $actions;
}
private function renderContent() {
$comment = $this->comment;
$engine = $this->getMarkupEngine();
if (!strlen($comment->getContent()) && empty($this->inlineComments)) {
return null;
} else {
- return hsprintf(
- '<div class="phabricator-remarkup">%s%s</div>',
+ return phutil_tag_div('phabricator-remarkup', array(
$engine->getOutput(
$comment,
PhabricatorAuditComment::MARKUP_FIELD_BODY),
- $this->renderInlines());
+ $this->renderInlines(),
+ ));
}
}
private function renderInlines() {
if (!$this->inlineComments) {
return null;
}
$engine = $this->getMarkupEngine();
$inlines_by_path = mgroup($this->inlineComments, 'getPathID');
$view = new PhabricatorInlineSummaryView();
foreach ($inlines_by_path as $path_id => $inlines) {
$path = idx($this->pathMap, $path_id);
if ($path === null) {
continue;
}
$items = array();
foreach ($inlines as $inline) {
$items[] = array(
'id' => $inline->getID(),
'line' => $inline->getLineNumber(),
'length' => $inline->getLineLength(),
'content' => $engine->getOutput(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY),
);
}
$view->addCommentGroup($path, $items);
}
return $view;
}
private function renderHandleList(array $phids) {
$result = array();
foreach ($phids as $phid) {
$result[] = $this->handles[$phid]->renderLink();
}
return phutil_implode_html(', ', $result);
}
private function renderClasses() {
$comment = $this->comment;
$classes = array();
switch ($comment->getAction()) {
case PhabricatorAuditActionConstants::ACCEPT:
$classes[] = 'audit-accept';
break;
case PhabricatorAuditActionConstants::CONCERN:
$classes[] = 'audit-concern';
break;
}
return $classes;
}
}
diff --git a/src/applications/feed/builder/PhabricatorFeedBuilder.php b/src/applications/feed/builder/PhabricatorFeedBuilder.php
index 180ca92bfb..a92cc52e95 100644
--- a/src/applications/feed/builder/PhabricatorFeedBuilder.php
+++ b/src/applications/feed/builder/PhabricatorFeedBuilder.php
@@ -1,70 +1,70 @@
<?php
final class PhabricatorFeedBuilder {
private $stories;
private $framed;
private $hovercards = false;
public function __construct(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
$this->stories = $stories;
}
public function setFramed($framed) {
$this->framed = $framed;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setShowHovercards($hover) {
$this->hovercards = $hover;
return $this;
}
public function buildView() {
if (!$this->user) {
throw new Exception('Call setUser() before buildView()!');
}
$user = $this->user;
$stories = $this->stories;
$null_view = new AphrontNullView();
require_celerity_resource('phabricator-feed-css');
$last_date = null;
foreach ($stories as $story) {
$story->setFramed($this->framed);
$story->setHovercard($this->hovercards);
$date = ucfirst(phabricator_relative_date($story->getEpoch(), $user));
if ($date !== $last_date) {
if ($last_date !== null) {
- $null_view->appendChild(hsprintf(
- '<div class="phabricator-feed-story-date-separator"></div>'));
+ $null_view->appendChild(
+ phutil_tag_div('phabricator-feed-story-date-separator'));
}
$last_date = $date;
$header = new PhabricatorActionHeaderView();
$header->setHeaderTitle($date);
$null_view->appendChild($header);
}
$view = $story->renderView();
$view->setUser($user);
$null_view->appendChild($view);
}
return id(new AphrontNullView())
->appendChild($null_view->render());
}
}
diff --git a/src/applications/feed/controller/PhabricatorFeedDetailController.php b/src/applications/feed/controller/PhabricatorFeedDetailController.php
index 1608b1633f..9b43f150bc 100644
--- a/src/applications/feed/controller/PhabricatorFeedDetailController.php
+++ b/src/applications/feed/controller/PhabricatorFeedDetailController.php
@@ -1,51 +1,49 @@
<?php
final class PhabricatorFeedDetailController extends PhabricatorFeedController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$story = id(new PhabricatorFeedQuery())
->setViewer($user)
->withChronologicalKeys(array($this->id))
->executeOne();
if (!$story) {
return new Aphront404Response();
}
$feed = array($story);
$builder = new PhabricatorFeedBuilder($feed);
$builder->setUser($user);
$feed_view = $builder->buildView();
$title = pht('Story');
- $feed_view = hsprintf(
- '<div class="phabricator-feed-frame">%s</div>',
- $feed_view);
+ $feed_view = phutil_tag_div('phabricator-feed-frame', $feed_view);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$feed_view,
),
array(
'title' => $title,
'device' => true,
));
}
}
diff --git a/src/applications/feed/controller/PhabricatorFeedListController.php b/src/applications/feed/controller/PhabricatorFeedListController.php
index f292735bb6..42dd0fb5f9 100644
--- a/src/applications/feed/controller/PhabricatorFeedListController.php
+++ b/src/applications/feed/controller/PhabricatorFeedListController.php
@@ -1,40 +1,38 @@
<?php
final class PhabricatorFeedListController extends PhabricatorFeedController
implements PhabricatorApplicationSearchResultsControllerInterface {
private $queryKey;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->queryKey = idx($data, 'queryKey');
}
public function processRequest() {
$request = $this->getRequest();
$controller = id(new PhabricatorApplicationSearchController($request))
->setQueryKey($this->queryKey)
->setSearchEngine(new PhabricatorFeedSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
}
public function renderResultsList(
array $feed,
PhabricatorSavedQuery $query) {
$builder = new PhabricatorFeedBuilder($feed);
$builder->setShowHovercards(true);
$builder->setUser($this->getRequest()->getUser());
$view = $builder->buildView();
- return hsprintf(
- '<div class="phabricator-feed-frame">%s</div>',
- $view);
+ return phutil_tag_div('phabricator-feed-frame', $view);
}
}
diff --git a/src/applications/feed/controller/PhabricatorFeedPublicStreamController.php b/src/applications/feed/controller/PhabricatorFeedPublicStreamController.php
index 84e8f3f155..b43e79450f 100644
--- a/src/applications/feed/controller/PhabricatorFeedPublicStreamController.php
+++ b/src/applications/feed/controller/PhabricatorFeedPublicStreamController.php
@@ -1,38 +1,39 @@
<?php
final class PhabricatorFeedPublicStreamController
extends PhabricatorFeedController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
if (!PhabricatorEnv::getEnvConfig('feed.public')) {
return new Aphront404Response();
}
$request = $this->getRequest();
$viewer = PhabricatorUser::getOmnipotentUser();
$query = new PhabricatorFeedQuery();
$query->setViewer($viewer);
$query->setLimit(100);
$stories = $query->execute();
$builder = new PhabricatorFeedBuilder($stories);
$builder
->setFramed(true)
->setUser($viewer);
- $view = hsprintf('<div class="phabricator-public-feed-frame">%s</div>',
+ $view = phutil_tag_div(
+ 'phabricator-public-feed-frame',
$builder->buildView());
return $this->buildStandardPageResponse(
$view,
array(
'title' => pht('Public Feed'),
'public' => true,
));
}
}
diff --git a/src/applications/feed/story/PhabricatorFeedStoryCommit.php b/src/applications/feed/story/PhabricatorFeedStoryCommit.php
index 7c99682089..488e5d81aa 100644
--- a/src/applications/feed/story/PhabricatorFeedStoryCommit.php
+++ b/src/applications/feed/story/PhabricatorFeedStoryCommit.php
@@ -1,102 +1,102 @@
<?php
final class PhabricatorFeedStoryCommit extends PhabricatorFeedStory {
public function getPrimaryObjectPHID() {
return $this->getValue('commitPHID');
}
public function getRequiredHandlePHIDs() {
return array(
$this->getValue('committerPHID'),
);
}
public function renderView() {
$data = $this->getStoryData();
$author = null;
if ($data->getValue('authorPHID')) {
$author = $this->linkTo($data->getValue('authorPHID'));
} else {
$author = $data->getValue('authorName');
}
$committer = null;
if ($data->getValue('committerPHID')) {
$committer = $this->linkTo($data->getValue('committerPHID'));
} else if ($data->getValue('committerName')) {
$committer = $data->getValue('committerName');
}
$commit = $this->linkTo($data->getValue('commitPHID'));
if (!$committer) {
$committer = $author;
$author = null;
}
if ($author) {
- $title = hsprintf(
+ $title = pht(
"%s committed %s (authored by %s)",
$committer,
$commit,
$author);
} else {
- $title = hsprintf(
+ $title = pht(
"%s committed %s",
$committer,
$commit);
}
$view = $this->newStoryView();
$view->setAppIcon('differential-dark');
$view->setTitle($title);
if ($data->getValue('authorPHID')) {
$view->setImage($this->getHandle($data->getAuthorPHID())->getImageURI());
}
$content = $this->renderSummary($data->getValue('summary'));
$view->appendChild($content);
return $view;
}
public function renderText() {
$author = null;
if ($this->getAuthorPHID()) {
$author = $this->getHandle($this->getAuthorPHID())->getLinkName();
} else {
$author = $this->getValue('authorName');
}
$committer = null;
if ($this->getValue('committerPHID')) {
$committer_handle = $this->getHandle($this->getValue('committerPHID'));
$committer = $committer_handle->getLinkName();
} else if ($this->getValue('committerName')) {
$committer = $this->getValue('committerName');
}
$commit_handle = $this->getHandle($this->getPrimaryObjectPHID());
$commit_uri = PhabricatorEnv::getURI($commit_handle->getURI());
$commit_name = $commit_handle->getLinkName();
if (!$committer) {
$committer = $author;
$author = null;
}
if ($author) {
$text = "{$committer} (authored by {$author})".
"committed {$commit_name} {$commit_uri}";
} else {
$text = "{$committer} committed {$commit_name} {$commit_uri}";
}
return $text;
}
}
diff --git a/src/applications/harbormaster/view/ShellLogView.php b/src/applications/harbormaster/view/ShellLogView.php
index db7f85548f..feaa8ffc07 100644
--- a/src/applications/harbormaster/view/ShellLogView.php
+++ b/src/applications/harbormaster/view/ShellLogView.php
@@ -1,101 +1,109 @@
<?php
final class ShellLogView extends AphrontView {
private $start = 1;
private $lines;
private $limit;
private $highlights = array();
public function setStart($start) {
$this->start = $start;
return $this;
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function setLines(array $lines) {
$this->lines = $lines;
return $this;
}
public function setHighlights(array $highlights) {
$this->highlights = array_fuse($highlights);
return $this;
}
public function render() {
require_celerity_resource('phabricator-source-code-view-css');
require_celerity_resource('syntax-highlighting-css');
Javelin::initBehavior('phabricator-oncopy', array());
$line_number = $this->start;
$rows = array();
foreach ($this->lines as $line) {
$hit_limit = $this->limit &&
($line_number == $this->limit) &&
(count($this->lines) != $this->limit);
if ($hit_limit) {
$content_number = '';
$content_line = phutil_tag(
'span',
array(
'class' => 'c',
),
pht('...'));
} else {
$content_number = $line_number;
$content_line = $line;
}
$row_attributes = array();
if (isset($this->highlights[$line_number])) {
$row_attributes['class'] = 'phabricator-source-highlight';
}
// TODO: Provide nice links.
+ $th = phutil_tag(
+ 'th',
+ array(
+ 'class' => 'phabricator-source-line',
+ 'style' => 'background-color: #fff;',
+ ),
+ $content_number);
+
+ $td = phutil_tag(
+ 'td',
+ array('class' => 'phabricator-source-code'),
+ $content_line);
+
$rows[] = phutil_tag(
'tr',
$row_attributes,
- hsprintf(
- '<th class="phabricator-source-line" '.
- 'style="background-color: #fff;">%s</th>'.
- '<td class="phabricator-source-code">%s</td>',
- $content_number,
- $content_line));
+ array($th, $td));
if ($hit_limit) {
break;
}
$line_number++;
}
$classes = array();
$classes[] = 'phabricator-source-code-view';
$classes[] = 'remarkup-code';
$classes[] = 'PhabricatorMonospaced';
return phutil_tag(
'div',
array(
'class' => 'phabricator-source-code-container',
'style' => 'background-color: black; color: white;'
),
phutil_tag(
'table',
array(
'class' => implode(' ', $classes),
'style' => 'background-color: black'
),
phutil_implode_html('', $rows)));
}
}
diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php
index 4ee5cab5d2..22595d3ada 100644
--- a/src/applications/herald/controller/HeraldTestConsoleController.php
+++ b/src/applications/herald/controller/HeraldTestConsoleController.php
@@ -1,131 +1,131 @@
<?php
final class HeraldTestConsoleController extends HeraldController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$request = $this->getRequest();
$object_name = trim($request->getStr('object_name'));
$e_name = true;
$errors = array();
if ($request->isFormPost()) {
if (!$object_name) {
$e_name = pht('Required');
$errors[] = pht('An object name is required.');
}
if (!$errors) {
$object = id(new PhabricatorObjectQuery())
->setViewer($user)
->withNames(array($object_name))
->executeOne();
if (!$object) {
$e_name = pht('Invalid');
$errors[] = pht('No object exists with that name.');
}
if (!$errors) {
// TODO: Let the adapters claim objects instead.
if ($object instanceof DifferentialRevision) {
$adapter = HeraldDifferentialRevisionAdapter::newLegacyAdapter(
$object,
$object->loadActiveDiff());
} else if ($object instanceof PhabricatorRepositoryCommit) {
$data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
'commitID = %d',
$object->getID());
$adapter = HeraldCommitAdapter::newLegacyAdapter(
$object->getRepository(),
$object,
$data);
} else if ($object instanceof ManiphestTask) {
$adapter = id(new HeraldManiphestTaskAdapter())
->setTask($object);
} else if ($object instanceof PholioMock) {
$adapter = id(new HeraldPholioMockAdapter())
->setMock($object);
} else {
throw new Exception("Can not build adapter for object!");
}
$rules = id(new HeraldRuleQuery())
->setViewer($user)
->withContentTypes(array($adapter->getAdapterContentType()))
->withDisabled(false)
->needConditionsAndActions(true)
->needAppliedToPHIDs(array($object->getPHID()))
->needValidateAuthors(true)
->execute();
$engine = id(new HeraldEngine())
->setDryRun(true);
$effects = $engine->applyRules($rules, $adapter);
$engine->applyEffects($effects, $adapter, $rules);
$xscript = $engine->getTranscript();
return id(new AphrontRedirectResponse())
->setURI('/herald/transcript/'.$xscript->getID().'/');
}
}
}
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Form Errors'));
$error_view->setErrors($errors);
} else {
$error_view = null;
}
$text = pht(
'Enter an object to test rules for, like a Diffusion commit (e.g., '.
'rX123) or a Differential revision (e.g., D123). You will be shown '.
'the results of a dry run on the object.');
$form = id(new AphrontFormView())
->setUser($user)
- ->appendChild(hsprintf(
- '<p class="aphront-form-instructions">%s</p>', $text))
+ ->appendChild(
+ phutil_tag('p', array('class' => 'aphront-form-instructions'), $text))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Object Name'))
->setName('object_name')
->setError($e_name)
->setValue($object_name))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Test Rules')));
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Herald Test Console'))
->setFormError($error_view)
->setForm($form);
$crumbs = id($this->buildApplicationCrumbs())
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Transcripts'))
->setHref($this->getApplicationURI('/transcript/')))
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Test Console')));
return $this->buildApplicationPage(
$box,
array(
'title' => pht('Test Console'),
'device' => true,
));
}
}
diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php
index 7cdf5aade1..e3297c0d55 100644
--- a/src/applications/herald/controller/HeraldTranscriptController.php
+++ b/src/applications/herald/controller/HeraldTranscriptController.php
@@ -1,508 +1,513 @@
<?php
final class HeraldTranscriptController extends HeraldController {
const FILTER_AFFECTED = 'affected';
const FILTER_OWNED = 'owned';
const FILTER_ALL = 'all';
private $id;
private $filter;
private $handles;
private $adapter;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$map = $this->getFilterMap();
$this->filter = idx($data, 'filter');
if (empty($map[$this->filter])) {
$this->filter = self::FILTER_AFFECTED;
}
}
private function getAdapter() {
return $this->adapter;
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$xscript = id(new HeraldTranscriptQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$xscript) {
return new Aphront404Response();
}
require_celerity_resource('herald-test-css');
$nav = $this->buildSideNav();
$object_xscript = $xscript->getObjectTranscript();
if (!$object_xscript) {
$notice = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Old Transcript'))
->appendChild(phutil_tag(
'p',
array(),
pht('Details of this transcript have been garbage collected.')));
$nav->appendChild($notice);
} else {
$map = HeraldAdapter::getEnabledAdapterMap($viewer);
$object_type = $object_xscript->getType();
if (empty($map[$object_type])) {
// TODO: We should filter these out in the Query, but we have to load
// the objectTranscript right now, which is potentially enormous. We
// should denormalize the object type, or move the data into a separate
// table, and then filter this earlier (and thus raise a better error).
// For now, just block access so we don't violate policies.
throw new Exception(
pht("This transcript has an invalid or inaccessible adapter."));
}
$this->adapter = HeraldAdapter::getAdapterForContentType($object_type);
$filter = $this->getFilterPHIDs();
$this->filterTranscript($xscript, $filter);
$phids = array_merge($filter, $this->getTranscriptPHIDs($xscript));
$phids = array_unique($phids);
$phids = array_filter($phids);
$handles = $this->loadViewerHandles($phids);
$this->handles = $handles;
if ($xscript->getDryRun()) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Dry Run'));
$notice->appendChild(pht('This was a dry run to test Herald '.
'rules, no actions were executed.'));
$nav->appendChild($notice);
}
$apply_xscript_panel = $this->buildApplyTranscriptPanel(
$xscript);
$nav->appendChild($apply_xscript_panel);
$action_xscript_panel = $this->buildActionTranscriptPanel(
$xscript);
$nav->appendChild($action_xscript_panel);
$object_xscript_panel = $this->buildObjectTranscriptPanel(
$xscript);
$nav->appendChild($object_xscript_panel);
}
$crumbs = id($this->buildApplicationCrumbs())
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Transcripts'))
->setHref($this->getApplicationURI('/transcript/')))
->addCrumb(
id(new PhabricatorCrumbView())
->setName($xscript->getID()));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Transcript'),
'device' => true,
));
}
protected function renderConditionTestValue($condition, $handles) {
$value = $condition->getTestValue();
if (!is_scalar($value) && $value !== null) {
foreach ($value as $key => $phid) {
$handle = idx($handles, $phid);
if ($handle) {
$value[$key] = $handle->getName();
} else {
// This shouldn't ever really happen as we are supposed to have
// grabbed handles for everything, but be super liberal in what
// we accept here since we expect all sorts of weird issues as we
// version the system.
$value[$key] = 'Unknown Object #'.$phid;
}
}
sort($value);
$value = implode(', ', $value);
}
- return hsprintf('<span class="condition-test-value">%s</span>', $value);
+ return phutil_tag('span', array('class' => 'condition-test-value'), $value);
}
private function buildSideNav() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/herald/transcript/'.$this->id.'/'));
$items = array();
$filters = $this->getFilterMap();
foreach ($filters as $key => $name) {
$nav->addFilter($key, $name);
}
$nav->selectFilter($this->filter, null);
return $nav;
}
protected function getFilterMap() {
return array(
self::FILTER_AFFECTED => pht('Rules that Affected Me'),
self::FILTER_OWNED => pht('Rules I Own'),
self::FILTER_ALL => pht('All Rules'),
);
}
protected function getFilterPHIDs() {
return array($this->getRequest()->getUser()->getPHID());
}
protected function getTranscriptPHIDs($xscript) {
$phids = array();
$object_xscript = $xscript->getObjectTranscript();
if (!$object_xscript) {
return array();
}
$phids[] = $object_xscript->getPHID();
foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
// TODO: This is total hacks. Add another amazing layer of abstraction.
$target = (array)$apply_xscript->getTarget();
foreach ($target as $phid) {
if ($phid) {
$phids[] = $phid;
}
}
}
foreach ($xscript->getRuleTranscripts() as $rule_xscript) {
$phids[] = $rule_xscript->getRuleOwner();
}
$condition_xscripts = $xscript->getConditionTranscripts();
if ($condition_xscripts) {
$condition_xscripts = call_user_func_array(
'array_merge',
$condition_xscripts);
}
foreach ($condition_xscripts as $condition_xscript) {
$value = $condition_xscript->getTestValue();
// TODO: Also total hacks.
if (is_array($value)) {
foreach ($value as $phid) {
if ($phid) { // TODO: Probably need to make sure this "looks like" a
// PHID or decrease the level of hacks here; this used
// to be an is_numeric() check in Facebook land.
$phids[] = $phid;
}
}
}
}
return $phids;
}
protected function filterTranscript($xscript, $filter_phids) {
$filter_owned = ($this->filter == self::FILTER_OWNED);
$filter_affected = ($this->filter == self::FILTER_AFFECTED);
if (!$filter_owned && !$filter_affected) {
// No filtering to be done.
return;
}
if (!$xscript->getObjectTranscript()) {
return;
}
$user_phid = $this->getRequest()->getUser()->getPHID();
$keep_apply_xscripts = array();
$keep_rule_xscripts = array();
$filter_phids = array_fill_keys($filter_phids, true);
$rule_xscripts = $xscript->getRuleTranscripts();
foreach ($xscript->getApplyTranscripts() as $id => $apply_xscript) {
$rule_id = $apply_xscript->getRuleID();
if ($filter_owned) {
if (empty($rule_xscripts[$rule_id])) {
// No associated rule so you can't own this effect.
continue;
}
if ($rule_xscripts[$rule_id]->getRuleOwner() != $user_phid) {
continue;
}
} else if ($filter_affected) {
$targets = (array)$apply_xscript->getTarget();
if (!array_select_keys($filter_phids, $targets)) {
continue;
}
}
$keep_apply_xscripts[$id] = true;
if ($rule_id) {
$keep_rule_xscripts[$rule_id] = true;
}
}
foreach ($rule_xscripts as $rule_id => $rule_xscript) {
if ($filter_owned && $rule_xscript->getRuleOwner() == $user_phid) {
$keep_rule_xscripts[$rule_id] = true;
}
}
$xscript->setRuleTranscripts(
array_intersect_key(
$xscript->getRuleTranscripts(),
$keep_rule_xscripts));
$xscript->setApplyTranscripts(
array_intersect_key(
$xscript->getApplyTranscripts(),
$keep_apply_xscripts));
$xscript->setConditionTranscripts(
array_intersect_key(
$xscript->getConditionTranscripts(),
$keep_rule_xscripts));
}
private function buildApplyTranscriptPanel($xscript) {
$handles = $this->handles;
$adapter = $this->getAdapter();
$rule_type_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
$action_names = $adapter->getActionNameMap($rule_type_global);
$rows = array();
foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
$target = $apply_xscript->getTarget();
switch ($apply_xscript->getAction()) {
case HeraldAdapter::ACTION_NOTHING:
$target = '';
break;
case HeraldAdapter::ACTION_FLAG:
$target = PhabricatorFlagColor::getColorName($target);
break;
default:
if ($target) {
foreach ($target as $k => $phid) {
$target[$k] = $handles[$phid]->getName();
}
$target = implode("\n", $target);
} else {
$target = '<empty>';
}
break;
}
if ($apply_xscript->getApplied()) {
- $success = pht('SUCCESS');
- $outcome =
- hsprintf('<span class="outcome-success">%s</span>', $success);
+ $outcome = phutil_tag(
+ 'span',
+ array('class' => 'outcome-success'),
+ pht('SUCCESS'));
} else {
- $failure = pht('FAILURE');
- $outcome =
- hsprintf('<span class="outcome-failure">%s</span>', $failure);
+ $outcome = phutil_tag(
+ 'span',
+ array('class' => 'outcome-failure'),
+ pht('FAILURE'));
}
$rows[] = array(
idx($action_names, $apply_xscript->getAction(), pht('Unknown')),
$target,
hsprintf(
'<strong>Taken because:</strong> %s<br />'.
'<strong>Outcome:</strong> %s %s',
$apply_xscript->getReason(),
$outcome,
$apply_xscript->getAppliedReason()),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(pht('No actions were taken.'));
$table->setHeaders(
array(
pht('Action'),
pht('Target'),
pht('Details'),
));
$table->setColumnClasses(
array(
'',
'',
'wide',
));
$panel = new AphrontPanelView();
$panel->setHeader(pht('Actions Taken'));
$panel->appendChild($table);
$panel->setNoBackground();
return $panel;
}
private function buildActionTranscriptPanel($xscript) {
$action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID');
$adapter = $this->getAdapter();
$field_names = $adapter->getFieldNameMap();
$condition_names = $adapter->getConditionNameMap();
$handles = $this->handles;
$rule_markup = array();
foreach ($xscript->getRuleTranscripts() as $rule_id => $rule) {
$cond_markup = array();
foreach ($xscript->getConditionTranscriptsForRule($rule_id) as $cond) {
if ($cond->getNote()) {
- $note = hsprintf(
- '<div class="herald-condition-note">%s</div>',
- $cond->getNote());
+ $note = phutil_tag_div('herald-condition-note', $cond->getNote());
} else {
$note = null;
}
if ($cond->getResult()) {
- $result = hsprintf(
- '<span class="herald-outcome condition-pass">'.
- "\xE2\x9C\x93".
- '</span>');
+ $result = phutil_tag(
+ 'span',
+ array('class' => 'herald-outcome condition-pass'),
+ "\xE2\x9C\x93");
} else {
- $result = hsprintf(
- '<span class="herald-outcome condition-fail">'.
- "\xE2\x9C\x98".
- '</span>');
+ $result = phutil_tag(
+ 'span',
+ array('class' => 'herald-outcome condition-fail'),
+ "\xE2\x9C\x98");
}
$cond_markup[] = phutil_tag(
'li',
array(),
pht(
'%s Condition: %s %s %s%s',
$result,
idx($field_names, $cond->getFieldName(), pht('Unknown')),
idx($condition_names, $cond->getCondition(), pht('Unknown')),
$this->renderConditionTestValue($cond, $handles),
$note));
}
if ($rule->getResult()) {
- $pass = pht('PASS');
- $result = hsprintf(
- '<span class="herald-outcome rule-pass">%s</span>', $pass);
+ $result = phutil_tag(
+ 'span',
+ array('class' => 'herald-outcome rule-pass'),
+ pht('PASS'));
$class = 'herald-rule-pass';
} else {
- $fail = pht('FAIL');
- $result = hsprintf(
- '<span class="herald-outcome rule-fail">%s</span>', $fail);
+ $result = phutil_tag(
+ 'span',
+ array('class' => 'herald-outcome rule-fail'),
+ pht('FAIL'));
$class = 'herald-rule-fail';
}
- $cond_markup[] = hsprintf('<li>%s %s</li>', $result, $rule->getReason());
+ $cond_markup[] = phutil_tag(
+ 'li',
+ array(),
+ array($result, $rule->getReason()));
$user_phid = $this->getRequest()->getUser()->getPHID();
$name = $rule->getRuleName();
$rule_markup[] =
phutil_tag(
'li',
array(
'class' => $class,
),
- hsprintf(
- '<div class="rule-name"><strong>%s</strong> %s</div>%s',
- $name,
- $handles[$rule->getRuleOwner()]->getName(),
- phutil_tag('ul', array(), $cond_markup)));
+ phutil_tag_div('rule-name', array(
+ phutil_tag('strong', array(), $name),
+ ' ',
+ phutil_tag('ul', array(), $cond_markup),
+ )));
}
$panel = '';
if ($rule_markup) {
$panel = new AphrontPanelView();
$panel->setHeader(pht('Rule Details'));
$panel->setNoBackground();
$panel->appendChild(phutil_tag(
'ul',
array('class' => 'herald-explain-list'),
$rule_markup));
}
return $panel;
}
private function buildObjectTranscriptPanel($xscript) {
$adapter = $this->getAdapter();
$field_names = $adapter->getFieldNameMap();
$object_xscript = $xscript->getObjectTranscript();
$data = array();
if ($object_xscript) {
$phid = $object_xscript->getPHID();
$handles = $this->loadViewerHandles(array($phid));
$data += array(
pht('Object Name') => $object_xscript->getName(),
pht('Object Type') => $object_xscript->getType(),
pht('Object PHID') => $phid,
pht('Object Link') => $handles[$phid]->renderLink(),
);
}
$data += $xscript->getMetadataMap();
if ($object_xscript) {
foreach ($object_xscript->getFields() as $field => $value) {
$field = idx($field_names, $field, '['.$field.'?]');
$data['Field: '.$field] = $value;
}
}
$rows = array();
foreach ($data as $name => $value) {
if (!($value instanceof PhutilSafeHTML)) {
if (!is_scalar($value) && !is_null($value)) {
$value = implode("\n", $value);
}
if (strlen($value) > 256) {
$value = phutil_tag(
'textarea',
array(
'class' => 'herald-field-value-transcript',
),
$value);
}
}
$rows[] = array($name, $value);
}
$table = new AphrontTableView($rows);
$table->setColumnClasses(
array(
'header',
'wide',
));
$panel = new AphrontPanelView();
$panel->setHeader(pht('Object Transcript'));
$panel->setNoBackground();
$panel->appendChild($table);
return $panel;
}
}
diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php
index 5b0979fd7f..67e4b673c0 100644
--- a/src/applications/maniphest/controller/ManiphestReportController.php
+++ b/src/applications/maniphest/controller/ManiphestReportController.php
@@ -1,752 +1,752 @@
<?php
/**
* @group maniphest
*/
final class ManiphestReportController extends ManiphestController {
private $view;
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($request->isFormPost()) {
$uri = $request->getRequestURI();
$project = head($request->getArr('set_project'));
$project = nonempty($project, null);
$uri = $uri->alter('project', $project);
$window = $request->getStr('set_window');
$uri = $uri->alter('window', $window);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/maniphest/report/'));
$nav->addLabel(pht('Open Tasks'));
$nav->addFilter('user', pht('By User'));
$nav->addFilter('project', pht('By Project'));
$nav->addLabel(pht('Burnup'));
$nav->addFilter('burn', pht('Burnup Rate'));
$this->view = $nav->selectFilter($this->view, 'user');
require_celerity_resource('maniphest-report-css');
switch ($this->view) {
case 'burn':
$core = $this->renderBurn();
break;
case 'user':
case 'project':
$core = $this->renderOpenTasks();
break;
default:
return new Aphront404Response();
}
$nav->appendChild($core);
$nav->setCrumbs(
$this->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Reports'))));
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Maniphest Reports'),
));
}
public function renderBurn() {
$request = $this->getRequest();
$user = $request->getUser();
$handle = null;
$project_phid = $request->getStr('project');
if ($project_phid) {
$phids = array($project_phid);
$handles = $this->loadViewerHandles($phids);
$handle = $handles[$project_phid];
}
$table = new ManiphestTransaction();
$conn = $table->establishConnection('r');
$joins = '';
if ($project_phid) {
$joins = qsprintf(
$conn,
'JOIN %T t ON x.objectPHID = t.phid
JOIN %T p ON p.taskPHID = t.phid AND p.projectPHID = %s',
id(new ManiphestTask())->getTableName(),
id(new ManiphestTaskProject())->getTableName(),
$project_phid);
}
$data = queryfx_all(
$conn,
'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q
WHERE transactionType = %s
ORDER BY x.dateCreated ASC',
$table->getTableName(),
$joins,
ManiphestTransaction::TYPE_STATUS);
$stats = array();
$day_buckets = array();
$open_tasks = array();
foreach ($data as $key => $row) {
// NOTE: Hack to avoid json_decode().
$oldv = trim($row['oldValue'], '"');
$newv = trim($row['newValue'], '"');
$old_is_open = ($oldv === (string)ManiphestTaskStatus::STATUS_OPEN);
$new_is_open = ($newv === (string)ManiphestTaskStatus::STATUS_OPEN);
$is_open = ($new_is_open && !$old_is_open);
$is_close = ($old_is_open && !$new_is_open);
$data[$key]['_is_open'] = $is_open;
$data[$key]['_is_close'] = $is_close;
if (!$is_open && !$is_close) {
// This is either some kind of bogus event, or a resolution change
// (e.g., resolved -> invalid). Just skip it.
continue;
}
$day_bucket = phabricator_format_local_time(
$row['dateCreated'],
$user,
'Yz');
$day_buckets[$day_bucket] = $row['dateCreated'];
if (empty($stats[$day_bucket])) {
$stats[$day_bucket] = array(
'open' => 0,
'close' => 0,
);
}
$stats[$day_bucket][$is_close ? 'close' : 'open']++;
}
$template = array(
'open' => 0,
'close' => 0,
);
$rows = array();
$rowc = array();
$last_month = null;
$last_month_epoch = null;
$last_week = null;
$last_week_epoch = null;
$week = null;
$month = null;
$last = last_key($stats) - 1;
$period = $template;
foreach ($stats as $bucket => $info) {
$epoch = $day_buckets[$bucket];
$week_bucket = phabricator_format_local_time(
$epoch,
$user,
'YW');
if ($week_bucket != $last_week) {
if ($week) {
$rows[] = $this->formatBurnRow(
'Week of '.phabricator_date($last_week_epoch, $user),
$week);
$rowc[] = 'week';
}
$week = $template;
$last_week = $week_bucket;
$last_week_epoch = $epoch;
}
$month_bucket = phabricator_format_local_time(
$epoch,
$user,
'Ym');
if ($month_bucket != $last_month) {
if ($month) {
$rows[] = $this->formatBurnRow(
phabricator_format_local_time($last_month_epoch, $user, 'F, Y'),
$month);
$rowc[] = 'month';
}
$month = $template;
$last_month = $month_bucket;
$last_month_epoch = $epoch;
}
$rows[] = $this->formatBurnRow(phabricator_date($epoch, $user), $info);
$rowc[] = null;
$week['open'] += $info['open'];
$week['close'] += $info['close'];
$month['open'] += $info['open'];
$month['close'] += $info['close'];
$period['open'] += $info['open'];
$period['close'] += $info['close'];
}
if ($week) {
$rows[] = $this->formatBurnRow(
pht('Week To Date'),
$week);
$rowc[] = 'week';
}
if ($month) {
$rows[] = $this->formatBurnRow(
pht('Month To Date'),
$month);
$rowc[] = 'month';
}
$rows[] = $this->formatBurnRow(
pht('All Time'),
$period);
$rowc[] = 'aggregate';
$rows = array_reverse($rows);
$rowc = array_reverse($rowc);
$table = new AphrontTableView($rows);
$table->setRowClasses($rowc);
$table->setHeaders(
array(
pht('Period'),
pht('Opened'),
pht('Closed'),
pht('Change'),
));
$table->setColumnClasses(
array(
'right wide',
'n',
'n',
'n',
));
if ($handle) {
$inst = pht(
"NOTE: This table reflects tasks <em>currently</em> in ".
"the project. If a task was opened in the past but added to ".
"the project recently, it is counted on the day it was ".
"opened, not the day it was categorized. If a task was part ".
"of this project in the past but no longer is, it is not ".
"counted at all.");
$header = pht("Task Burn Rate for Project %s", $handle->renderLink());
- $caption = hsprintf("<p>%s</p>", $inst);
+ $caption = phutil_tag('p', array(), $inst);
} else {
$header = pht("Task Burn Rate for All Tasks");
$caption = null;
}
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->setCaption($caption);
$panel->appendChild($table);
$tokens = array();
if ($handle) {
$tokens = array($handle);
}
$filter = $this->renderReportFilters($tokens, $has_window = false);
$id = celerity_generate_unique_node_id();
$chart = phutil_tag(
'div',
array(
'id' => $id,
'style' => 'border: 1px solid #6f6f6f; '.
'margin: 1em 2em; '.
'height: 400px; ',
),
'');
list($burn_x, $burn_y) = $this->buildSeries($data);
require_celerity_resource('raphael-core');
require_celerity_resource('raphael-g');
require_celerity_resource('raphael-g-line');
Javelin::initBehavior('line-chart', array(
'hardpoint' => $id,
'x' => array(
$burn_x,
),
'y' => array(
$burn_y,
),
'xformat' => 'epoch',
));
return array($filter, $chart, $panel);
}
private function renderReportFilters(array $tokens, $has_window) {
$request = $this->getRequest();
$user = $request->getUser();
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchproject/')
->setLabel(pht('Project'))
->setLimit(1)
->setName('set_project')
->setValue($tokens));
if ($has_window) {
list($window_str, $ignored, $window_error) = $this->getWindow();
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Recently Means'))
->setName('set_window')
->setCaption(
pht('Configure the cutoff for the "Recently Closed" column.'))
->setValue($window_str)
->setError($window_error));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Filter By Project')));
$filter = new AphrontListFilterView();
$filter->appendChild($form);
return $filter;
}
private function buildSeries(array $data) {
$out = array();
$counter = 0;
foreach ($data as $row) {
$t = (int)$row['dateCreated'];
if ($row['_is_close']) {
--$counter;
$out[$t] = $counter;
} else if ($row['_is_open']) {
++$counter;
$out[$t] = $counter;
}
}
return array(array_keys($out), array_values($out));
}
private function formatBurnRow($label, $info) {
$delta = $info['open'] - $info['close'];
$fmt = number_format($delta);
if ($delta > 0) {
$fmt = '+'.$fmt;
- $fmt = hsprintf('<span class="red">%s</span>', $fmt);
+ $fmt = phutil_tag('span', array('class' => 'red'), $fmt);
} else {
- $fmt = hsprintf('<span class="green">%s</span>', $fmt);
+ $fmt = phutil_tag('span', array('class' => 'green'), $fmt);
}
return array(
$label,
number_format($info['open']),
number_format($info['close']),
$fmt);
}
public function renderOpenTasks() {
$request = $this->getRequest();
$user = $request->getUser();
$query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$project_phid = $request->getStr('project');
$project_handle = null;
if ($project_phid) {
$phids = array($project_phid);
$handles = $this->loadViewerHandles($phids);
$project_handle = $handles[$project_phid];
$query->withAnyProjects($phids);
}
$tasks = $query->execute();
$recently_closed = $this->loadRecentlyClosedTasks();
$date = phabricator_date(time(), $user);
switch ($this->view) {
case 'user':
$result = mgroup($tasks, 'getOwnerPHID');
$leftover = idx($result, '', array());
unset($result['']);
$result_closed = mgroup($recently_closed, 'getOwnerPHID');
$leftover_closed = idx($result_closed, '', array());
unset($result_closed['']);
$base_link = '/maniphest/?assigned=';
$leftover_name = phutil_tag('em', array(), pht('(Up For Grabs)'));
$col_header = pht('User');
$header = pht('Open Tasks by User and Priority (%s)', $date);
break;
case 'project':
$result = array();
$leftover = array();
foreach ($tasks as $task) {
$phids = $task->getProjectPHIDs();
if ($phids) {
foreach ($phids as $project_phid) {
$result[$project_phid][] = $task;
}
} else {
$leftover[] = $task;
}
}
$result_closed = array();
$leftover_closed = array();
foreach ($recently_closed as $task) {
$phids = $task->getProjectPHIDs();
if ($phids) {
foreach ($phids as $project_phid) {
$result_closed[$project_phid][] = $task;
}
} else {
$leftover_closed[] = $task;
}
}
$base_link = '/maniphest/?allProjects[]=';
$leftover_name = phutil_tag('em', array(), pht('(No Project)'));
$col_header = pht('Project');
$header = pht('Open Tasks by Project and Priority (%s)', $date);
break;
}
$phids = array_keys($result);
$handles = $this->loadViewerHandles($phids);
$handles = msort($handles, 'getName');
$order = $request->getStr('order', 'name');
list($order, $reverse) = AphrontTableView::parseSort($order);
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips', array());
$rows = array();
$pri_total = array();
foreach (array_merge($handles, array(null)) as $handle) {
if ($handle) {
if (($project_handle) &&
($project_handle->getPHID() == $handle->getPHID())) {
// If filtering by, e.g., "bugs", don't show a "bugs" group.
continue;
}
$tasks = idx($result, $handle->getPHID(), array());
$name = phutil_tag(
'a',
array(
'href' => $base_link.$handle->getPHID(),
),
$handle->getName());
$closed = idx($result_closed, $handle->getPHID(), array());
} else {
$tasks = $leftover;
$name = $leftover_name;
$closed = $leftover_closed;
}
$taskv = $tasks;
$tasks = mgroup($tasks, 'getPriority');
$row = array();
$row[] = $name;
$total = 0;
foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $label) {
$n = count(idx($tasks, $pri, array()));
if ($n == 0) {
$row[] = '-';
} else {
$row[] = number_format($n);
}
$total += $n;
}
$row[] = number_format($total);
list($link, $oldest_all) = $this->renderOldest($taskv);
$row[] = $link;
$normal_or_better = array();
foreach ($taskv as $id => $task) {
// TODO: This is sort of a hard-code for the default "normal" status.
// When reports are more powerful, this should be made more general.
if ($task->getPriority() < 50) {
continue;
}
$normal_or_better[$id] = $task;
}
list($link, $oldest_pri) = $this->renderOldest($normal_or_better);
$row[] = $link;
if ($closed) {
$task_ids = implode(',', mpull($closed, 'getID'));
$row[] = phutil_tag(
'a',
array(
'href' => '/maniphest/?ids='.$task_ids,
'target' => '_blank',
),
number_format(count($closed)));
} else {
$row[] = '-';
}
switch ($order) {
case 'total':
$row['sort'] = $total;
break;
case 'oldest-all':
$row['sort'] = $oldest_all;
break;
case 'oldest-pri':
$row['sort'] = $oldest_pri;
break;
case 'closed':
$row['sort'] = count($closed);
break;
case 'name':
default:
$row['sort'] = $handle ? $handle->getName() : '~';
break;
}
$rows[] = $row;
}
$rows = isort($rows, 'sort');
foreach ($rows as $k => $row) {
unset($rows[$k]['sort']);
}
if ($reverse) {
$rows = array_reverse($rows);
}
$cname = array($col_header);
$cclass = array('pri right wide');
$pri_map = ManiphestTaskPriority::getShortNameMap();
foreach ($pri_map as $pri => $label) {
$cname[] = $label;
$cclass[] = 'n';
}
$cname[] = 'Total';
$cclass[] = 'n';
$cname[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => pht('Oldest open task.'),
'size' => 200,
),
),
pht('Oldest (All)'));
$cclass[] = 'n';
$cname[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => pht('Oldest open task, excluding those with Low or '.
'Wishlist priority.'),
'size' => 200,
),
),
pht('Oldest (Pri)'));
$cclass[] = 'n';
list($ignored, $window_epoch) = $this->getWindow();
$edate = phabricator_datetime($window_epoch, $user);
$cname[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => pht('Closed after %s', $edate),
'size' => 260
),
),
pht('Recently Closed'));
$cclass[] = 'n';
$table = new AphrontTableView($rows);
$table->setHeaders($cname);
$table->setColumnClasses($cclass);
$table->makeSortable(
$request->getRequestURI(),
'order',
$order,
$reverse,
array(
'name',
null,
null,
null,
null,
null,
null,
'total',
'oldest-all',
'oldest-pri',
'closed',
));
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->appendChild($table);
$tokens = array();
if ($project_handle) {
$tokens = array($project_handle);
}
$filter = $this->renderReportFilters($tokens, $has_window = true);
return array($filter, $panel);
}
/**
* Load all the tasks that have been recently closed.
*/
private function loadRecentlyClosedTasks() {
list($ignored, $window_epoch) = $this->getWindow();
$table = new ManiphestTask();
$xtable = new ManiphestTransaction();
$conn_r = $table->establishConnection('r');
$tasks = queryfx_all(
$conn_r,
'SELECT t.* FROM %T t JOIN %T x ON x.objectPHID = t.phid
WHERE t.status != 0
AND x.oldValue IN (null, %s, %s)
AND x.newValue NOT IN (%s, %s)
AND t.dateModified >= %d
AND x.dateCreated >= %d',
$table->getTableName(),
$xtable->getTableName(),
// TODO: Gross. This table is not meant to be queried like this. Build
// real stats tables.
json_encode((int)ManiphestTaskStatus::STATUS_OPEN),
json_encode((string)ManiphestTaskStatus::STATUS_OPEN),
json_encode((int)ManiphestTaskStatus::STATUS_OPEN),
json_encode((string)ManiphestTaskStatus::STATUS_OPEN),
$window_epoch,
$window_epoch);
return id(new ManiphestTask())->loadAllFromArray($tasks);
}
/**
* Parse the "Recently Means" filter into:
*
* - A string representation, like "12 AM 7 days ago" (default);
* - a locale-aware epoch representation; and
* - a possible error.
*/
private function getWindow() {
$request = $this->getRequest();
$user = $request->getUser();
$window_str = $this->getRequest()->getStr('window', '12 AM 7 days ago');
$error = null;
$window_epoch = null;
// Do locale-aware parsing so that the user's timezone is assumed for
// time windows like "3 PM", rather than assuming the server timezone.
$window_epoch = PhabricatorTime::parseLocalTime($window_str, $user);
if (!$window_epoch) {
$error = 'Invalid';
$window_epoch = time() - (60 * 60 * 24 * 7);
}
// If the time ends up in the future, convert it to the corresponding time
// and equal distance in the past. This is so users can type "6 days" (which
// means "6 days from now") and get the behavior of "6 days ago", rather
// than no results (because the window epoch is in the future). This might
// be a little confusing because it casues "tomorrow" to mean "yesterday"
// and "2022" (or whatever) to mean "ten years ago", but these inputs are
// nonsense anyway.
if ($window_epoch > time()) {
$window_epoch = time() - ($window_epoch - time());
}
return array($window_str, $window_epoch, $error);
}
private function renderOldest(array $tasks) {
assert_instances_of($tasks, 'ManiphestTask');
$oldest = null;
foreach ($tasks as $id => $task) {
if (($oldest === null) ||
($task->getDateCreated() < $tasks[$oldest]->getDateCreated())) {
$oldest = $id;
}
}
if ($oldest === null) {
return array('-', 0);
}
$oldest = $tasks[$oldest];
$raw_age = (time() - $oldest->getDateCreated());
$age = number_format($raw_age / (24 * 60 * 60)).' d';
$link = javelin_tag(
'a',
array(
'href' => '/T'.$oldest->getID(),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => 'T'.$oldest->getID().': '.$oldest->getTitle(),
),
'target' => '_blank',
),
$age);
return array($link, $raw_age);
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php b/src/applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php
index ee185362c5..98725245f1 100644
--- a/src/applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php
@@ -1,28 +1,26 @@
<?php
/**
* @group maniphest
*/
final class ManiphestTaskDescriptionPreviewController
extends ManiphestController {
public function processRequest() {
$request = $this->getRequest();
$description = $request->getStr('description');
$task = new ManiphestTask();
$task->setDescription($description);
$output = PhabricatorMarkupEngine::renderOneObject(
$task,
ManiphestTask::MARKUP_FIELD_DESCRIPTION,
$request->getUser());
- $content = hsprintf(
- '<div class="phabricator-remarkup">%s</div>',
- $output);
+ $content = phutil_tag_div('phabricator-remarkup', $output);
return id(new AphrontAjaxResponse())
->setContent($content);
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
index 99b2d907d2..36665045a0 100644
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -1,666 +1,667 @@
<?php
final class ManiphestTaskDetailController extends ManiphestController {
private $id;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$e_title = null;
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
$task = id(new ManiphestTaskQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$task) {
return new Aphront404Response();
}
$workflow = $request->getStr('workflow');
$parent_task = null;
if ($workflow && is_numeric($workflow)) {
$parent_task = id(new ManiphestTaskQuery())
->setViewer($user)
->withIDs(array($workflow))
->executeOne();
}
$transactions = id(new ManiphestTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($task->getPHID()))
->needComments(true)
->execute();
$field_list = PhabricatorCustomField::getObjectFields(
$task,
PhabricatorCustomField::ROLE_VIEW);
foreach ($field_list->getFields() as $field) {
$field->setObject($task);
$field->setViewer($user);
}
$field_list->readFieldsFromStorage($task);
$aux_fields = $field_list->getFields();
$e_commit = PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT;
$e_dep_on = PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK;
$e_dep_by = PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK;
$e_rev = PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV;
$e_mock = PhabricatorEdgeConfig::TYPE_TASK_HAS_MOCK;
$phid = $task->getPHID();
$query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($phid))
->withEdgeTypes(
array(
$e_commit,
$e_dep_on,
$e_dep_by,
$e_rev,
$e_mock,
));
$edges = idx($query->execute(), $phid);
$phids = array_fill_keys($query->getDestinationPHIDs(), true);
foreach ($task->getCCPHIDs() as $phid) {
$phids[$phid] = true;
}
foreach ($task->getProjectPHIDs() as $phid) {
$phids[$phid] = true;
}
if ($task->getOwnerPHID()) {
$phids[$task->getOwnerPHID()] = true;
}
$phids[$task->getAuthorPHID()] = true;
$attached = $task->getAttached();
foreach ($attached as $type => $list) {
foreach ($list as $phid => $info) {
$phids[$phid] = true;
}
}
if ($parent_task) {
$phids[$parent_task->getPHID()] = true;
}
$phids = array_keys($phids);
$this->loadHandles($phids);
$handles = $this->getLoadedHandles();
$context_bar = null;
if ($parent_task) {
$context_bar = new AphrontContextBarView();
$context_bar->addButton(phutil_tag(
'a',
array(
'href' => '/maniphest/task/create/?parent='.$parent_task->getID(),
'class' => 'green button',
),
pht('Create Another Subtask')));
$context_bar->appendChild(hsprintf(
'Created a subtask of <strong>%s</strong>',
$this->getHandle($parent_task->getPHID())->renderLink()));
} else if ($workflow == 'create') {
$context_bar = new AphrontContextBarView();
$context_bar->addButton(phutil_tag('label', array(), 'Create Another'));
$context_bar->addButton(phutil_tag(
'a',
array(
'href' => '/maniphest/task/create/?template='.$task->getID(),
'class' => 'green button',
),
pht('Similar Task')));
$context_bar->addButton(phutil_tag(
'a',
array(
'href' => '/maniphest/task/create/',
'class' => 'green button',
),
pht('Empty Task')));
$context_bar->appendChild(pht('New task created.'));
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
$engine->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION);
foreach ($transactions as $modern_xaction) {
if ($modern_xaction->getComment()) {
$engine->addObject(
$modern_xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$resolution_types = ManiphestTaskStatus::getTaskStatusMap();
$transaction_types = array(
PhabricatorTransactions::TYPE_COMMENT => pht('Comment'),
ManiphestTransaction::TYPE_STATUS => pht('Close Task'),
ManiphestTransaction::TYPE_OWNER => pht('Reassign / Claim'),
ManiphestTransaction::TYPE_CCS => pht('Add CCs'),
ManiphestTransaction::TYPE_PRIORITY => pht('Change Priority'),
ManiphestTransaction::TYPE_ATTACH => pht('Upload File'),
ManiphestTransaction::TYPE_PROJECTS => pht('Associate Projects'),
);
// Remove actions the user doesn't have permission to take.
$requires = array(
ManiphestTransaction::TYPE_OWNER =>
ManiphestCapabilityEditAssign::CAPABILITY,
ManiphestTransaction::TYPE_PRIORITY =>
ManiphestCapabilityEditPriority::CAPABILITY,
ManiphestTransaction::TYPE_PROJECTS =>
ManiphestCapabilityEditProjects::CAPABILITY,
ManiphestTransaction::TYPE_STATUS =>
ManiphestCapabilityEditStatus::CAPABILITY,
);
foreach ($transaction_types as $type => $name) {
if (isset($requires[$type])) {
if (!$this->hasApplicationCapability($requires[$type])) {
unset($transaction_types[$type]);
}
}
}
if ($task->getStatus() == ManiphestTaskStatus::STATUS_OPEN) {
$resolution_types = array_select_keys(
$resolution_types,
array(
ManiphestTaskStatus::STATUS_CLOSED_RESOLVED,
ManiphestTaskStatus::STATUS_CLOSED_WONTFIX,
ManiphestTaskStatus::STATUS_CLOSED_INVALID,
ManiphestTaskStatus::STATUS_CLOSED_SPITE,
));
} else {
$resolution_types = array(
ManiphestTaskStatus::STATUS_OPEN => 'Reopened',
);
$transaction_types[ManiphestTransaction::TYPE_STATUS] =
'Reopen Task';
unset($transaction_types[ManiphestTransaction::TYPE_PRIORITY]);
unset($transaction_types[ManiphestTransaction::TYPE_OWNER]);
}
$default_claim = array(
$user->getPHID() => $user->getUsername().' ('.$user->getRealName().')',
);
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
$task->getPHID());
if ($draft) {
$draft_text = $draft->getDraft();
} else {
$draft_text = null;
}
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
// Prevent tasks from being closed "out of spite" in serious business
// installs.
unset($resolution_types[ManiphestTaskStatus::STATUS_CLOSED_SPITE]);
}
$comment_form = new AphrontFormView();
$comment_form
->setUser($user)
->setAction('/maniphest/transaction/save/')
->setEncType('multipart/form-data')
->addHiddenInput('taskID', $task->getID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Action'))
->setName('action')
->setOptions($transaction_types)
->setID('transaction-action'))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Resolution'))
->setName('resolution')
->setControlID('resolution')
->setControlStyle('display: none')
->setOptions($resolution_types))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Assign To'))
->setName('assign_to')
->setControlID('assign_to')
->setControlStyle('display: none')
->setID('assign-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('CCs'))
->setName('ccs')
->setControlID('ccs')
->setControlStyle('display: none')
->setID('cc-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Priority'))
->setName('priority')
->setOptions($priority_map)
->setControlID('priority')
->setControlStyle('display: none')
->setValue($task->getPriority()))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName('projects')
->setControlID('projects')
->setControlStyle('display: none')
->setID('projects-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormFileControl())
->setLabel(pht('File'))
->setName('file')
->setControlID('file')
->setControlStyle('display: none'))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Comments'))
->setName('comments')
->setValue($draft_text)
->setID('transaction-comments')
->setUser($user))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? pht('Submit') : pht('Avast!')));
$control_map = array(
ManiphestTransaction::TYPE_STATUS => 'resolution',
ManiphestTransaction::TYPE_OWNER => 'assign_to',
ManiphestTransaction::TYPE_CCS => 'ccs',
ManiphestTransaction::TYPE_PRIORITY => 'priority',
ManiphestTransaction::TYPE_PROJECTS => 'projects',
ManiphestTransaction::TYPE_ATTACH => 'file',
);
$tokenizer_map = array(
ManiphestTransaction::TYPE_PROJECTS => array(
'id' => 'projects-tokenizer',
'src' => '/typeahead/common/projects/',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a project name...'),
),
ManiphestTransaction::TYPE_OWNER => array(
'id' => 'assign-tokenizer',
'src' => '/typeahead/common/users/',
'value' => $default_claim,
'limit' => 1,
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user name...'),
),
ManiphestTransaction::TYPE_CCS => array(
'id' => 'cc-tokenizer',
'src' => '/typeahead/common/mailable/',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user or mailing list...'),
),
);
// TODO: Initializing these behaviors for logged out users fatals things.
if ($user->isLoggedIn()) {
Javelin::initBehavior('maniphest-transaction-controls', array(
'select' => 'transaction-action',
'controlMap' => $control_map,
'tokenizers' => $tokenizer_map,
));
Javelin::initBehavior('maniphest-transaction-preview', array(
'uri' => '/maniphest/transaction/preview/'.$task->getID().'/',
'preview' => 'transaction-preview',
'comments' => 'transaction-comments',
'action' => 'transaction-action',
'map' => $control_map,
'tokenizers' => $tokenizer_map,
));
}
$comment_header = id(new PHUIHeaderView())
->setHeader($is_serious ? pht('Add Comment') : pht('Weigh In'));
- $preview_panel = hsprintf(
- '<div class="aphront-panel-preview">
- <div id="transaction-preview">
- <div class="aphront-panel-preview-loading-text">%s</div>
- </div>
- </div>',
- pht('Loading preview...'));
+ $preview_panel = phutil_tag_div(
+ 'aphront-panel-preview',
+ phutil_tag(
+ 'div',
+ array('id' => 'transaction-preview'),
+ phutil_tag_div(
+ 'aphront-panel-preview-loading-text',
+ pht('Loading preview...'))));
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setObjectPHID($task->getPHID())
->setTransactions($transactions)
->setMarkupEngine($engine);
$object_name = 'T'.$task->getID();
$actions = $this->buildActionView($task);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($object_name)
->setHref('/'.$object_name))
->setActionList($actions);
$header = $this->buildHeaderView($task);
$properties = $this->buildPropertyView(
$task, $field_list, $edges, $actions);
$description = $this->buildDescriptionView($task, $engine);
if (!$user->isLoggedIn()) {
// TODO: Eventually, everything should run through this. For now, we're
// only using it to get a consistent "Login to Comment" button.
$comment_form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setRequestURI($request->getRequestURI());
$preview_panel = null;
}
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
if ($description) {
$object_box->addPropertyList($description);
}
$comment_box = id(new PHUIObjectBoxView())
->setFlush(true)
->setHeader($comment_header)
->appendChild($comment_form);
return $this->buildApplicationPage(
array(
$crumbs,
$context_bar,
$object_box,
$timeline,
$comment_box,
$preview_panel,
),
array(
'title' => 'T'.$task->getID().' '.$task->getTitle(),
'pageObjects' => array($task->getPHID()),
'device' => true,
));
}
private function buildHeaderView(ManiphestTask $task) {
$view = id(new PHUIHeaderView())
->setHeader($task->getTitle())
->setUser($this->getRequest()->getUser())
->setPolicyObject($task);
$status = $task->getStatus();
$status_name = ManiphestTaskStatus::renderFullDescription($status);
$view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name);
return $view;
}
private function buildActionView(ManiphestTask $task) {
$viewer = $this->getRequest()->getUser();
$viewer_phid = $viewer->getPHID();
$viewer_is_cc = in_array($viewer_phid, $task->getCCPHIDs());
$id = $task->getID();
$phid = $task->getPHID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$task,
PhabricatorPolicyCapability::CAN_EDIT);
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($task)
->setObjectURI($this->getRequest()->getRequestURI());
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Task'))
->setIcon('edit')
->setHref($this->getApplicationURI("/task/edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($task->getOwnerPHID() === $viewer_phid) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Automatically Subscribed'))
->setDisabled(true)
->setIcon('enable'));
} else {
$action = $viewer_is_cc ? 'rem' : 'add';
$name = $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe');
$icon = $viewer_is_cc ? 'disable' : 'check';
$view->addAction(
id(new PhabricatorActionView())
->setName($name)
->setHref("/maniphest/subscribe/{$action}/{$id}/")
->setRenderAsForm(true)
->setUser($viewer)
->setIcon($icon));
}
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Merge Duplicates In'))
->setHref("/search/attach/{$phid}/TASK/merge/")
->setWorkflow(true)
->setIcon('merge')
->setDisabled(!$can_edit)
->setWorkflow(true));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Subtask'))
->setHref($this->getApplicationURI("/task/create/?parent={$id}"))
->setIcon('fork'));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Dependencies'))
->setHref("/search/attach/{$phid}/TASK/dependencies/")
->setWorkflow(true)
->setIcon('link')
->setDisabled(!$can_edit)
->setWorkflow(true));
return $view;
}
private function buildPropertyView(
ManiphestTask $task,
PhabricatorCustomFieldList $field_list,
array $edges,
PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($task)
->setActionList($actions);
$view->addProperty(
pht('Assigned To'),
$task->getOwnerPHID()
? $this->getHandle($task->getOwnerPHID())->renderLink()
: phutil_tag('em', array(), pht('None')));
$view->addProperty(
pht('Priority'),
ManiphestTaskPriority::getTaskPriorityName($task->getPriority()));
$view->addProperty(
pht('Subscribers'),
$task->getCCPHIDs()
? $this->renderHandlesForPHIDs($task->getCCPHIDs(), ',')
: phutil_tag('em', array(), pht('None')));
$view->addProperty(
pht('Author'),
$this->getHandle($task->getAuthorPHID())->renderLink());
$source = $task->getOriginalEmailSource();
if ($source) {
$subject = '[T'.$task->getID().'] '.$task->getTitle();
$view->addProperty(
pht('From Email'),
phutil_tag(
'a',
array(
'href' => 'mailto:'.$source.'?subject='.$subject
),
$source));
}
$view->addProperty(
pht('Projects'),
$task->getProjectPHIDs()
? $this->renderHandlesForPHIDs($task->getProjectPHIDs(), ',')
: phutil_tag('em', array(), pht('None')));
$edge_types = array(
PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK
=> pht('Dependent Tasks'),
PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK
=> pht('Depends On'),
PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV
=> pht('Differential Revisions'),
PhabricatorEdgeConfig::TYPE_TASK_HAS_MOCK
=> pht('Pholio Mocks'),
);
$revisions_commits = array();
$handles = $this->getLoadedHandles();
$commit_phids = array_keys(
$edges[PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT]);
if ($commit_phids) {
$commit_drev = PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV;
$drev_edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($commit_phids)
->withEdgeTypes(array($commit_drev))
->execute();
foreach ($commit_phids as $phid) {
$revisions_commits[$phid] = $handles[$phid]->renderLink();
$revision_phid = key($drev_edges[$phid][$commit_drev]);
$revision_handle = idx($handles, $revision_phid);
if ($revision_handle) {
$task_drev = PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV;
unset($edges[$task_drev][$revision_phid]);
$revisions_commits[$phid] = hsprintf(
'%s / %s',
$revision_handle->renderLink($revision_handle->getName()),
$revisions_commits[$phid]);
}
}
}
foreach ($edge_types as $edge_type => $edge_name) {
if ($edges[$edge_type]) {
$view->addProperty(
$edge_name,
$this->renderHandlesForPHIDs(array_keys($edges[$edge_type])));
}
}
if ($revisions_commits) {
$view->addProperty(
pht('Commits'),
phutil_implode_html(phutil_tag('br'), $revisions_commits));
}
$attached = $task->getAttached();
if (!is_array($attached)) {
$attached = array();
}
$file_infos = idx($attached, PhabricatorFilePHIDTypeFile::TYPECONST);
if ($file_infos) {
$file_phids = array_keys($file_infos);
// TODO: These should probably be handles or something; clean this up
// as we sort out file attachments.
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs($file_phids)
->execute();
$file_view = new PhabricatorFileLinkListView();
$file_view->setFiles($files);
$view->addProperty(
pht('Files'),
$file_view->render());
}
$field_list->appendFieldsToPropertyList(
$task,
$viewer,
$view);
$view->invokeWillRenderEvent();
return $view;
}
private function buildDescriptionView(
ManiphestTask $task,
PhabricatorMarkupEngine $engine) {
$section = null;
if (strlen($task->getDescription())) {
$section = new PHUIPropertyListView();
$section->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$section->addTextContent(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION)));
}
return $section;
}
}
diff --git a/src/applications/notification/controller/PhabricatorNotificationListController.php b/src/applications/notification/controller/PhabricatorNotificationListController.php
index 1557c8acc8..b497620be6 100644
--- a/src/applications/notification/controller/PhabricatorNotificationListController.php
+++ b/src/applications/notification/controller/PhabricatorNotificationListController.php
@@ -1,81 +1,79 @@
<?php
final class PhabricatorNotificationListController
extends PhabricatorNotificationController {
private $filter;
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/notification/'));
$nav->addFilter('all', 'All Notifications');
$nav->addFilter('unread', 'Unread Notifications');
$filter = $nav->selectFilter($this->filter, 'all');
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setOffset($request->getInt('offset'));
$query = new PhabricatorNotificationQuery();
$query->setViewer($user);
$query->setUserPHID($user->getPHID());
switch ($filter) {
case 'unread':
$query->withUnread(true);
$header = pht('Unread Notifications');
$no_data = pht('You have no unread notifications.');
break;
default:
$header = pht('Notifications');
$no_data = pht('You have no notifications.');
break;
}
$notifications = $query->executeWithOffsetPager($pager);
if ($notifications) {
$builder = new PhabricatorNotificationBuilder($notifications);
$view = $builder->buildView()->render();
} else {
- $view = hsprintf(
- '<div class="phabricator-notification no-notifications">%s</div>',
+ $view = phutil_tag_div(
+ 'phabricator-notification no-notifications',
$no_data);
}
- $view = hsprintf(
- '<div class="phabricator-notification-list">%s</div>',
- $view);
+ $view = phutil_tag_div('phabricator-notification-list', $view);
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->addButton(
javelin_tag(
'a',
array(
'href' => '/notification/clear/',
'class' => 'button',
'sigil' => 'workflow',
),
'Mark All Read'));
$panel->appendChild($view);
$panel->appendChild($pager);
$nav->appendChild($panel);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Notifications',
));
}
}
diff --git a/src/applications/notification/controller/PhabricatorNotificationPanelController.php b/src/applications/notification/controller/PhabricatorNotificationPanelController.php
index 567aec38d5..8adc1e15a9 100644
--- a/src/applications/notification/controller/PhabricatorNotificationPanelController.php
+++ b/src/applications/notification/controller/PhabricatorNotificationPanelController.php
@@ -1,51 +1,51 @@
<?php
final class PhabricatorNotificationPanelController
extends PhabricatorNotificationController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$query = new PhabricatorNotificationQuery();
$query->setViewer($user);
$query->setUserPHID($user->getPHID());
$query->setLimit(15);
$stories = $query->execute();
if ($stories) {
$builder = new PhabricatorNotificationBuilder($stories);
$notifications_view = $builder->buildView();
$content = $notifications_view->render();
} else {
- $content = hsprintf(
- '<div class="phabricator-notification no-notifications">%s</div>',
+ $content = phutil_tag_div(
+ 'phabricator-notification no-notifications',
pht('You have no notifications.'));
}
$content = hsprintf(
'<div class="phabricator-notification-header">%s</div>'.
'%s'.
'<div class="phabricator-notification-view-all">%s</div>',
pht('Notifications'),
$content,
phutil_tag(
'a',
array(
'href' => '/notification/',
),
'View All Notifications'));
$unread_count = id(new PhabricatorFeedStoryNotification())
->countUnread($user);
$json = array(
'content' => $content,
'number' => (int)$unread_count,
);
return id(new AphrontAjaxResponse())->setContent($json);
}
}
diff --git a/src/applications/owners/controller/PhabricatorOwnersDeleteController.php b/src/applications/owners/controller/PhabricatorOwnersDeleteController.php
index dd1cf89b59..306658eb6b 100644
--- a/src/applications/owners/controller/PhabricatorOwnersDeleteController.php
+++ b/src/applications/owners/controller/PhabricatorOwnersDeleteController.php
@@ -1,42 +1,40 @@
<?php
final class PhabricatorOwnersDeleteController
extends PhabricatorOwnersController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$package = id(new PhabricatorOwnersPackage())->load($this->id);
if (!$package) {
return new Aphront404Response();
}
if ($request->isDialogFormPost()) {
$package->attachActorPHID($user->getPHID());
$package->delete();
return id(new AphrontRedirectResponse())->setURI('/owners/');
}
$text = pht('Are you sure you want to delete the "%s" package? This '.
'operation can not be undone.', $package->getName());
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle('Really delete this package?')
- ->appendChild(hsprintf(
- '<p>%s</p>',
- $text))
+ ->appendChild(phutil_tag('p', array(), $text))
->addSubmitButton(pht('Delete'))
->addCancelButton('/owners/package/'.$package->getID().'/')
->setSubmitURI($request->getRequestURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleEditController.php b/src/applications/people/controller/PhabricatorPeopleEditController.php
index f952899e89..ab466dabf7 100644
--- a/src/applications/people/controller/PhabricatorPeopleEditController.php
+++ b/src/applications/people/controller/PhabricatorPeopleEditController.php
@@ -1,837 +1,836 @@
<?php
final class PhabricatorPeopleEditController
extends PhabricatorPeopleController {
private $id;
private $view;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->view = idx($data, 'view');
}
public function processRequest() {
$request = $this->getRequest();
$admin = $request->getUser();
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
if ($this->id) {
$user = id(new PhabricatorUser())->load($this->id);
if (!$user) {
return new Aphront404Response();
}
$base_uri = '/people/edit/'.$user->getID().'/';
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit User'))
->setHref('/people/edit/'));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($user->getFullName())
->setHref($base_uri));
} else {
$user = new PhabricatorUser();
$base_uri = '/people/edit/';
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Create New User'))
->setHref($base_uri));
}
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($base_uri));
$nav->addLabel(pht('User Information'));
$nav->addFilter('basic', pht('Basic Information'));
$nav->addFilter('role', pht('Edit Roles'));
$nav->addFilter('cert', pht('Conduit Certificate'));
$nav->addFilter('profile',
pht('View Profile'), '/p/'.$user->getUsername().'/');
$nav->addLabel(pht('Special'));
$nav->addFilter('rename', pht('Change Username'));
if ($user->getIsSystemAgent()) {
$nav->addFilter('picture', pht('Set Account Picture'));
}
$nav->addFilter('delete', pht('Delete User'));
if (!$user->getID()) {
$this->view = 'basic';
}
$view = $nav->selectFilter($this->view, 'basic');
$content = array();
if ($request->getStr('saved')) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Changes Saved'));
$notice->appendChild(
phutil_tag('p', array(), pht('Your changes were saved.')));
$content[] = $notice;
}
switch ($view) {
case 'basic':
$response = $this->processBasicRequest($user);
break;
case 'role':
$response = $this->processRoleRequest($user);
break;
case 'cert':
$response = $this->processCertificateRequest($user);
break;
case 'rename':
$response = $this->processRenameRequest($user);
break;
case 'picture':
$response = $this->processSetAccountPicture($user);
break;
case 'delete':
$response = $this->processDeleteRequest($user);
break;
default:
return new Aphront404Response();
}
if ($response instanceof AphrontResponse) {
return $response;
}
$content[] = $response;
if ($user->getID()) {
$nav->appendChild($content);
} else {
$nav = $this->buildSideNavView();
$nav->selectFilter('edit');
$nav->appendChild($content);
}
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Edit User'),
'device' => true,
));
}
private function processBasicRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$e_username = true;
$e_realname = true;
$e_email = true;
$errors = array();
$welcome_checked = true;
$new_email = null;
$request = $this->getRequest();
if ($request->isFormPost()) {
$welcome_checked = $request->getInt('welcome');
$is_new = !$user->getID();
if ($is_new) {
$user->setUsername($request->getStr('username'));
$new_email = $request->getStr('email');
if (!strlen($new_email)) {
$errors[] = pht('Email is required.');
$e_email = pht('Required');
} else if (!PhabricatorUserEmail::isAllowedAddress($new_email)) {
$e_email = pht('Invalid');
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
} else {
$e_email = null;
}
}
$user->setRealName($request->getStr('realname'));
if (!strlen($user->getUsername())) {
$errors[] = pht("Username is required.");
$e_username = pht('Required');
} else if (!PhabricatorUser::validateUsername($user->getUsername())) {
$errors[] = PhabricatorUser::describeValidUsername();
$e_username = pht('Invalid');
} else {
$e_username = null;
}
if (!strlen($user->getRealName())) {
$errors[] = pht('Real name is required.');
$e_realname = pht('Required');
} else {
$e_realname = null;
}
if (!$errors) {
try {
if (!$is_new) {
id(new PhabricatorUserEditor())
->setActor($admin)
->updateUser($user);
} else {
$email = id(new PhabricatorUserEmail())
->setAddress($new_email)
->setIsVerified(0);
id(new PhabricatorUserEditor())
->setActor($admin)
->createNewUser($user, $email);
if ($request->getStr('role') == 'agent') {
id(new PhabricatorUserEditor())
->setActor($admin)
->makeSystemAgentUser($user, true);
}
}
if ($welcome_checked) {
$user->sendWelcomeEmail($admin);
}
$response = id(new AphrontRedirectResponse())
->setURI('/people/edit/'.$user->getID().'/?saved=true');
return $response;
} catch (AphrontQueryDuplicateKeyException $ex) {
$errors[] = pht('Username and email must be unique.');
$same_username = id(new PhabricatorUser())
->loadOneWhere('username = %s', $user->getUsername());
$same_email = id(new PhabricatorUserEmail())
->loadOneWhere('address = %s', $new_email);
if ($same_username) {
$e_username = pht('Duplicate');
}
if ($same_email) {
$e_email = pht('Duplicate');
}
}
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
$form = new AphrontFormView();
$form->setUser($admin);
if ($user->getID()) {
$form->setAction('/people/edit/'.$user->getID().'/');
} else {
$form->setAction('/people/edit/');
}
if ($user->getID()) {
$is_immutable = true;
} else {
$is_immutable = false;
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Username'))
->setName('username')
->setValue($user->getUsername())
->setError($e_username)
->setDisabled($is_immutable))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Real Name'))
->setName('realname')
->setValue($user->getRealName())
->setError($e_realname));
if (!$user->getID()) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setDisabled($is_immutable)
->setValue($new_email)
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
->setError($e_email));
} else {
$email = $user->loadPrimaryEmail();
if ($email) {
$status = $email->getIsVerified() ?
pht('Verified') : pht('Unverified');
} else {
$status = pht('No Email Address');
}
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Email'))
->setValue($status));
$form->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'welcome',
1,
pht('Re-send "Welcome to Phabricator" email.'),
false));
}
$form->appendChild($this->getRoleInstructions());
if (!$user->getID()) {
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Role'))
->setName('role')
->setValue('user')
->setOptions(
array(
'user' => pht('Normal User'),
'agent' => pht('System Agent'),
))
->setCaption(
pht('You can create a "system agent" account for bots, '.
'scripts, etc.')))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'welcome',
1,
pht('Send "Welcome to Phabricator" email.'),
$welcome_checked));
} else {
$roles = array();
if ($user->getIsSystemAgent()) {
$roles[] = pht('System Agent');
}
if ($user->getIsAdmin()) {
$roles[] = pht('Admin');
}
if ($user->getIsDisabled()) {
$roles[] = pht('Disabled');
}
if (!$roles) {
$roles[] = pht('Normal User');
}
$roles = implode(', ', $roles);
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Roles'))
->setValue($roles));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save')));
if ($user->getID()) {
$title = pht('Edit User');
} else {
$title = pht('Create New User');
}
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormError($error_view)
->setForm($form);
return array($form_box);
}
private function processRoleRequest(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$is_self = ($user->getID() == $admin->getID());
$errors = array();
if ($request->isFormPost()) {
$log_template = PhabricatorUserLog::newLog(
$admin,
$user,
null);
$logs = array();
if ($is_self) {
$errors[] = pht("You can not edit your own role.");
} else {
$new_admin = (bool)$request->getBool('is_admin');
$old_admin = (bool)$user->getIsAdmin();
if ($new_admin != $old_admin) {
id(new PhabricatorUserEditor())
->setActor($admin)
->makeAdminUser($user, $new_admin);
}
$new_disabled = (bool)$request->getBool('is_disabled');
$old_disabled = (bool)$user->getIsDisabled();
if ($new_disabled != $old_disabled) {
id(new PhabricatorUserEditor())
->setActor($admin)
->disableUser($user, $new_disabled);
}
}
if (!$errors) {
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('saved', 'true'));
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($admin)
->setAction($request->getRequestURI()->alter('saved', null));
if ($is_self) {
$inst = pht('NOTE: You can not edit your own role.');
- $form->appendChild(hsprintf(
- '<p class="aphront-form-instructions">%s</p>', $inst));
+ $form->appendChild(
+ phutil_tag('p', array('class' => 'aphront-form-instructions'), $inst));
}
$form
->appendChild($this->getRoleInstructions())
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_admin',
1,
pht('Administrator'),
$user->getIsAdmin())
->setDisabled($is_self))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_disabled',
1,
pht('Disabled'),
$user->getIsDisabled())
->setDisabled($is_self))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'is_agent',
1,
pht('System Agent (Bot/Script User)'),
$user->getIsSystemAgent())
->setDisabled(true));
if (!$is_self) {
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Edit Role')));
}
$title = pht('Edit Role');
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormError($error_view)
->setForm($form);
return array($form_box);
}
private function processCertificateRequest($user) {
$request = $this->getRequest();
$admin = $request->getUser();
$inst = pht('You can use this certificate '.
'to write scripts or bots which interface with Phabricator over '.
'Conduit.');
$form = new AphrontFormView();
$form
->setUser($admin)
->setAction($request->getRequestURI())
- ->appendChild(hsprintf(
- '<p class="aphront-form-instructions">%s</p>', $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(
'<p class="aphront-form-instructions">'.
'<strong>%s</strong> '.
'%s'.
'</p>'.
'<p class="aphront-form-instructions">'.
'%s'.
'</p>'.
'<p class="aphront-form-instructions">'.
'%s'.
'</p>', $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(
'<p>%s</p><p>%s</p>', $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(
'<p class="aphront-form-instructions">'.
'<strong>%s</strong> %s'.
'</p>'.
'<p class="aphront-form-instructions">'.
'%s'.
'</p>', $str1, $str2, $str3))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Username'))
->setValue($user->getUsername()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Confirm'))
->setValue($username)
->setName('username')
->setCaption(pht("Type the username again to confirm deletion."))
->setError($e_username))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Delete User')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Delete User'))
->setFormError($errors)
->setForm($form);
return array($form_box);
}
private function getRoleInstructions() {
$roles_link = phutil_tag(
'a',
array(
'href' => PhabricatorEnv::getDoclink(
'article/User_Guide_Account_Roles.html'),
'target' => '_blank',
),
pht('User Guide: Account Roles'));
- $inst = pht('For a detailed explanation of account roles, see %s.',
- $roles_link);
- return hsprintf(
- '<p class="aphront-form-instructions">%s</p>',
- $inst);
+ return phutil_tag(
+ 'p',
+ array('class' => 'aphront-form-instructions'),
+ pht('For a detailed explanation of account roles, see %s.', $roles_link));
}
private function processSetAccountPicture(PhabricatorUser $user) {
$request = $this->getRequest();
$admin = $request->getUser();
$profile = $user->loadUserProfile();
if (!$profile->getID()) {
$profile->setTitle('');
$profile->setBlurb('');
}
$supported_formats = PhabricatorFile::getTransformableImageFormats();
$e_image = null;
$errors = array();
if ($request->isFormPost()) {
$default_image = $request->getExists('default_image');
if ($default_image) {
$profile->setProfileImagePHID(null);
$user->setProfileImagePHID(null);
} else if ($request->getFileExists('image')) {
$file = null;
$file = PhabricatorFile::newFromPHPUpload(
$_FILES['image'],
array(
'authorPHID' => $admin->getPHID(),
));
$okay = $file->isTransformableImage();
if ($okay) {
$xformer = new PhabricatorImageTransformer();
// Generate the large picture for the profile page.
$large_xformed = $xformer->executeProfileTransform(
$file,
$width = 280,
$min_height = 140,
$max_height = 420);
$profile->setProfileImagePHID($large_xformed->getPHID());
// Generate the small picture for comments, etc.
$small_xformed = $xformer->executeProfileTransform(
$file,
$width = 50,
$min_height = 50,
$max_height = 50);
$user->setProfileImagePHID($small_xformed->getPHID());
} else {
$e_image = pht('Not Supported');
$errors[] =
pht('This server only supports these image formats:').
' ' .implode(', ', $supported_formats);
}
}
if (!$errors) {
$user->save();
$profile->save();
$response = id(new AphrontRedirectResponse())
->setURI('/people/edit/'.$user->getID().'/picture/');
return $response;
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Form Errors'));
$error_view->setErrors($errors);
} else {
if ($request->getStr('saved')) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$error_view->setTitle(pht('Changes Saved'));
$error_view->appendChild(
phutil_tag('p', array(), pht('Your changes have been saved.')));
$error_view = $error_view->render();
}
}
$img_src = $user->loadProfileImageURI();
$form = new AphrontFormView();
$form
->setUser($admin)
->setAction($request->getRequestURI())
->setEncType('multipart/form-data')
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Profile Image'))
->setValue(
phutil_tag(
'img',
array(
'src' => $img_src,
))))
->appendChild(
id(new AphrontFormImageControl())
->setLabel(pht('Change Image'))
->setName('image')
->setError($e_image)
->setCaption(
pht('Supported formats: %s', implode(', ', $supported_formats))));
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save'))
->addCancelButton('/people/edit/'.$user->getID().'/'));
$panel = new AphrontPanelView();
$panel->setHeader(pht('Set Profile Picture'));
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->setNoBackground();
$panel->appendChild($form);
return array($error_view, $panel);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php
index 5098438f50..261d2f27a6 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php
@@ -1,136 +1,134 @@
<?php
final class PhabricatorPeopleProfileController
extends PhabricatorPeopleController {
private $username;
public function shouldRequireAdmin() {
return false;
}
public function willProcessRequest(array $data) {
$this->username = idx($data, 'username');
}
public function processRequest() {
$viewer = $this->getRequest()->getUser();
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withUsernames(array($this->username))
->needProfileImage(true)
->executeOne();
if (!$user) {
return new Aphront404Response();
}
require_celerity_resource('phabricator-profile-css');
$profile = $user->loadUserProfile();
$username = phutil_escape_uri($user->getUserName());
$picture = $user->loadProfileImageURI();
$header = id(new PHUIHeaderView())
->setHeader($user->getUserName().' ('.$user->getRealName().')')
->setSubheader($profile->getTitle())
->setImage($picture);
$actions = id(new PhabricatorActionListView())
->setObject($user)
->setObjectURI($this->getRequest()->getRequestURI())
->setUser($viewer);
$can_edit = ($user->getPHID() == $viewer->getPHID());
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Profile'))
->setHref($this->getApplicationURI('editprofile/'.$user->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('image')
->setName(pht('Edit Profile Picture'))
->setHref($this->getApplicationURI('picture/'.$user->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($viewer->getIsAdmin()) {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('blame')
->setName(pht('Administrate User'))
->setHref($this->getApplicationURI('edit/'.$user->getID().'/')));
}
$properties = $this->buildPropertyView($user, $actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($user->getUsername()));
$feed = $this->renderUserFeed($user);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$feed,
),
array(
'title' => $user->getUsername(),
'device' => true,
));
}
private function buildPropertyView(
PhabricatorUser $user,
PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($user)
->setActionList($actions);
$field_list = PhabricatorCustomField::getObjectFields(
$user,
PhabricatorCustomField::ROLE_VIEW);
$field_list->appendFieldsToPropertyList($user, $viewer, $view);
return $view;
}
private function renderUserFeed(PhabricatorUser $user) {
$viewer = $this->getRequest()->getUser();
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$user->getPHID(),
));
$query->setLimit(100);
$query->setViewer($viewer);
$stories = $query->execute();
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($viewer);
$builder->setShowHovercards(true);
$view = $builder->buildView();
- return hsprintf(
- '<div class="profile-feed profile-wrap-responsive">
- %s
- </div>',
+ return phutil_tag_div(
+ 'profile-feed profile-wrap-responsive',
$view->render());
}
}
diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php
index 494bc4f4db..06e80891e8 100644
--- a/src/applications/phame/controller/post/PhamePostEditController.php
+++ b/src/applications/phame/controller/post/PhamePostEditController.php
@@ -1,204 +1,201 @@
<?php
/**
* @group phame
*/
final class PhamePostEditController
extends PhameController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->id) {
$post = id(new PhamePostQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$post) {
return new Aphront404Response();
}
$cancel_uri = $this->getApplicationURI('/post/view/'.$this->id.'/');
$submit_button = pht('Save Changes');
$page_title = pht('Edit Post');
} else {
$blog = id(new PhameBlogQuery())
->setViewer($user)
->withIDs(array($request->getInt('blog')))
->executeOne();
if (!$blog) {
return new Aphront404Response();
}
$post = id(new PhamePost())
->setBloggerPHID($user->getPHID())
->setBlogPHID($blog->getPHID())
->setBlog($blog)
->setDatePublished(0)
->setVisibility(PhamePost::VISIBILITY_DRAFT);
$cancel_uri = $this->getApplicationURI('/blog/view/'.$blog->getID().'/');
$submit_button = pht('Save Draft');
$page_title = pht('Create Post');
}
$e_phame_title = null;
$e_title = true;
$errors = array();
if ($request->isFormPost()) {
$comments = $request->getStr('comments_widget');
$data = array('comments_widget' => $comments);
$phame_title = $request->getStr('phame_title');
$phame_title = PhabricatorSlug::normalize($phame_title);
$title = $request->getStr('title');
$post->setTitle($title);
$post->setPhameTitle($phame_title);
$post->setBody($request->getStr('body'));
$post->setConfigData($data);
if ($phame_title == '/') {
$errors[] = pht('Phame title must be nonempty.');
$e_phame_title = pht('Required');
}
if (!strlen($title)) {
$errors[] = pht('Title must be nonempty.');
$e_title = pht('Required');
} else {
$e_title = null;
}
if (!$errors) {
try {
$post->save();
$uri = $this->getApplicationURI('/post/view/'.$post->getID().'/');
return id(new AphrontRedirectResponse())->setURI($uri);
} catch (AphrontQueryDuplicateKeyException $e) {
$e_phame_title = pht('Not Unique');
$errors[] = pht('Another post already uses this slug. '.
'Each post must have a unique slug.');
}
}
}
$handle = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs(array($post->getBlogPHID()))
->executeOne();
$form = id(new AphrontFormView())
->setUser($user)
->addHiddenInput('blog', $request->getInt('blog'))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Blog'))
->setValue($handle->renderLink()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Title'))
->setName('title')
->setValue($post->getTitle())
->setID('post-title')
->setError($e_title))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Phame Title'))
->setName('phame_title')
->setValue(rtrim($post->getPhameTitle(), '/'))
->setID('post-phame-title')
->setCaption(pht('Up to 64 alphanumeric characters '.
'with underscores for spaces. '.
'Formatting is enforced.'))
->setError($e_phame_title))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Body'))
->setName('body')
->setValue($post->getBody())
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setID('post-body')
->setUser($user)
->setDisableMacros(true))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Comments Widget'))
->setName('comments_widget')
->setvalue($post->getCommentsWidget())
->setOptions($post->getCommentsWidgetOptionsForSelect()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($submit_button));
- $preview_panel = hsprintf(
- '<div class="aphront-panel-preview">
- <div class="phame-post-preview-header">
- Post Preview
- </div>
- <div id="post-preview">
- <div class="aphront-panel-preview-loading-text">
- Loading preview...
- </div>
- </div>
- </div>');
+ $loading = phutil_tag_div(
+ 'aphront-panel-preview-loading-text',
+ pht('Loading preview...'));
+
+ $preview_panel = phutil_tag_div('aphront-panel-preview', array(
+ phutil_tag_div('phame-post-preview-header', pht('Post Preview')),
+ phutil_tag('div', array('id' => 'post-preview'), $loading),
+ ));
require_celerity_resource('phame-css');
Javelin::initBehavior(
'phame-post-preview',
array(
'preview' => 'post-preview',
'body' => 'post-body',
'title' => 'post-title',
'phame_title' => 'post-phame-title',
'uri' => '/phame/post/preview/',
));
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Errors saving post.'))
->setErrors($errors);
} else {
$error_view = null;
}
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($page_title)
->setFormError($error_view)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($page_title)
->setHref($this->getApplicationURI('/post/view/'.$this->id.'/')));
$nav = $this->renderSideNavFilterView(null);
$nav->appendChild(
array(
$crumbs,
$form_box,
$preview_panel,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $page_title,
'device' => true,
));
}
}
diff --git a/src/applications/phame/controller/post/PhamePostPreviewController.php b/src/applications/phame/controller/post/PhamePostPreviewController.php
index 06fe3de4da..8353b98d72 100644
--- a/src/applications/phame/controller/post/PhamePostPreviewController.php
+++ b/src/applications/phame/controller/post/PhamePostPreviewController.php
@@ -1,30 +1,30 @@
<?php
/**
* @group phame
*/
final class PhamePostPreviewController
extends PhameController {
protected function getSideNavFilter() {
return null;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$body = $request->getStr('body');
$post = id(new PhamePost())
->setBody($body);
$content = PhabricatorMarkupEngine::renderOneObject(
$post,
PhamePost::MARKUP_FIELD_BODY,
$user);
- $content = hsprintf('<div class="phabricator-remarkup">%s</div>', $content);
+ $content = phutil_tag_div('phabricator-remarkup', $content);
return id(new AphrontAjaxResponse())->setContent($content);
}
}
diff --git a/src/applications/phame/skins/PhameBasicBlogSkin.php b/src/applications/phame/skins/PhameBasicBlogSkin.php
index 850a5f0a92..b248bc6b92 100644
--- a/src/applications/phame/skins/PhameBasicBlogSkin.php
+++ b/src/applications/phame/skins/PhameBasicBlogSkin.php
@@ -1,325 +1,325 @@
<?php
/**
* @task paging Paging
* @task internal Internals
* @group phame
*/
abstract class PhameBasicBlogSkin extends PhameBlogSkin {
private $pager;
private $title;
private $description;
private $oGType;
private $uriPath;
public function setURIPath($uri_path) {
$this->uriPath = $uri_path;
return $this;
}
public function getURIPath() {
return $this->uriPath;
}
protected function setOGType($og_type) {
$this->oGType = $og_type;
return $this;
}
protected function getOGType() {
return $this->oGType;
}
protected function setDescription($description) {
$this->description = $description;
return $this;
}
protected function getDescription() {
return $this->description;
}
protected function setTitle($title) {
$this->title = $title;
return $this;
}
protected function getTitle() {
return $this->title;
}
public function processRequest() {
$request = $this->getRequest();
$content = $this->renderContent($request);
if (!$content) {
$content = $this->render404Page();
}
$content = array(
$this->renderHeader(),
$content,
$this->renderFooter(),
);
$view = id(new PhabricatorBarePageView())
->setRequest($request)
->setController($this)
->setDeviceReady(true)
->setTitle($this->getBlog()->getName());
if ($this->getPreview()) {
$view->setFrameable(true);
}
$view->appendChild($content);
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
}
public function getSkinName() {
return get_class($this);
}
abstract protected function renderHeader();
abstract protected function renderFooter();
protected function renderPostDetail(PhamePostView $post) {
return $post;
}
protected function renderPostList(array $posts) {
$summaries = array();
foreach ($posts as $post) {
$summaries[] = $post->renderWithSummary();
}
$list = phutil_tag(
'div',
array(
'class' => 'phame-post-list',
),
id(new AphrontNullView())->appendChild($summaries)->render());
$pager = null;
if ($this->renderOlderPageLink() || $this->renderNewerPageLink()) {
$pager = phutil_tag(
'div',
array(
'class' => 'phame-pager',
),
array(
$this->renderOlderPageLink(),
$this->renderNewerPageLink(),
));
}
return array(
$list,
$pager,
);
}
protected function render404Page() {
- return hsprintf('<h2>404 Not Found</h2>');
+ return phutil_tag('h2', array(), pht('404 Not Found'));
}
final public function getResourceURI($resource) {
$root = $this->getSpecification()->getRootDirectory();
$path = $root.DIRECTORY_SEPARATOR.$resource;
$data = Filesystem::readFile($path);
$hash = PhabricatorHash::digest($data);
$hash = substr($hash, 0, 6);
$id = $this->getBlog()->getID();
$uri = '/phame/r/'.$id.'/'.$hash.'/'.$resource;
$uri = PhabricatorEnv::getCDNURI($uri);
return $uri;
}
/* -( Paging )------------------------------------------------------------- */
/**
* @task paging
*/
public function getPageSize() {
return 100;
}
/**
* @task paging
*/
protected function getOlderPageURI() {
if ($this->pager) {
$next = $this->pager->getNextPageID();
if ($next) {
return $this->getURI('older/'.$next.'/');
}
}
return null;
}
/**
* @task paging
*/
protected function renderOlderPageLink() {
$uri = $this->getOlderPageURI();
if (!$uri) {
return null;
}
return phutil_tag(
'a',
array(
'class' => 'phame-page-link phame-page-older',
'href' => $uri,
),
pht("\xE2\x80\xB9 Older"));
}
/**
* @task paging
*/
protected function getNewerPageURI() {
if ($this->pager) {
$next = $this->pager->getPrevPageID();
if ($next) {
return $this->getURI('newer/'.$next.'/');
}
}
return null;
}
/**
* @task paging
*/
protected function renderNewerPageLink() {
$uri = $this->getNewerPageURI();
if (!$uri) {
return null;
}
return phutil_tag(
'a',
array(
'class' => 'phame-page-link phame-page-newer',
'href' => $uri,
),
pht("Newer \xE2\x80\xBA"));
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
protected function renderContent(AphrontRequest $request) {
$user = $request->getUser();
$matches = null;
$path = $request->getPath();
// default to the blog-wide values
$this->setTitle($this->getBlog()->getName());
$this->setDescription($this->getBlog()->getDescription());
$this->setOGType('website');
$this->setURIPath('');
if (preg_match('@^/post/(?P<name>.*)$@', $path, $matches)) {
$post = id(new PhamePostQuery())
->setViewer($user)
->withBlogPHIDs(array($this->getBlog()->getPHID()))
->withPhameTitles(array($matches['name']))
->executeOne();
if ($post) {
$description = $post->getMarkupText(PhamePost::MARKUP_FIELD_SUMMARY);
$this->setTitle($post->getTitle());
$this->setDescription($description);
$this->setOGType('article');
$this->setURIPath('post/'.$post->getPhameTitle());
$view = head($this->buildPostViews(array($post)));
return $this->renderPostDetail($view);
}
} else {
$pager = new AphrontCursorPagerView();
if (preg_match('@^/older/(?P<before>\d+)/$@', $path, $matches)) {
$pager->setAfterID($matches['before']);
} else if (preg_match('@^/newer/(?P<after>\d)/$@', $path, $matches)) {
$pager->setBeforeID($matches['after']);
} else if (preg_match('@^/$@', $path, $matches)) {
// Just show the first page.
} else {
return null;
}
$pager->setPageSize($this->getPageSize());
$posts = id(new PhamePostQuery())
->setViewer($user)
->withBlogPHIDs(array($this->getBlog()->getPHID()))
->executeWithCursorPager($pager);
$this->pager = $pager;
if ($posts) {
$views = $this->buildPostViews($posts);
return $this->renderPostList($views);
}
}
return null;
}
private function buildPostViews(array $posts) {
assert_instances_of($posts, 'PhamePost');
$user = $this->getRequest()->getUser();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$phids = array();
foreach ($posts as $post) {
$engine->addObject($post, PhamePost::MARKUP_FIELD_BODY);
$engine->addObject($post, PhamePost::MARKUP_FIELD_SUMMARY);
$phids[] = $post->getBloggerPHID();
}
$handles = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs($phids)
->execute();
$engine->process();
$views = array();
foreach ($posts as $post) {
$view = id(new PhamePostView())
->setUser($user)
->setSkin($this)
->setPost($post)
->setBody($engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY))
->setSummary($engine->getOutput($post, PhamePost::MARKUP_FIELD_SUMMARY))
->setAuthor($handles[$post->getBloggerPHID()]);
$post->makeEphemeral();
if (!$post->getDatePublished()) {
$post->setDatePublished(time());
}
$views[] = $view;
}
return $views;
}
}
diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php
index bec14d9a21..2ca3b17e96 100644
--- a/src/applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php
+++ b/src/applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php
@@ -1,29 +1,28 @@
<?php
final class PhabricatorXHPASTViewFramesetController
extends PhabricatorXHPASTViewController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$id = $this->id;
$response = new AphrontWebpageResponse();
$response->setFrameable(true);
- $response->setContent(hsprintf(
- '<frameset cols="33%%, 34%%, 33%%">'.
- '<frame src="/xhpast/input/%s/" />'.
- '<frame src="/xhpast/tree/%s/" />'.
- '<frame src="/xhpast/stream/%s/" />'.
- '</frameset>',
- $id,
- $id,
- $id));
+ $response->setContent(phutil_tag(
+ 'frameset',
+ array('cols' => '33%, 34%, 33%'),
+ array(
+ phutil_tag('frame', array('src' => "/xhpast/input/{$id}/")),
+ phutil_tag('frame', array('src' => "/xhpast/tree/{$id}/")),
+ phutil_tag('frame', array('src' => "/xhpast/stream/{$id}/")),
+ )));
return $response;
}
}
diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php
index 6db052d98f..3e926db60a 100644
--- a/src/applications/phriction/controller/PhrictionDiffController.php
+++ b/src/applications/phriction/controller/PhrictionDiffController.php
@@ -1,298 +1,296 @@
<?php
/**
* @group phriction
*/
final class PhrictionDiffController
extends PhrictionController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$document = id(new PhrictionDocument())->load($this->id);
if (!$document) {
return new Aphront404Response();
}
$current = id(new PhrictionContent())->load($document->getContentID());
$l = $request->getInt('l');
$r = $request->getInt('r');
$ref = $request->getStr('ref');
if ($ref) {
list($l, $r) = explode(',', $ref);
}
$content = id(new PhrictionContent())->loadAllWhere(
'documentID = %d AND version IN (%Ld)',
$document->getID(),
array($l, $r));
$content = mpull($content, null, 'getVersion');
$content_l = idx($content, $l, null);
$content_r = idx($content, $r, null);
if (!$content_l || !$content_r) {
return new Aphront404Response();
}
$text_l = $content_l->getContent();
$text_r = $content_r->getContent();
$text_l = phutil_utf8_hard_wrap($text_l, 80);
$text_l = implode("\n", $text_l);
$text_r = phutil_utf8_hard_wrap($text_r, 80);
$text_r = implode("\n", $text_r);
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent($text_l, $text_r);
$changeset->setOldProperties(
array(
'Title' => $content_l->getTitle(),
));
$changeset->setNewProperties(
array(
'Title' => $content_r->getTitle(),
));
$whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
$parser = new DifferentialChangesetParser();
$parser->setChangeset($changeset);
$parser->setRenderingReference("{$l},{$r}");
$parser->setWhitespaceMode($whitespace_mode);
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
$engine->process();
$parser->setMarkupEngine($engine);
$spec = $request->getStr('range');
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$output = $parser->render($range_s, $range_e, $mask);
if ($request->isAjax()) {
return id(new PhabricatorChangesetResponse())
->setRenderedChangeset($output);
}
require_celerity_resource('differential-changeset-view-css');
require_celerity_resource('syntax-highlighting-css');
require_celerity_resource('phriction-document-css');
Javelin::initBehavior('differential-show-more', array(
'uri' => '/phriction/diff/'.$document->getID().'/',
'whitespace' => $whitespace_mode,
));
$slug = $document->getSlug();
$revert_l = $this->renderRevertButton($content_l, $current);
$revert_r = $this->renderRevertButton($content_r, $current);
$crumbs = $this->buildApplicationCrumbs();
$crumb_views = $this->renderBreadcrumbs($slug);
foreach ($crumb_views as $view) {
$crumbs->addCrumb($view);
}
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('History'))
->setHref(PhrictionDocument::getSlugURI($slug, 'history')));
$title = pht("Version %s vs %s", $l, $r);
$header = id(new PHUIHeaderView())
->setHeader($title);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($request->getRequestURI()));
$comparison_table = $this->renderComparisonTable(
array(
$content_r,
$content_l,
));
$navigation_table = null;
if ($l + 1 == $r) {
$nav_l = ($l > 1);
$nav_r = ($r != $current->getVersion());
$uri = $request->getRequestURI();
if ($nav_l) {
$link_l = phutil_tag(
'a',
array(
'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1),
'class' => 'button',
),
pht("\xC2\xAB Previous Change"));
} else {
$link_l = phutil_tag(
'a',
array(
'href' => '#',
'class' => 'button grey disabled',
),
pht('Original Change'));
}
$link_r = null;
if ($nav_r) {
$link_r = phutil_tag(
'a',
array(
'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1),
'class' => 'button',
),
pht("Next Change \xC2\xBB"));
} else {
$link_r = phutil_tag(
'a',
array(
'href' => '#',
'class' => 'button grey disabled',
),
pht('Most Recent Change'));
}
- $navigation_table = hsprintf(
- '<table class="phriction-history-nav-table">
- <tr>
- <td class="nav-prev">%s</td>
- <td class="nav-next">%s</td>
- </tr>
- </table>',
- $link_l,
- $link_r);
+ $navigation_table = phutil_tag(
+ 'table',
+ array('class' => 'phriction-history-nav-table'),
+ phutil_tag('tr', array(), array(
+ phutil_tag('td', array('class' => 'nav-prev'), $link_l),
+ phutil_tag('td', array('class' => 'nav-next'), $link_r),
+ )));
}
$output = hsprintf(
'<br><div class="phriction-document-history-diff">'.
'%s%s'.
'<table class="phriction-revert-table">'.
'<tr><td>%s</td><td>%s</td>'.
'</table>'.
'%s'.
'</div>',
$comparison_table->render(),
$navigation_table,
$revert_l,
$revert_r,
$output);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($output);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
),
array(
'title' => pht('Document History'),
'device' => true,
));
}
private function renderRevertButton(
PhrictionContent $content,
PhrictionContent $current) {
$document_id = $content->getDocumentID();
$version = $content->getVersion();
$hidden_statuses = array(
PhrictionChangeType::CHANGE_DELETE => true, // Silly
PhrictionChangeType::CHANGE_MOVE_AWAY => true, // Plain silly
PhrictionChangeType::CHANGE_STUB => true, // Utterly silly
);
if (isset($hidden_statuses[$content->getChangeType()])) {
// Don't show an edit/revert button for changes which deleted, moved or
// stubbed the content since it's silly.
return null;
}
if ($content->getID() == $current->getID()) {
return phutil_tag(
'a',
array(
'href' => '/phriction/edit/'.$document_id.'/',
'class' => 'button grey',
),
pht('Edit Current Version'));
}
return phutil_tag(
'a',
array(
'href' => '/phriction/edit/'.$document_id.'/?revert='.$version,
'class' => 'button grey',
),
pht('Revert to Version %s...', $version));
}
private function renderComparisonTable(array $content) {
assert_instances_of($content, 'PhrictionContent');
$user = $this->getRequest()->getUser();
$phids = mpull($content, 'getAuthorPHID');
$handles = $this->loadViewerHandles($phids);
$list = new PHUIObjectItemListView();
$list->setFlush(true);
$first = true;
foreach ($content as $c) {
$author = $handles[$c->getAuthorPHID()]->renderLink();
$item = id(new PHUIObjectItemView())
->setHeader(pht('%s by %s, %s',
PhrictionChangeType::getChangeTypeLabel($c->getChangeType()),
$author,
pht('Version %s', $c->getVersion())))
->addAttribute(pht('%s %s',
phabricator_date($c->getDateCreated(), $user),
phabricator_time($c->getDateCreated(), $user)));
if ($c->getDescription()) {
$item->addAttribute($c->getDescription());
}
if ($first == true) {
$item->setBarColor('green');
$first = false;
} else {
$item->setBarColor('red');
}
$list->addItem($item);
}
return $list;
}
}
diff --git a/src/applications/phriction/controller/PhrictionNewController.php b/src/applications/phriction/controller/PhrictionNewController.php
index 22089b1b46..609907955c 100644
--- a/src/applications/phriction/controller/PhrictionNewController.php
+++ b/src/applications/phriction/controller/PhrictionNewController.php
@@ -1,85 +1,85 @@
<?php
/**
* @group phriction
*/
final class PhrictionNewController extends PhrictionController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$slug = PhabricatorSlug::normalize($request->getStr('slug'));
if ($request->isFormPost()) {
$document = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
$slug);
$prompt = $request->getStr('prompt', 'no');
$document_exists = $document && $document->getStatus() ==
PhrictionDocumentStatus::STATUS_EXISTS;
if ($document_exists && $prompt == 'no') {
$dialog = new AphrontDialogView();
$dialog->setSubmitURI('/phriction/new/')
->setTitle(pht('Edit Existing Document?'))
->setUser($user)
->appendChild(pht(
'The document %s already exists. Do you want to edit it instead?',
- hsprintf('<tt>%s</tt>', $slug)))
+ phutil_tag('tt', array(), $slug)))
->addHiddenInput('slug', $slug)
->addHiddenInput('prompt', 'yes')
->addCancelButton('/w/')
->addSubmitButton(pht('Edit Document'));
return id(new AphrontDialogResponse())->setDialog($dialog);
} else if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProjectQuery())
->setViewer($user)
->withPhrictionSlugs(array(
PhrictionDocument::getProjectSlugIdentifier($slug)))
->executeOne();
if (!$project) {
$dialog = new AphrontDialogView();
$dialog->setSubmitURI('/w/')
->setTitle(pht('Oops!'))
->setUser($user)
->appendChild(pht(
'You cannot create wiki pages under "projects/",
because they are reserved as project pages.
Create a new project with this name first.'))
->addCancelButton('/w/', 'Okay');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
$uri = '/phriction/edit/?slug='.$slug;
return id(new AphrontRedirectResponse())
->setURI($uri);
}
if ($slug == '/') {
$slug = '';
}
$view = id(new PHUIFormLayoutView())
->appendChild(id(new AphrontFormTextControl())
->setLabel('/w/')
->setValue($slug)
->setName('slug'));
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle(pht('New Document'))
->setSubmitURI('/phriction/new/')
->appendChild(phutil_tag('p',
array(),
pht('Create a new document at')))
->appendChild($view)
->addSubmitButton(pht('Create'))
->addCancelButton('/w/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php
index ebc252cba8..d40aec3c05 100644
--- a/src/applications/phriction/storage/PhrictionContent.php
+++ b/src/applications/phriction/storage/PhrictionContent.php
@@ -1,102 +1,98 @@
<?php
/**
* @task markup Markup Interface
*
* @group phriction
*/
final class PhrictionContent extends PhrictionDAO
implements PhabricatorMarkupInterface {
const MARKUP_FIELD_BODY = 'markup:body';
protected $id;
protected $documentID;
protected $version;
protected $authorPHID;
protected $title;
protected $slug;
protected $content;
protected $description;
protected $changeType;
protected $changeRef;
public function renderContent(PhabricatorUser $viewer) {
return PhabricatorMarkupEngine::renderOneObject(
$this,
self::MARKUP_FIELD_BODY,
$viewer);
}
/* -( Markup Interface )--------------------------------------------------- */
/**
* @task markup
*/
public function getMarkupFieldKey($field) {
if ($this->shouldUseMarkupCache($field)) {
$id = $this->getID();
} else {
$id = PhabricatorHash::digest($this->getMarkupText($field));
}
return "phriction:{$field}:{$id}";
}
/**
* @task markup
*/
public function getMarkupText($field) {
return $this->getContent();
}
/**
* @task markup
*/
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newPhrictionMarkupEngine();
}
/**
* @task markup
*/
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
$toc = PhutilRemarkupEngineRemarkupHeaderBlockRule::renderTableOfContents(
$engine);
if ($toc) {
- $toc = hsprintf(
- '<div class="phabricator-remarkup-toc">'.
- '<div class="phabricator-remarkup-toc-header">%s</div>'.
- '%s'.
- '</div>',
- pht('Table of Contents'),
- $toc);
+ $toc = phutil_tag_div('phabricator-remarkup-toc', array(
+ phutil_tag_div(
+ 'phabricator-remarkup-toc-header',
+ pht('Table of Contents')),
+ $toc,
+ ));
}
- return hsprintf(
- '<div class="phabricator-remarkup">%s%s</div>',
- $toc,
- $output);
+ return phutil_tag_div('phabricator-remarkup', array($toc, $output));
}
/**
* @task markup
*/
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index 826f5e72ac..21ecf19e39 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -1,295 +1,295 @@
<?php
final class PhabricatorProjectProfileController
extends PhabricatorProjectController {
private $id;
private $page;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
->needMembers(true)
->needProfiles(true)
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$profile = $project->getProfile();
$picture = $profile->getProfileImageURI();
require_celerity_resource('phabricator-profile-css');
$tasks = $this->renderTasksPage($project, $profile);
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$project->getPHID(),
));
$query->setLimit(50);
$query->setViewer($this->getRequest()->getUser());
$stories = $query->execute();
$feed = $this->renderStories($stories);
$people = $this->renderPeoplePage($project, $profile);
$content = id(new AphrontMultiColumnView())
->addColumn($people)
->addColumn($feed)
->setFluidLayout(true);
- $content = hsprintf(
- '<div class="phabricator-project-layout">%s%s</div>',
- $tasks,
- $content);
+ $content = phutil_tag_div(
+ 'phabricator-project-layout',
+ array($tasks, $content));
$header = id(new PHUIHeaderView())
->setHeader($project->getName())
->setUser($user)
->setPolicyObject($project)
->setImage($picture);
if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) {
$header->setStatus('oh-ok', '', pht('Active'));
} else {
$header->setStatus('policy-noone', '', pht('Archived'));
}
$actions = $this->buildActionListView($project);
$properties = $this->buildPropertyListView($project, $profile, $actions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($project->getName()));
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$content,
),
array(
'title' => $project->getName(),
'device' => true,
));
}
private function renderPeoplePage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$member_phids = $project->getMemberPHIDs();
$handles = $this->loadViewerHandles($member_phids);
$affiliated = array();
foreach ($handles as $phids => $handle) {
$affiliated[] = phutil_tag('li', array(), $handle->renderLink());
}
if ($affiliated) {
$affiliated = phutil_tag('ul', array(), $affiliated);
} else {
- $affiliated = hsprintf('<p><em>%s</em></p>', 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(
- '<div class="phabricator-profile-info-group profile-wrap-responsive">'.
- '<h1 class="phabricator-profile-info-header">%s</h1>'.
- '<div class="phabricator-profile-info-pane">%s</div>'.
- '</div>',
- 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(
- '<div class="profile-feed profile-wrap-responsive">'.
- '%s'.
- '</div>',
+ return phutil_tag_div(
+ 'profile-feed profile-wrap-responsive',
$view->render());
}
private function renderTasksPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$user = $this->getRequest()->getUser();
$query = id(new ManiphestTaskQuery())
->setViewer($user)
->withAnyProjects(array($project->getPHID()))
->withStatus(ManiphestTaskQuery::STATUS_OPEN)
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
->setLimit(10);
$tasks = $query->execute();
$phids = mpull($tasks, 'getOwnerPHID');
$phids = array_merge(
$phids,
array_mergev(mpull($tasks, 'getProjectPHIDs')));
$phids = array_filter($phids);
$handles = $this->loadViewerHandles($phids);
$task_list = new ManiphestTaskListView();
$task_list->setUser($user);
$task_list->setTasks($tasks);
$task_list->setHandles($handles);
$list = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE)
->appendChild($task_list);
$content = id(new PHUIObjectBoxView())
->setHeaderText(pht('Open Tasks'))
->appendChild($list);
return $content;
}
private function buildActionListView(PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $project->getID();
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($project)
->setObjectURI($request->getRequestURI());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Project'))
->setIcon('edit')
->setHref($this->getApplicationURI("edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Members'))
->setIcon('edit')
->setHref($this->getApplicationURI("members/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Picture'))
->setIcon('image')
->setHref($this->getApplicationURI("picture/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$action = null;
if (!$project->isUserMember($viewer->getPHID())) {
$can_join = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_JOIN);
$action = id(new PhabricatorActionView())
->setUser($viewer)
->setRenderAsForm(true)
->setHref('/project/update/'.$project->getID().'/join/')
->setIcon('new')
->setDisabled(!$can_join)
->setName(pht('Join Project'));
} else {
$action = id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/update/'.$project->getID().'/leave/')
->setIcon('delete')
->setName(pht('Leave Project...'));
}
$view->addAction($action);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('View History'))
->setHref($this->getApplicationURI("history/{$id}/"))
->setIcon('transcript'));
return $view;
}
private function buildPropertyListView(
PhabricatorProject $project,
PhabricatorProjectProfile $profile,
PhabricatorActionListView $actions) {
$request = $this->getRequest();
$viewer = $request->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($project)
->setActionList($actions);
$view->addProperty(
pht('Created'),
phabricator_datetime($project->getDateCreated(), $viewer));
$view->addSectionHeader(pht('Description'));
$view->addTextContent(
PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($profile->getBlurb()),
'default',
$viewer));
return $view;
}
}
diff --git a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php b/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php
index 2330177d42..f98075ddfd 100644
--- a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php
+++ b/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php
@@ -1,116 +1,117 @@
<?php
final class PhabricatorRepositoryArcanistProjectEditController
extends PhabricatorRepositoryController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = id(new PhabricatorRepositoryArcanistProject())->load($this->id);
if (!$project) {
return new Aphront404Response();
}
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($user)
->execute();
$repos = array(
0 => 'None',
);
foreach ($repositories as $repository) {
$callsign = $repository->getCallsign();
$name = $repository->getname();
$repos[$repository->getID()] = "r{$callsign} ({$name})";
}
// note "None" will still be first thanks to 'r' prefix
asort($repos);
if ($request->isFormPost()) {
$indexed = $request->getStrList('symbolIndexLanguages');
$indexed = array_map('strtolower', $indexed);
$project->setSymbolIndexLanguages($indexed);
$project->setSymbolIndexProjects($request->getArr('symbolIndexProjects'));
$repo_id = $request->getInt('repository', 0);
if (isset($repos[$repo_id])) {
$project->setRepositoryID($repo_id);
$project->save();
return id(new AphrontRedirectResponse())
->setURI('/repository/');
}
}
$langs = $project->getSymbolIndexLanguages();
if ($langs) {
$langs = implode(', ', $langs);
} else {
$langs = null;
}
if ($project->getSymbolIndexProjects()) {
$uses = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs($project->getSymbolIndexProjects())
->execute();
} else {
$uses = array();
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Name')
->setValue($project->getName()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('PHID')
->setValue($project->getPHID()))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Repository')
->setOptions($repos)
->setName('repository')
->setValue($project->getRepositoryID()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Indexed Languages')
->setName('symbolIndexLanguages')
- ->setCaption(
- hsprintf('Separate with commas, for example: <tt>php, py</tt>'))
+ ->setCaption(pht(
+ 'Separate with commas, for example: %s',
+ phutil_tag('tt', array(), 'php, py')))
->setValue($langs))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel('Uses Symbols From')
->setName('symbolIndexProjects')
->setDatasource('/typeahead/common/arcanistprojects/')
->setValue($uses))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton('/repository/')
->setValue('Save'));
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
$panel->setHeader('Edit Arcanist Project');
$panel->appendChild($form);
return $this->buildStandardPageResponse(
$panel,
array(
'title' => 'Edit Project',
));
}
}
diff --git a/src/applications/search/controller/PhabricatorSearchController.php b/src/applications/search/controller/PhabricatorSearchController.php
index f9296b8a7b..78ef4be93d 100644
--- a/src/applications/search/controller/PhabricatorSearchController.php
+++ b/src/applications/search/controller/PhabricatorSearchController.php
@@ -1,296 +1,295 @@
<?php
/**
* @group search
*/
final class PhabricatorSearchController
extends PhabricatorSearchBaseController {
private $key;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->key = idx($data, 'key');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->key) {
$query = id(new PhabricatorSearchQuery())->loadOneWhere(
'queryKey = %s',
$this->key);
if (!$query) {
return new Aphront404Response();
}
} else {
$query = new PhabricatorSearchQuery();
if ($request->isFormPost()) {
$query_str = $request->getStr('query');
$pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP;
if ($request->getStr('jump') != 'no' &&
$user && $user->loadPreferences()->getPreference($pref_jump, 1)) {
$response = PhabricatorJumpNavHandler::getJumpResponse(
$user,
$query_str);
} else {
$response = null;
}
if ($response) {
return $response;
} else {
$query->setQuery($query_str);
if ($request->getStr('scope')) {
switch ($request->getStr('scope')) {
case PhabricatorSearchScope::SCOPE_OPEN_REVISIONS:
$query->setParameter('open', 1);
$query->setParameter(
'type',
DifferentialPHIDTypeRevision::TYPECONST);
break;
case PhabricatorSearchScope::SCOPE_OPEN_TASKS:
$query->setParameter('open', 1);
$query->setParameter(
'type',
ManiphestPHIDTypeTask::TYPECONST);
break;
case PhabricatorSearchScope::SCOPE_WIKI:
$query->setParameter(
'type',
PhrictionPHIDTypeDocument::TYPECONST);
break;
case PhabricatorSearchScope::SCOPE_COMMITS:
$query->setParameter(
'type',
PhabricatorRepositoryPHIDTypeCommit::TYPECONST);
break;
default:
break;
}
} else {
if (strlen($request->getStr('type'))) {
$query->setParameter('type', $request->getStr('type'));
}
if ($request->getArr('author')) {
$query->setParameter('author', $request->getArr('author'));
}
if ($request->getArr('owner')) {
$query->setParameter('owner', $request->getArr('owner'));
}
if ($request->getArr('subscribers')) {
$query->setParameter('subscribers',
$request->getArr('subscribers'));
}
if ($request->getInt('open')) {
$query->setParameter('open', $request->getInt('open'));
}
if ($request->getArr('project')) {
$query->setParameter('project', $request->getArr('project'));
}
}
$query->save();
return id(new AphrontRedirectResponse())
->setURI('/search/'.$query->getQueryKey().'/');
}
}
}
$options = array(
'' => 'All Documents',
) + PhabricatorSearchAbstractDocument::getSupportedTypes();
$status_options = array(
0 => 'Open and Closed Documents',
1 => 'Open Documents',
);
$phids = array_merge(
$query->getParameter('author', array()),
$query->getParameter('owner', array()),
$query->getParameter('subscribers', array()),
$query->getParameter('project', array()));
$handles = $this->loadViewerHandles($phids);
$author_value = array_select_keys(
$handles,
$query->getParameter('author', array()));
$owner_value = array_select_keys(
$handles,
$query->getParameter('owner', array()));
$subscribers_value = array_select_keys(
$handles,
$query->getParameter('subscribers', array()));
$project_value = array_select_keys(
$handles,
$query->getParameter('project', array()));
$search_form = new AphrontFormView();
$search_form
->setUser($user)
->setAction('/search/')
->appendChild(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'jump',
'value' => 'no',
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Search')
->setName('query')
->setValue($query->getQuery()))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Document Type')
->setName('type')
->setOptions($options)
->setValue($query->getParameter('type')))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Document Status')
->setName('open')
->setOptions($status_options)
->setValue($query->getParameter('open')))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('author')
->setLabel('Author')
->setDatasource('/typeahead/common/users/')
->setValue($author_value))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('owner')
->setLabel('Owner')
->setDatasource('/typeahead/common/searchowner/')
->setValue($owner_value)
->setCaption(
'Tip: search for "Up For Grabs" to find unowned documents.'))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('subscribers')
->setLabel('Subscribers')
->setDatasource('/typeahead/common/users/')
->setValue($subscribers_value))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('project')
->setLabel('Project')
->setDatasource('/typeahead/common/projects/')
->setValue($project_value))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Search'));
$search_panel = new AphrontListFilterView();
$search_panel->appendChild($search_form);
require_celerity_resource('phabricator-search-results-css');
if ($query->getID()) {
$limit = 20;
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'page');
$pager->setPageSize($limit);
$pager->setOffset($request->getInt('page'));
$query->setParameter('limit', $limit + 1);
$query->setParameter('offset', $pager->getOffset());
$engine = PhabricatorSearchEngineSelector::newSelector()->newEngine();
$results = $engine->executeSearch($query);
$results = $pager->sliceResults($results);
// If there are any objects which match the query by name, and we're
// not paging through the results, prefix the results with the named
// objects.
if (!$request->getInt('page')) {
$named = id(new PhabricatorObjectQuery())
->setViewer($user)
->withNames(array($query->getQuery()))
->execute();
if ($named) {
$results = array_merge(array_keys($named), $results);
}
}
if ($results) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs($results)
->execute();
$objects = id(new PhabricatorObjectQuery())
->setViewer($user)
->withPHIDs($results)
->execute();
$results = array();
foreach ($handles as $phid => $handle) {
$view = id(new PhabricatorSearchResultView())
->setHandle($handle)
->setQuery($query)
->setObject(idx($objects, $phid));
$results[] = $view->render();
}
- $results = hsprintf(
- '<div class="phabricator-search-result-list">'.
- '%s'.
- '<div class="search-results-pager">%s</div>'.
- '</div>',
+ $results = phutil_tag_div('phabricator-search-result-list', array(
phutil_implode_html("\n", $results),
- $pager->render());
+ phutil_tag_div('search-results-pager', $pager->render()),
+ ));
} else {
- $results = hsprintf(
- '<div class="phabricator-search-result-list">'.
- '<p class="phabricator-search-no-results">No search results.</p>'.
- '</div>');
+ $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 eb79fa2c5a..e44d86e8f9 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelConduit.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelConduit.php
@@ -1,109 +1,112 @@
<?php
final class PhabricatorSettingsPanelConduit
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'conduit';
}
public function getPanelName() {
return pht('Conduit');
}
public function getPanelGroup() {
return pht('Authentication');
}
public function processRequest(AphrontRequest $request) {
$user = $request->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(
- '<p class="aphront-form-instructions">%s</p>',
+ ->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('<tt>%s</tt>', '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(
- '<p class="aphront-form-instructions">%s</p>', $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 9a6100f467..79d6d3d3b5 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php
@@ -1,158 +1,158 @@
<?php
final class PhabricatorSettingsPanelDisplayPreferences
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'display';
}
public function getPanelName() {
return pht('Display Preferences');
}
public function getPanelGroup() {
return pht('Application Settings');
}
public function processRequest(AphrontRequest $request) {
$user = $request->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 = <<<EXAMPLE
// This is what your monospaced font currently looks like.
function helloWorld() {
alert("Hello world!");
}
EXAMPLE;
$editor_doc_link = phutil_tag(
'a',
array(
'href' => 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<br />(%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 02495e88bf..b7bacccdf7 100644
--- a/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
+++ b/src/applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php
@@ -1,355 +1,355 @@
<?php
final class PhabricatorSettingsPanelEmailAddresses
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'email';
}
public function getPanelName() {
return pht('Email Addresses');
}
public function getPanelGroup() {
return pht('Email');
}
public function processRequest(AphrontRequest $request) {
$user = $request->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(
- '<p>%s</p>',
- 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(
- '<p>If you change your primary address, Phabricator will send'.
- ' all email to %s.</p>',
- $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 e26dd0e6c9..7a66906684 100644
--- a/src/applications/system/PhabricatorDebugController.php
+++ b/src/applications/system/PhabricatorDebugController.php
@@ -1,39 +1,39 @@
<?php
/**
* This controller eases debugging of application problems that don't repro
* locally by allowing installs to add arbitrary debugging code easily. To use
* it:
*
* - Write some diagnostic script.
* - Instruct the user to install it in `/support/debug.php`.
* - Tell them to visit `/debug/`.
*/
final class PhabricatorDebugController extends PhabricatorController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
if (!Filesystem::pathExists($this->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('<pre>%s</pre>', $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 92a41bce8e..1557b499e2 100644
--- a/webroot/index.php
+++ b/webroot/index.php
@@ -1,137 +1,142 @@
<?php
require_once dirname(dirname(__FILE__)).'/support/PhabricatorStartup.php';
PhabricatorStartup::didStartup();
try {
PhabricatorStartup::loadCoreLibraries();
PhabricatorEnv::initializeWebEnvironment();
// This is the earliest we can get away with this, we need env config first.
PhabricatorAccessLog::init();
$access_log = PhabricatorAccessLog::getLog();
PhabricatorStartup::setGlobal('log.access', $access_log);
$access_log->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(
- '<div style="background: #eeddff; white-space: pre-wrap;
- z-index: 200000; position: relative; padding: 8px;
- font-family: monospace;">%s</div>',
+ echo phutil_tag(
+ 'div',
+ array('style' =>
+ 'background: #eeddff;'.
+ 'white-space: pre-wrap;'.
+ 'z-index: 200000;'.
+ 'position: relative;'.
+ 'padding: 8px;'.
+ 'font-family: monospace'),
$unexpected_output);
}
}
$sink->writeResponse($response);
} catch (Exception $ex) {
$write_guard->dispose();
$access_log->write();
if ($original_exception) {
$ex = new PhutilAggregateException(
"Multiple exceptions during processing and rendering.",
array(
$original_exception,
$ex,
));
}
PhabricatorStartup::didFatal('[Rendering Exception] '.$ex->getMessage());
}
$write_guard->dispose();
$access_log->setData(
array(
'c' => $response->getHTTPResponseCode(),
'T' => PhabricatorStartup::getMicrosecondsSinceStart(),
));
DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log);
} catch (Exception $ex) {
PhabricatorStartup::didFatal("[Exception] ".$ex->getMessage());
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jul 27, 3:27 PM (1 w, 10 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
185958
Default Alt Text
(463 KB)

Event Timeline