Page MenuHomestyx hydra

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/applications/audit/controller/PhabricatorAuditListController.php b/src/applications/audit/controller/PhabricatorAuditListController.php
index 0e1b7a5477..ce2667dc78 100644
--- a/src/applications/audit/controller/PhabricatorAuditListController.php
+++ b/src/applications/audit/controller/PhabricatorAuditListController.php
@@ -1,504 +1,503 @@
<?php
final class PhabricatorAuditListController extends PhabricatorAuditController {
private $name;
private $filterStatus;
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter');
$this->name = idx($data, 'name');
}
public function processRequest() {
$request = $this->getRequest();
$nav = $this->buildSideNavView();
if ($request->isFormPost()) {
// If the list filter is POST'ed, redirect to GET so the page can be
// bookmarked.
$uri = $request->getRequestURI();
$phid = head($request->getArr('set_phid'));
$user = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$phid);
$uri = $request->getRequestURI();
if ($user) {
$username = phutil_escape_uri($user->getUsername());
$uri = '/audit/view/'.$this->filter.'/'.$username.'/';
} else if ($phid) {
$uri = $request->getRequestURI();
$uri = $uri->alter('phid', $phid);
}
return id(new AphrontRedirectResponse())->setURI($uri);
}
$this->filterStatus = $request->getStr('status', 'all');
$handle = $this->loadHandle();
$nav->appendChild($this->buildListFilters($handle));
$title = null;
$message = null;
if (!$handle) {
switch ($this->filter) {
case 'project':
$title = pht('Choose A Project');
$message = pht('Choose a project to view audits for.');
break;
case 'repository':
$title = pht('Choose A Repository');
$message = pht('Choose a repository to view audits for.');
break;
case 'package':
case 'packagecommits':
$title = pht('Choose a Package');
$message = pht('Choose a package to view audits for.');
break;
}
}
if (!$message) {
$nav->appendChild($this->buildViews($handle));
} else {
$panel = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NODATA)
->setTitle($title)
->appendChild($message);
$nav->appendChild($panel);
}
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Audits'),
'device' => true,
- 'dust' => true,
));
}
private function buildListFilters(PhabricatorObjectHandle $handle = null) {
$request = $this->getRequest();
$user = $request->getUser();
$form = new AphrontFormView();
$form->setUser($user);
$form->setNoShading(true);
$show_status = false;
$show_user = false;
$show_project = false;
$show_package = false;
$show_repository = false;
switch ($this->filter) {
case 'audits':
case 'commits':
$show_status = true;
break;
case 'active':
$show_user = true;
break;
case 'author':
case 'user':
$show_user = true;
$show_status = true;
break;
case 'project':
$show_project = true;
$show_status = true;
break;
case 'repository':
$show_repository = true;
$show_status = true;
break;
case 'package':
case 'packagecommits':
$show_package = true;
$show_status = true;
break;
}
if ($show_user || $show_project || $show_package || $show_repository) {
if ($show_user) {
$uri = '/typeahead/common/users/';
$label = pht('User');
} else if ($show_project) {
$uri = '/typeahead/common/projects/';
$label = pht('Project');
} else if ($show_package) {
$uri = '/typeahead/common/packages/';
$label = pht('Package');
} else if ($show_repository) {
$uri = '/typeahead/common/repositories/';
$label = pht('Repository');
}
$tok_value = null;
if ($handle) {
$tok_value = array(
$handle->getPHID() => $handle->getFullName(),
);
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setName('set_phid')
->setLabel($label)
->setLimit(1)
->setDatasource($uri)
->setValue($tok_value));
}
if ($show_status) {
$form->appendChild(
id(new AphrontFormToggleButtonsControl())
->setName('status')
->setLabel(pht('Status'))
->setBaseURI($request->getRequestURI(), 'status')
->setValue($this->filterStatus)
->setButtons(
array(
'all' => pht('All'),
'open' => pht('Open'),
'concern' => pht('Concern Raised'),
)));
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Filter Audits')));
$view = new AphrontListFilterView();
$view->appendChild($form);
return $view;
}
private function loadHandle() {
$request = $this->getRequest();
$default = null;
switch ($this->filter) {
case 'user':
case 'active':
case 'author':
$default = $request->getUser()->getPHID();
if ($this->name) {
$user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$this->name);
if ($user) {
$default = $user->getPHID();
}
}
break;
}
$phid = $request->getStr('phid', $default);
if (!$phid) {
return null;
}
$phids = array($phid);
$handles = $this->loadViewerHandles($phids);
$handle = $handles[$phid];
$this->validateHandle($handle);
return $handle;
}
private function validateHandle(PhabricatorObjectHandle $handle) {
$type = $handle->getType();
switch ($this->filter) {
case 'active':
case 'user':
case 'author':
if ($type !== PhabricatorPeoplePHIDTypeUser::TYPECONST) {
throw new Exception("PHID must be a user PHID!");
}
break;
case 'package':
case 'packagecommits':
if ($type !== PhabricatorOwnersPHIDTypePackage::TYPECONST) {
throw new Exception("PHID must be a package PHID!");
}
break;
case 'project':
if ($type !== PhabricatorProjectPHIDTypeProject::TYPECONST) {
throw new Exception("PHID must be a project PHID!");
}
break;
case 'repository':
if ($type !== PhabricatorRepositoryPHIDTypeRepository::TYPECONST) {
throw new Exception("PHID must be a repository PHID!");
}
break;
case 'audits':
case 'commits':
break;
default:
throw new Exception("Unknown filter '{$this->filter}'!");
}
}
private function buildViews(PhabricatorObjectHandle $handle = null) {
$views = array();
switch ($this->filter) {
case 'active':
$views[] = $this->buildCommitView($handle);
$views[] = $this->buildAuditView($handle);
break;
case 'audits':
case 'user':
case 'package':
case 'project':
case 'repository':
$views[] = $this->buildAuditView($handle);
break;
case 'commits':
case 'packagecommits':
case 'author':
$views[] = $this->buildCommitView($handle);
break;
}
return $views;
}
private function buildAuditView(PhabricatorObjectHandle $handle = null) {
$request = $this->getRequest();
$query = new PhabricatorAuditQuery();
$use_pager = ($this->filter != 'active');
if ($use_pager) {
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setOffset($request->getInt('offset'));
$query->setOffset($pager->getOffset());
$query->setLimit($pager->getPageSize() + 1);
}
$awaiting = null;
$phids = null;
$repository_phids = null;
switch ($this->filter) {
case 'user':
case 'active':
$obj = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$handle->getPHID());
if (!$obj) {
throw new Exception("Invalid user!");
}
$phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($obj);
$awaiting = $obj;
break;
case 'project':
case 'package':
$phids = array($handle->getPHID());
break;
case 'repository':
$repository_phids = array($handle->getPHID());
break;
case 'audits';
break;
default:
throw new Exception("Unknown filter!");
}
if ($phids) {
$query->withAuditorPHIDs($phids);
}
if ($repository_phids) {
$query->withRepositoryPHIDs($repository_phids);
}
if ($awaiting) {
$query->withAwaitingUser($awaiting);
}
switch ($this->filter) {
case 'active':
$query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
break;
default:
switch ($this->filterStatus) {
case 'open':
$query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
break;
case 'concern':
$query->withStatus(PhabricatorAuditQuery::STATUS_CONCERN);
break;
}
break;
}
if ($handle) {
$handle_name = $handle->getFullName();
} else {
$handle_name = null;
}
switch ($this->filter) {
case 'active':
$header = pht('Required Audits');
$nodata = pht('No commits require your audit.');
break;
case 'user':
$header = pht("Audits for %s", $handle_name);
$nodata = pht("No matching audits by %s.", $handle_name);
break;
case 'audits':
$header = pht('Audits');
$nodata = pht('No matching audits.');
break;
case 'project':
$header = pht("Audits in Project %s", $handle_name);
$nodata = pht("No matching audits in project %s.", $handle_name);
break;
case 'package':
$header = pht("Audits for Package %s", $handle_name);
$nodata = pht("No matching audits in package %s.", $handle_name);
break;
case 'repository':
$header = pht("Audits in Repository %s", $handle_name);
$nodata = pht("No matching audits in repository %s.", $handle_name);
break;
}
$query->needCommitData(true);
$audits = $query->execute();
if ($use_pager) {
$audits = $pager->sliceResults($audits);
}
$view = new PhabricatorAuditListView();
$view->setAudits($audits);
$view->setCommits($query->getCommits());
$view->setUser($request->getUser());
$view->setNoDataString($nodata);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->appendChild($view);
$panel->setNoBackground();
if ($use_pager) {
$panel->appendChild($pager);
}
return $panel;
}
private function buildCommitView(PhabricatorObjectHandle $handle = null) {
$request = $this->getRequest();
$query = new PhabricatorAuditCommitQuery();
$query->needCommitData(true);
$query->needAudits(true);
$use_pager = ($this->filter != 'active');
if ($use_pager) {
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setOffset($request->getInt('offset'));
$query->setOffset($pager->getOffset());
$query->setLimit($pager->getPageSize() + 1);
}
switch ($this->filter) {
case 'active':
case 'author':
$query->withAuthorPHIDs(array($handle->getPHID()));
break;
case 'packagecommits':
$query->withPackagePHIDs(array($handle->getPHID()));
break;
}
switch ($this->filter) {
case 'active':
$query->withStatus(PhabricatorAuditCommitQuery::STATUS_CONCERN);
break;
default:
switch ($this->filterStatus) {
case 'open':
$query->withStatus(PhabricatorAuditCommitQuery::STATUS_OPEN);
break;
case 'concern':
$query->withStatus(PhabricatorAuditCommitQuery::STATUS_CONCERN);
break;
}
break;
}
if ($handle) {
$handle_name = $handle->getName();
} else {
$handle_name = null;
}
switch ($this->filter) {
case 'active':
$header = pht('Problem Commits');
$nodata = pht('None of your commits have open concerns.');
break;
case 'author':
$header = pht("Commits by %s", $handle_name);
$nodata = pht("No matching commits by %s.", $handle_name);
break;
case 'commits':
$header = pht("Commits");
$nodata = pht("No matching commits.");
break;
case 'packagecommits':
$header = pht("Commits in Package %s", $handle_name);
$nodata = pht("No matching commits in package %s.", $handle_name);
break;
}
$commits = $query->execute();
if ($use_pager) {
$commits = $pager->sliceResults($commits);
}
$view = new PhabricatorAuditCommitListView();
$view->setUser($request->getUser());
$view->setCommits($commits);
$view->setNoDataString($nodata);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->appendChild($view);
$panel->setNoBackground();
if ($use_pager) {
$panel->appendChild($pager);
}
return $panel;
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php b/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php
index 0b0030f2a9..9323ab5a93 100644
--- a/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php
+++ b/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php
@@ -1,94 +1,93 @@
<?php
final class PhabricatorAuthConfirmLinkController
extends PhabricatorAuthController {
private $accountKey;
public function willProcessRequest(array $data) {
$this->accountKey = idx($data, 'akey');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$result = $this->loadAccountForRegistrationOrLinking($this->accountKey);
list($account, $provider, $response) = $result;
if ($response) {
return $response;
}
if (!$provider->shouldAllowAccountLink()) {
return $this->renderError(pht('This account is not linkable.'));
}
$panel_uri = '/settings/panel/external/';
if ($request->isFormPost()) {
$account->setUserPHID($viewer->getPHID());
$account->save();
$this->clearRegistrationCookies();
// TODO: Send the user email about the new account link.
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
// TODO: Provide more information about the external account. Clicking
// through this form blindly is dangerous.
// TODO: If the user has password authentication, require them to retype
// their password here.
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Confirm %s Account Link', $provider->getProviderName()))
->addCancelButton($panel_uri)
->addSubmitButton(pht('Confirm Account Link'));
$form = id(new AphrontFormLayoutView())
->setFullWidth(true)
->appendChild(
phutil_tag(
'div',
array(
'class' => 'aphront-form-instructions',
),
pht(
"Confirm the link with this %s account. This account will be ".
"able to log in to your Phabricator account.",
$provider->getProviderName())))
->appendChild(
id(new PhabricatorAuthAccountView())
->setUser($viewer)
->setExternalAccount($account)
->setAuthProvider($provider));
$dialog->appendChild($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Confirm Link'))
->setHref($panel_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($provider->getProviderName()));
return $this->buildApplicationPage(
array(
$crumbs,
$dialog,
),
array(
'title' => pht('Confirm External Account Link'),
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthController.php b/src/applications/auth/controller/PhabricatorAuthController.php
index aa23034a4b..51db692f50 100644
--- a/src/applications/auth/controller/PhabricatorAuthController.php
+++ b/src/applications/auth/controller/PhabricatorAuthController.php
@@ -1,238 +1,237 @@
<?php
abstract class PhabricatorAuthController extends PhabricatorController {
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->setApplicationName(pht('Login'));
$page->setBaseURI('/login/');
$page->setTitle(idx($data, 'title'));
$page->appendChild($view);
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
}
protected function renderErrorPage($title, array $messages) {
$view = new AphrontErrorView();
$view->setTitle($title);
$view->setErrors($messages);
return $this->buildApplicationPage(
$view,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
/**
* Returns true if this install is newly setup (i.e., there are no user
* accounts yet). In this case, we enter a special mode to permit creation
* of the first account form the web UI.
*/
protected function isFirstTimeSetup() {
// If there are any auth providers, this isn't first time setup, even if
// we don't have accounts.
if (PhabricatorAuthProvider::getAllEnabledProviders()) {
return false;
}
// Otherwise, check if there are any user accounts. If not, we're in first
// time setup.
$any_users = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->setLimit(1)
->execute();
return !$any_users;
}
/**
* Log a user into a web session and return an @{class:AphrontResponse} which
* corresponds to continuing the login process.
*
* Normally, this is a redirect to the validation controller which makes sure
* the user's cookies are set. However, event listeners can intercept this
* event and do something else if they prefer.
*
* @param PhabricatorUser User to log the viewer in as.
* @return AphrontResponse Response which continues the login process.
*/
protected function loginUser(PhabricatorUser $user) {
$response = $this->buildLoginValidateResponse($user);
$session_type = 'web';
$event_type = PhabricatorEventType::TYPE_AUTH_WILLLOGINUSER;
$event_data = array(
'user' => $user,
'type' => $session_type,
'response' => $response,
'shouldLogin' => true,
);
$event = id(new PhabricatorEvent($event_type, $event_data))
->setUser($user);
PhutilEventEngine::dispatchEvent($event);
$should_login = $event->getValue('shouldLogin');
if ($should_login) {
$session_key = $user->establishSession($session_type);
// NOTE: We allow disabled users to login and roadblock them later, so
// there's no check for users being disabled here.
$request = $this->getRequest();
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
$this->clearRegistrationCookies();
}
return $event->getValue('response');
}
protected function clearRegistrationCookies() {
$request = $this->getRequest();
// Clear the registration key.
$request->clearCookie('phreg');
// Clear the client ID / OAuth state key.
$request->clearCookie('phcid');
}
private function buildLoginValidateResponse(PhabricatorUser $user) {
$validate_uri = new PhutilURI($this->getApplicationURI('validate/'));
$validate_uri->setQueryParam('phusr', $user->getUsername());
return id(new AphrontRedirectResponse())->setURI((string)$validate_uri);
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Authentication Error'),
array(
$message,
));
}
protected function loadAccountForRegistrationOrLinking($account_key) {
$request = $this->getRequest();
$viewer = $request->getUser();
$account = null;
$provider = null;
$response = null;
if (!$account_key) {
$response = $this->renderError(
pht('Request did not include account key.'));
return array($account, $provider, $response);
}
// NOTE: We're using the omnipotent user because the actual user may not
// be logged in yet, and because we want to tailor an error message to
// distinguish between "not usable" and "does not exist". We do explicit
// checks later on to make sure this account is valid for the intended
// operation.
$account = id(new PhabricatorExternalAccountQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withAccountSecrets(array($account_key))
->needImages(true)
->executeOne();
if (!$account) {
$response = $this->renderError(pht('No valid linkable account.'));
return array($account, $provider, $response);
}
if ($account->getUserPHID()) {
if ($account->getUserPHID() != $viewer->getUserPHID()) {
$response = $this->renderError(
pht(
'The account you are attempting to register or link is already '.
'linked to another user.'));
} else {
$response = $this->renderError(
pht(
'The account you are attempting to link is already linked '.
'to your account.'));
}
return array($account, $provider, $response);
}
$registration_key = $request->getCookie('phreg');
// NOTE: This registration key check is not strictly necessary, because
// we're only creating new accounts, not linking existing accounts. It
// might be more hassle than it is worth, especially for email.
//
// The attack this prevents is getting to the registration screen, then
// copy/pasting the URL and getting someone else to click it and complete
// the process. They end up with an account bound to credentials you
// control. This doesn't really let you do anything meaningful, though,
// since you could have simply completed the process yourself.
if (!$registration_key) {
$response = $this->renderError(
pht(
'Your browser did not submit a registration key with the request. '.
'You must use the same browser to begin and complete registration. '.
'Check that cookies are enabled and try again.'));
return array($account, $provider, $response);
}
// We store the digest of the key rather than the key itself to prevent a
// theoretical attacker with read-only access to the database from
// hijacking registration sessions.
$actual = $account->getProperty('registrationKey');
$expect = PhabricatorHash::digest($registration_key);
if ($actual !== $expect) {
$response = $this->renderError(
pht(
'Your browser submitted a different registration key than the one '.
'associated with this account. You may need to clear your cookies.'));
return array($account, $provider, $response);
}
$other_account = id(new PhabricatorExternalAccount())->loadAllWhere(
'accountType = %s AND accountDomain = %s AND accountID = %s
AND id != %d',
$account->getAccountType(),
$account->getAccountDomain(),
$account->getAccountID(),
$account->getID());
if ($other_account) {
$response = $this->renderError(
pht(
'The account you are attempting to register with already belongs '.
'to another user.'));
return array($account, $provider, $response);
}
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
$account->getProviderKey());
if (!$provider) {
$response = $this->renderError(
pht(
'The account you are attempting to register with uses a nonexistent '.
'or disabled authentication provider (with key "%s"). An '.
'administrator may have recently disabled this provider.',
$account->getProviderKey()));
return array($account, $provider, $response);
}
return array($account, $provider, null);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthLinkController.php b/src/applications/auth/controller/PhabricatorAuthLinkController.php
index cb7d29c0d2..326474fe2b 100644
--- a/src/applications/auth/controller/PhabricatorAuthLinkController.php
+++ b/src/applications/auth/controller/PhabricatorAuthLinkController.php
@@ -1,138 +1,137 @@
<?php
final class PhabricatorAuthLinkController
extends PhabricatorAuthController {
private $action;
private $providerKey;
public function willProcessRequest(array $data) {
$this->providerKey = $data['pkey'];
$this->action = $data['action'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
$this->providerKey);
if (!$provider) {
return new Aphront404Response();
}
switch ($this->action) {
case 'link':
if (!$provider->shouldAllowAccountLink()) {
return $this->renderErrorPage(
pht('Account Not Linkable'),
array(
pht('This provider is not configured to allow linking.'),
));
}
break;
case 'refresh':
if (!$provider->shouldAllowAccountRefresh()) {
return $this->renderErrorPage(
pht('Account Not Refreshable'),
array(
pht('This provider does not allow refreshing.'),
));
}
break;
default:
return new Aphront400Response();
}
$account = id(new PhabricatorExternalAccount())->loadOneWhere(
'accountType = %s AND accountDomain = %s AND userPHID = %s',
$provider->getProviderType(),
$provider->getProviderDomain(),
$viewer->getPHID());
switch ($this->action) {
case 'link':
if ($account) {
return $this->renderErrorPage(
pht('Account Already Linked'),
array(
pht(
'Your Phabricator account is already linked to an external '.
'account for this provider.'),
));
}
break;
case 'refresh':
if (!$account) {
return $this->renderErrorPage(
pht('No Account Linked'),
array(
pht(
'You do not have a linked account on this provider, and thus '.
'can not refresh it.'),
));
}
break;
default:
return new Aphront400Response();
}
$panel_uri = '/settings/panel/external/';
$request->setCookie('phcid', Filesystem::readRandomCharacters(16));
switch ($this->action) {
case 'link':
$form = $provider->buildLinkForm($this);
break;
case 'refresh':
$form = $provider->buildRefreshForm($this);
break;
default:
return new Aphront400Response();
}
if ($provider->isLoginFormAButton()) {
require_celerity_resource('auth-css');
$form = phutil_tag(
'div',
array(
'class' => 'phabricator-link-button pl',
),
$form);
}
switch ($this->action) {
case 'link':
$name = pht('Link Account');
$title = pht('Link %s Account', $provider->getProviderName());
break;
case 'refresh':
$name = pht('Refresh Account');
$title = pht('Refresh %s Account', $provider->getProviderName());
break;
default:
return new Aphront400Response();
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Link Account'))
->setHref($panel_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($provider->getProviderName($name)));
return $this->buildApplicationPage(
array(
$crumbs,
$form,
),
array(
'title' => $title,
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthLoginController.php b/src/applications/auth/controller/PhabricatorAuthLoginController.php
index 2e99ac82db..4552dc15ee 100644
--- a/src/applications/auth/controller/PhabricatorAuthLoginController.php
+++ b/src/applications/auth/controller/PhabricatorAuthLoginController.php
@@ -1,223 +1,222 @@
<?php
final class PhabricatorAuthLoginController
extends PhabricatorAuthController {
private $providerKey;
private $provider;
public function shouldRequireLogin() {
return false;
}
public function willProcessRequest(array $data) {
$this->providerKey = $data['pkey'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$response = $this->loadProvider();
if ($response) {
return $response;
}
$provider = $this->provider;
list($account, $response) = $provider->processLoginRequest($this);
if ($response) {
return $response;
}
if (!$account) {
throw new Exception(
"Auth provider failed to load an account from processLoginRequest()!");
}
if ($account->getUserPHID()) {
// The account is already attached to a Phabricator user, so this is
// either a login or a bad account link request.
if (!$viewer->isLoggedIn()) {
if ($provider->shouldAllowLogin()) {
return $this->processLoginUser($account);
} else {
return $this->renderError(
pht(
'The external account ("%s") you just authenticated with is '.
'not configured to allow logins on this Phabricator install. '.
'An administrator may have recently disabled it.',
$provider->getProviderName()));
}
} else if ($viewer->getPHID() == $account->getUserPHID()) {
// This is either an attempt to re-link an existing and already
// linked account (which is silly) or a refresh of an external account
// (e.g., an OAuth account).
return id(new AphrontRedirectResponse())
->setURI('/settings/panel/external/');
} else {
return $this->renderError(
pht(
'The external account ("%s") you just used to login is alerady '.
'associated with another Phabricator user account. Login to the '.
'other Phabricator account and unlink the external account before '.
'linking it to a new Phabricator account.',
$provider->getProviderName()));
}
} else {
// The account is not yet attached to a Phabricator user, so this is
// either a registration or an account link request.
if (!$viewer->isLoggedIn()) {
if ($provider->shouldAllowRegistration()) {
return $this->processRegisterUser($account);
} else {
return $this->renderError(
pht(
'The external account ("%s") you just authenticated with is '.
'not configured to allow registration on this Phabricator '.
'install. An administrator may have recently disabled it.',
$provider->getProviderName()));
}
} else {
if ($provider->shouldAllowAccountLink()) {
return $this->processLinkUser($account);
} else {
return $this->renderError(
pht(
'The external account ("%s") you just authenticated with is '.
'not configured to allow account linking on this Phabricator '.
'install. An administrator may have recently disabled it.',
$provider->getProviderName()));
}
}
}
// This should be unreachable, but fail explicitly if we get here somehow.
return new Aphront400Response();
}
private function processLoginUser(PhabricatorExternalAccount $account) {
$user = id(new PhabricatorUser())->loadOneWhere(
'phid = %s',
$account->getUserPHID());
if (!$user) {
return $this->renderError(
pht(
'The external account you just logged in with is not associated '.
'with a valid Phabricator user.'));
}
return $this->loginUser($user);
}
private function processRegisterUser(PhabricatorExternalAccount $account) {
$account_secret = $account->getAccountSecret();
$register_uri = $this->getApplicationURI('register/'.$account_secret.'/');
return $this->setAccountKeyAndContinue($account, $register_uri);
}
private function processLinkUser(PhabricatorExternalAccount $account) {
$account_secret = $account->getAccountSecret();
$confirm_uri = $this->getApplicationURI('confirmlink/'.$account_secret.'/');
return $this->setAccountKeyAndContinue($account, $confirm_uri);
}
private function setAccountKeyAndContinue(
PhabricatorExternalAccount $account,
$next_uri) {
if ($account->getUserPHID()) {
throw new Exception("Account is already registered or linked.");
}
// Regenerate the registration secret key, set it on the external account,
// set a cookie on the user's machine, and redirect them to registration.
// See PhabricatorAuthRegisterController for discussion of the registration
// key.
$registration_key = Filesystem::readRandomCharacters(32);
$account->setProperty(
'registrationKey',
PhabricatorHash::digest($registration_key));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$account->save();
unset($unguarded);
$this->getRequest()->setCookie('phreg', $registration_key);
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
private function loadProvider() {
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
$this->providerKey);
if (!$provider) {
return $this->renderError(
pht(
'The account you are attempting to login with uses a nonexistent '.
'or disabled authentication provider (with key "%s"). An '.
'administrator may have recently disabled this provider.',
$this->providerKey));
}
$this->provider = $provider;
return null;
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Login Failed'),
array($message));
}
public function buildProviderPageResponse(
PhabricatorAuthProvider $provider,
$content) {
$crumbs = $this->buildApplicationCrumbs();
if ($this->getRequest()->getUser()->isLoggedIn()) {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Link Account'))
->setHref($provider->getSettingsURI()));
} else {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Login'))
->setHref($this->getApplicationURI('start/')));
}
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($provider->getProviderName()));
return $this->buildApplicationPage(
array(
$crumbs,
$content,
),
array(
'title' => pht('Login'),
'device' => true,
- 'dust' => true,
));
}
public function buildProviderErrorResponse(
PhabricatorAuthProvider $provider,
$message) {
$message = pht(
'Authentication provider ("%s") encountered an error during login. %s',
$provider->getProviderName(),
$message);
return $this->renderError($message);
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php
index 85324ae090..33bd5c9e5f 100644
--- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php
+++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php
@@ -1,468 +1,467 @@
<?php
final class PhabricatorAuthRegisterController
extends PhabricatorAuthController {
private $accountKey;
public function shouldRequireLogin() {
return false;
}
public function willProcessRequest(array $data) {
$this->accountKey = idx($data, 'akey');
}
public function processRequest() {
$request = $this->getRequest();
if ($request->getUser()->isLoggedIn()) {
return $this->renderError(pht('You are already logged in.'));
}
$is_setup = false;
if (strlen($this->accountKey)) {
$result = $this->loadAccountForRegistrationOrLinking($this->accountKey);
list($account, $provider, $response) = $result;
$is_default = false;
} else if ($this->isFirstTimeSetup()) {
list($account, $provider, $response) = $this->loadSetupAccount();
$is_default = true;
$is_setup = true;
} else {
list($account, $provider, $response) = $this->loadDefaultAccount();
$is_default = true;
}
if ($response) {
return $response;
}
if (!$provider->shouldAllowRegistration()) {
// TODO: This is a routine error if you click "Login" on an external
// auth source which doesn't allow registration. The error should be
// more tailored.
return $this->renderError(
pht(
'The account you are attempting to register with uses an '.
'authentication provider ("%s") which does not allow registration. '.
'An administrator may have recently disabled registration with this '.
'provider.',
$provider->getProviderName()));
}
$user = new PhabricatorUser();
$default_username = $account->getUsername();
$default_realname = $account->getRealName();
$default_email = $account->getEmail();
if ($default_email) {
// If the account source provided an email but it's not allowed by
// the configuration, just pretend we didn't get an email at all.
if (!PhabricatorUserEmail::isAllowedAddress($default_email)) {
$default_email = null;
}
// If the account source provided an email, but another account already
// has that email, just pretend we didn't get an email.
// TODO: See T3340.
if ($default_email) {
$same_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$default_email);
if ($same_email) {
$default_email = null;
}
}
}
$profile = id(new PhabricatorRegistrationProfile())
->setDefaultUsername($default_username)
->setDefaultEmail($default_email)
->setDefaultRealName($default_realname)
->setCanEditUsername(true)
->setCanEditEmail(($default_email === null))
->setCanEditRealName(true)
->setShouldVerifyEmail(false);
$event_type = PhabricatorEventType::TYPE_AUTH_WILLREGISTERUSER;
$event_data = array(
'account' => $account,
'profile' => $profile,
);
$event = id(new PhabricatorEvent($event_type, $event_data))
->setUser($user);
PhutilEventEngine::dispatchEvent($event);
$default_username = $profile->getDefaultUsername();
$default_email = $profile->getDefaultEmail();
$default_realname = $profile->getDefaultRealName();
$can_edit_username = $profile->getCanEditUsername();
$can_edit_email = $profile->getCanEditEmail();
$can_edit_realname = $profile->getCanEditRealName();
$must_set_password = $provider->shouldRequireRegistrationPassword();
$can_edit_anything = $profile->getCanEditAnything() || $must_set_password;
$force_verify = $profile->getShouldVerifyEmail();
$value_username = $default_username;
$value_realname = $default_realname;
$value_email = $default_email;
$value_password = null;
$errors = array();
$e_username = strlen($value_username) ? null : true;
$e_realname = strlen($value_realname) ? null : true;
$e_email = strlen($value_email) ? null : true;
$e_password = true;
$min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');
$min_len = (int)$min_len;
if ($request->isFormPost() || !$can_edit_anything) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
if ($can_edit_username) {
$value_username = $request->getStr('username');
if (!strlen($value_username)) {
$e_username = pht('Required');
$errors[] = pht('Username is required.');
} else if (!PhabricatorUser::validateUsername($value_username)) {
$e_username = pht('Invalid');
$errors[] = PhabricatorUser::describeValidUsername();
} else {
$e_username = null;
}
}
if ($must_set_password) {
$value_password = $request->getStr('password');
$value_confirm = $request->getStr('confirm');
if (!strlen($value_password)) {
$e_password = pht('Required');
$errors[] = pht('You must choose a password.');
} else if ($value_password !== $value_confirm) {
$e_password = pht('No Match');
$errors[] = pht('Password and confirmation must match.');
} else if (strlen($value_password) < $min_len) {
$e_password = pht('Too Short');
$errors[] = pht(
'Password is too short (must be at least %d characters long).',
$min_len);
} else {
$e_password = null;
}
}
if ($can_edit_email) {
$value_email = $request->getStr('email');
if (!strlen($value_email)) {
$e_email = pht('Required');
$errors[] = pht('Email is required.');
} else if (!PhabricatorUserEmail::isAllowedAddress($value_email)) {
$e_email = pht('Invalid');
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
} else {
$e_email = null;
}
}
if ($can_edit_realname) {
$value_realname = $request->getStr('realName');
if (!strlen($value_realname)) {
$e_realname = pht('Required');
$errors[] = pht('Real name is required.');
} else {
$e_realname = null;
}
}
if (!$errors) {
$image = $this->loadProfilePicture($account);
if ($image) {
$user->setProfileImagePHID($image->getPHID());
}
try {
if ($force_verify) {
$verify_email = true;
} else {
$verify_email =
($account->getEmailVerified()) &&
($value_email === $default_email);
}
$email_obj = id(new PhabricatorUserEmail())
->setAddress($value_email)
->setIsVerified((int)$verify_email);
$user->setUsername($value_username);
$user->setRealname($value_realname);
$user->openTransaction();
$editor = id(new PhabricatorUserEditor())
->setActor($user);
$editor->createNewUser($user, $email_obj);
if ($must_set_password) {
$envelope = new PhutilOpaqueEnvelope($value_password);
$editor->changePassword($user, $envelope);
}
if ($is_setup) {
$editor->makeAdminUser($user, true);
}
$account->setUserPHID($user->getPHID());
$provider->willRegisterAccount($account);
$account->save();
$user->saveTransaction();
if (!$email_obj->getIsVerified()) {
$email_obj->sendVerificationEmail($user);
}
return $this->loginUser($user);
} catch (AphrontQueryDuplicateKeyException $exception) {
$same_username = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$user->getUserName());
$same_email = id(new PhabricatorUserEmail())->loadOneWhere(
'address = %s',
$value_email);
if ($same_username) {
$e_username = pht('Duplicate');
$errors[] = pht('Another user already has that username.');
}
if ($same_email) {
// TODO: See T3340.
$e_email = pht('Duplicate');
$errors[] = pht('Another user already has that email.');
}
if (!$same_username && !$same_email) {
throw $exception;
}
}
}
unset($unguarded);
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Registration Failed'));
$error_view->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($request->getUser());
if (!$is_default) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('External Account'))
->setValue(
id(new PhabricatorAuthAccountView())
->setUser($request->getUser())
->setExternalAccount($account)
->setAuthProvider($provider)));
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Phabricator Username'))
->setName('username')
->setValue($value_username)
->setError($e_username));
if ($must_set_password) {
$form->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('Password'))
->setName('password')
->setError($e_password)
->setCaption(
$min_len
? pht('Minimum length of %d characters.', $min_len)
: null));
$form->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('Confirm Password'))
->setName('confirm')
->setError($e_password));
}
if ($can_edit_email) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($value_email)
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
->setError($e_email));
}
if ($can_edit_realname) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Real Name'))
->setName('realName')
->setValue($value_realname)
->setError($e_realname));
}
$submit = id(new AphrontFormSubmitControl());
if ($is_setup) {
$submit
->setValue(pht('Create Admin Account'));
} else {
$submit
->addCancelButton($this->getApplicationURI('start/'))
->setValue(pht('Register Phabricator Account'));
}
$form->appendChild($submit);
$crumbs = $this->buildApplicationCrumbs();
if ($is_setup) {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Setup Admin Account')));
$title = pht('Welcome to Phabricator');
} else {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Register')));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($provider->getProviderName()));
$title = pht('Phabricator Registration');
}
$welcome_view = null;
if ($is_setup) {
$welcome_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Welcome to Phabricator'))
->appendChild(
pht(
'Installation is complete. Register your administrator account '.
'below to log in. You will be able to configure options and add '.
'other authentication mechanisms (like LDAP or OAuth) later on.'));
}
return $this->buildApplicationPage(
array(
$crumbs,
$welcome_view,
$error_view,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function loadDefaultAccount() {
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
$account = null;
$provider = null;
$response = null;
foreach ($providers as $key => $candidate_provider) {
if (!$candidate_provider->shouldAllowRegistration()) {
unset($providers[$key]);
continue;
}
if (!$candidate_provider->isDefaultRegistrationProvider()) {
unset($providers[$key]);
}
}
if (!$providers) {
$response = $this->renderError(
pht(
"There are no configured default registration providers."));
return array($account, $provider, $response);
} else if (count($providers) > 1) {
$response = $this->renderError(
pht(
"There are too many configured default registration providers."));
return array($account, $provider, $response);
}
$provider = head($providers);
$account = $provider->getDefaultExternalAccount();
return array($account, $provider, $response);
}
private function loadSetupAccount() {
$provider = new PhabricatorAuthProviderPassword();
$account = $provider->getDefaultExternalAccount();
$response = null;
return array($account, $provider, $response);
}
private function loadProfilePicture(PhabricatorExternalAccount $account) {
$phid = $account->getProfileImagePHID();
if (!$phid) {
return null;
}
// NOTE: Use of omnipotent user is okay here because the registering user
// can not control the field value, and we can't use their user object to
// do meaningful policy checks anyway since they have not registered yet.
// Reaching this means the user holds the account secret key and the
// registration secret key, and thus has permission to view the image.
$file = id(new PhabricatorFileQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($phid))
->executeOne();
if (!$file) {
return null;
}
try {
$xformer = new PhabricatorImageTransformer();
return $xformer->executeProfileTransform(
$file,
$width = 50,
$min_height = 50,
$max_height = 50);
} catch (Exception $ex) {
phlog($ex);
return null;
}
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Registration Failed'),
array($message));
}
}
diff --git a/src/applications/auth/controller/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php
index d6cde983c1..468e1f8617 100644
--- a/src/applications/auth/controller/PhabricatorAuthStartController.php
+++ b/src/applications/auth/controller/PhabricatorAuthStartController.php
@@ -1,199 +1,198 @@
<?php
final class PhabricatorAuthStartController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
if ($viewer->isLoggedIn()) {
// Kick the user home if they are already logged in.
return id(new AphrontRedirectResponse())->setURI('/');
}
if ($request->isAjax()) {
return $this->processAjaxRequest();
}
if ($request->isConduit()) {
return $this->processConduitRequest();
}
if ($request->getCookie('phusr') && $request->getCookie('phsid')) {
// The session cookie is invalid, so clear it.
$request->clearCookie('phusr');
$request->clearCookie('phsid');
return $this->renderError(
pht(
"Your login session is invalid. Try reloading the page and logging ".
"in again. If that does not work, clear your browser cookies."));
}
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
foreach ($providers as $key => $provider) {
if (!$provider->shouldAllowLogin()) {
unset($providers[$key]);
}
}
if (!$providers) {
if ($this->isFirstTimeSetup()) {
// If this is a fresh install, let the user register their admin
// account.
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI('/register/'));
}
return $this->renderError(
pht(
"This Phabricator install is not configured with any enabled ".
"authentication providers which can be used to log in. If you ".
"have accidentally locked yourself out by disabling all providers, ".
"you can use `phabricator/bin/auth recover <username>` to ".
"recover access to an administrative account."));
}
$next_uri = $request->getStr('next');
if (!$next_uri) {
$next_uri_path = $this->getRequest()->getPath();
if ($next_uri_path == '/auth/start/') {
$next_uri = '/';
} else {
$next_uri = $this->getRequest()->getRequestURI();
}
}
if (!$request->isFormPost()) {
$request->setCookie('next_uri', $next_uri);
$request->setCookie('phcid', Filesystem::readRandomCharacters(16));
}
$not_buttons = array();
$are_buttons = array();
$providers = msort($providers, 'getLoginOrder');
foreach ($providers as $provider) {
if ($provider->isLoginFormAButton()) {
$are_buttons[] = $provider->buildLoginForm($this);
} else {
$not_buttons[] = $provider->buildLoginForm($this);
}
}
$out = array();
$out[] = $not_buttons;
if ($are_buttons) {
require_celerity_resource('auth-css');
foreach ($are_buttons as $key => $button) {
$are_buttons[$key] = phutil_tag(
'div',
array(
'class' => 'phabricator-login-button mmb',
),
$button);
}
// If we only have one button, add a second pretend button so that we
// always have two columns. This makes it easier to get the alignments
// looking reasonable.
if (count($are_buttons) == 1) {
$are_buttons[] = null;
}
$button_columns = id(new AphrontMultiColumnView())
->setFluidLayout(true);
$are_buttons = array_chunk($are_buttons, ceil(count($are_buttons) / 2));
foreach ($are_buttons as $column) {
$button_columns->addColumn($column);
}
$out[] = phutil_tag(
'div',
array(
'class' => 'phabricator-login-buttons',
),
$button_columns);
}
$login_message = PhabricatorEnv::getEnvConfig('auth.login-message');
$login_message = phutil_safe_html($login_message);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Login')));
return $this->buildApplicationPage(
array(
$crumbs,
$login_message,
$out,
),
array(
'title' => pht('Login to Phabricator'),
'device' => true,
- 'dust' => true,
));
}
private function processAjaxRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
// We end up here if the user clicks a workflow link that they need to
// login to use. We give them a dialog saying "You need to login...".
if ($request->isDialogFormPost()) {
return id(new AphrontRedirectResponse())->setURI(
$request->getRequestURI());
}
$dialog = new AphrontDialogView();
$dialog->setUser($viewer);
$dialog->setTitle(pht('Login Required'));
$dialog->appendChild(pht('You must login to continue.'));
$dialog->addSubmitButton(pht('Login'));
$dialog->addCancelButton('/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function processConduitRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
// A common source of errors in Conduit client configuration is getting
// the request path wrong. The client will end up here, so make some
// effort to give them a comprehensible error message.
$request_path = $this->getRequest()->getPath();
$conduit_path = '/api/<method>';
$example_path = '/api/conduit.ping';
$message = pht(
'ERROR: You are making a Conduit API request to "%s", but the correct '.
'HTTP request path to use in order to access a COnduit method is "%s" '.
'(for example, "%s"). Check your configuration.',
$request_path,
$conduit_path,
$example_path);
return id(new AphrontPlainTextResponse())->setContent($message);
}
protected function renderError($message) {
return $this->renderErrorPage(
pht('Authentication Failure'),
array($message));
}
}
diff --git a/src/applications/auth/controller/PhabricatorEmailLoginController.php b/src/applications/auth/controller/PhabricatorEmailLoginController.php
index c01c0df5b4..8ebc2a001d 100644
--- a/src/applications/auth/controller/PhabricatorEmailLoginController.php
+++ b/src/applications/auth/controller/PhabricatorEmailLoginController.php
@@ -1,160 +1,159 @@
<?php
final class PhabricatorEmailLoginController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$request = $this->getRequest();
if (!PhabricatorAuthProviderPassword::getPasswordProvider()) {
return new Aphront400Response();
}
$e_email = true;
$e_captcha = true;
$errors = array();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($request->isFormPost()) {
$e_email = null;
$e_captcha = pht('Again');
$captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
if (!$captcha_ok) {
$errors[] = pht("Captcha response is incorrect, try again.");
$e_captcha = pht('Invalid');
}
$email = $request->getStr('email');
if (!strlen($email)) {
$errors[] = pht("You must provide an email address.");
$e_email = pht('Required');
}
if (!$errors) {
// NOTE: Don't validate the email unless the captcha is good; this makes
// it expensive to fish for valid email addresses while giving the user
// a better error if they goof their email.
$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_user) {
$errors[] =
pht("There is no account associated with that email address.");
$e_email = pht("Invalid");
}
if (!$errors) {
$uri = $target_user->getEmailLoginURI($target_email);
if ($is_serious) {
$body = <<<EOBODY
You can use this link to reset your Phabricator password:
{$uri}
EOBODY;
} else {
$body = <<<EOBODY
Condolences on forgetting your password. You can use this link to reset it:
{$uri}
After you set a new password, consider writing it down on a sticky note and
attaching it to your monitor so you don't forget again! Choosing a very short,
easy-to-remember password like "cat" or "1234" might also help.
Best Wishes,
Phabricator
EOBODY;
}
// NOTE: Don't set the user as 'from', or they may not receive the
// mail if they have the "don't send me email about my own actions"
// preference set.
$mail = new PhabricatorMetaMTAMail();
$mail->setSubject('[Phabricator] Password Reset');
$mail->addTos(
array(
$target_user->getPHID(),
));
$mail->setBody($body);
$mail->saveAndSend();
$view = new AphrontRequestFailureView();
$view->setHeader(pht('Check Your Email'));
$view->appendChild(phutil_tag('p', array(), pht(
'An email has been sent with a link you can use to login.')));
return $this->buildStandardPageResponse(
$view,
array(
'title' => pht('Email Sent'),
));
}
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
}
$email_auth = new AphrontFormLayoutView();
$email_auth->appendChild($error_view);
$email_auth
->setUser($request->getUser())
->setFullWidth(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($request->getStr('email'))
->setError($e_email))
->appendChild(
id(new AphrontFormRecaptchaControl())
->setLabel(pht('Captcha'))
->setError($e_captcha));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Reset Password')));
$dialog = new AphrontDialogView();
$dialog->setUser($request->getUser());
$dialog->setTitle(pht(
'Forgot Password / Email Login'));
$dialog->appendChild($email_auth);
$dialog->addSubmitButton(pht('Send Email'));
$dialog->setSubmitURI('/login/email/');
return $this->buildApplicationPage(
array(
$crumbs,
$dialog,
),
array(
'title' => pht('Forgot Password'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/auth/controller/PhabricatorEmailVerificationController.php b/src/applications/auth/controller/PhabricatorEmailVerificationController.php
index ac43cf7055..37eb76ee06 100644
--- a/src/applications/auth/controller/PhabricatorEmailVerificationController.php
+++ b/src/applications/auth/controller/PhabricatorEmailVerificationController.php
@@ -1,81 +1,80 @@
<?php
final class PhabricatorEmailVerificationController
extends PhabricatorAuthController {
private $code;
public function willProcessRequest(array $data) {
$this->code = $data['code'];
}
public function shouldRequireEmailVerification() {
// Since users need to be able to hit this endpoint in order to verify
// email, we can't ever require email verification here.
return false;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'userPHID = %s AND verificationCode = %s',
$user->getPHID(),
$this->code);
$color = PhabricatorActionHeaderView::HEADER_DARK_GREY;
if (!$email) {
$title = pht('Unable to Verify Email');
$content = pht(
'The verification code you provided is incorrect, or the email '.
'address has been removed, or the email address is owned by another '.
'user. Make sure you followed the link in the email correctly and are '.
'logged in with the user account associated with the email address.');
$color = PhabricatorActionHeaderView::HEADER_RED;
$continue = pht('Rats!');
} else if ($email->getIsVerified()) {
$title = pht('Address Already Verified');
$content = pht(
'This email address has already been verified.');
$continue = pht('Continue to Phabricator');
} else {
$guard = AphrontWriteGuard::beginScopedUnguardedWrites();
$email->setIsVerified(1);
$email->save();
unset($guard);
$title = pht('Address Verified');
$content = pht(
'The email address %s is now verified.',
phutil_tag('strong', array(), $email->getAddress()));
$continue = pht('Continue to Phabricator');
}
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle($title)
->setHeaderColor($color)
->setMethod('GET')
->addCancelButton('/', $continue)
->appendChild($content);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Verify Email')));
return $this->buildApplicationPage(
array(
$crumbs,
$dialog,
),
array(
'title' => pht('Verify Email'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/auth/controller/config/PhabricatorAuthEditController.php b/src/applications/auth/controller/config/PhabricatorAuthEditController.php
index fcd619a2c0..7ada830569 100644
--- a/src/applications/auth/controller/config/PhabricatorAuthEditController.php
+++ b/src/applications/auth/controller/config/PhabricatorAuthEditController.php
@@ -1,287 +1,286 @@
<?php
final class PhabricatorAuthEditController
extends PhabricatorAuthProviderConfigController {
private $providerClass;
private $configID;
public function willProcessRequest(array $data) {
$this->providerClass = idx($data, 'className');
$this->configID = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
if ($this->configID) {
$config = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($this->configID))
->executeOne();
if (!$config) {
return new Aphront404Response();
}
$provider = $config->getProvider();
if (!$provider) {
return new Aphront404Response();
}
$is_new = false;
} else {
$providers = PhabricatorAuthProvider::getAllBaseProviders();
foreach ($providers as $candidate_provider) {
if (get_class($candidate_provider) === $this->providerClass) {
$provider = $candidate_provider;
break;
}
}
if (!$provider) {
return new Aphront404Response();
}
// TODO: When we have multi-auth providers, support them here.
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->withProviderClasses(array(get_class($provider)))
->execute();
if ($configs) {
$id = head($configs)->getID();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setMethod('GET')
->setSubmitURI($this->getApplicationURI('config/edit/'.$id.'/'))
->setTitle(pht('Provider Already Configured'))
->appendChild(
pht(
'This provider ("%s") already exists, and you can not add more '.
'than one instance of it. You can edit the existing provider, '.
'or you can choose a different provider.',
$provider->getProviderName()))
->addCancelButton($this->getApplicationURI('config/new/'))
->addSubmitButton(pht('Edit Existing Provider'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$config = $provider->getDefaultProviderConfig();
$provider->attachProviderConfig($config);
$is_new = true;
}
$errors = array();
$v_registration = $config->getShouldAllowRegistration();
$v_link = $config->getShouldAllowLink();
$v_unlink = $config->getShouldAllowUnlink();
if ($request->isFormPost()) {
$properties = $provider->readFormValuesFromRequest($request);
list($errors, $issues, $properties) = $provider->processEditForm(
$request,
$properties);
$xactions = array();
if (!$errors) {
if ($is_new) {
$config->setProviderType($provider->getProviderType());
$config->setProviderDomain($provider->getProviderDomain());
}
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION)
->setNewValue($request->getInt('allowRegistration', 0));
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_LINK)
->setNewValue($request->getInt('allowLink', 0));
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK)
->setNewValue($request->getInt('allowUnlink', 0));
foreach ($properties as $key => $value) {
$xactions[] = id(new PhabricatorAuthProviderConfigTransaction())
->setTransactionType(
PhabricatorAuthProviderConfigTransaction::TYPE_PROPERTY)
->setMetadataValue('auth:property', $key)
->setNewValue($value);
}
if ($is_new) {
$config->save();
}
$editor = id(new PhabricatorAuthProviderConfigEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->applyTransactions($config, $xactions);
return id(new AphrontRedirectResponse())->setURI(
$this->getApplicationURI());
}
} else {
$properties = $provider->readFormValuesFromProvider();
$issues = array();
}
if ($errors) {
$errors = id(new AphrontErrorView())->setErrors($errors);
}
if ($is_new) {
$button = pht('Add Provider');
$crumb = pht('Add Provider');
$title = pht('Add Authentication Provider');
$cancel_uri = $this->getApplicationURI('/config/new/');
} else {
$button = pht('Save');
$crumb = pht('Edit Provider');
$title = pht('Edit Authentication Provider');
$cancel_uri = $this->getApplicationURI();
}
$str_registration = hsprintf(
'<strong>%s:</strong> %s',
pht('Allow Registration'),
pht(
'Allow users to register new Phabricator accounts using this '.
'provider. If you disable registration, users can still use this '.
'provider to log in to existing accounts, but will not be able to '.
'create new accounts.'));
$str_link = hsprintf(
'<strong>%s:</strong> %s',
pht('Allow Linking Accounts'),
pht(
'Allow users to link account credentials for this provider to '.
'existing Phabricator accounts. There is normally no reason to '.
'disable this unless you are trying to move away from a provider '.
'and want to stop users from creating new account links.'));
$str_unlink = hsprintf(
'<strong>%s:</strong> %s',
pht('Allow Unlinking Accounts'),
pht(
'Allow users to unlink account credentials for this provider from '.
'existing Phabricator accounts. If you disable this, Phabricator '.
'accounts will be permanently bound to provider accounts.'));
$status_tag = id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE);
if ($is_new) {
$status_tag
->setName(pht('New Provider'))
->setBackgroundColor('blue');
} else if ($config->getIsEnabled()) {
$status_tag
->setName(pht('Enabled'))
->setBackgroundColor('green');
} else {
$status_tag
->setName(pht('Disabled'))
->setBackgroundColor('red');
}
$form = id(new AphrontFormView())
->setUser($viewer)
->setFlexible(true)
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Provider'))
->setValue($provider->getProviderName()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Status'))
->setValue($status_tag))
->appendChild(
id(new AphrontFormCheckboxControl())
->setLabel(pht('Allow'))
->addCheckbox(
'allowRegistration',
1,
$str_registration,
$v_registration))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'allowLink',
1,
$str_link,
$v_link))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'allowUnlink',
1,
$str_unlink,
$v_unlink));
$provider->extendEditForm($request, $form, $properties, $issues);
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($button));
$help = $provider->getConfigurationHelp();
if ($help) {
$form->appendChild(id(new PHUIFormDividerControl()));
$form->appendRemarkupInstructions($help);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($crumb));
$xaction_view = null;
if (!$is_new) {
$xactions = id(new PhabricatorAuthProviderConfigTransactionQuery())
->withObjectPHIDs(array($config->getPHID()))
->setViewer($viewer)
->execute();
foreach ($xactions as $xaction) {
$xaction->setProvider($provider);
}
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($config->getPHID())
->setTransactions($xactions);
}
return $this->buildApplicationPage(
array(
$crumbs,
$errors,
$form,
$xaction_view,
),
array(
'title' => $title,
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php
index 6d3f09d48c..e5c6b28867 100644
--- a/src/applications/auth/controller/config/PhabricatorAuthListController.php
+++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php
@@ -1,105 +1,104 @@
<?php
final class PhabricatorAuthListController
extends PhabricatorAuthProviderConfigController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$configs = id(new PhabricatorAuthProviderConfigQuery())
->setViewer($viewer)
->execute();
$list = new PhabricatorObjectItemListView();
foreach ($configs as $config) {
$item = new PhabricatorObjectItemView();
$id = $config->getID();
$edit_uri = $this->getApplicationURI('config/edit/'.$id.'/');
$enable_uri = $this->getApplicationURI('config/enable/'.$id.'/');
$disable_uri = $this->getApplicationURI('config/disable/'.$id.'/');
$provider = $config->getProvider();
if ($provider) {
$name = $provider->getProviderName();
} else {
$name = $config->getProviderType().' ('.$config->getProviderClass().')';
}
$item
->setHeader($name);
if ($provider) {
$item->setHref($edit_uri);
} else {
$item->addAttribute(pht('Provider Implementation Missing!'));
}
$domain = null;
if ($provider) {
$domain = $provider->getProviderDomain();
if ($domain !== 'self') {
$item->addAttribute($domain);
}
}
if ($config->getShouldAllowRegistration()) {
$item->addAttribute(pht('Allows Registration'));
}
if ($config->getIsEnabled()) {
$item->setBarColor('green');
$item->addAction(
id(new PHUIListItemView())
->setIcon('delete')
->setHref($disable_uri)
->addSigil('workflow'));
} else {
$item->setBarColor('grey');
$item->addIcon('delete-grey', pht('Disabled'));
$item->addAction(
id(new PHUIListItemView())
->setIcon('new')
->setHref($enable_uri)
->addSigil('workflow'));
}
$list->addItem($item);
}
$list->setNoDataString(
pht(
'%s You have not added authentication providers yet. Use "%s" to add '.
'a provider, which will let users register new Phabricator accounts '.
'and log in.',
phutil_tag(
'strong',
array(),
pht('No Providers Configured:')),
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI('config/new/'),
),
pht('Add Authentication Provider'))));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Auth Providers')));
return $this->buildApplicationPage(
array(
$crumbs,
$list,
),
array(
'title' => pht('Authentication Providers'),
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/auth/controller/config/PhabricatorAuthNewController.php b/src/applications/auth/controller/config/PhabricatorAuthNewController.php
index 15c98bdaea..1c37f454cc 100644
--- a/src/applications/auth/controller/config/PhabricatorAuthNewController.php
+++ b/src/applications/auth/controller/config/PhabricatorAuthNewController.php
@@ -1,103 +1,102 @@
<?php
final class PhabricatorAuthNewController
extends PhabricatorAuthProviderConfigController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$providers = PhabricatorAuthProvider::getAllBaseProviders();
$e_provider = null;
$errors = array();
if ($request->isFormPost()) {
$provider_string = $request->getStr('provider');
if (!strlen($provider_string)) {
$e_provider = pht('Required');
$errors[] = pht('You must select an authentication provider.');
} else {
$found = false;
foreach ($providers as $provider) {
if (get_class($provider) === $provider_string) {
$found = true;
break;
}
}
if (!$found) {
$e_provider = pht('Invalid');
$errors[] = pht('You must select a valid provider.');
}
}
if (!$errors) {
return id(new AphrontRedirectResponse())->setURI(
$this->getApplicationURI('/config/new/'.$provider_string.'/'));
}
}
if ($errors) {
$errors = id(new AphrontErrorView())->setErrors($errors);
}
$options = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Provider'))
->setName('provider')
->setError($e_provider);
$configured = PhabricatorAuthProvider::getAllProviders();
$configured_classes = array();
foreach ($configured as $configured_provider) {
$configured_classes[get_class($configured_provider)] = true;
}
// Sort providers by login order, and move disabled providers to the
// bottom.
$providers = msort($providers, 'getLoginOrder');
$providers = array_diff_key($providers, $configured_classes) + $providers;
foreach ($providers as $provider) {
if (isset($configured_classes[get_class($provider)])) {
$disabled = true;
$description = pht('This provider is already configured.');
} else {
$disabled = false;
$description = $provider->getDescriptionForCreate();
}
$options->addButton(
get_class($provider),
$provider->getNameForCreate(),
$description,
$disabled ? 'disabled' : null,
$disabled);
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild($options)
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI())
->setValue(pht('Continue')));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Add Provider')));
return $this->buildApplicationPage(
array(
$crumbs,
$errors,
$form,
),
array(
'title' => pht('Add Authentication Provider'),
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php
index c67a6fe32f..00cd140908 100644
--- a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php
+++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php
@@ -1,42 +1,41 @@
<?php
final class PhabricatorChatLogChannelListController
extends PhabricatorChatLogController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$channels = id(new PhabricatorChatLogChannelQuery())
->setViewer($user)
->execute();
$list = new PhabricatorObjectItemListView();
foreach ($channels as $channel) {
$item = id(new PhabricatorObjectItemView())
->setHeader($channel->getChannelName())
->setHref('/chatlog/channel/'.$channel->getID().'/')
->addAttribute($channel->getServiceName())
->addAttribute($channel->getServiceType());
$list->addItem($item);
}
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Channel List'))
->setHref($this->getApplicationURI()));
return $this->buildApplicationPage(
array(
$crumbs,
$list,
),
array(
'title' => pht('Channel List'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
index ae45f72035..d1aada812a 100644
--- a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
+++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php
@@ -1,264 +1,263 @@
<?php
final class PhabricatorChatLogChannelLogController
extends PhabricatorChatLogController {
private $channelID;
public function willProcessRequest(array $data) {
$this->channelID = $data['channelID'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$uri = clone $request->getRequestURI();
$uri->setQueryParams(array());
$pager = new AphrontCursorPagerView();
$pager->setURI($uri);
$pager->setPageSize(250);
$query = id(new PhabricatorChatLogQuery())
->setViewer($user)
->withChannelIDs(array($this->channelID));
$channel = id(new PhabricatorChatLogChannelQuery())
->setViewer($user)
->withIDs(array($this->channelID))
->executeOne();
if (!$channel) {
return new Aphront404Response();
}
list($after, $before, $map) = $this->getPagingParameters($request, $query);
$pager->setAfterID($after);
$pager->setBeforeID($before);
$logs = $query->executeWithCursorPager($pager);
// Show chat logs oldest-first.
$logs = array_reverse($logs);
// Divide all the logs into blocks, where a block is the same author saying
// several things in a row. A block ends when another user speaks, or when
// two minutes pass without the author speaking.
$blocks = array();
$block = null;
$last_author = null;
$last_epoch = null;
foreach ($logs as $log) {
$this_author = $log->getAuthor();
$this_epoch = $log->getEpoch();
// Decide whether we should start a new block or not.
$new_block = ($this_author !== $last_author) ||
($this_epoch - (60 * 2) > $last_epoch);
if ($new_block) {
if ($block) {
$blocks[] = $block;
}
$block = array(
'id' => $log->getID(),
'epoch' => $this_epoch,
'author' => $this_author,
'logs' => array($log),
);
} else {
$block['logs'][] = $log;
}
$last_author = $this_author;
$last_epoch = $this_epoch;
}
if ($block) {
$blocks[] = $block;
}
// Figure out CSS classes for the blocks. We alternate colors between
// lines, and highlight the entire block which contains the target ID or
// date, if applicable.
foreach ($blocks as $key => $block) {
$classes = array();
if ($key % 2) {
$classes[] = 'alternate';
}
$ids = mpull($block['logs'], 'getID', 'getID');
if (array_intersect_key($ids, $map)) {
$classes[] = 'highlight';
}
$blocks[$key]['class'] = $classes ? implode(' ', $classes) : null;
}
require_celerity_resource('phabricator-chatlog-css');
$out = array();
foreach ($blocks as $block) {
$author = $block['author'];
$author = phutil_utf8_shorten($author, 18);
$author = phutil_tag('td', array('class' => 'author'), $author);
$href = $uri->alter('at', $block['id']);
$timestamp = $block['epoch'];
$timestamp = phabricator_datetime($timestamp, $user);
$timestamp = phutil_tag(
'a',
array(
'href' => $href,
'class' => 'timestamp'
),
$timestamp);
$message = mpull($block['logs'], 'getMessage');
$message = implode("\n", $message);
$message = phutil_tag(
'td',
array(
'class' => 'message'
),
array(
$timestamp,
$message));
$out[] = phutil_tag(
'tr',
array(
'class' => $block['class'],
),
array(
$author,
$message));
}
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($channel->getChannelName())
->setHref($uri));
$form = id(new AphrontFormView())
->setUser($user)
->setMethod('GET')
->setAction($uri)
->setNoShading(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Date'))
->setName('date')
->setValue($request->getStr('date')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Jump')));
$filter = new AphrontListFilterView();
$filter->appendChild($form);
$table = phutil_tag(
'table',
array(
'class' => 'phabricator-chat-log'
),
$out);
$log = phutil_tag(
'div',
array(
'class' => 'phabricator-chat-log-panel'
),
$table);
$content = phutil_tag(
'div',
array(
'class' => 'phabricator-chat-log-wrap'
),
$log);
return $this->buildApplicationPage(
array(
$crumbs,
$filter,
$content,
$pager,
),
array(
'title' => pht('Channel Log'),
'device' => true,
- 'dust' => true,
));
}
/**
* From request parameters, figure out where we should jump to in the log.
* We jump to either a date or log ID, but load a few lines of context before
* it so the user can see the nearby conversation.
*/
private function getPagingParameters(
AphrontRequest $request,
PhabricatorChatLogQuery $query) {
$user = $request->getUser();
$at_id = $request->getInt('at');
$at_date = $request->getStr('date');
$context_log = null;
$map = array();
$query = clone $query;
$query->setLimit(8);
if ($at_id) {
// Jump to the log in question, and load a few lines of context before
// it.
$context_logs = $query
->setAfterID($at_id)
->execute();
$context_log = last($context_logs);
$map = array(
$at_id => true,
);
} else if ($at_date) {
$timestamp = PhabricatorTime::parseLocalTime($at_date, $user);
if ($timestamp) {
$context_logs = $query
->withMaximumEpoch($timestamp)
->execute();
$context_log = last($context_logs);
$target_log = head($context_logs);
if ($target_log) {
$map = array(
$target_log->getID() => true,
);
}
}
}
if ($context_log) {
$after = null;
$before = $context_log->getID() - 1;
} else {
$after = $request->getInt('after');
$before = $request->getInt('before');
}
return array($after, $before, $map);
}
}
diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
index 37f9505d19..3a25e954b0 100644
--- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php
@@ -1,503 +1,502 @@
<?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 PhabricatorHeaderView())
->setHeader(pht('Method Parameters'));
$result_head = id(new PhabricatorHeaderView())
->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,
- 'dust' => 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);
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/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
index 7d2ac5b3a3..57441044c0 100644
--- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
@@ -1,132 +1,131 @@
<?php
/**
* @group conduit
*/
final class PhabricatorConduitConsoleController
extends PhabricatorConduitController {
private $method;
public function willProcessRequest(array $data) {
$this->method = $data['method'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$method = id(new PhabricatorConduitMethodQuery())
->setViewer($viewer)
->withMethods(array($this->method))
->executeOne();
if (!$method) {
return new Aphront404Response();
}
$status = $method->getMethodStatus();
$reason = $method->getMethodStatusDescription();
$status_view = null;
if ($status != ConduitAPIMethod::METHOD_STATUS_STABLE) {
$status_view = new AphrontErrorView();
switch ($status) {
case ConduitAPIMethod::METHOD_STATUS_DEPRECATED:
$status_view->setTitle('Deprecated Method');
$status_view->appendChild(
nonempty($reason, "This method is deprecated."));
break;
case ConduitAPIMethod::METHOD_STATUS_UNSTABLE:
$status_view->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$status_view->setTitle('Unstable Method');
$status_view->appendChild(
nonempty(
$reason,
"This method is new and unstable. Its interface is subject ".
"to change."));
break;
}
}
$error_types = $method->defineErrorTypes();
if ($error_types) {
$error_description = array();
foreach ($error_types as $error => $meaning) {
$error_description[] = hsprintf(
'<li><strong>%s:</strong> %s</li>',
$error,
$meaning);
}
$error_description = phutil_tag('ul', array(), $error_description);
} else {
$error_description = "This method does not raise any specific errors.";
}
$form = new AphrontFormView();
$form
->setUser($request->getUser())
->setAction('/api/'.$this->method)
->addHiddenInput('allowEmptyParams', 1)
->setFlexible(true)
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Description')
->setValue($method->getMethodDescription()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Returns')
->setValue($method->defineReturnType()))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Errors')
->setValue($error_description))
->appendChild(hsprintf(
'<p class="aphront-form-instructions">Enter parameters using '.
'<strong>JSON</strong>. For instance, to enter a list, type: '.
'<tt>["apple", "banana", "cherry"]</tt>'));
$params = $method->defineParamTypes();
foreach ($params as $param => $desc) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel($param)
->setName("params[{$param}]")
->setCaption($desc));
}
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Output Format')
->setName('output')
->setOptions(
array(
'human' => 'Human Readable',
'json' => 'JSON',
)))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI())
->setValue('Call Method'));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($method->getAPIMethodName()));
return $this->buildApplicationPage(
array(
$crumbs,
$status_view,
$form,
),
array(
'title' => $method->getAPIMethodName(),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/conduit/controller/PhabricatorConduitLogController.php b/src/applications/conduit/controller/PhabricatorConduitLogController.php
index 1c235d8d8c..ee95ff0533 100644
--- a/src/applications/conduit/controller/PhabricatorConduitLogController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitLogController.php
@@ -1,136 +1,135 @@
<?php
/**
* @group conduit
*/
final class PhabricatorConduitLogController
extends PhabricatorConduitController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$conn_table = new PhabricatorConduitConnectionLog();
$call_table = new PhabricatorConduitMethodCallLog();
$conn_r = $call_table->establishConnection('r');
$pager = new AphrontCursorPagerView();
$pager->readFromRequest($request);
$pager->setPageSize(500);
$query = id(new PhabricatorConduitLogQuery())
->setViewer($viewer);
$methods = $request->getStrList('methods');
if ($methods) {
$query->withMethods($methods);
}
$calls = $query->executeWithCursorPager($pager);
$conn_ids = array_filter(mpull($calls, 'getConnectionID'));
$conns = array();
if ($conn_ids) {
$conns = $conn_table->loadAllWhere(
'id IN (%Ld)',
$conn_ids);
}
$table = $this->renderCallTable($calls, $conns);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Call Logs')));
return $this->buildApplicationPage(
array(
$crumbs,
$table,
$pager,
),
array(
'title' => 'Conduit Logs',
'device' => true,
- 'dust' => true,
));
}
private function renderCallTable(array $calls, array $conns) {
assert_instances_of($calls, 'PhabricatorConduitMethodCallLog');
assert_instances_of($conns, 'PhabricatorConduitConnectionLog');
$viewer = $this->getRequest()->getUser();
$methods = id(new PhabricatorConduitMethodQuery())
->setViewer($viewer)
->execute();
$methods = mpull($methods, null, 'getAPIMethodName');
$rows = array();
foreach ($calls as $call) {
$conn = idx($conns, $call->getConnectionID());
if ($conn) {
$name = $conn->getUserName();
$client = ' (via '.$conn->getClient().')';
} else {
$name = null;
$client = null;
}
$method = idx($methods, $call->getMethod());
if ($method) {
switch ($method->getMethodStatus()) {
case ConduitAPIMethod::METHOD_STATUS_STABLE:
$status = null;
break;
case ConduitAPIMethod::METHOD_STATUS_UNSTABLE:
$status = pht('Unstable');
break;
case ConduitAPIMethod::METHOD_STATUS_DEPRECATED:
$status = pht('Deprecated');
break;
}
} else {
$status = pht('Unknown');
}
$rows[] = array(
$call->getConnectionID(),
$name,
array($call->getMethod(), $client),
$status,
$call->getError(),
number_format($call->getDuration()).' us',
phabricator_datetime($call->getDateCreated(), $viewer),
);
}
$table = id(new AphrontTableView($rows))
->setDeviceReadyTable(true);
$table->setHeaders(
array(
'Connection',
'User',
'Method',
'Status',
'Error',
'Duration',
'Date',
));
$table->setColumnClasses(
array(
'',
'',
'wide',
'',
'',
'n',
'right',
));
return $table;
}
}
diff --git a/src/applications/conduit/controller/PhabricatorConduitTokenController.php b/src/applications/conduit/controller/PhabricatorConduitTokenController.php
index fb9887ceee..694671a0dc 100644
--- a/src/applications/conduit/controller/PhabricatorConduitTokenController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitTokenController.php
@@ -1,67 +1,66 @@
<?php
/**
* @group conduit
*/
final class PhabricatorConduitTokenController
extends PhabricatorConduitController {
public function processRequest() {
$user = $this->getRequest()->getUser();
// Ideally we'd like to verify this, but it's fine to leave it unguarded
// for now and verifying it would need some Ajax junk or for the user to
// click a button or similar.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$old_token = id(new PhabricatorConduitCertificateToken())
->loadOneWhere(
'userPHID = %s',
$user->getPHID());
if ($old_token) {
$old_token->delete();
}
$token = id(new PhabricatorConduitCertificateToken())
->setUserPHID($user->getPHID())
->setToken(Filesystem::readRandomCharacters(40))
->save();
unset($unguarded);
$pre_instructions = pht(
'Copy and paste this token into the prompt given to you by '.
'`arc install-certificate`');
$post_instructions = pht(
'After you copy and paste this token, `arc` will complete '.
'the certificate install process for you.');
$form = id(new AphrontFormView())
->setUser($user)
->appendRemarkupInstructions($pre_instructions)
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Token'))
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setValue($token->getToken()))
->appendRemarkupInstructions($post_instructions);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Install Certificate')));
return $this->buildApplicationPage(
array(
$crumbs,
$form,
),
array(
'title' => pht('Certificate Install Token'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php
index 00f6dc1797..f02d4d751e 100644
--- a/src/applications/config/controller/PhabricatorConfigAllController.php
+++ b/src/applications/config/controller/PhabricatorConfigAllController.php
@@ -1,138 +1,137 @@
<?php
final class PhabricatorConfigAllController
extends PhabricatorConfigController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$db_values = id(new PhabricatorConfigEntry())
->loadAllWhere('namespace = %s', 'default');
$db_values = mpull($db_values, null, 'getConfigKey');
$rows = array();
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
ksort($options);
foreach ($options as $option) {
$key = $option->getKey();
if ($option->getMasked()) {
$value = phutil_tag('em', array(), pht('Masked'));
} else if ($option->getHidden()) {
$value = phutil_tag('em', array(), pht('Hidden'));
} else {
$value = PhabricatorEnv::getEnvConfig($key);
$value = PhabricatorConfigJSON::prettyPrintJSON($value);
}
$db_value = idx($db_values, $key);
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $this->getApplicationURI('edit/'.$key.'/'),
),
$key),
$value,
$db_value && !$db_value->getIsDeleted() ? pht('Customized') : '',
);
}
$table = id(new AphrontTableView($rows))
->setDeviceReadyTable(true)
->setColumnClasses(
array(
'',
'wide',
))
->setHeaders(
array(
pht('Key'),
pht('Value'),
pht('Customized'),
));
$title = pht('Current Settings');
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
$panel = new AphrontPanelView();
$panel->appendChild($table);
$panel->setNoBackground();
$versions = $this->loadVersions();
$version_property_list = id(new PhabricatorPropertyListView());
foreach ($versions as $version) {
list($name, $hash) = $version;
$version_property_list->addProperty($name, $hash);
}
$phabricator_root = dirname(phutil_get_library_root('phabricator'));
$version_path = $phabricator_root.'/conf/local/VERSION';
if (Filesystem::pathExists($version_path)) {
$version_from_file = Filesystem::readFile($version_path);
$version_property_list->addProperty(
pht('Local Version'),
$version_from_file);
}
$nav = $this->buildSideNavView();
$nav->selectFilter('all/');
$nav->setCrumbs($crumbs);
$nav->appendChild($version_property_list);
$nav->appendChild($panel);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function loadVersions() {
$specs = array(
array(
'name' => pht('Phabricator Version'),
'root' => 'phabricator',
),
array(
'name' => pht('Arcanist Version'),
'root' => 'arcanist',
),
array(
'name' => pht('libphutil Version'),
'root' => 'phutil',
),
);
$futures = array();
foreach ($specs as $key => $spec) {
$root = dirname(phutil_get_library_root($spec['root']));
$futures[$key] = id(new ExecFuture('git log --format=%%H -n 1 --'))
->setCWD($root);
}
$results = array();
foreach ($futures as $key => $future) {
list($err, $stdout) = $future->resolve();
if (!$err) {
$name = trim($stdout);
} else {
$name = pht('Unknown');
}
$results[$key] = array($specs[$key]['name'], $name);
}
return array_select_keys($results, array_keys($specs));
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php
index 329fd62614..280bbf6f69 100644
--- a/src/applications/config/controller/PhabricatorConfigGroupController.php
+++ b/src/applications/config/controller/PhabricatorConfigGroupController.php
@@ -1,120 +1,119 @@
<?php
final class PhabricatorConfigGroupController
extends PhabricatorConfigController {
private $groupKey;
public function willProcessRequest(array $data) {
$this->groupKey = $data['key'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$groups = PhabricatorApplicationConfigOptions::loadAll();
$options = idx($groups, $this->groupKey);
if (!$options) {
return new Aphront404Response();
}
$title = pht('%s Configuration', $options->getName());
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$list = $this->buildOptionList($options->getOptions());
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Config'))
->setHref($this->getApplicationURI()))
->addCrumb(
id(new PhabricatorCrumbView())
->setName($options->getName())
->setHref($this->getApplicationURI()));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$list,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function buildOptionList(array $options) {
assert_instances_of($options, 'PhabricatorConfigOption');
require_celerity_resource('config-options-css');
$db_values = array();
if ($options) {
$db_values = id(new PhabricatorConfigEntry())->loadAllWhere(
'configKey IN (%Ls) AND namespace = %s',
mpull($options, 'getKey'),
'default');
$db_values = mpull($db_values, null, 'getConfigKey');
}
$engine = id(new PhabricatorMarkupEngine())
->setViewer($this->getRequest()->getUser());
foreach ($options as $option) {
$engine->addObject($option, 'summary');
}
$engine->process();
$list = new PhabricatorObjectItemListView();
$list->setStackable(true);
foreach ($options as $option) {
$summary = $engine->getOutput($option, 'summary');
$item = id(new PhabricatorObjectItemView())
->setHeader($option->getKey())
->setHref('/config/edit/'.$option->getKey().'/')
->addAttribute($summary);
if (!$option->getHidden() && !$option->getMasked()) {
$current_value = PhabricatorEnv::getEnvConfig($option->getKey());
$current_value = PhabricatorConfigJSON::prettyPrintJSON(
$current_value);
$current_value = phutil_tag(
'div',
array(
'class' => 'config-options-current-value',
),
array(
phutil_tag('span', array(), pht('Current Value:')),
' '.$current_value,
));
$item->appendChild($current_value);
}
$db_value = idx($db_values, $option->getKey());
if ($db_value && !$db_value->getIsDeleted()) {
$item->addIcon('edit', pht('Customized'));
}
if ($option->getHidden()) {
$item->addIcon('unpublish', pht('Hidden'));
} else if ($option->getMasked()) {
$item->addIcon('unpublish-grey', pht('Masked'));
} else if ($option->getLocked()) {
$item->addIcon('lock', pht('Locked'));
}
$list->addItem($item);
}
return $list;
}
}
diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php
index 9818fce2b3..c730dac71f 100644
--- a/src/applications/config/controller/PhabricatorConfigListController.php
+++ b/src/applications/config/controller/PhabricatorConfigListController.php
@@ -1,62 +1,61 @@
<?php
final class PhabricatorConfigListController
extends PhabricatorConfigController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$nav = $this->buildSideNavView();
$nav->selectFilter('/');
$groups = PhabricatorApplicationConfigOptions::loadAll();
$list = $this->buildConfigOptionsList($groups);
$title = pht('Phabricator Configuration');
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$nav->appendChild(
array(
$header,
$list,
));
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Config'))
->setHref($this->getApplicationURI()));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function buildConfigOptionsList(array $groups) {
assert_instances_of($groups, 'PhabricatorApplicationConfigOptions');
$list = new PhabricatorObjectItemListView();
$list->setStackable(true);
$groups = msort($groups, 'getName');
foreach ($groups as $group) {
$item = id(new PhabricatorObjectItemView())
->setHeader($group->getName())
->setHref('/config/group/'.$group->getKey().'/')
->addAttribute($group->getDescription());
$list->addItem($item);
}
return $list;
}
}
diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php
index f277cb623f..77cb811ba7 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php
@@ -1,143 +1,142 @@
<?php
/**
* @group countdown
*/
final class PhabricatorCountdownEditController
extends PhabricatorCountdownController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->id) {
$countdown = id(new PhabricatorCountdownQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
// If no countdown is found
if (!$countdown) {
return new Aphront404Response();
}
} else {
$countdown = new PhabricatorCountdown();
$countdown->setEpoch(time());
}
$error_view = null;
$e_text = null;
if ($request->isFormPost()) {
$errors = array();
$title = $request->getStr('title');
$epoch = $request->getStr('epoch');
$e_text = null;
if (!strlen($title)) {
$e_text = pht('Required');
$errors[] = pht('You must give the countdown a name.');
}
if (strlen($epoch)) {
$timestamp = PhabricatorTime::parseLocalTime($epoch, $user);
if (!$timestamp) {
$errors[] = pht(
'You entered an incorrect date. You can enter date '.
'like \'2011-06-26 13:33:37\' to create an event at '.
'13:33:37 on the 26th of June 2011.');
}
} else {
$e_epoch = pht('Required');
$errors[] = pht('You must specify the end date for a countdown.');
}
if (!count($errors)) {
$countdown->setTitle($title);
$countdown->setEpoch($timestamp);
$countdown->setAuthorPHID($user->getPHID());
$countdown->save();
return id(new AphrontRedirectResponse())
->setURI('/countdown/'.$countdown->getID().'/');
} else {
$error_view = id(new AphrontErrorView())
->setErrors($errors)
->setTitle(pht('It\'s not The Final Countdown (du nu nuuu nun)' .
' until you fix these problem'));
}
}
if ($countdown->getEpoch()) {
$display_epoch = phabricator_datetime($countdown->getEpoch(), $user);
} else {
$display_epoch = $request->getStr('epoch');
}
$crumbs = $this->buildApplicationCrumbs();
$cancel_uri = '/countdown/';
if ($countdown->getID()) {
$cancel_uri = '/countdown/'.$countdown->getID().'/';
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('C'.$countdown->getID())
->setHref($cancel_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit')));
$submit_label = pht('Save Changes');
} else {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Create Countdown')));
$submit_label = pht('Create Countdown');
}
$form = id(new AphrontFormView())
->setUser($user)
->setAction($request->getRequestURI()->getPath())
->setFlexible(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Title'))
->setValue($countdown->getTitle())
->setName('title'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('End Date'))
->setValue($display_epoch)
->setName('epoch')
->setCaption(pht('Examples: '.
'2011-12-25 or 3 hours or '.
'June 8 2011, 5 PM.')))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($submit_label));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => pht('Edit Countdown'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php
index 9c7186c241..e65ca17f62 100644
--- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php
+++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php
@@ -1,116 +1,115 @@
<?php
/**
* @group countdown
*/
final class PhabricatorCountdownViewController
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))
->executeOne();
if (!$countdown) {
return new Aphront404Response();
}
$countdown_view = id(new PhabricatorCountdownView())
->setUser($user)
->setCountdown($countdown)
->setHeadless(true);
$id = $countdown->getID();
$title = $countdown->getTitle();
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName("C{$id}"));
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = $this->buildActionListView($countdown);
$properties = $this->buildPropertyListView($countdown);
$content = array(
$crumbs,
$header,
$actions,
$properties,
$countdown_view,
);
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function buildActionListView(PhabricatorCountdown $countdown) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $countdown->getID();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$countdown,
PhabricatorPolicyCapability::CAN_EDIT);
$view->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Countdown'))
->setHref($this->getApplicationURI("edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$view->addAction(
id(new PhabricatorActionView())
->setIcon('delete')
->setName(pht('Delete Countdown'))
->setHref($this->getApplicationURI("delete/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true));
return $view;
}
private function buildPropertyListView(PhabricatorCountdown $countdown) {
$request = $this->getRequest();
$viewer = $request->getUser();
$this->loadHandles(array($countdown->getAuthorPHID()));
$view = id(new PhabricatorPropertyListView())
->setUser($viewer);
$view->addProperty(
pht('Author'),
$this->getHandle($countdown->getAuthorPHID())->renderLink());
return $view;
}
}
diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php
index 75510c827f..a380c8e93b 100644
--- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php
@@ -1,192 +1,191 @@
<?php
final class PhabricatorDaemonConsoleController
extends PhabricatorDaemonController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$completed = id(new PhabricatorWorkerArchiveTask())->loadAllWhere(
'dateModified > %d',
time() - (60 * 15));
$failed = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
'failureTime > %d',
time() - (60 * 15));
$completed_info = array();
foreach ($completed as $completed_task) {
$class = $completed_task->getTaskClass();
if (empty($completed_info[$class])) {
$completed_info[$class] = array(
'n' => 0,
'duration' => 0,
);
}
$completed_info[$class]['n']++;
$duration = $completed_task->getDuration();
$completed_info[$class]['duration'] += $duration;
}
$completed_info = isort($completed_info, 'n');
$rows = array();
foreach ($completed_info as $class => $info) {
$rows[] = array(
$class,
number_format($info['n']),
number_format((int)($info['duration'] / $info['n'])).' us',
);
}
if ($failed) {
$rows[] = array(
phutil_tag('em', array(), pht('Temporary Failures')),
count($failed),
null,
);
}
$completed_table = new AphrontTableView($rows);
$completed_table->setNoDataString(
pht('No tasks have completed in the last 15 minutes.'));
$completed_table->setHeaders(
array(
pht('Class'),
pht('Count'),
pht('Avg'),
));
$completed_table->setColumnClasses(
array(
'wide',
'n',
'n',
));
$completed_header = id(new PhabricatorHeaderView())
->setHeader(pht('Recently Completed Tasks (Last 15m)'));
$completed_panel = new AphrontPanelView();
$completed_panel->appendChild($completed_table);
$completed_panel->setNoBackground();
$logs = id(new PhabricatorDaemonLogQuery())
->setViewer($user)
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->execute();
$daemon_header = id(new PhabricatorHeaderView())
->setHeader(pht('Active Daemons'));
$daemon_table = new PhabricatorDaemonLogListView();
$daemon_table->setUser($user);
$daemon_table->setDaemonLogs($logs);
$tasks = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
'leaseOwner IS NOT NULL');
$rows = array();
foreach ($tasks as $task) {
$rows[] = array(
$task->getID(),
$task->getTaskClass(),
$task->getLeaseOwner(),
$task->getLeaseExpires() - time(),
$task->getFailureCount(),
phutil_tag(
'a',
array(
'href' => '/daemon/task/'.$task->getID().'/',
'class' => 'button small grey',
),
pht('View Task')),
);
}
$leased_table = new AphrontTableView($rows);
$leased_table->setHeaders(
array(
pht('ID'),
pht('Class'),
pht('Owner'),
pht('Expires'),
pht('Failures'),
'',
));
$leased_table->setColumnClasses(
array(
'n',
'wide',
'',
'',
'n',
'action',
));
$leased_table->setNoDataString(pht('No tasks are leased by workers.'));
$leased_panel = new AphrontPanelView();
$leased_panel->setHeader('Leased Tasks');
$leased_panel->appendChild($leased_table);
$leased_panel->setNoBackground();
$task_table = new PhabricatorWorkerActiveTask();
$queued = queryfx_all(
$task_table->establishConnection('r'),
'SELECT taskClass, count(*) N FROM %T GROUP BY taskClass
ORDER BY N DESC',
$task_table->getTableName());
$rows = array();
foreach ($queued as $row) {
$rows[] = array(
$row['taskClass'],
number_format($row['N']),
);
}
$queued_table = new AphrontTableView($rows);
$queued_table->setHeaders(
array(
pht('Class'),
pht('Count'),
));
$queued_table->setColumnClasses(
array(
'wide',
'n',
));
$queued_table->setNoDataString(pht('Task queue is empty.'));
$queued_panel = new AphrontPanelView();
$queued_panel->setHeader(pht('Queued Tasks'));
$queued_panel->appendChild($queued_table);
$queued_panel->setNoBackground();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Console')));
$nav = $this->buildSideNavView();
$nav->selectFilter('/');
$nav->appendChild(
array(
$crumbs,
$completed_header,
$completed_panel,
$daemon_header,
$daemon_table,
$queued_panel,
$leased_panel,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Console'),
- 'dust' => true,
));
}
}
diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogListController.php b/src/applications/daemon/controller/PhabricatorDaemonLogListController.php
index 75a473dcc0..32aa288f46 100644
--- a/src/applications/daemon/controller/PhabricatorDaemonLogListController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonLogListController.php
@@ -1,41 +1,40 @@
<?php
final class PhabricatorDaemonLogListController
extends PhabricatorDaemonController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$pager = new AphrontCursorPagerView();
$pager->readFromRequest($request);
$logs = id(new PhabricatorDaemonLogQuery())
->setViewer($viewer)
->executeWithCursorPager($pager);
$daemon_table = new PhabricatorDaemonLogListView();
$daemon_table->setUser($request->getUser());
$daemon_table->setDaemonLogs($logs);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('All Daemons')));
$nav = $this->buildSideNavView();
$nav->selectFilter('log');
$nav->setCrumbs($crumbs);
$nav->appendChild($daemon_table);
$nav->appendChild($pager);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('All Daemons'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
index 41e6ce160d..c8b4e32756 100644
--- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
+++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
@@ -1,259 +1,258 @@
<?php
final class PhabricatorWorkerTaskDetailController
extends PhabricatorDaemonController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$task = id(new PhabricatorWorkerActiveTask())->load($this->id);
if (!$task) {
$task = id(new PhabricatorWorkerArchiveTask())->load($this->id);
}
if (!$task) {
$title = pht('Task Does Not Exist');
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('No Such Task'));
$error_view->appendChild(phutil_tag(
'p',
array(),
pht('This task may have recently been garbage collected.')));
$error_view->setSeverity(AphrontErrorView::SEVERITY_NODATA);
$content = $error_view;
} else {
$title = pht('Task %d', $task->getID());
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Task %d (%s)',
$task->getID(),
$task->getTaskClass()));
$actions = $this->buildActionListView($task);
$properties = $this->buildPropertyListView($task);
$retry_head = id(new PhabricatorHeaderView())
->setHeader(pht('Retries'));
$retry_info = $this->buildRetryListView($task);
$content = array(
$header,
$actions,
$properties,
$retry_head,
$retry_info,
);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$content,
),
array(
'title' => $title,
- 'dust' => true,
'device' => true,
));
}
private function buildActionListView(PhabricatorWorkerTask $task) {
$request = $this->getRequest();
$user = $request->getUser();
$id = $task->getID();
$view = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($request->getRequestURI());
if ($task->isArchived()) {
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
$can_retry = ($task->getResult() != $result_success);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Retry Task'))
->setHref($this->getApplicationURI('/task/'.$id.'/retry/'))
->setIcon('undo')
->setWorkflow(true)
->setDisabled(!$can_retry));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Cancel Task'))
->setHref($this->getApplicationURI('/task/'.$id.'/cancel/'))
->setIcon('delete')
->setWorkflow(true));
}
$can_release = (!$task->isArchived()) &&
($task->getLeaseOwner());
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Free Lease'))
->setHref($this->getApplicationURI('/task/'.$id.'/release/'))
->setIcon('unlock')
->setWorkflow(true)
->setDisabled(!$can_release));
return $view;
}
private function buildPropertyListView(PhabricatorWorkerTask $task) {
$view = new PhabricatorPropertyListView();
if ($task->isArchived()) {
switch ($task->getResult()) {
case PhabricatorWorkerArchiveTask::RESULT_SUCCESS:
$status = pht('Complete');
break;
case PhabricatorWorkerArchiveTask::RESULT_FAILURE:
$status = pht('Failed');
break;
case PhabricatorWorkerArchiveTask::RESULT_CANCELLED:
$status = pht('Cancelled');
break;
default:
throw new Exception("Unknown task status!");
}
} else {
$status = pht('Queued');
}
$view->addProperty(
pht('Task Status'),
$status);
$view->addProperty(
pht('Task Class'),
$task->getTaskClass());
if ($task->getLeaseExpires()) {
if ($task->getLeaseExpires() > time()) {
$lease_status = pht('Leased');
} else {
$lease_status = pht('Lease Expired');
}
} else {
$lease_status = phutil_tag('em', array(), pht('Not Leased'));
}
$view->addProperty(
pht('Lease Status'),
$lease_status);
$view->addProperty(
pht('Lease Owner'),
$task->getLeaseOwner()
? $task->getLeaseOwner()
: phutil_tag('em', array(), pht('None')));
if ($task->getLeaseExpires() && $task->getLeaseOwner()) {
$expires = ($task->getLeaseExpires() - time());
$expires = phabricator_format_relative_time_detailed($expires);
} else {
$expires = phutil_tag('em', array(), pht('None'));
}
$view->addProperty(
pht('Lease Expires'),
$expires);
if ($task->isArchived()) {
$duration = number_format($task->getDuration()).' us';
} else {
$duration = phutil_tag('em', array(), pht('Not Completed'));
}
$view->addProperty(
pht('Duration'),
$duration);
$data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
$task->setData($data->getData());
$worker = $task->getWorkerInstance();
$data = $worker->renderForDisplay();
$view->addProperty(
pht('Data'),
$data);
return $view;
}
private function buildRetryListView(PhabricatorWorkerTask $task) {
$view = new PhabricatorPropertyListView();
$data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
$task->setData($data->getData());
$worker = $task->getWorkerInstance();
$view->addProperty(
pht('Failure Count'),
$task->getFailureCount());
$retry_count = $worker->getMaximumRetryCount();
if ($retry_count === null) {
$max_retries = phutil_tag('em', array(), pht('Retries Forever'));
$retry_count = INF;
} else {
$max_retries = $retry_count;
}
$view->addProperty(
pht('Maximum Retries'),
$max_retries);
$projection = clone $task;
$projection->makeEphemeral();
$next = array();
for ($ii = $task->getFailureCount(); $ii < $retry_count; $ii++) {
$projection->setFailureCount($ii);
$next[] = $worker->getWaitBeforeRetry($projection);
if (count($next) > 10) {
break;
}
}
if ($next) {
$cumulative = 0;
foreach ($next as $key => $duration) {
if ($duration === null) {
$duration = 60;
}
$cumulative += $duration;
$next[$key] = phabricator_format_relative_time($cumulative);
}
if ($ii != $retry_count) {
$next[] = '...';
}
$retries_in = implode(', ', $next);
} else {
$retries_in = pht('No More Retries');
}
$view->addProperty(
pht('Retries After'),
$retries_in);
return $view;
}
}
diff --git a/src/applications/differential/controller/DifferentialDiffCreateController.php b/src/applications/differential/controller/DifferentialDiffCreateController.php
index efc3c70a12..20e8f87b58 100644
--- a/src/applications/differential/controller/DifferentialDiffCreateController.php
+++ b/src/applications/differential/controller/DifferentialDiffCreateController.php
@@ -1,109 +1,108 @@
<?php
final class DifferentialDiffCreateController extends DifferentialController {
public function processRequest() {
$request = $this->getRequest();
$errors = array();
$e_diff = null;
$e_file = null;
if ($request->isFormPost()) {
$diff = null;
if ($request->getFileExists('diff-file')) {
$diff = PhabricatorFile::readUploadedFileData($_FILES['diff-file']);
} else {
$diff = $request->getStr('diff');
}
if (!strlen($diff)) {
$errors[] = pht(
"You can not create an empty diff. Copy/paste a diff, or upload a ".
"diff file.");
$e_diff = pht('Required');
$e_file = pht('Required');
}
if (!$errors) {
$call = new ConduitCall(
'differential.createrawdiff',
array(
'diff' => $diff,
));
$call->setUser($request->getUser());
$result = $call->execute();
$path = id(new PhutilURI($result['uri']))->getPath();
return id(new AphrontRedirectResponse())->setURI($path);
}
}
$form = new AphrontFormView();
$form->setFlexible(true);
$arcanist_href = PhabricatorEnv::getDoclink(
'article/Arcanist_User_Guide.html');
$arcanist_link = phutil_tag(
'a',
array(
'href' => $arcanist_href,
'target' => '_blank',
),
'Arcanist');
$cancel_uri = $this->getApplicationURI();
$form
->setAction('/differential/diff/create/')
->setEncType('multipart/form-data')
->setUser($request->getUser())
->appendInstructions(
pht(
'The best way to create a Differential diff is by using %s, but you '.
'can also just paste a diff (for example, from %s, %s or %s) into '.
'this box, or upload a diff file.',
$arcanist_link,
phutil_tag('tt', array(), 'svn diff'),
phutil_tag('tt', array(), 'git diff'),
phutil_tag('tt', array(), 'hg diff')))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Raw Diff'))
->setName('diff')
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setError($e_diff))
->appendChild(
id(new AphrontFormFileControl())
->setLabel(pht('Raw Diff From File'))
->setName('diff-file')
->setError($e_file))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue(pht("Create Diff")));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Create Diff')));
if ($errors) {
$errors = id(new AphrontErrorView())
->setErrors($errors);
}
return $this->buildApplicationPage(
array(
$crumbs,
$errors,
$form
),
array(
'title' => pht('Create Diff'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php
index 881a3e2026..346927e909 100644
--- a/src/applications/differential/controller/DifferentialDiffViewController.php
+++ b/src/applications/differential/controller/DifferentialDiffViewController.php
@@ -1,168 +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>',
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)
->setFlexible(true)
->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 PhabricatorHeaderView())
->setHeader(pht('Properties'));
$property_view = new PhabricatorPropertyListView();
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'),
- 'dust' => true,
));
}
}
diff --git a/src/applications/differential/controller/DifferentialRevisionEditController.php b/src/applications/differential/controller/DifferentialRevisionEditController.php
index e6b1c91c9c..bda7093d5d 100644
--- a/src/applications/differential/controller/DifferentialRevisionEditController.php
+++ b/src/applications/differential/controller/DifferentialRevisionEditController.php
@@ -1,225 +1,224 @@
<?php
final class DifferentialRevisionEditController extends DifferentialController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
if (!$this->id) {
$this->id = $request->getInt('revisionID');
}
if ($this->id) {
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->needRelationships(true)
->needReviewerStatus(true)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
} else {
$revision = new DifferentialRevision();
$revision->attachRelationships(array());
}
$aux_fields = $this->loadAuxiliaryFields($revision);
$diff_id = $request->getInt('diffID');
if ($diff_id) {
$diff = id(new DifferentialDiffQuery())
->setViewer($viewer)
->withIDs(array($diff_id))
->executeOne();
if (!$diff) {
return new Aphront404Response();
}
if ($diff->getRevisionID()) {
// TODO: Redirect?
throw new Exception("This diff is already attached to a revision!");
}
} else {
$diff = null;
}
$errors = array();
if ($request->isFormPost() && !$request->getStr('viaDiffView')) {
foreach ($aux_fields as $aux_field) {
$aux_field->setValueFromRequest($request);
try {
$aux_field->validateField();
} catch (DifferentialFieldValidationException $ex) {
$errors[] = $ex->getMessage();
}
}
if (!$errors) {
$is_new = !$revision->getID();
$user = $request->getUser();
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_DIFFERENTIAL_WILLEDITREVISION,
array(
'revision' => $revision,
'new' => $is_new,
));
$event->setUser($user);
$event->setAphrontRequest($request);
PhutilEventEngine::dispatchEvent($event);
$editor = new DifferentialRevisionEditor($revision);
$editor->setActor($request->getUser());
if ($diff) {
$editor->addDiff($diff, $request->getStr('comments'));
}
$editor->setAuxiliaryFields($aux_fields);
$editor->save();
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_DIFFERENTIAL_DIDEDITREVISION,
array(
'revision' => $revision,
'new' => $is_new,
));
$event->setUser($user);
$event->setAphrontRequest($request);
PhutilEventEngine::dispatchEvent($event);
return id(new AphrontRedirectResponse())
->setURI('/D'.$revision->getID());
}
}
$aux_phids = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionEdit();
}
$phids = array_mergev($aux_phids);
$phids = array_unique($phids);
$handles = $this->loadViewerHandles($phids);
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setHandles(array_select_keys($handles, $aux_phids[$key]));
}
$form = new AphrontFormView();
$form->setUser($request->getUser());
$form->setFlexible(true);
if ($diff) {
$form->addHiddenInput('diffID', $diff->getID());
}
if ($revision->getID()) {
$form->setAction('/differential/revision/edit/'.$revision->getID().'/');
} else {
$form->setAction('/differential/revision/edit/');
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
if ($diff && $revision->getID()) {
$form
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Comments'))
->setName('comments')
->setCaption(pht("Explain what's new in this diff."))
->setValue($request->getStr('comments')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save')))
->appendChild(
id(new AphrontFormDividerControl()));
}
$preview = array();
foreach ($aux_fields as $aux_field) {
$control = $aux_field->renderEditControl();
if ($control) {
$form->appendChild($control);
}
$preview[] = $aux_field->renderEditPreview();
}
$submit = id(new AphrontFormSubmitControl())
->setValue('Save');
if ($diff) {
$submit->addCancelButton('/differential/diff/'.$diff->getID().'/');
} else {
$submit->addCancelButton('/D'.$revision->getID());
}
$form->appendChild($submit);
$crumbs = $this->buildApplicationCrumbs();
if ($revision->getID()) {
if ($diff) {
$title = pht('Update Differential Revision');
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('D'.$revision->getID())
->setHref('/differential/diff/'.$diff->getID().'/'));
} else {
$title = pht('Edit Differential Revision');
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('D'.$revision->getID())
->setHref('/D'.$revision->getID()));
}
} else {
$title = pht('Create New Differential Revision');
}
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
$preview),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function loadAuxiliaryFields(DifferentialRevision $revision) {
$user = $this->getRequest()->getUser();
$aux_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setRevision($revision);
if (!$aux_field->shouldAppearOnEdit()) {
unset($aux_fields[$key]);
} else {
$aux_field->setUser($user);
}
}
return DifferentialAuxiliaryField::loadFromStorage(
$revision,
$aux_fields);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php
index ffe66a6055..7fac6961a3 100644
--- a/src/applications/diffusion/controller/DiffusionBrowseController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseController.php
@@ -1,275 +1,274 @@
<?php
final class DiffusionBrowseController extends DiffusionController {
public function processRequest() {
$drequest = $this->diffusionRequest;
$is_file = false;
if ($this->getRequest()->getStr('before')) {
$is_file = true;
} else if ($this->getRequest()->getStr('grep') == '') {
$results = DiffusionBrowseResultSet::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.browsequery',
array(
'path' => $drequest->getPath(),
'commit' => $drequest->getCommit(),
)));
$reason = $results->getReasonForEmptyResultSet();
$is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE);
}
if ($is_file) {
$controller = new DiffusionBrowseFileController($this->getRequest());
$controller->setDiffusionRequest($drequest);
$controller->setCurrentApplication($this->getCurrentApplication());
return $this->delegateToController($controller);
}
$content = array();
if ($drequest->getTagContent()) {
$title = pht('Tag: %s', $drequest->getSymbolicCommit());
$tag_view = new AphrontPanelView();
$tag_view->setHeader($title);
$tag_view->appendChild(
$this->markupText($drequest->getTagContent()));
$content[] = $tag_view;
}
$content[] = $this->renderSearchForm();
if ($this->getRequest()->getStr('grep') != '') {
$content[] = $this->renderSearchResults();
} else {
if (!$results->isValidResults()) {
$empty_result = new DiffusionEmptyResultView();
$empty_result->setDiffusionRequest($drequest);
$empty_result->setDiffusionBrowseResultSet($results);
$empty_result->setView($this->getRequest()->getStr('view'));
$content[] = $empty_result;
} else {
$phids = array();
foreach ($results->getPaths() as $result) {
$data = $result->getLastCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
}
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$browse_table = new DiffusionBrowseTableView();
$browse_table->setDiffusionRequest($drequest);
$browse_table->setHandles($handles);
$browse_table->setPaths($results->getPaths());
$browse_table->setUser($this->getRequest()->getUser());
$browse_panel = new AphrontPanelView();
$browse_panel->appendChild($browse_table);
$browse_panel->setNoBackground();
$content[] = $browse_panel;
}
$content[] = $this->buildOpenRevisions();
$readme = $this->callConduitWithDiffusionRequest(
'diffusion.readmequery',
array(
'paths' => $results->getPathDicts(),
));
if ($readme) {
$box = new PHUIBoxView();
$box->setShadow(true);
$box->appendChild($readme);
$box->addPadding(PHUI::PADDING_LARGE);
$box->addMargin(PHUI::MARGIN_LARGE);
$header = id(new PhabricatorHeaderView())
->setHeader(pht('README'));
$content[] = array(
$header,
$box,
);
}
}
$nav = $this->buildSideNav('browse', false);
$nav->appendChild($content);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'browse',
));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'device' => true,
- 'dust' => true,
'title' => array(
nonempty(basename($drequest->getPath()), '/'),
$drequest->getRepository()->getCallsign().' Repository',
),
));
}
private function renderSearchForm() {
$drequest = $this->getDiffusionRequest();
$form = id(new AphrontFormView())
->setUser($this->getRequest()->getUser())
->setMethod('GET')
->setNoShading(true);
switch ($drequest->getRepository()->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$form->appendChild(pht('Search is not available in Subversion.'));
break;
default:
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Search Here'))
->setName('grep')
->setValue($this->getRequest()->getStr('grep'))
->setCaption(pht('Regular expression')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Grep')));
break;
}
$filter = new AphrontListFilterView();
$filter->appendChild($form);
return $filter;
}
private function renderSearchResults() {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$results = array();
$no_data = pht('No results found.');
$limit = 100;
$page = $this->getRequest()->getInt('page', 0);
$pager = new AphrontPagerView();
$pager->setPageSize($limit);
$pager->setOffset($page);
$pager->setURI($this->getRequest()->getRequestURI(), 'page');
try {
$results = $this->callConduitWithDiffusionRequest(
'diffusion.searchquery',
array(
'grep' => $this->getRequest()->getStr('grep'),
'stableCommitName' => $drequest->getStableCommitName(),
'path' => $drequest->getPath(),
'limit' => $limit + 1,
'offset' => $page));
} catch (ConduitException $ex) {
$err = $ex->getErrorDescription();
if ($err != '') {
return id(new AphrontErrorView())
->setTitle(pht('Search Error'))
->appendChild($err);
}
}
$results = $pager->sliceResults($results);
require_celerity_resource('syntax-highlighting-css');
// NOTE: This can be wrong because we may find the string inside the
// comment. But it's correct in most cases and highlighting the whole file
// would be too expensive.
$futures = array();
$engine = PhabricatorSyntaxHighlighter::newEngine();
foreach ($results as $result) {
list($path, $line, $string) = $result;
$futures["{$path}:{$line}"] = $engine->getHighlightFuture(
$engine->getLanguageFromFilename($path),
ltrim($string));
}
try {
Futures($futures)->limit(8)->resolveAll();
} catch (PhutilSyntaxHighlighterException $ex) {
}
$rows = array();
foreach ($results as $result) {
list($path, $line, $string) = $result;
$href = $drequest->generateURI(array(
'action' => 'browse',
'path' => $path,
'line' => $line,
));
try {
$string = $futures["{$path}:{$line}"]->resolve();
} catch (PhutilSyntaxHighlighterException $ex) {
}
$string = phutil_tag(
'pre',
array('class' => 'PhabricatorMonospaced'),
$string);
$path = Filesystem::readablePath($path, $drequest->getPath());
$rows[] = array(
phutil_tag('a', array('href' => $href), $path),
$line,
$string,
);
}
$table = id(new AphrontTableView($rows))
->setClassName('remarkup-code')
->setHeaders(array(pht('Path'), pht('Line'), pht('String')))
->setColumnClasses(array('', 'n', 'wide'))
->setNoDataString($no_data);
return id(new AphrontPanelView())
->setHeader(pht('Search Results'))
->appendChild($table)
->appendChild($pager);
}
private function markupText($text) {
$engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine();
$engine->setConfig('viewer', $this->getRequest()->getUser());
$text = $engine->markupText($text);
$text = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$text);
return $text;
}
}
diff --git a/src/applications/diffusion/controller/DiffusionChangeController.php b/src/applications/diffusion/controller/DiffusionChangeController.php
index 0e3aa40ab4..e6def447b0 100644
--- a/src/applications/diffusion/controller/DiffusionChangeController.php
+++ b/src/applications/diffusion/controller/DiffusionChangeController.php
@@ -1,83 +1,82 @@
<?php
final class DiffusionChangeController extends DiffusionController {
public function processRequest() {
$drequest = $this->diffusionRequest;
$content = array();
$data = $this->callConduitWithDiffusionRequest(
'diffusion.diffquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath()));
$drequest->setCommit($data['effectiveCommit']);
$raw_changes = ArcanistDiffChange::newFromConduit($data['changes']);
$diff = DifferentialDiff::newFromRawChanges($raw_changes);
$changesets = $diff->getChangesets();
$changeset = reset($changesets);
if (!$changeset) {
// TODO: Refine this.
return new Aphront404Response();
}
$repository = $drequest->getRepository();
$callsign = $repository->getCallsign();
$commit = $drequest->getRawCommit();
$changesets = array(
0 => $changeset,
);
$changeset_view = new DifferentialChangesetListView();
$changeset_view->setTitle(DiffusionView::nameCommit($repository, $commit));
$changeset_view->setChangesets($changesets);
$changeset_view->setVisibleChangesets($changesets);
$changeset_view->setRenderingReferences(
array(
0 => $drequest->generateURI(array('action' => 'rendering-ref'))
));
$raw_params = array(
'action' => 'browse',
'params' => array(
'view' => 'raw',
),
);
$right_uri = $drequest->generateURI($raw_params);
$raw_params['params']['before'] = $drequest->getRawCommit();
$left_uri = $drequest->generateURI($raw_params);
$changeset_view->setRawFileURIs($left_uri, $right_uri);
$changeset_view->setRenderURI(
'/diffusion/'.$callsign.'/diff/');
$changeset_view->setWhitespace(
DifferentialChangesetParser::WHITESPACE_SHOW_ALL);
$changeset_view->setUser($this->getRequest()->getUser());
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
$content[] = $changeset_view->render();
$nav = $this->buildSideNav('change', true);
$nav->appendChild($content);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'change',
));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Change'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php
index f9c318f5db..c75f932258 100644
--- a/src/applications/diffusion/controller/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitController.php
@@ -1,988 +1,987 @@
<?php
final class DiffusionCommitController extends DiffusionController {
const CHANGES_LIMIT = 100;
private $auditAuthorityPHIDs;
private $highlightedAudits;
public function willProcessRequest(array $data) {
// This controller doesn't use blob/path stuff, just pass the dictionary
// in directly instead of using the AphrontRequest parsing mechanism.
$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();
if (!$commit) {
$exists = $this->callConduitWithDiffusionRequest(
'diffusion.existsquery',
array('commit' => $drequest->getCommit()));
if (!$exists) {
return new Aphront404Response();
}
return $this->buildStandardPageResponse(
id(new AphrontErrorView())
->setTitle(pht('Error displaying commit.'))
->appendChild(pht('Failed to load the commit because the commit has '.
'not been parsed yet.')),
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 PhabricatorHeaderView())
->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 PhabricatorPropertyListView())
->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 = $repository->linkBugtraq($message, $revision);
$message = $engine->markupText($message);
$property_list->invokeWillRenderEvent();
$property_list->addTextContent(
phutil_tag(
'div',
array(
'class' => 'diffusion-commit-message phabricator-remarkup',
),
$message));
$content[] = $top_anchor;
$content[] = $headsup_view;
$content[] = $headsup_actions;
$content[] = $property_list;
}
$content[] = $this->buildComments($commit);
$hard_limit = 1000;
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$change_query->setLimit($hard_limit + 1);
$changes = $change_query->loadChanges();
$was_limited = (count($changes) > $hard_limit);
if ($was_limited) {
$changes = array_slice($changes, 0, $hard_limit);
}
$content[] = $this->buildMergesTable($commit);
$owners_paths = array();
if ($this->highlightedAudits) {
$packages = id(new PhabricatorOwnersPackage())->loadAllWhere(
'phid IN (%Ls)',
mpull($this->highlightedAudits, 'getAuditorPHID'));
if ($packages) {
$owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere(
'repositoryPHID = %s AND packageID IN (%Ld)',
$repository->getPHID(),
mpull($packages, 'getID'));
}
}
$change_table = new DiffusionCommitChangeTableView();
$change_table->setDiffusionRequest($drequest);
$change_table->setPathChanges($changes);
$change_table->setOwnersPaths($owners_paths);
$count = count($changes);
$bad_commit = null;
if ($count == 0) {
$bad_commit = queryfx_one(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT * FROM %T WHERE fullCommitName = %s',
PhabricatorRepository::TABLE_BADCOMMIT,
'r'.$callsign.$commit->getCommitIdentifier());
}
if ($bad_commit) {
$error_panel = new AphrontErrorView();
$error_panel->setTitle(pht('Bad Commit'));
$error_panel->appendChild($bad_commit['description']);
$content[] = $error_panel;
} else if ($is_foreign) {
// Don't render anything else.
} else if (!count($changes)) {
$no_changes = new AphrontErrorView();
$no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$no_changes->setTitle(pht('Not Yet Parsed'));
// TODO: This can also happen with weird SVN changes that don't do
// anything (or only alter properties?), although the real no-changes case
// is extremely rare and might be impossible to produce organically. We
// should probably write some kind of "Nothing Happened!" change into the
// DB once we parse these changes so we can distinguish between
// "not parsed yet" and "no changes".
$no_changes->appendChild(
pht("This commit hasn't been fully parsed yet (or doesn't affect any ".
"paths)."));
$content[] = $no_changes;
} else if ($was_limited) {
$huge_commit = new AphrontErrorView();
$huge_commit->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$huge_commit->setTitle(pht('Enormous Commit'));
$huge_commit->appendChild(
pht(
'This commit is enormous, and affects more than %d files. '.
'Changes are not shown.',
$hard_limit));
$content[] = $huge_commit;
} else {
$change_panel = new AphrontPanelView();
$change_panel->setHeader("Changes (".number_format($count).")");
$change_panel->setID('toc');
if ($count > self::CHANGES_LIMIT) {
$show_all_button = phutil_tag(
'a',
array(
'class' => 'button green',
'href' => '?show_all=true',
),
pht('Show All Changes'));
$warning_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle('Very Large Commit')
->appendChild(phutil_tag(
'p',
array(),
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) {
$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());
$crumbs = $this->buildCrumbs(array(
'commit' => true,
));
$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()),
- 'dust' => true,
));
}
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) {
$props['Auditors'] = $this->renderAuditStatusView($audit_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');
$user = $this->getRequest()->getUser();
$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 PhabricatorHeaderView();
$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>');
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
return phutil_tag(
'div',
array(
'id' => $pane_id,
),
hsprintf(
'<div class="differential-add-comment-panel">%s%s%s%s</div>',
id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true)
->render(),
$header,
$form,
$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());
// TODO -- integrate permissions into whether or not this action is shown
$uri = '/diffusion/'.$repository->getCallSign().'/commit/'.
$commit->getCommitIdentifier().'/edit/';
$action = id(new PhabricatorActionView())
->setName(pht('Edit Commit'))
->setHref($uri)
->setIcon('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);
$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('Accepted'));
break;
case PhabricatorAuditStatusConstants::CLOSED:
$item->setIcon('accept-blue', pht('Accepted'));
break;
case PhabricatorAuditStatusConstants::CC:
$item->setIcon('info-dark', pht('Subscribed'));
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;
}
}
diff --git a/src/applications/diffusion/controller/DiffusionCommitEditController.php b/src/applications/diffusion/controller/DiffusionCommitEditController.php
index 8345a6eedd..c798cc89fb 100644
--- a/src/applications/diffusion/controller/DiffusionCommitEditController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitEditController.php
@@ -1,100 +1,99 @@
<?php
final class DiffusionCommitEditController extends DiffusionController {
public function willProcessRequest(array $data) {
$data['user'] = $this->getRequest()->getUser();
$this->diffusionRequest = DiffusionRequest::newFromDictionary($data);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->getDiffusionRequest();
$callsign = $drequest->getRepository()->getCallsign();
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
$page_title = pht('Edit Diffusion Commit');
if (!$commit) {
return new Aphront404Response();
}
$commit_phid = $commit->getPHID();
$edge_type = PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT;
$current_proj_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$commit_phid,
$edge_type);
$handles = $this->loadViewerHandles($current_proj_phids);
$proj_t_values = mpull($handles, 'getFullName', 'getPHID');
if ($request->isFormPost()) {
$proj_phids = $request->getArr('projects');
$new_proj_phids = array_values($proj_phids);
$rem_proj_phids = array_diff($current_proj_phids,
$new_proj_phids);
$editor = id(new PhabricatorEdgeEditor());
$editor->setActor($user);
foreach ($rem_proj_phids as $phid) {
$editor->removeEdge($commit_phid, $edge_type, $phid);
}
foreach ($new_proj_phids as $phid) {
$editor->addEdge($commit_phid, $edge_type, $phid);
}
$editor->save();
id(new PhabricatorSearchIndexer())
->indexDocumentByPHID($commit->getPHID());
return id(new AphrontRedirectResponse())
->setURI('/r'.$callsign.$commit->getCommitIdentifier());
}
$tokenizer_id = celerity_generate_unique_node_id();
$form = id(new AphrontFormView())
->setUser($user)
->setAction($request->getRequestURI()->getPath())
->setFlexible(true)
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName('projects')
->setValue($proj_t_values)
->setID($tokenizer_id)
->setCaption(
javelin_tag(
'a',
array(
'href' => '/project/create/',
'mustcapture' => true,
'sigil' => 'project-create',
),
pht('Create New Project')))
->setDatasource('/typeahead/common/projects/'));;
Javelin::initBehavior('project-create', array(
'tokenizerID' => $tokenizer_id,
));
$submit = id(new AphrontFormSubmitControl())
->setValue(pht('Save'))
->addCancelButton('/r'.$callsign.$commit->getCommitIdentifier());
$form->appendChild($submit);
$header = new PhabricatorHeaderView();
$header->setHeader(pht('Edit Diffusion Commit'));
return $this->buildApplicationPage(
array(
$header,
$form,
),
array(
'title' => $page_title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionExternalController.php b/src/applications/diffusion/controller/DiffusionExternalController.php
index a1e7b52291..63e30fc7a3 100644
--- a/src/applications/diffusion/controller/DiffusionExternalController.php
+++ b/src/applications/diffusion/controller/DiffusionExternalController.php
@@ -1,138 +1,137 @@
<?php
final class DiffusionExternalController extends DiffusionController {
public function willProcessRequest(array $data) {
// Don't build a DiffusionRequest.
}
public function processRequest() {
$request = $this->getRequest();
$uri = $request->getStr('uri');
$id = $request->getStr('id');
$repositories = id(new PhabricatorRepository())->loadAll();
if ($uri) {
$uri_path = id(new PhutilURI($uri))->getPath();
$matches = array();
// Try to figure out which tracked repository this external lives in by
// comparing repository metadata. We look for an exact match, but accept
// a partial match.
foreach ($repositories as $key => $repository) {
$remote_uri = new PhutilURI($repository->getRemoteURI());
if ($remote_uri->getPath() == $uri_path) {
$matches[$key] = 1;
}
if ($repository->getPublicRemoteURI() == $uri) {
$matches[$key] = 2;
}
if ($repository->getRemoteURI() == $uri) {
$matches[$key] = 3;
}
}
arsort($matches);
$best_match = head_key($matches);
if ($best_match) {
$repository = $repositories[$best_match];
$redirect = DiffusionRequest::generateDiffusionURI(
array(
'action' => 'browse',
'callsign' => $repository->getCallsign(),
'branch' => $repository->getDefaultBranch(),
'commit' => $id,
));
return id(new AphrontRedirectResponse())->setURI($redirect);
}
}
// TODO: This is a rare query but does a table scan, add a key?
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
'commitIdentifier = %s',
$id);
if (empty($commits)) {
$desc = null;
if ($uri) {
$desc = $uri.', at ';
}
$desc .= $id;
$content = id(new AphrontErrorView())
->setTitle(pht('Unknown External'))
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->appendChild(phutil_tag(
'p',
array(),
pht("This external (%s) does not appear in any tracked ".
"repository. It may exist in an untracked repository that ".
"Diffusion does not know about.", $desc)));
} else if (count($commits) == 1) {
$commit = head($commits);
$repo = $repositories[$commit->getRepositoryID()];
$redirect = DiffusionRequest::generateDiffusionURI(
array(
'action' => 'browse',
'callsign' => $repo->getCallsign(),
'branch' => $repo->getDefaultBranch(),
'commit' => $commit->getCommitIdentifier(),
));
return id(new AphrontRedirectResponse())->setURI($redirect);
} else {
$rows = array();
foreach ($commits as $commit) {
$repo = $repositories[$commit->getRepositoryID()];
$href = DiffusionRequest::generateDiffusionURI(
array(
'action' => 'browse',
'callsign' => $repo->getCallsign(),
'branch' => $repo->getDefaultBranch(),
'commit' => $commit->getCommitIdentifier(),
));
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $href,
),
'r'.$repo->getCallsign().$commit->getCommitIdentifier()),
$commit->loadCommitData()->getSummary(),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Commit'),
pht('Description'),
));
$table->setColumnClasses(
array(
'pri',
'wide',
));
$content = new AphrontPanelView();
$content->setHeader(pht('Multiple Matching Commits'));
$content->setCaption(
pht('This external reference matches multiple known commits.'));
$content->appendChild($table);
}
return $this->buildApplicationPage(
$content,
array(
'title' => pht('Unresolvable External'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php
index 530e342762..ab37b920a5 100644
--- a/src/applications/diffusion/controller/DiffusionHistoryController.php
+++ b/src/applications/diffusion/controller/DiffusionHistoryController.php
@@ -1,107 +1,106 @@
<?php
final class DiffusionHistoryController extends DiffusionController {
public function processRequest() {
$drequest = $this->diffusionRequest;
$request = $this->getRequest();
$page_size = $request->getInt('pagesize', 100);
$offset = $request->getInt('page', 0);
$params = array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'offset' => $offset,
'limit' => $page_size + 1);
if (!$request->getBool('copies')) {
$params['needDirectChanges'] = true;
$params['needChildChanges'] = true;
}
$history_results = $this->callConduitWithDiffusionRequest(
'diffusion.historyquery',
$params);
$history = DiffusionPathChange::newFromConduit(
$history_results['pathChanges']);
$pager = new AphrontPagerView();
$pager->setPageSize($page_size);
$pager->setOffset($offset);
if (count($history) == $page_size + 1) {
array_pop($history);
$pager->setHasMorePages(true);
} else {
$pager->setHasMorePages(false);
}
$pager->setURI($request->getRequestURI(), 'page');
$show_graph = !strlen($drequest->getPath());
$content = array();
if ($request->getBool('copies')) {
$button_title = pht('Hide Copies/Branches');
$copies_new = null;
} else {
$button_title = pht('Show Copies/Branches');
$copies_new = true;
}
$button = phutil_tag(
'a',
array(
'class' => 'button small grey',
'href' => $request->getRequestURI()->alter('copies', $copies_new),
),
$button_title);
$history_table = new DiffusionHistoryTableView();
$history_table->setUser($request->getUser());
$history_table->setDiffusionRequest($drequest);
$history_table->setHistory($history);
$history_table->loadRevisions();
$phids = $history_table->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$history_table->setHandles($handles);
if ($show_graph) {
$history_table->setParents($history_results['parents']);
$history_table->setIsHead($offset == 0);
}
$history_panel = new AphrontPanelView();
$history_panel->setHeader(pht('History'));
$history_panel->addButton($button);
$history_panel->appendChild($history_table);
$history_panel->appendChild($pager);
$history_panel->setNoBackground();
$content[] = $history_panel;
// TODO: Sometimes we do have a change view, we need to look at the most
// recent history entry to figure it out.
$nav = $this->buildSideNav('history', false);
$nav->appendChild($content);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'history',
));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'device' => true,
- 'dust' => true,
'title' => array(
pht('History'),
pht('%s Repository', $drequest->getRepository()->getCallsign()),
),
));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionHomeController.php b/src/applications/diffusion/controller/DiffusionHomeController.php
index ba9caf98c9..ea65c8a3da 100644
--- a/src/applications/diffusion/controller/DiffusionHomeController.php
+++ b/src/applications/diffusion/controller/DiffusionHomeController.php
@@ -1,207 +1,206 @@
<?php
final class DiffusionHomeController extends DiffusionController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$shortcuts = id(new PhabricatorRepositoryShortcut())->loadAll();
if ($shortcuts) {
$shortcuts = msort($shortcuts, 'getSequence');
$rows = array();
foreach ($shortcuts as $shortcut) {
$rows[] = array(
$shortcut->getName(),
$shortcut->getHref(),
$shortcut->getDescription(),
);
}
$list = new PhabricatorObjectItemListView();
$list->setCards(true);
$list->setFlush(true);
foreach ($rows as $row) {
$item = id(new PhabricatorObjectItemView())
->setHeader($row[0])
->setHref($row[1])
->setSubhead(($row[2] ? $row[2] : pht('No Description')));
$list->addItem($item);
}
$shortcut_panel = id(new AphrontPanelView())
->setNoBackground(true)
->setHeader(pht('Shortcuts'))
->appendChild($list);
} else {
$shortcut_panel = null;
}
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($user)
->execute();
foreach ($repositories as $key => $repo) {
if (!$repo->isTracked()) {
unset($repositories[$key]);
}
}
$repositories = msort($repositories, 'getName');
$repository_ids = mpull($repositories, 'getID');
$summaries = array();
$commits = array();
if ($repository_ids) {
$summaries = queryfx_all(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT * FROM %T WHERE repositoryID IN (%Ld)',
PhabricatorRepository::TABLE_SUMMARY,
$repository_ids);
$summaries = ipull($summaries, null, 'repositoryID');
$commit_ids = array_filter(ipull($summaries, 'lastCommitID'));
if ($commit_ids) {
$commit = new PhabricatorRepositoryCommit();
$commits = $commit->loadAllWhere('id IN (%Ld)', $commit_ids);
$commits = mpull($commits, null, 'getRepositoryID');
}
}
$branch = new PhabricatorRepositoryBranch();
$lint_messages = queryfx_all(
$branch->establishConnection('r'),
'SELECT b.repositoryID, b.name, COUNT(lm.id) AS n
FROM %T b
LEFT JOIN %T lm ON b.id = lm.branchID
GROUP BY b.id',
$branch->getTableName(),
PhabricatorRepository::TABLE_LINTMESSAGE);
$lint_messages = igroup($lint_messages, 'repositoryID');
$rows = array();
$show_lint = false;
foreach ($repositories as $repository) {
$id = $repository->getID();
$commit = idx($commits, $id);
$size = idx(idx($summaries, $id, array()), 'size', '-');
if ($size != '-') {
$size = hsprintf(
'<a href="%s">%s</a>',
DiffusionRequest::generateDiffusionURI(array(
'callsign' => $repository->getCallsign(),
'action' => 'history',
)),
pht('%s Commits', new PhutilNumber($size)));
}
$lint_count = '';
$lint_branches = ipull(idx($lint_messages, $id, array()), 'n', 'name');
$branch = $repository->getDefaultArcanistBranch();
if (isset($lint_branches[$branch])) {
$show_lint = true;
$lint_count = phutil_tag(
'a',
array(
'href' => DiffusionRequest::generateDiffusionURI(array(
'callsign' => $repository->getCallsign(),
'action' => 'lint',
)),
),
pht('%s Lint Messages', new PhutilNumber($lint_branches[$branch])));
}
$datetime = '';
if ($commit) {
$date = phabricator_date($commit->getEpoch(), $user);
$time = phabricator_time($commit->getEpoch(), $user);
$datetime = $date.' '.$time;
}
$rows[] = array(
$repository->getName(),
('/diffusion/'.$repository->getCallsign().'/'),
PhabricatorRepositoryType::getNameForRepositoryType(
$repository->getVersionControlSystem()),
$size,
$lint_count,
$commit
? DiffusionView::linkCommit(
$repository,
$commit->getCommitIdentifier(),
$commit->getSummary())
: pht('No Commits'),
$datetime
);
}
$repository_tool_uri = PhabricatorEnv::getProductionURI('/repository/');
$repository_tool = phutil_tag('a',
array(
'href' => $repository_tool_uri,
),
'repository tool');
$preface = pht('This instance of Phabricator does not have any '.
'configured repositories.');
if ($user->getIsAdmin()) {
$no_repositories_txt = hsprintf(
'%s %s',
$preface,
pht(
'To setup one or more repositories, visit the %s.',
$repository_tool));
} else {
$no_repositories_txt = hsprintf(
'%s %s',
$preface,
pht(
'Ask an administrator to setup one or more repositories '.
'via the %s.',
$repository_tool));
}
$list = new PhabricatorObjectItemListView();
$list->setCards(true);
$list->setFlush(true);
foreach ($rows as $row) {
$item = id(new PhabricatorObjectItemView())
->setHeader($row[0])
->setSubHead($row[5])
->setHref($row[1])
->addAttribute(($row[2] ? $row[2] : pht('No Information')))
->addAttribute(($row[3] ? $row[3] : pht('0 Commits')))
->addIcon('none', $row[6]);
if ($show_lint) {
$item->addAttribute($row[4]);
}
$list->addItem($item);
}
$list = id(new AphrontPanelView())
->setNoBackground(true)
->setHeader(pht('Repositories'))
->appendChild($list);
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('All Repositories'))
->setHref($this->getApplicationURI()));
return $this->buildApplicationPage(
array(
$crumbs,
$shortcut_panel,
$list,
),
array(
'title' => pht('Diffusion'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php
index 604041c45a..c97f71c49d 100644
--- a/src/applications/diffusion/controller/DiffusionLintController.php
+++ b/src/applications/diffusion/controller/DiffusionLintController.php
@@ -1,237 +1,236 @@
<?php
final class DiffusionLintController extends DiffusionController {
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[$user->getPHID()] = $user->getFullName();
} else {
$phids = $request->getArr('owner');
$phid = reset($phids);
$handles = $this->loadViewerHandles(array($phid));
$owners[$phid] = $handles[$phid]->getFullName();
}
}
$codes = $this->loadLintCodes(array_keys($owners));
if ($codes && !$drequest) {
$branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
'id IN (%Ld)',
array_unique(ipull($codes, 'branchID')));
$repositories = id(new PhabricatorRepository())->loadAllWhere(
'id IN (%Ld)',
array_unique(mpull($branches, 'getRepositoryID')));
$drequests = array();
foreach ($branches as $id => $branch) {
$drequests[$id] = DiffusionRequest::newFromDictionary(array(
'user' => $user,
'repository' => $repositories[$branch->getRepositoryID()],
'branch' => $branch->getName(),
));
}
}
$rows = array();
foreach ($codes as $code) {
if (!$this->diffusionRequest) {
$drequest = $drequests[$code['branchID']];
}
$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()),
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) {
$link = hsprintf(
'<a href="%s">%s</a>',
$drequest->generateURI(array(
'action' => 'lint',
'lint' => '',
)),
pht('Switch to List View'));
} else {
$form = id(new AphrontFormView())
->setUser($user)
->setMethod('GET')
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setLimit(1)
->setName('owner')
->setLabel(pht('Owner'))
->setValue($owners))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Filter'));
$content[] = id(new AphrontListFilterView())->appendChild($form);
}
$content[] = id(new AphrontPanelView())
->setHeader(pht('%d Lint Message(s)', array_sum(ipull($codes, 'n'))))
->setCaption($link)
->appendChild($table);
$title = array('Lint');
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'lint',
));
if ($this->diffusionRequest) {
$title[] = $drequest->getCallsign();
$content = $this->buildSideNav('lint', false)
->setCrumbs($crumbs)
->appendChild($content);
} else {
array_unshift($content, $crumbs);
}
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
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 PhabricatorRepository())->loadAllWhere(
'phid IN (%Ls)',
array_unique(mpull($paths, 'getRepositoryPHID')));
$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, $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));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionLintDetailsController.php b/src/applications/diffusion/controller/DiffusionLintDetailsController.php
index 578abb6432..2e3c729b23 100644
--- a/src/applications/diffusion/controller/DiffusionLintDetailsController.php
+++ b/src/applications/diffusion/controller/DiffusionLintDetailsController.php
@@ -1,153 +1,152 @@
<?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(
'action' => 'lint',
'path' => $message['path'],
)),
substr($message['path'], strlen($drequest->getPath()) + 1));
$line = hsprintf(
'<a href="%s">%s</a>',
$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');
$lint = $drequest->getLint();
$link = hsprintf(
'<a href="%s">%s</a>',
$drequest->generateURI(array(
'action' => 'lint',
'lint' => null,
)),
pht('Switch to Grouped View'));
$content[] = id(new AphrontPanelView())
->setHeader(
($lint != '' ? $lint." \xC2\xB7 " : '').
pht('%d Lint Message(s)', count($messages)))
->setCaption($link)
->appendChild($table)
->appendChild($pager);
$nav = $this->buildSideNav('lint', false);
$nav->appendChild($content);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'lint',
));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'device' => true,
- 'dust' => 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/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php
index 9b9bf41c2c..007d6e4bed 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php
@@ -1,292 +1,291 @@
<?php
final class DiffusionRepositoryController extends DiffusionController {
public function processRequest() {
$drequest = $this->diffusionRequest;
$content = array();
$crumbs = $this->buildCrumbs();
$content[] = $crumbs;
$content[] = $this->buildPropertiesTable($drequest->getRepository());
$history_results = $this->callConduitWithDiffusionRequest(
'diffusion.historyquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'offset' => 0,
'limit' => 15));
$history = DiffusionPathChange::newFromConduit(
$history_results['pathChanges']);
$browse_results = DiffusionBrowseResultSet::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.browsequery',
array(
'path' => $drequest->getPath(),
'commit' => $drequest->getCommit(),
)));
$browse_paths = $browse_results->getPaths();
$phids = array();
foreach ($history as $item) {
$data = $item->getCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
foreach ($browse_paths as $item) {
$data = $item->getLastCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$readme = $this->callConduitWithDiffusionRequest(
'diffusion.readmequery',
array(
'paths' => $browse_results->getPathDicts()
));
$history_table = new DiffusionHistoryTableView();
$history_table->setUser($this->getRequest()->getUser());
$history_table->setDiffusionRequest($drequest);
$history_table->setHandles($handles);
$history_table->setHistory($history);
$history_table->loadRevisions();
$history_table->setParents($history_results['parents']);
$history_table->setIsHead(true);
$callsign = $drequest->getRepository()->getCallsign();
$all = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'history',
)),
),
pht('View Full Commit History'));
$panel = new AphrontPanelView();
$panel->setHeader(pht("Recent Commits &middot; %s", $all));
$panel->appendChild($history_table);
$panel->setNoBackground();
$content[] = $panel;
$browse_table = new DiffusionBrowseTableView();
$browse_table->setDiffusionRequest($drequest);
$browse_table->setHandles($handles);
$browse_table->setPaths($browse_paths);
$browse_table->setUser($this->getRequest()->getUser());
$browse_panel = new AphrontPanelView();
$browse_panel->setHeader(phutil_tag(
'a',
array('href' => $drequest->generateURI(array('action' => 'browse'))),
pht('Browse Repository')));
$browse_panel->appendChild($browse_table);
$browse_panel->setNoBackground();
$content[] = $browse_panel;
$content[] = $this->buildTagListTable($drequest);
$content[] = $this->buildBranchListTable($drequest);
if ($readme) {
$box = new PHUIBoxView();
$box->setShadow(true);
$box->appendChild($readme);
$box->addPadding(PHUI::PADDING_LARGE);
$panel = new AphrontPanelView();
$panel->setHeader(pht('README'));
$panel->setNoBackground();
$panel->appendChild($box);
$content[] = $panel;
}
return $this->buildApplicationPage(
$content,
array(
'title' => $drequest->getRepository()->getName(),
- 'dust' => true,
'device' => true,
));
}
private function buildPropertiesTable(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader($repository->getName());
$view = id(new PhabricatorPropertyListView())
->setUser($user);
$view->addProperty(pht('Callsign'), $repository->getCallsign());
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$view->addProperty(
pht('Clone URI'),
$repository->getPublicRemoteURI());
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$view->addProperty(
pht('Repository Root'),
$repository->getPublicRemoteURI());
break;
}
$description = $repository->getDetail('description');
if (strlen($description)) {
$description = PhabricatorMarkupEngine::renderOneObject(
$repository,
'description',
$user);
$view->addTextContent($description);
}
return array($header, $view);
}
private function buildBranchListTable(DiffusionRequest $drequest) {
if ($drequest->getBranch() !== null) {
$limit = 15;
$branches = DiffusionBranchInformation::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.branchquery',
array(
'limit' => $limit
)));
if (!$branches) {
return null;
}
$more_branches = (count($branches) > $limit);
$branches = array_slice($branches, 0, $limit);
$commits = id(new PhabricatorAuditCommitQuery())
->withIdentifiers(
$drequest->getRepository()->getID(),
mpull($branches, 'getHeadCommitIdentifier'))
->needCommitData(true)
->execute();
$table = new DiffusionBranchTableView();
$table->setDiffusionRequest($drequest);
$table->setBranches($branches);
$table->setCommits($commits);
$table->setUser($this->getRequest()->getUser());
$panel = new AphrontPanelView();
$panel->setHeader(pht('Branches'));
$panel->setNoBackground();
if ($more_branches) {
$panel->setCaption(pht('Showing %d branches.', $limit));
}
$panel->addButton(
phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'branches',
)),
'class' => 'grey button',
),
pht("Show All Branches \xC2\xBB")));
$panel->appendChild($table);
return $panel;
}
return null;
}
private function buildTagListTable(DiffusionRequest $drequest) {
$tag_limit = 15;
$tags = array();
try {
$tags = DiffusionRepositoryTag::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.tagsquery',
array('limit' => $tag_limit + 1)));
} catch (ConduitException $e) {
if ($e->getMessage() != 'ERR-UNSUPPORTED-VCS') {
throw $e;
}
}
if (!$tags) {
return null;
}
$more_tags = (count($tags) > $tag_limit);
$tags = array_slice($tags, 0, $tag_limit);
$commits = id(new PhabricatorAuditCommitQuery())
->withIdentifiers(
$drequest->getRepository()->getID(),
mpull($tags, 'getCommitIdentifier'))
->needCommitData(true)
->execute();
$view = new DiffusionTagListView();
$view->setDiffusionRequest($drequest);
$view->setTags($tags);
$view->setUser($this->getRequest()->getUser());
$view->setCommits($commits);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader(pht('Tags'));
if ($more_tags) {
$panel->setCaption(pht('Showing the %d most recent tags.', $tag_limit));
}
$panel->addButton(
phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'tags',
)),
'class' => 'grey button',
),
pht("Show All Tags \xC2\xBB")));
$panel->appendChild($view);
return $panel;
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php
index fce57d063b..f395b72567 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php
@@ -1,545 +1,544 @@
<?php
final class DiffusionRepositoryCreateController extends DiffusionController {
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$form = id(new PHUIPagedFormView())
->setUser($viewer)
->addPage('vcs', $this->buildVCSPage())
->addPage('name', $this->buildNamePage())
->addPage('auth', $this->buildAuthPage())
->addPage('done', $this->buildDonePage());
if ($request->isFormPost()) {
$form->readFromRequest($request);
if ($form->isComplete()) {
// TODO: This exception is heartwarming but should probably take more
// substantive actions.
throw new Exception("GOOD JOB AT FORM");
}
} else {
$form->readFromObject(null);
}
$title = pht('Import Repository');
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
/* -( Page: VCS Type )----------------------------------------------------- */
private function buildVCSPage() {
return id(new PHUIFormPageView())
->setPageName(pht('Repository Type'))
->setUser($this->getRequest()->getUser())
->setValidateFormPageCallback(array($this, 'validateVCSPage'))
->addControl(
id(new AphrontFormRadioButtonControl())
->setName('vcs')
->setLabel(pht('Type'))
->addButton(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT,
pht('Git'),
pht(
'Import a Git repository (for example, a repository hosted '.
'on GitHub).'))
->addButton(
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL,
pht('Mercurial'),
pht(
'Import a Mercurial repository (for example, a repository '.
'hosted on Bitbucket).'))
->addButton(
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN,
pht('Subversion'),
pht('Import a Subversion repository.'))
->addButton(
PhabricatorRepositoryType::REPOSITORY_TYPE_PERFORCE,
pht('Perforce'),
pht(
'Perforce is not directly supported, but you can import '.
'a Perforce repository as a Git repository using %s.',
phutil_tag(
'a',
array(
'href' =>
'http://www.perforce.com/product/components/git-fusion',
'target' => '_blank',
),
pht('Perforce Git Fusion'))),
'disabled',
$disabled = true));
}
public function validateVCSPage(PHUIFormPageView $page) {
$valid = array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => true,
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => true,
PhabricatorRepositoryType::REPOSITORY_TYPE_SVN => true,
);
$c_vcs = $page->getControl('vcs');
$v_vcs = $c_vcs->getValue();
if (!$v_vcs) {
$c_vcs->setError(pht('Required'));
$page->addPageError(
pht('You must select a version control system.'));
} else if (empty($valid[$v_vcs])) {
$c_vcs->setError(pht('Invalid'));
$page->addPageError(
pht('You must select a valid version control system.'));
}
return $c_vcs->isValid();
}
/* -( Page: Name and Callsign )-------------------------------------------- */
private function buildNamePage() {
return id(new PHUIFormPageView())
->setUser($this->getRequest()->getUser())
->setPageName(pht('Repository Name and Location'))
->setValidateFormPageCallback(array($this, 'validateNamePage'))
->setAdjustFormPageCallback(array($this, 'adjustNamePage'))
->addRemarkupInstructions(
pht(
'**Choose a human-readable name for this repository**, like '.
'"CompanyName Mobile App" or "CompanyName Backend Server". You '.
'can change this later.'))
->addControl(
id(new AphrontFormTextControl())
->setName('name')
->setLabel(pht('Name'))
->setCaption(pht('Human-readable repository name.')))
->addRemarkupInstructions(
pht(
'**Choose a "Callsign" for the repository.** This is a short, '.
'unique string which identifies commits elsewhere in Phabricator. '.
'For example, you might use `M` for your mobile app repository '.
'and `B` for your backend repository.'.
"\n\n".
'**Callsigns must be UPPERCASE**, and can not be edited after the '.
'repository is created. Generally, you should choose short '.
'callsigns.'))
->addControl(
id(new AphrontFormTextControl())
->setName('callsign')
->setLabel(pht('Callsign'))
->setCaption(pht('Short UPPERCASE identifier.')))
->addControl(
id(new AphrontFormTextControl())
->setName('remoteURI'));
}
public function adjustNamePage(PHUIFormPageView $page) {
$form = $page->getForm();
$is_git = false;
$is_svn = false;
$is_mercurial = false;
switch ($form->getPage('vcs')->getControl('vcs')->getValue()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$is_git = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$is_svn = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$is_mercurial = true;
break;
default:
throw new Exception("Unsupported VCS!");
}
$has_local = ($is_git || $is_mercurial);
if ($is_git) {
$uri_label = pht('Remote URI');
$instructions = pht(
'Enter the URI to clone this Git repository from. It should usually '.
'look like one of these examples:'.
"\n\n".
"| Example Git Remote URIs |\n".
"| ----------------------- |\n".
"| `git@github.com:example/example.git` |\n".
"| `ssh://user@host.com/git/example.git` |\n".
"| `file:///local/path/to/repo` |\n".
"| `https://example.com/repository.git` |\n");
} else if ($is_mercurial) {
$uri_label = pht('Remote URI');
$instructions = pht(
'Enter the URI to clone this Mercurial repository from. It should '.
'usually look like one of these examples:'.
"\n\n".
"| Example Mercurial Remote URIs |\n".
"| ----------------------- |\n".
"| `ssh://hg@bitbucket.org/example/repository` |\n".
"| `file:///local/path/to/repo` |\n");
} else if ($is_svn) {
$uri_label = pht('Repository Root');
$instructions = pht(
'Enter the **Repository Root** for this Subversion repository. '.
'You can figure this out by running `svn info` in a working copy '.
'and looking at the value in the `Repository Root` field. It '.
'should be a URI and will usually look like these:'.
"\n\n".
"| Example Subversion Repository Root URIs |\n".
"| ------------------------------ |\n".
"| `http://svn.example.org/svnroot/` |\n".
"| `svn+ssh://svn.example.com/svnroot/` |\n".
"| `svn://svn.example.net/svnroot/` |\n".
"| `file:///local/path/to/svnroot/` |\n".
"\n\n".
"Make sure you specify the root of the repository, not a ".
"subdirectory.");
} else {
throw new Exception("Unsupported VCS!");
}
$page->addRemarkupInstructions($instructions, 'remoteURI');
$page->getControl('remoteURI')->setLabel($uri_label);
}
public function validateNamePage(PHUIFormPageView $page) {
$c_name = $page->getControl('name');
$v_name = $c_name->getValue();
if (!strlen($v_name)) {
$c_name->setError(pht('Required'));
$page->addPageError(
pht('You must choose a name for this repository.'));
}
$c_call = $page->getControl('callsign');
$v_call = $c_call->getValue();
if (!strlen($v_call)) {
$c_call->setError(pht('Required'));
$page->addPageError(
pht('You must choose a callsign for this repository.'));
} else if (!preg_match('/^[A-Z]+$/', $v_call)) {
$c_call->setError(pht('Invalid'));
$page->addPageError(
pht('The callsign must contain only UPPERCASE letters.'));
} else {
$exists = false;
try {
$repo = id(new PhabricatorRepositoryQuery())
->setViewer($this->getRequest()->getUser())
->withCallsigns(array($v_call))
->executeOne();
$exists = (bool)$repo;
} catch (PhabricatorPolicyException $ex) {
$exists = true;
}
if ($exists) {
$c_call->setError(pht('Not Unique'));
$page->addPageError(
pht(
'Another repository already uses that callsign. You must choose '.
'a unique callsign.'));
}
}
$c_remote = $page->getControl('remoteURI');
$v_remote = $c_remote->getValue();
if (!strlen($v_remote)) {
$c_remote->setError(pht('Required'));
$page->addPageError(
pht("You must specify a URI."));
} else {
$proto = $this->getRemoteURIProtocol($v_remote);
if ($proto === 'file') {
if (!preg_match('@^file:///@', $v_remote)) {
$c_remote->setError(pht('Invalid'));
$page->addPageError(
pht(
"URIs using the 'file://' protocol should have three slashes ".
"(e.g., 'file:///absolute/path/to/file'). You only have two. ".
"Add another one."));
}
}
switch ($proto) {
case 'ssh':
case 'http':
case 'https':
case 'file':
case 'git':
case 'svn':
case 'svn+ssh':
break;
default:
$c_remote->setError(pht('Invalid'));
$page->addPageError(
pht(
"The URI protocol is unrecognized. It should begin ".
"'ssh://', 'http://', 'https://', 'file://', 'git://', ".
"'svn://', 'svn+ssh://', or be in the form ".
"'git@domain.com:path'."));
break;
}
}
return $c_name->isValid() &&
$c_call->isValid() &&
$c_remote->isValid();
}
/* -( Page: Authentication )----------------------------------------------- */
public function buildAuthPage() {
return id(new PHUIFormPageView())
->setPageName(pht('Authentication'))
->setUser($this->getRequest()->getUser())
->setAdjustFormPageCallback(array($this, 'adjustAuthPage'))
->addControl(
id(new AphrontFormTextControl())
->setName('ssh-login')
->setLabel('SSH User'))
->addControl(
id(new AphrontFormTextAreaControl())
->setName('ssh-key')
->setLabel('SSH Private Key')
->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT)
->setCaption(
hsprintf('Specify the entire private key, <em>or</em>...')))
->addControl(
id(new AphrontFormTextControl())
->setName('ssh-keyfile')
->setLabel('SSH Private Key Path')
->setCaption(
'...specify a path on disk where the daemon should '.
'look for a private key.'))
->addControl(
id(new AphrontFormTextControl())
->setName('http-login')
->setLabel('Username'))
->addControl(
id(new AphrontFormPasswordControl())
->setName('http-pass')
->setLabel('Password'));
}
public function adjustAuthPage($page) {
$form = $page->getForm();
$remote_uri = $form->getPage('name')->getControl('remoteURI')->getValue();
$vcs = $form->getPage('vcs')->getControl('vcs')->getValue();
$proto = $this->getRemoteURIProtocol($remote_uri);
$remote_user = $this->getRemoteURIUser($remote_uri);
$page->getControl('ssh-login')->setHidden(true);
$page->getControl('ssh-key')->setHidden(true);
$page->getControl('ssh-keyfile')->setHidden(true);
$page->getControl('http-login')->setHidden(true);
$page->getControl('http-pass')->setHidden(true);
if ($this->isSSHProtocol($proto)) {
$page->getControl('ssh-login')->setHidden(false);
$page->getControl('ssh-key')->setHidden(false);
$page->getControl('ssh-keyfile')->setHidden(false);
$c_login = $page->getControl('ssh-login');
if (!strlen($c_login->getValue())) {
$c_login->setValue($remote_user);
}
$page->addRemarkupInstructions(
pht(
'Enter the username and private key to use to connect to the '.
'the repository hosted at:'.
"\n\n".
" lang=text\n".
" %s".
"\n\n".
'You can either copy/paste the entire private key, or put it '.
'somewhere on disk and provide the path to it.',
$remote_uri),
'ssh-login');
} else if ($this->isUsernamePasswordProtocol($proto)) {
$page->getControl('http-login')->setHidden(false);
$page->getControl('http-pass')->setHidden(false);
$page->addRemarkupInstructions(
pht(
'Enter the a username and pasword used to connect to the '.
'repository hosted at:'.
"\n\n".
" lang=text\n".
" %s".
"\n\n".
"If this repository does not require a username or password, ".
"you can leave these fields blank.",
$remote_uri),
'http-login');
} else if ($proto == 'file') {
$page->addRemarkupInstructions(
pht(
'You do not need to configure any authentication options for '.
'repositories accessed over the `file://` protocol. Continue '.
'to the next step.'),
'ssh-login');
} else {
throw new Exception("Unknown URI protocol!");
}
}
public function validateAuthPage(PHUIFormPageView $page) {
$form = $page->getForm();
$remote_uri = $form->getPage('remote')->getControl('remoteURI')->getValue();
$proto = $this->getRemoteURIProtocol($remote_uri);
if ($this->isSSHProtocol($proto)) {
$c_user = $page->getControl('ssh-login');
$c_key = $page->getControl('ssh-key');
$c_file = $page->getControl('ssh-keyfile');
$v_user = $c_user->getValue();
$v_key = $c_key->getValue();
$v_file = $c_file->getValue();
if (!strlen($v_user)) {
$c_user->setError(pht('Required'));
$page->addPageError(
pht('You must provide an SSH login username to connect over SSH.'));
}
if (!strlen($v_key) && !strlen($v_file)) {
$c_key->setError(pht('No Key'));
$c_file->setError(pht('No Key'));
$page->addPageError(
pht(
'You must provide either a private key or the path to a private '.
'key to connect over SSH.'));
} else if (strlen($v_key) && strlen($v_file)) {
$c_key->setError(pht('Ambiguous'));
$c_file->setError(pht('Ambiguous'));
$page->addPageError(
pht(
'Provide either a private key or the path to a private key, not '.
'both.'));
} else if (!preg_match('/PRIVATE KEY/', $v_key)) {
$c_key->setError(pht('Invalid'));
$page->addPageError(
pht(
'The private key you provided is missing the PRIVATE KEY header. '.
'You should include the header and footer. (Did you provide a '.
'public key by mistake?)'));
}
return $c_user->isValid() &&
$c_key->isValid() &&
$c_file->isValid();
} else if ($this->isUsernamePasswordProtocol($proto)) {
return true;
} else {
return true;
}
}
/* -( Page: Done )--------------------------------------------------------- */
private function buildDonePage() {
return id(new PHUIFormPageView())
->setPageName(pht('Repository Ready!'))
->setValidateFormPageCallback(array($this, 'validateDonePage'))
->setUser($this->getRequest()->getUser())
->addControl(
id(new AphrontFormRadioButtonControl())
->setName('activate')
->setLabel(pht('Start Now'))
->addButton(
'start',
pht('Start Import Now'),
pht(
'Start importing the repository right away. This will import '.
'the entire repository using default settings.'))
->addButton(
'wait',
pht('Configure More Options First'),
pht(
'Configure more options before beginning the repository '.
'import. This will let you fine-tune settings.. You can '.
'start the import whenever you are ready.')));
}
public function validateDonePage(PHUIFormPageView $page) {
$c_activate = $page->getControl('activate');
$v_activate = $c_activate->getValue();
if ($v_activate != 'start' && $v_activate != 'wait') {
$c_activate->setError(pht('Required'));
$page->addPageError(
pht('Make a choice about repository activation.'));
}
return $c_activate->isValid();
}
/* -( Internal )----------------------------------------------------------- */
private function getRemoteURIProtocol($raw_uri) {
$uri = new PhutilURI($raw_uri);
if ($uri->getProtocol()) {
return strtolower($uri->getProtocol());
}
$git_uri = new PhutilGitURI($raw_uri);
if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) {
return 'ssh';
}
return null;
}
private function getRemoteURIUser($raw_uri) {
$uri = new PhutilURI($raw_uri);
if ($uri->getUser()) {
return $uri->getUser();
}
$git_uri = new PhutilGitURI($raw_uri);
if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) {
return $git_uri->getUser();
}
return null;
}
private function isSSHProtocol($proto) {
return ($proto == 'git' || $proto == 'ssh' || $proto == 'svn+ssh');
}
private function isUsernamePasswordProtocol($proto) {
return ($proto == 'http' || $proto == 'https' || $proto == 'svn');
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
index c5ac2e8449..3bce3b2763 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
@@ -1,133 +1,132 @@
<?php
final class DiffusionRepositoryEditBasicController extends DiffusionController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->diffusionRequest;
$repository = $drequest->getRepository();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($user)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($repository->getID()))
->executeOne();
if (!$repository) {
return new Aphront404Response();
}
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
$v_name = $repository->getName();
$v_desc = $repository->getDetail('description');
$e_name = true;
$errors = array();
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
$v_desc = $request->getStr('description');
if (!strlen($v_name)) {
$e_name = pht('Required');
$errors[] = pht('Repository name is required.');
} else {
$e_name = null;
}
if (!$errors) {
$xactions = array();
$template = id(new PhabricatorRepositoryTransaction());
$type_name = PhabricatorRepositoryTransaction::TYPE_NAME;
$type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION;
$xactions[] = id(clone $template)
->setTransactionType($type_name)
->setNewValue($v_name);
$xactions[] = id(clone $template)
->setTransactionType($type_desc)
->setNewValue($v_desc);
id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->setActor($user)
->applyTransactions($repository, $xactions);
return id(new AphrontRedirectResponse())->setURI($edit_uri);
}
}
$content = array();
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Basics')));
$content[] = $crumbs;
$title = pht('Edit %s', $repository->getName());
if ($errors) {
$content[] = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->appendChild(
id(new AphrontFormTextControl())
->setName('name')
->setLabel(pht('Name'))
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new PhabricatorRemarkupControl())
->setName('description')
->setLabel(pht('Description'))
->setValue($v_desc))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save'))
->addCancelButton($edit_uri))
->appendChild(id(new PHUIFormDividerControl()))
->appendRemarkupInstructions($this->getReadmeInstructions());
$content[] = $form;
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
- 'dust' => true,
'device' => true,
));
}
private function getReadmeInstructions() {
return pht(<<<EOTEXT
You can also create a `README` file at the repository root (or in any
subdirectory) to provide information about the repository. These formats are
supported:
| File Name | Rendered As... |
|-----------------|----------------|
| `README` | Plain Text |
| `README.txt` | Plain Text |
| `README.remarkup` | Remarkup |
| `README.md` | Remarkup |
| `README.rainbow` | \xC2\xA1Fiesta! |
EOTEXT
);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
index f15e9ea6fa..7c2682980e 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
@@ -1,164 +1,163 @@
<?php
final class DiffusionRepositoryEditController extends DiffusionController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->diffusionRequest;
$repository = $drequest->getRepository();
$content = array();
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit')));
$content[] = $crumbs;
$title = pht('Edit %s', $repository->getName());
$content[] = id(new PhabricatorHeaderView())
->setHeader($title);
$content[] = $this->buildBasicActions($repository);
$content[] = $this->buildBasicProperties($repository);
$content[] = id(new PhabricatorHeaderView())
->setHeader(pht('Text Encoding'));
$content[] = $this->buildEncodingActions($repository);
$content[] = $this->buildEncodingProperties($repository);
$content[] = id(new PhabricatorHeaderView())
->setHeader(pht('Edit History'));
$xactions = id(new PhabricatorRepositoryTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($repository->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setObjectPHID($repository->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
$content[] = $xaction_view;
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function buildBasicActions(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$repository,
PhabricatorPolicyCapability::CAN_EDIT);
$edit = id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Basic Information'))
->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/'))
->setDisabled(!$can_edit);
$view->addAction($edit);
return $view;
}
private function buildBasicProperties(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($user);
$view->addProperty(pht('Name'), $repository->getName());
$view->addProperty(pht('ID'), $repository->getID());
$view->addProperty(pht('PHID'), $repository->getPHID());
$type = PhabricatorRepositoryType::getNameForRepositoryType(
$repository->getVersionControlSystem());
$view->addProperty(pht('Type'), $type);
$view->addProperty(pht('Callsign'), $repository->getCallsign());
$description = $repository->getDetail('description');
$view->addSectionHeader(pht('Description'));
if (!strlen($description)) {
$description = phutil_tag('em', array(), pht('No description provided.'));
} else {
$description = PhabricatorMarkupEngine::renderOneObject(
$repository,
'description',
$user);
}
$view->addTextContent($description);
return $view;
}
private function buildEncodingActions(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$repository,
PhabricatorPolicyCapability::CAN_EDIT);
$edit = id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Text Encoding'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/encoding/'))
->setDisabled(!$can_edit);
$view->addAction($edit);
return $view;
}
private function buildEncodingProperties(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($user);
$encoding = $repository->getDetail('encoding');
if (!$encoding) {
$encoding = phutil_tag('em', array(), pht('Use Default (UTF-8)'));
}
$view->addProperty(pht('Encoding'), $encoding);
return $view;
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php
index c9f8414784..56d0441045 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php
@@ -1,120 +1,119 @@
<?php
final class DiffusionRepositoryEditEncodingController
extends DiffusionController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->diffusionRequest;
$repository = $drequest->getRepository();
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($user)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($repository->getID()))
->executeOne();
if (!$repository) {
return new Aphront404Response();
}
$edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
$v_encoding = $repository->getDetail('encoding');
$e_encoding = null;
$errors = array();
if ($request->isFormPost()) {
$v_encoding = $request->getStr('encoding');
if (!$errors) {
$xactions = array();
$template = id(new PhabricatorRepositoryTransaction());
$type_encoding = PhabricatorRepositoryTransaction::TYPE_ENCODING;
$xactions[] = id(clone $template)
->setTransactionType($type_encoding)
->setNewValue($v_encoding);
try {
id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->setActor($user)
->applyTransactions($repository, $xactions);
return id(new AphrontRedirectResponse())->setURI($edit_uri);
} catch (Exception $ex) {
$errors[] = $ex->getMessage();
}
}
}
$content = array();
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Encoding')));
$content[] = $crumbs;
$title = pht('Edit %s', $repository->getName());
if ($errors) {
$content[] = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->appendRemarkupInstructions($this->getEncodingInstructions())
->appendChild(
id(new AphrontFormTextControl())
->setName('encoding')
->setLabel(pht('Text Encoding'))
->setValue($v_encoding)
->setError($e_encoding))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Encoding'))
->addCancelButton($edit_uri));
$content[] = $form;
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
- 'dust' => true,
'device' => true,
));
}
private function getEncodingInstructions() {
return pht(<<<EOT
If source code in this repository uses a character encoding other than UTF-8
(for example, `ISO-8859-1`), specify it here.
**Normally, you can leave this field blank.** If your source code is written in
ASCII or UTF-8, everything will work correctly.
Source files will be translated from the specified encoding to UTF-8 when they
are read from the repository, before they are displayed in Diffusion.
See [[%s | UTF-8 and Character Encoding]] for more information on how
Phabricator handles text encodings.
EOT
,
PhabricatorEnv::getDoclink(
'article/User_Guide_UTF-8_and_Character_Encoding.html'));
}
}
diff --git a/src/applications/diffusion/controller/DiffusionSymbolController.php b/src/applications/diffusion/controller/DiffusionSymbolController.php
index 3d2700ea18..c389ee1052 100644
--- a/src/applications/diffusion/controller/DiffusionSymbolController.php
+++ b/src/applications/diffusion/controller/DiffusionSymbolController.php
@@ -1,156 +1,155 @@
<?php
final class DiffusionSymbolController extends DiffusionController {
private $name;
public function willProcessRequest(array $data) {
$this->name = $data['name'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$query = new DiffusionSymbolQuery();
$query->setName($this->name);
if ($request->getStr('context') !== null) {
$query->setContext($request->getStr('context'));
}
if ($request->getStr('type')) {
$query->setType($request->getStr('type'));
}
if ($request->getStr('lang')) {
$query->setLanguage($request->getStr('lang'));
}
if ($request->getStr('projects')) {
$phids = $request->getStr('projects');
$phids = explode(',', $phids);
$phids = array_filter($phids);
if ($phids) {
$projects = id(new PhabricatorRepositoryArcanistProject())
->loadAllWhere(
'phid IN (%Ls)',
$phids);
$projects = mpull($projects, 'getID');
if ($projects) {
$query->setProjectIDs($projects);
}
}
}
$query->needPaths(true);
$query->needArcanistProjects(true);
$query->needRepositories(true);
$symbols = $query->execute();
// For PHP builtins, jump to php.net documentation.
if ($request->getBool('jump') && count($symbols) == 0) {
if ($request->getStr('lang', 'php') == 'php') {
if ($request->getStr('type', 'function') == 'function') {
$functions = get_defined_functions();
if (in_array($this->name, $functions['internal'])) {
return id(new AphrontRedirectResponse())
->setURI('http://www.php.net/function.'.$this->name);
}
}
if ($request->getStr('type', 'class') == 'class') {
if (class_exists($this->name, false) ||
interface_exists($this->name, false)) {
if (id(new ReflectionClass($this->name))->isInternal()) {
return id(new AphrontRedirectResponse())
->setURI('http://www.php.net/class.'.$this->name);
}
}
}
}
}
$rows = array();
foreach ($symbols as $symbol) {
$project = $symbol->getArcanistProject();
if ($project) {
$project_name = $project->getName();
} else {
$project_name = '-';
}
$file = $symbol->getPath();
$line = $symbol->getLineNumber();
$repo = $symbol->getRepository();
if ($repo) {
$href = $symbol->getURI();
if ($request->getBool('jump') && count($symbols) == 1) {
// If this is a clickthrough from Differential, just jump them
// straight to the target if we got a single hit.
return id(new AphrontRedirectResponse())->setURI($href);
}
$location = phutil_tag(
'a',
array(
'href' => $href,
),
$file.':'.$line);
} else if ($file) {
$location = $file.':'.$line;
} else {
$location = '?';
}
$rows[] = array(
$symbol->getSymbolType(),
$symbol->getSymbolContext(),
$symbol->getSymbolName(),
$symbol->getSymbolLanguage(),
$project_name,
$location,
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Type'),
pht('Context'),
pht('Name'),
pht('Language'),
pht('Project'),
pht('File'),
));
$table->setColumnClasses(
array(
'',
'',
'pri',
'',
'',
'',
));
$table->setNoDataString(
pht("No matching symbol could be found in any indexed project."));
$panel = new AphrontPanelView();
$panel->setHeader(pht('Similar Symbols'));
$panel->appendChild($table);
return $this->buildApplicationPage(
array(
$panel,
),
array(
'title' => pht('Find Symbol'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/directory/controller/PhabricatorDirectoryMainController.php b/src/applications/directory/controller/PhabricatorDirectoryMainController.php
index 0df34e733c..9b9c469820 100644
--- a/src/applications/directory/controller/PhabricatorDirectoryMainController.php
+++ b/src/applications/directory/controller/PhabricatorDirectoryMainController.php
@@ -1,477 +1,476 @@
<?php
final class PhabricatorDirectoryMainController
extends PhabricatorDirectoryController {
private $filter;
private $minipanels = array();
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter');
}
public function processRequest() {
$user = $this->getRequest()->getUser();
if ($this->filter == 'jump') {
return $this->buildJumpResponse();
}
$nav = $this->buildNav();
$project_query = new PhabricatorProjectQuery();
$project_query->setViewer($user);
$project_query->withMemberPHIDs(array($user->getPHID()));
$projects = $project_query->execute();
return $this->buildMainResponse($nav, $projects);
}
private function buildMainResponse($nav, array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$maniphest = 'PhabricatorApplicationManiphest';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$unbreak_panel = $this->buildUnbreakNowPanel();
$triage_panel = $this->buildNeedsTriagePanel($projects);
$tasks_panel = $this->buildTasksPanel();
} else {
$unbreak_panel = null;
$triage_panel = null;
$tasks_panel = null;
}
if (PhabricatorEnv::getEnvConfig('welcome.html') !== null) {
$welcome_panel = $this->buildWelcomePanel();
} else {
$welcome_panel = null;
}
$jump_panel = $this->buildJumpPanel();
$revision_panel = $this->buildRevisionPanel();
$audit_panel = $this->buildAuditPanel();
$commit_panel = $this->buildCommitPanel();
$content = array(
$jump_panel,
$welcome_panel,
$unbreak_panel,
$triage_panel,
$revision_panel,
$tasks_panel,
$audit_panel,
$commit_panel,
$this->minipanels,
);
$nav->appendChild($content);
$nav->appendChild(new PhabricatorGlobalUploadTargetView());
return $this->buildApplicationPage(
$nav,
array(
'title' => 'Phabricator',
'device' => true,
- 'dust' => true,
));
}
private function buildJumpResponse() {
$request = $this->getRequest();
$jump = $request->getStr('jump');
$response = PhabricatorJumpNavHandler::jumpPostResponse($jump);
if ($response) {
return $response;
} else if ($request->isFormPost()) {
$query = new PhabricatorSearchQuery();
$query->setQuery($jump);
$query->save();
return id(new AphrontRedirectResponse())
->setURI('/search/'.$query->getQueryKey().'/');
} else {
return id(new AphrontRedirectResponse())->setURI('/');
}
}
private function buildUnbreakNowPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withPriority(ManiphestTaskPriority::PRIORITY_UNBREAK_NOW);
$task_query->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
'No "Unbreak Now!" Tasks',
'Nothing appears to be critically broken right now.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Unbreak Now!');
$panel->setCaption('Open tasks with "Unbreak Now!" priority.');
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/maniphest/view/all/',
'class' => 'grey button',
),
"View All Unbreak Now \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
$panel->setNoBackground();
return $panel;
}
private function buildNeedsTriagePanel(array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
if ($projects) {
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$task_query->withAnyProjects(mpull($projects, 'getPHID'));
$task_query->setLimit(10);
$tasks = $task_query->execute();
} else {
$tasks = array();
}
if (!$tasks) {
return $this->renderMiniPanel(
'No "Needs Triage" Tasks',
hsprintf(
'No tasks in <a href="/project/">projects you are a member of</a> '.
'need triage.'));
}
$panel = new AphrontPanelView();
$panel->setHeader('Needs Triage');
$panel->setCaption(hsprintf(
'Open tasks with "Needs Triage" priority in '.
'<a href="/project/">projects you are a member of</a>.'));
$panel->addButton(
phutil_tag(
'a',
array(
// TODO: This should filter to just your projects' need-triage
// tasks?
'href' => '/maniphest/view/projecttriage/',
'class' => 'grey button',
),
"View All Triage \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
$panel->setNoBackground();
return $panel;
}
private function buildRevisionPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$revision_query = id(new DifferentialRevisionQuery())
->setViewer($user)
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->withResponsibleUsers(array($user_phid))
->needRelationships(true);
$revisions = $revision_query->execute();
list($blocking, $active, ) = DifferentialRevisionQuery::splitResponsible(
$revisions,
array($user_phid));
if (!$blocking && !$active) {
return $this->renderMiniPanel(
'No Waiting Revisions',
'No revisions are waiting on you.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Revisions Waiting on You');
$panel->setCaption('Revisions waiting for you for review or commit.');
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/differential/',
'class' => 'button grey',
),
"View Active Revisions \xC2\xBB"));
$revision_view = id(new DifferentialRevisionListView())
->setHighlightAge(true)
->setRevisions(array_merge($blocking, $active))
->setFields(DifferentialRevisionListView::getDefaultFields($user))
->setUser($user)
->loadAssets();
$phids = array_merge(
array($user_phid),
$revision_view->getRequiredHandlePHIDs());
$handles = $this->loadViewerHandles($phids);
$revision_view->setHandles($handles);
$list_view = $revision_view->render();
$list_view->setFlush(true);
$panel->appendChild($list_view);
$panel->setNoBackground();
return $panel;
}
private function buildWelcomePanel() {
$panel = new AphrontPanelView();
$panel->appendChild(
phutil_safe_html(
PhabricatorEnv::getEnvConfig('welcome.html')));
$panel->setNoBackground();
return $panel;
}
private function buildTasksPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY);
$task_query->withOwners(array($user_phid));
$task_query->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
'No Assigned Tasks',
'You have no assigned tasks.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Assigned Tasks');
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/maniphest/',
'class' => 'button grey',
),
"View Active Tasks \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
$panel->setNoBackground();
return $panel;
}
private function buildTaskListView(array $tasks) {
assert_instances_of($tasks, 'ManiphestTask');
$user = $this->getRequest()->getUser();
$phids = array_merge(
array_filter(mpull($tasks, 'getOwnerPHID')),
array_mergev(mpull($tasks, 'getProjectPHIDs')));
$handles = $this->loadViewerHandles($phids);
$view = new ManiphestTaskListView();
$view->setTasks($tasks);
$view->setUser($user);
$view->setHandles($handles);
return $view;
}
private function buildJumpPanel($query=null) {
$request = $this->getRequest();
$user = $request->getUser();
$uniq_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'phabricator-autofocus',
array(
'id' => $uniq_id,
));
require_celerity_resource('phabricator-jump-nav');
$doc_href = PhabricatorEnv::getDocLink('article/Jump_Nav_User_Guide.html');
$doc_link = phutil_tag(
'a',
array(
'href' => $doc_href,
),
'Jump Nav User Guide');
$jump_input = phutil_tag(
'input',
array(
'type' => 'text',
'class' => 'phabricator-jump-nav',
'name' => 'jump',
'id' => $uniq_id,
'value' => $query,
));
$jump_caption = phutil_tag(
'p',
array(
'class' => 'phabricator-jump-nav-caption',
),
hsprintf(
'Enter the name of an object like <tt>D123</tt> to quickly jump to '.
'it. See %s or type <tt>help</tt>.',
$doc_link));
$form = phabricator_form(
$user,
array(
'action' => '/jump/',
'method' => 'POST',
'class' => 'phabricator-jump-nav-form',
),
array(
$jump_input,
$jump_caption,
));
$panel = new AphrontPanelView();
$panel->setNoBackground();
// $panel->appendChild();
$list_filter = new AphrontListFilterView();
$list_filter->appendChild($form);
$container = phutil_tag('div',
array('class' => 'phabricator-jump-nav-container'),
$list_filter);
return $container;
}
private function renderMiniPanel($title, $body) {
$panel = new AphrontMiniPanelView();
$panel->appendChild(
phutil_tag(
'p',
array(
),
array(
phutil_tag('strong', array(), $title.': '),
$body
)));
$this->minipanels[] = $panel;
}
public function buildAuditPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$query = new PhabricatorAuditQuery();
$query->withAuditorPHIDs($phids);
$query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
$query->withAwaitingUser($user);
$query->needCommitData(true);
$query->setLimit(10);
$audits = $query->execute();
$commits = $query->getCommits();
if (!$audits) {
return $this->renderMinipanel(
'No Audits',
'No commits are waiting for you to audit them.');
}
$view = new PhabricatorAuditListView();
$view->setAudits($audits);
$view->setCommits($commits);
$view->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader('Audits');
$panel->setCaption('Commits awaiting your audit.');
$panel->appendChild($view);
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/audit/',
'class' => 'button grey',
),
"View Active Audits \xC2\xBB"));
$panel->setNoBackground();
return $panel;
}
public function buildCommitPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = array($user->getPHID());
$query = new PhabricatorAuditCommitQuery();
$query->withAuthorPHIDs($phids);
$query->withStatus(PhabricatorAuditCommitQuery::STATUS_CONCERN);
$query->needCommitData(true);
$query->setLimit(10);
$commits = $query->execute();
if (!$commits) {
return $this->renderMinipanel(
'No Problem Commits',
'No one has raised concerns with your commits.');
}
$view = new PhabricatorAuditCommitListView();
$view->setCommits($commits);
$view->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader('Problem Commits');
$panel->setCaption('Commits which auditors have raised concerns about.');
$panel->appendChild($view);
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/audit/',
'class' => 'button grey',
),
"View Problem Commits \xC2\xBB"));
$panel->setNoBackground();
return $panel;
}
}
diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php
index f5cc12b6ac..0ab2caa099 100644
--- a/src/applications/diviner/controller/DivinerAtomController.php
+++ b/src/applications/diviner/controller/DivinerAtomController.php
@@ -1,148 +1,147 @@
<?php
final class DivinerAtomController extends DivinerController {
private $bookName;
private $atomType;
private $atomName;
private $atomContext;
private $atomIndex;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->bookName = $data['book'];
$this->atomType = $data['type'];
$this->atomName = $data['name'];
$this->atomContext = nonempty(idx($data, 'context'), null);
$this->atomIndex = nonempty(idx($data, 'index'), null);
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($this->bookName))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
$symbol = id(new DivinerAtomQuery())
->setViewer($viewer)
->withBookPHIDs(array($book->getPHID()))
->withTypes(array($this->atomType))
->withNames(array($this->atomName))
->withContexts(array($this->atomContext))
->withIndexes(array($this->atomIndex))
->needAtoms(true)
->executeOne();
if (!$symbol) {
return new Aphront404Response();
}
$atom = $symbol->getAtom();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($book->getShortTitle())
->setHref('/book/'.$book->getName().'/'));
$atom_short_title = $atom->getDocblockMetaValue(
'short',
$symbol->getTitle());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($atom_short_title));
$header = id(new PhabricatorHeaderView())
->setHeader($symbol->getTitle())
->addTag(
id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setBackgroundColor(PhabricatorTagView::COLOR_BLUE)
->setName($this->renderAtomTypeName($atom->getType())));
$properties = id(new PhabricatorPropertyListView());
$group = $atom->getDocblockMetaValue('group');
if ($group) {
$group_name = $book->getGroupName($group);
} else {
$group_name = null;
}
$properties->addProperty(
pht('Defined'),
$atom->getFile().':'.$atom->getLine());
$field = 'default';
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->addObject($symbol, $field)
->process();
$content = $engine->getOutput($symbol, $field);
$toc = $engine->getEngineMetadata(
$symbol,
$field,
PhutilRemarkupEngineRemarkupHeaderBlockRule::KEY_HEADER_TOC,
array());
$document = id(new PHUIDocumentView())
->setBook($book->getTitle(), $group_name)
->setHeader($header)
->appendChild($properties)
->appendChild(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
array(
$content,
)));
if ($toc) {
$side = new PHUIListView();
$side->addMenuItem(
id(new PHUIListItemView())
->setName(pht('Contents'))
->setType(PHUIListItemView::TYPE_LABEL));
foreach ($toc as $key => $entry) {
$side->addMenuItem(
id(new PHUIListItemView())
->setName($entry[1])
->setHref('#'.$key));
}
$document->setSideNav($side, PHUIDocumentView::NAV_TOP);
}
return $this->buildApplicationPage(
array(
$crumbs,
$document,
),
array(
'title' => $symbol->getTitle(),
- 'dust' => true,
'device' => true,
));
}
private function renderAtomTypeName($name) {
return phutil_utf8_ucwords($name);
}
}
diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php
index 31c21c78b7..aeb0cb7493 100644
--- a/src/applications/diviner/controller/DivinerBookController.php
+++ b/src/applications/diviner/controller/DivinerBookController.php
@@ -1,100 +1,99 @@
<?php
final class DivinerBookController extends DivinerController {
private $bookName;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->bookName = $data['book'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($this->bookName))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($book->getShortTitle())
->setHref('/book/'.$book->getName().'/'));
$header = id(new PhabricatorHeaderView())->setHeader($book->getTitle());
$properties = $this->buildPropertyList($book);
$atoms = id(new DivinerAtomQuery())
->setViewer($viewer)
->withBookPHIDs(array($book->getPHID()))
->execute();
$atoms = msort($atoms, 'getSortKey');
$group_spec = $book->getConfig('groups');
if (!is_array($group_spec)) {
$group_spec = array();
}
$groups = mgroup($atoms, 'getGroupName');
$groups = array_select_keys($groups, array_keys($group_spec)) + $groups;
if (isset($groups[''])) {
$no_group = $groups[''];
unset($groups['']);
$groups[''] = $no_group;
}
$out = array();
foreach ($groups as $group => $atoms) {
$group_name = $book->getGroupName($group);
$out[] = id(new PhabricatorHeaderView())
->setHeader($group_name);
$out[] = $this->renderAtomList($atoms);
}
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$properties,
$out,
),
array(
'title' => $book->getTitle(),
- 'dust' => true,
'device' => true,
));
}
private function buildPropertyList(DivinerLiveBook $book) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($user);
$policies = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$book);
$view->addProperty(
pht('Visible To'),
$policies[PhabricatorPolicyCapability::CAN_VIEW]);
$view->addProperty(
pht('Updated'),
phabricator_datetime($book->getDateModified(), $user));
return $view;
}
}
diff --git a/src/applications/diviner/controller/DivinerFindController.php b/src/applications/diviner/controller/DivinerFindController.php
index 3728a52f32..a84e734642 100644
--- a/src/applications/diviner/controller/DivinerFindController.php
+++ b/src/applications/diviner/controller/DivinerFindController.php
@@ -1,72 +1,71 @@
<?php
final class DivinerFindController extends DivinerController {
public function shouldAllowPublic() {
return true;
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$book_name = $request->getStr('book');
$book = null;
if ($book_name) {
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($book_name))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
}
$query = id(new DivinerAtomQuery())
->setViewer($viewer)
->withNames(
array(
$request->getStr('name'),
// TODO: This could probably be more smartly normalized in the DB,
// but just fake it for now.
phutil_utf8_strtolower($request->getStr('name')),
));
if ($book) {
$query->withBookPHIDs(array($book->getPHID()));
}
$context = $request->getStr('context');
if (strlen($context)) {
$query->withContexts(array($context));
}
$type = $request->getStr('type');
if (strlen($type)) {
$query->withTypes(array($type));
}
$atoms = $query->execute();
if (!$atoms) {
return new Aphront404Response();
}
if (count($atoms) == 1 && $request->getBool('jump')) {
$atom_uri = head($atoms)->getURI();
return id(new AphrontRedirectResponse())->setURI($atom_uri);
}
$list = $this->renderAtomList($atoms);
return $this->buildApplicationPage(
$list,
array(
'title' => 'derp',
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/diviner/controller/DivinerLegacyController.php b/src/applications/diviner/controller/DivinerLegacyController.php
index ca69ca2cfe..cc6c84e86e 100644
--- a/src/applications/diviner/controller/DivinerLegacyController.php
+++ b/src/applications/diviner/controller/DivinerLegacyController.php
@@ -1,60 +1,59 @@
<?php
final class DivinerLegacyController extends DivinerController {
public function processRequest() {
// TODO: Temporary implementation until Diviner is up and running inside
// Phabricator.
$links = array(
'http://www.phabricator.com/docs/phabricator/' => array(
'name' => 'Phabricator Ducks',
'flavor' => 'Oops, that should say "Docs".',
),
'http://www.phabricator.com/docs/arcanist/' => array(
'name' => 'Arcanist Docs',
'flavor' => 'Words have never been so finely crafted.',
),
'http://www.phabricator.com/docs/libphutil/' => array(
'name' => 'libphutil Docs',
'flavor' => 'Soothing prose; seductive poetry.',
),
'http://www.phabricator.com/docs/javelin/' => array(
'name' => 'Javelin Docs',
'flavor' => 'O, what noble scribe hath penned these words?',
),
);
$request = $this->getRequest();
$viewer = $request->getUser();
$list = id(new PhabricatorObjectItemListView())
->setUser($viewer);
foreach ($links as $href => $link) {
$item = id(new PhabricatorObjectItemView())
->setHref($href)
->setHeader($link['name'])
->addAttribute($link['flavor']);
$list->addItem($item);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Documentation')));
return $this->buildApplicationPage(
array(
$crumbs,
$list,
),
array(
'title' => pht('Documentation'),
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/feed/controller/PhabricatorFeedDetailController.php b/src/applications/feed/controller/PhabricatorFeedDetailController.php
index aaa9278e96..1608b1633f 100644
--- a/src/applications/feed/controller/PhabricatorFeedDetailController.php
+++ b/src/applications/feed/controller/PhabricatorFeedDetailController.php
@@ -1,52 +1,51 @@
<?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);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$feed_view,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/herald/controller/HeraldNewController.php b/src/applications/herald/controller/HeraldNewController.php
index d467ede5f4..2cb59ee912 100644
--- a/src/applications/herald/controller/HeraldNewController.php
+++ b/src/applications/herald/controller/HeraldNewController.php
@@ -1,91 +1,90 @@
<?php
final class HeraldNewController extends HeraldController {
private $contentType;
private $ruleType;
public function willProcessRequest(array $data) {
$this->contentType = idx($data, 'type');
$this->ruleType = idx($data, 'rule_type');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$content_type_map = HeraldAdapter::getEnabledAdapterMap();
if (empty($content_type_map[$this->contentType])) {
$this->contentType = head_key($content_type_map);
}
$rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
if (empty($rule_type_map[$this->ruleType])) {
$this->ruleType = HeraldRuleTypeConfig::RULE_TYPE_PERSONAL;
}
// Reorder array to put "personal" first.
$rule_type_map = array_select_keys(
$rule_type_map,
array(
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL,
)) + $rule_type_map;
$captions = array(
HeraldRuleTypeConfig::RULE_TYPE_PERSONAL =>
pht('Personal rules notify you about events. You own them, but '.
'they can only affect you.'),
HeraldRuleTypeConfig::RULE_TYPE_GLOBAL =>
pht('Global rules notify anyone about events. No one owns them, and '.
'anyone can edit them. Usually, Global rules are used to notify '.
'mailing lists.'),
);
$radio = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Type'))
->setName('rule_type')
->setValue($this->ruleType);
foreach ($rule_type_map as $value => $name) {
$radio->addButton(
$value,
$name,
idx($captions, $value));
}
$form = id(new AphrontFormView())
->setUser($user)
->setAction('/herald/edit/')
->setFlexible(true)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('New Rule for'))
->setName('content_type')
->setValue($this->contentType)
->setOptions($content_type_map))
->appendChild($radio)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Create Rule'))
->addCancelButton($this->getApplicationURI()));
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Create Rule')));
return $this->buildApplicationPage(
array(
$crumbs,
$form,
),
array(
'title' => pht('Create Herald Rule'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php
index faa1b87b81..e4c31e764f 100644
--- a/src/applications/herald/controller/HeraldRuleController.php
+++ b/src/applications/herald/controller/HeraldRuleController.php
@@ -1,539 +1,538 @@
<?php
final class HeraldRuleController extends HeraldController {
private $id;
private $filter;
public function willProcessRequest(array $data) {
$this->id = (int)idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$content_type_map = HeraldAdapter::getEnabledAdapterMap();
$rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
if ($this->id) {
$id = $this->id;
$rule = id(new HeraldRuleQuery())
->setViewer($user)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$rule) {
return new Aphront404Response();
}
$cancel_uri = $this->getApplicationURI("rule/{$id}/");
} else {
$rule = new HeraldRule();
$rule->setAuthorPHID($user->getPHID());
$rule->setMustMatchAll(true);
$content_type = $request->getStr('content_type');
$rule->setContentType($content_type);
$rule_type = $request->getStr('rule_type');
if (!isset($rule_type_map[$rule_type])) {
$rule_type = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
}
$rule->setRuleType($rule_type);
$cancel_uri = $this->getApplicationURI();
}
$adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
$local_version = id(new HeraldRule())->getConfigVersion();
if ($rule->getConfigVersion() > $local_version) {
throw new Exception(
"This rule was created with a newer version of Herald. You can not ".
"view or edit it in this older version. Upgrade your Phabricator ".
"deployment.");
}
// Upgrade rule version to our version, since we might add newly-defined
// conditions, etc.
$rule->setConfigVersion($local_version);
$rule_conditions = $rule->loadConditions();
$rule_actions = $rule->loadActions();
$rule->attachConditions($rule_conditions);
$rule->attachActions($rule_actions);
$e_name = true;
$errors = array();
if ($request->isFormPost() && $request->getStr('save')) {
list($e_name, $errors) = $this->saveRule($adapter, $rule, $request);
if (!$errors) {
$id = $rule->getID();
$uri = $this->getApplicationURI("rule/{$id}/");
return id(new AphrontRedirectResponse())->setURI($uri);
}
}
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Form Errors'));
$error_view->setErrors($errors);
} else {
$error_view = null;
}
$must_match_selector = $this->renderMustMatchSelector($rule);
$repetition_selector = $this->renderRepetitionSelector($rule, $adapter);
$handles = $this->loadHandlesForRule($rule);
require_celerity_resource('herald-css');
$content_type_name = $content_type_map[$rule->getContentType()];
$rule_type_name = $rule_type_map[$rule->getRuleType()];
$form = id(new AphrontFormView())
->setUser($user)
->setID('herald-rule-edit-form')
->addHiddenInput('content_type', $rule->getContentType())
->addHiddenInput('rule_type', $rule->getRuleType())
->addHiddenInput('save', 1)
->appendChild(
// Build this explicitly (instead of using addHiddenInput())
// so we can add a sigil to it.
javelin_tag(
'input',
array(
'type' => 'hidden',
'name' => 'rule',
'sigil' => 'rule',
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Rule Name'))
->setName('name')
->setError($e_name)
->setValue($rule->getName()));
$form
->appendChild(
id(new AphrontFormMarkupControl())
->setValue(pht(
"This %s rule triggers for %s.",
phutil_tag('strong', array(), $rule_type_name),
phutil_tag('strong', array(), $content_type_name))))
->appendChild(
id(new AphrontFormInsetView())
->setTitle(pht('Conditions'))
->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'create-condition',
'mustcapture' => true
),
pht('New Condition')))
->setDescription(
pht('When %s these conditions are met:', $must_match_selector))
->setContent(javelin_tag(
'table',
array(
'sigil' => 'rule-conditions',
'class' => 'herald-condition-table'
),
'')))
->appendChild(
id(new AphrontFormInsetView())
->setTitle(pht('Action'))
->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'create-action',
'mustcapture' => true,
),
pht('New Action')))
->setDescription(pht(
'Take these actions %s this rule matches:',
$repetition_selector))
->setContent(javelin_tag(
'table',
array(
'sigil' => 'rule-actions',
'class' => 'herald-action-table',
),
'')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Rule'))
->addCancelButton($cancel_uri));
$this->setupEditorBehavior($rule, $handles, $adapter);
$title = $rule->getID()
? pht('Edit Herald Rule')
: pht('Create Herald Rule');
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => pht('Edit Rule'),
- 'dust' => true,
'device' => true,
));
}
private function saveRule(HeraldAdapter $adapter, $rule, $request) {
$rule->setName($request->getStr('name'));
$rule->setMustMatchAll(($request->getStr('must_match') == 'all'));
$repetition_policy_param = $request->getStr('repetition_policy');
$rule->setRepetitionPolicy(
HeraldRepetitionPolicyConfig::toInt($repetition_policy_param));
$e_name = true;
$errors = array();
if (!strlen($rule->getName())) {
$e_name = pht("Required");
$errors[] = pht("Rule must have a name.");
}
$data = json_decode($request->getStr('rule'), true);
if (!is_array($data) ||
!$data['conditions'] ||
!$data['actions']) {
throw new Exception("Failed to decode rule data.");
}
$conditions = array();
foreach ($data['conditions'] as $condition) {
if ($condition === null) {
// We manage this as a sparse array on the client, so may receive
// NULL if conditions have been removed.
continue;
}
$obj = new HeraldCondition();
$obj->setFieldName($condition[0]);
$obj->setFieldCondition($condition[1]);
if (is_array($condition[2])) {
$obj->setValue(array_keys($condition[2]));
} else {
$obj->setValue($condition[2]);
}
try {
$adapter->willSaveCondition($obj);
} catch (HeraldInvalidConditionException $ex) {
$errors[] = $ex->getMessage();
}
$conditions[] = $obj;
}
$actions = array();
foreach ($data['actions'] as $action) {
if ($action === null) {
// Sparse on the client; removals can give us NULLs.
continue;
}
if (!isset($action[1])) {
// Legitimate for any action which doesn't need a target, like
// "Do nothing".
$action[1] = null;
}
$obj = new HeraldAction();
$obj->setAction($action[0]);
$obj->setTarget($action[1]);
try {
$adapter->willSaveAction($rule, $obj);
} catch (HeraldInvalidActionException $ex) {
$errors[] = $ex;
}
$actions[] = $obj;
}
$rule->attachConditions($conditions);
$rule->attachActions($actions);
if (!$errors) {
try {
$edit_action = $rule->getID() ? 'edit' : 'create';
$rule->openTransaction();
$rule->save();
$rule->saveConditions($conditions);
$rule->saveActions($actions);
$rule->logEdit($request->getUser()->getPHID(), $edit_action);
$rule->saveTransaction();
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_name = pht("Not Unique");
$errors[] = pht("Rule name is not unique. Choose a unique name.");
}
}
return array($e_name, $errors);
}
private function setupEditorBehavior(
HeraldRule $rule,
array $handles,
HeraldAdapter $adapter) {
$serial_conditions = array(
array('default', 'default', ''),
);
if ($rule->getConditions()) {
$serial_conditions = array();
foreach ($rule->getConditions() as $condition) {
$value = $condition->getValue();
if (is_array($value)) {
$value_map = array();
foreach ($value as $k => $fbid) {
$value_map[$fbid] = $handles[$fbid]->getName();
}
$value = $value_map;
}
$serial_conditions[] = array(
$condition->getFieldName(),
$condition->getFieldCondition(),
$value,
);
}
}
$serial_actions = array(
array('default', ''),
);
if ($rule->getActions()) {
$serial_actions = array();
foreach ($rule->getActions() as $action) {
switch ($action->getAction()) {
case HeraldAdapter::ACTION_FLAG:
$current_value = $action->getTarget();
break;
default:
$target_map = array();
foreach ((array)$action->getTarget() as $fbid) {
$target_map[$fbid] = $handles[$fbid]->getName();
}
$current_value = $target_map;
break;
}
$serial_actions[] = array(
$action->getAction(),
$current_value,
);
}
}
$all_rules = $this->loadRulesThisRuleMayDependUpon($rule);
$all_rules = mpull($all_rules, 'getName', 'getID');
asort($all_rules);
$all_fields = $adapter->getFieldNameMap();
$all_conditions = $adapter->getConditionNameMap();
$all_actions = $adapter->getActionNameMap($rule->getRuleType());
$fields = $adapter->getFields();
$field_map = array_select_keys($all_fields, $fields);
$actions = $adapter->getActions($rule->getRuleType());
$action_map = array_select_keys($all_actions, $actions);
$config_info = array();
$config_info['fields'] = $field_map;
$config_info['conditions'] = $all_conditions;
$config_info['actions'] = $action_map;
foreach ($config_info['fields'] as $field => $name) {
$field_conditions = $adapter->getConditionsForField($field);
$config_info['conditionMap'][$field] = $field_conditions;
}
foreach ($config_info['fields'] as $field => $fname) {
foreach ($config_info['conditionMap'][$field] as $condition) {
$value_type = $adapter->getValueTypeForFieldAndCondition(
$field,
$condition);
$config_info['values'][$field][$condition] = $value_type;
}
}
$config_info['rule_type'] = $rule->getRuleType();
foreach ($config_info['actions'] as $action => $name) {
$config_info['targets'][$action] = $adapter->getValueTypeForAction(
$action,
$rule->getRuleType());
}
Javelin::initBehavior(
'herald-rule-editor',
array(
'root' => 'herald-rule-edit-form',
'conditions' => (object)$serial_conditions,
'actions' => (object)$serial_actions,
'template' => $this->buildTokenizerTemplates() + array(
'rules' => $all_rules,
'colors' => PhabricatorFlagColor::getColorNameMap(),
'defaultColor' => PhabricatorFlagColor::COLOR_BLUE,
),
'author' => array($rule->getAuthorPHID() =>
$handles[$rule->getAuthorPHID()]->getName()),
'info' => $config_info,
));
}
private function loadHandlesForRule($rule) {
$phids = array();
foreach ($rule->getActions() as $action) {
if (!is_array($action->getTarget())) {
continue;
}
foreach ($action->getTarget() as $target) {
$target = (array)$target;
foreach ($target as $phid) {
$phids[] = $phid;
}
}
}
foreach ($rule->getConditions() as $condition) {
$value = $condition->getValue();
if (is_array($value)) {
foreach ($value as $phid) {
$phids[] = $phid;
}
}
}
$phids[] = $rule->getAuthorPHID();
return $this->loadViewerHandles($phids);
}
/**
* Render the selector for the "When (all of | any of) these conditions are
* met:" element.
*/
private function renderMustMatchSelector($rule) {
return AphrontFormSelectControl::renderSelectTag(
$rule->getMustMatchAll() ? 'all' : 'any',
array(
'all' => pht('all of'),
'any' => pht('any of'),
),
array(
'name' => 'must_match',
));
}
/**
* Render the selector for "Take these actions (every time | only the first
* time) this rule matches..." element.
*/
private function renderRepetitionSelector($rule, HeraldAdapter $adapter) {
$repetition_policy = HeraldRepetitionPolicyConfig::toString(
$rule->getRepetitionPolicy());
$repetition_options = $adapter->getRepetitionOptions();
$repetition_names = HeraldRepetitionPolicyConfig::getMap();
$repetition_map = array_select_keys($repetition_names, $repetition_options);
if (count($repetition_map) < 2) {
return head($repetition_names);
} else {
return AphrontFormSelectControl::renderSelectTag(
$repetition_policy,
$repetition_map,
array(
'name' => 'repetition_policy',
));
}
}
protected function buildTokenizerTemplates() {
$template = new AphrontTokenizerTemplateView();
$template = $template->render();
return array(
'source' => array(
'email' => '/typeahead/common/mailable/',
'user' => '/typeahead/common/users/',
'repository' => '/typeahead/common/repositories/',
'package' => '/typeahead/common/packages/',
'project' => '/typeahead/common/projects/',
),
'markup' => $template,
);
}
/**
* Load rules for the "Another Herald rule..." condition dropdown, which
* allows one rule to depend upon the success or failure of another rule.
*/
private function loadRulesThisRuleMayDependUpon(HeraldRule $rule) {
$viewer = $this->getRequest()->getUser();
// Any rule can depend on a global rule.
$all_rules = id(new HeraldRuleQuery())
->setViewer($viewer)
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL))
->withContentTypes(array($rule->getContentType()))
->execute();
if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) {
// Personal rules may depend upon your other personal rules.
$all_rules += id(new HeraldRuleQuery())
->setViewer($viewer)
->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL))
->withContentTypes(array($rule->getContentType()))
->withAuthorPHIDs(array($rule->getAuthorPHID()))
->execute();
}
// A rule can not depend upon itself.
unset($all_rules[$rule->getID()]);
return $all_rules;
}
}
diff --git a/src/applications/herald/controller/HeraldRuleEditHistoryController.php b/src/applications/herald/controller/HeraldRuleEditHistoryController.php
index 25b0cbde9b..03ad026c01 100644
--- a/src/applications/herald/controller/HeraldRuleEditHistoryController.php
+++ b/src/applications/herald/controller/HeraldRuleEditHistoryController.php
@@ -1,59 +1,58 @@
<?php
final class HeraldRuleEditHistoryController extends HeraldController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$edit_query = new HeraldEditLogQuery();
if ($this->id) {
$edit_query->withRuleIDs(array($this->id));
}
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setOffset($request->getStr('offset'));
$edits = $edit_query->executeWithOffsetPager($pager);
$need_phids = mpull($edits, 'getEditorPHID');
$handles = $this->loadViewerHandles($need_phids);
$list_view = id(new HeraldRuleEditHistoryView())
->setEdits($edits)
->setHandles($handles)
->setUser($this->getRequest()->getUser());
$panel = new AphrontPanelView();
$panel->setHeader(pht('Edit History'));
$panel->appendChild($list_view);
$panel->setNoBackground();
$crumbs = $this
->buildApplicationCrumbs($can_create = false)
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit History'))
->setHref($this->getApplicationURI('herald/history')));
$nav = $this->buildSideNavView();
$nav->selectFilter('history');
$nav->appendChild($panel);
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Rule Edit History'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php
index 64d21000e5..2470042e8b 100644
--- a/src/applications/herald/controller/HeraldRuleViewController.php
+++ b/src/applications/herald/controller/HeraldRuleViewController.php
@@ -1,114 +1,113 @@
<?php
final class HeraldRuleViewController extends HeraldController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$rule = id(new HeraldRuleQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->needConditionsAndActions(true)
->executeOne();
if (!$rule) {
return new Aphront404Response();
}
$header = id(new PhabricatorHeaderView())
->setHeader($rule->getName());
$actions = $this->buildActionView($rule);
$properties = $this->buildPropertyView($rule);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Rule %d', $rule->getID())));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
),
array(
'title' => $rule->getName(),
'device' => true,
- 'dust' => true,
));
}
private function buildActionView(HeraldRule $rule) {
$viewer = $this->getRequest()->getUser();
$id = $rule->getID();
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($rule)
->setObjectURI($this->getApplicationURI("rule/{$id}/"));
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$rule,
PhabricatorPolicyCapability::CAN_EDIT);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Rule'))
->setHref($this->getApplicationURI("edit/{$id}/"))
->setIcon('edit')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $view;
}
private function buildPropertyView(HeraldRule $rule) {
$viewer = $this->getRequest()->getUser();
$this->loadHandles(array($rule->getAuthorPHID()));
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($rule);
$view->addProperty(
pht('Rule Type'),
idx(HeraldRuleTypeConfig::getRuleTypeMap(), $rule->getRuleType()));
if ($rule->isPersonalRule()) {
$view->addProperty(
pht('Author'),
$this->getHandle($rule->getAuthorPHID())->renderLink());
}
$adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType());
if ($adapter) {
$view->addProperty(
pht('Applies To'),
idx(HeraldAdapter::getEnabledAdapterMap(), $rule->getContentType()));
$view->invokeWillRenderEvent();
$view->addSectionHeader(pht('Rule Description'));
$view->addTextContent(
phutil_tag(
'div',
array(
'style' => 'white-space: pre-wrap;',
),
$adapter->renderRuleAsText($rule)));
}
return $view;
}
}
diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php
index ba9450e9be..80ec24e47c 100644
--- a/src/applications/herald/controller/HeraldTestConsoleController.php
+++ b/src/applications/herald/controller/HeraldTestConsoleController.php
@@ -1,147 +1,146 @@
<?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) {
$matches = null;
$object = null;
if (preg_match('/^D(\d+)$/', $object_name, $matches)) {
$object = id(new DifferentialRevision())->load($matches[1]);
if (!$object) {
$e_name = pht('Invalid');
$errors[] = pht('No Differential Revision with that ID exists.');
}
} else if (preg_match('/^r([A-Z]+)(\w+)$/', $object_name, $matches)) {
$repo = id(new PhabricatorRepository())->loadOneWhere(
'callsign = %s',
$matches[1]);
if (!$repo) {
$e_name = pht('Invalid');
$errors[] = pht('There is no repository with the callsign: %s.',
$matches[1]);
}
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'repositoryID = %d AND commitIdentifier = %s',
$repo->getID(),
$matches[2]);
if (!$commit) {
$e_name = pht('Invalid');
$errors[] = pht('There is no commit with that identifier.');
}
$object = $commit;
} else {
$e_name = pht('Invalid');
$errors[] = pht('This object name is not recognized.');
}
if (!$errors) {
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(
$repo,
$object,
$data);
} else {
throw new Exception("Can not build adapter for object!");
}
$rules = id(new HeraldRuleQuery())
->setViewer($user)
->withContentTypes(array($adapter->getAdapterContentType()))
->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(
id(new AphrontFormTextControl())
->setLabel(pht('Object Name'))
->setName('object_name')
->setError($e_name)
->setValue($object_name))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Test Rules')));
$nav = $this->buildSideNavView();
$nav->selectFilter('test');
$nav->appendChild(
array(
$error_view,
$form,
));
$crumbs = id($this->buildApplicationCrumbs())
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Transcripts'))
->setHref($this->getApplicationURI('/transcript/')))
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Test Console')));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Test Console'),
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php
index 5f596c180a..9442a2792e 100644
--- a/src/applications/herald/controller/HeraldTranscriptController.php
+++ b/src/applications/herald/controller/HeraldTranscriptController.php
@@ -1,494 +1,493 @@
<?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() {
$xscript = id(new HeraldTranscript())->load($this->id);
if (!$xscript) {
throw new Exception('Uknown transcript!');
}
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 {
$this->adapter = HeraldAdapter::getAdapterForContentType(
$object_xscript->getType());
$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,
- 'dust' => 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);
}
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);
} else {
$failure = pht('FAILURE');
$outcome =
hsprintf('<span class="outcome-failure">%s</span>', $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());
} else {
$note = null;
}
if ($cond->getResult()) {
$result = hsprintf(
'<span class="herald-outcome condition-pass">'.
"\xE2\x9C\x93".
'</span>');
} else {
$result = hsprintf(
'<span class="herald-outcome condition-fail">'.
"\xE2\x9C\x98".
'</span>');
}
$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);
$class = 'herald-rule-pass';
} else {
$fail = pht('FAIL');
$result = hsprintf(
'<span class="herald-outcome rule-fail">%s</span>', $fail);
$class = 'herald-rule-fail';
}
$cond_markup[] = hsprintf('<li>%s %s</li>', $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)));
}
$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/herald/controller/HeraldTranscriptListController.php b/src/applications/herald/controller/HeraldTranscriptListController.php
index cbf8fd5485..e07c9bd340 100644
--- a/src/applications/herald/controller/HeraldTranscriptListController.php
+++ b/src/applications/herald/controller/HeraldTranscriptListController.php
@@ -1,117 +1,116 @@
<?php
final class HeraldTranscriptListController extends HeraldController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
// Get one page of data together with the pager.
// Pull these objects manually since the serialized fields are gigantic.
$transcript = new HeraldTranscript();
$conn_r = $transcript->establishConnection('r');
$phid = $request->getStr('phid');
$where_clause = '';
if ($phid) {
$where_clause = qsprintf(
$conn_r,
'WHERE objectPHID = %s',
$phid);
}
$pager = new AphrontPagerView();
$pager->setOffset($request->getInt('offset'));
$pager->setURI($request->getRequestURI(), 'offset');
$limit_clause = qsprintf(
$conn_r,
'LIMIT %d, %d',
$pager->getOffset(),
$pager->getPageSize() + 1);
$data = queryfx_all(
$conn_r,
'SELECT id, objectPHID, time, duration, dryRun FROM %T
%Q
ORDER BY id DESC
%Q',
$transcript->getTableName(),
$where_clause,
$limit_clause);
$data = $pager->sliceResults($data);
// Render the table.
$handles = array();
if ($data) {
$phids = ipull($data, 'objectPHID', 'objectPHID');
$handles = $this->loadViewerHandles($phids);
}
$rows = array();
foreach ($data as $xscript) {
$rows[] = array(
phabricator_date($xscript['time'], $user),
phabricator_time($xscript['time'], $user),
$handles[$xscript['objectPHID']]->renderLink(),
$xscript['dryRun'] ? 'Yes' : '',
number_format((int)(1000 * $xscript['duration'])).' ms',
phutil_tag(
'a',
array(
'href' => '/herald/transcript/'.$xscript['id'].'/',
'class' => 'button small grey',
),
pht('View Transcript')),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Date'),
pht('Time'),
pht('Object'),
pht('Dry Run'),
pht('Duration'),
pht('View'),
));
$table->setColumnClasses(
array(
'',
'right',
'wide wrap',
'',
'',
'action',
));
// Render the whole page.
$panel = new AphrontPanelView();
$panel->setHeader(pht('Herald Transcripts'));
$panel->appendChild($table);
$panel->appendChild($pager);
$panel->setNoBackground();
$nav = $this->buildSideNavView();
$nav->selectFilter('transcript');
$nav->appendChild($panel);
$crumbs = id($this->buildApplicationCrumbs())
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Transcripts')));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Herald Transcripts'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php
index 11f04fa3c6..ed686aaf32 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php
@@ -1,196 +1,195 @@
<?php
/**
* @group legalpad
*/
final class LegalpadDocumentEditController extends LegalpadController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if (!$this->id) {
$is_create = true;
$document = id(new LegalpadDocument())
->setVersions(0)
->setCreatorPHID($user->getPHID())
->setContributorCount(0)
->setRecentContributorPHIDs(array())
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->setEditPolicy(PhabricatorPolicies::POLICY_USER);
$body = id(new LegalpadDocumentBody())
->setCreatorPHID($user->getPHID());
$document->attachDocumentBody($body);
$document->setDocumentBodyPHID(PhabricatorPHIDConstants::PHID_VOID);
$title = null;
$text = null;
} else {
$is_create = false;
$document = id(new LegalpadDocumentQuery())
->setViewer($user)
->needDocumentBodies(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($this->id))
->executeOne();
if (!$document) {
return new Aphront404Response();
}
$title = $document->getDocumentBody()->getTitle();
$text = $document->getDocumentBody()->getText();
}
$e_title = true;
$e_text = true;
$errors = array();
$can_view = null;
$can_edit = null;
if ($request->isFormPost()) {
$xactions = array();
$title = $request->getStr('title');
if (!strlen($title)) {
$e_title = pht('Required');
$errors[] = pht('The document title may not be blank.');
} else {
$xactions[] = id(new LegalpadTransaction())
->setTransactionType(LegalpadTransactionType::TYPE_TITLE)
->setNewValue($title);
}
$text = $request->getStr('text');
if (!strlen($text)) {
$e_text = pht('Required');
$errors[] = pht('The document may not be blank.');
} else {
$xactions[] = id(new LegalpadTransaction())
->setTransactionType(LegalpadTransactionType::TYPE_TEXT)
->setNewValue($text);
}
$can_view = $request->getStr('can_view');
$xactions[] = id(new LegalpadTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($can_view);
$can_edit = $request->getStr('can_edit');
$xactions[] = id(new LegalpadTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($can_edit);
if (!$errors) {
$editor = id(new LegalpadDocumentEditor())
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setActor($user);
$xactions = $editor->applyTransactions($document, $xactions);
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI('view/'.$document->getID()));
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('A Fatal Omission!'))
->setErrors($errors);
// set these to what was specified in the form on post
$document->setViewPolicy($can_view);
$document->setEditPolicy($can_edit);
}
$form = id(new AphrontFormView())
->setFlexible(true)
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setID('document-title')
->setLabel(pht('Title'))
->setError($e_title)
->setValue($title)
->setName('title'))
->appendChild(
id(new PhabricatorRemarkupControl())
->setID('document-text')
->setLabel(pht('Text'))
->setError($e_text)
->setValue($text)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setName('text'));
$policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($document)
->execute();
$form
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($document)
->setPolicies($policies)
->setName('can_view'))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($document)
->setPolicies($policies)
->setName('can_edit'));
$submit = new AphrontFormSubmitControl();
if ($is_create) {
$submit->setValue(pht('Create Document'));
$title = pht('Create Document');
$short = pht('Create');
} else {
$submit->setValue(pht('Update Document'));
$submit->addCancelButton(
$this->getApplicationURI('view/'.$document->getID()));
$title = pht('Update Document');
$short = pht('Update');
}
$form
->appendChild($submit);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNav());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())->setName($short));
$preview = id(new PHUIRemarkupPreviewPanel())
->setHeader(pht('Document Preview'))
->setPreviewURI($this->getApplicationURI('document/preview/'))
->setControlID('document-text')
->setSkin('document');
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
$preview
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php
index 5841adebfe..946e4fb5a5 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php
@@ -1,236 +1,235 @@
<?php
/**
* @group legalpad
*/
final class LegalpadDocumentSignController extends LegalpadController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$document = id(new LegalpadDocumentQuery())
->setViewer($user)
->withIDs(array($this->id))
->needDocumentBodies(true)
->executeOne();
if (!$document) {
return new Aphront404Response();
}
$signature = id(new LegalpadDocumentSignature())
->loadOneWhere(
'documentPHID = %s AND documentVersion = %d AND signerPHID = %s',
$document->getPHID(),
$document->getVersions(),
$user->getPHID());
if (!$signature) {
$has_signed = false;
$error_view = null;
$signature = id(new LegalpadDocumentSignature())
->setSignerPHID($user->getPHID())
->setDocumentPHID($document->getPHID())
->setDocumentVersion($document->getVersions());
$data = array(
'name' => $user->getRealName(),
'email' => $user->loadPrimaryEmailAddress());
$signature->setSignatureData($data);
} else {
$has_signed = true;
$error_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('You have already agreed to these terms.'));
$data = $signature->getSignatureData();
}
$e_name = true;
$e_email = true;
$e_address_1 = true;
$errors = array();
if ($request->isFormPost()) {
$name = $request->getStr('name');
$email = $request->getStr('email');
$address_1 = $request->getStr('address_1');
$address_2 = $request->getStr('address_2');
$phone = $request->getStr('phone');
$agree = $request->getExists('agree');
if (!$name) {
$e_name = pht('Required');
$errors[] = pht('Name field is required.');
}
$data['name'] = $name;
if (!$email) {
$e_email = pht('Required');
$errors[] = pht('Email field is required.');
} else {
$addr_obj = new PhutilEmailAddress($email);
$domain = $addr_obj->getDomainName();
if (!$domain) {
$e_email = pht('Invalid');
$errors[] = pht('A valid email is required.');
}
}
$data['email'] = $email;
if (!$address_1) {
$e_address_1 = pht('Required');
$errors[] = pht('Address line 1 field is required.');
}
$data['address_1'] = $address_1;
$data['address_2'] = $address_2;
$data['phone'] = $phone;
$signature->setSignatureData($data);
if (!$agree) {
$errors[] = pht(
'You must check "I agree to the terms laid forth above."');
}
if (!$errors) {
$signature->save();
$has_signed = true;
$error_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Signature successful. Thank you.'));
} else {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Error in submission.'))
->setErrors($errors);
}
}
$document_body = $document->getDocumentBody();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$engine->addObject(
$document_body,
LegalpadDocumentBody::MARKUP_FIELD_TEXT);
$engine->process();
$title = $document_body->getTitle();
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$content = array(
id(new PHUIDocumentView())
->setHeader($header)
->appendChild($this->buildDocument($engine, $document_body)),
$error_view,
$this->buildSignatureForm(
$document_body,
$signature,
$has_signed,
$e_name,
$e_email,
$e_address_1));
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
'pageObjects' => array($document->getPHID()),
));
}
private function buildDocument(
PhabricatorMarkupEngine
$engine, LegalpadDocumentBody $body) {
require_celerity_resource('legalpad-documentbody-css');
return phutil_tag(
'div',
array(
'class' => 'legalpad-documentbody'
),
$engine->getOutput($body, LegalpadDocumentBody::MARKUP_FIELD_TEXT));
}
private function buildSignatureForm(
LegalpadDocumentBody $body,
LegalpadDocumentSignature $signature,
$has_signed = false,
$e_name = true,
$e_email = true,
$e_address_1 = true) {
$user = $this->getRequest()->getUser();
if ($has_signed) {
$instructions = pht('Thank you for signing and agreeing.');
} else {
$instructions = pht('Please enter the following information.');
}
$data = $signature->getSignatureData();
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->appendChild(
id(new AphrontFormInsetView())
->setTitle(pht('Sign and Agree'))
->setDescription($instructions)
->setContent(phutil_tag('br', array()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setValue(idx($data, 'name', ''))
->setName('name')
->setError($e_name)
->setDisabled($has_signed))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setValue(idx($data, 'email', ''))
->setName('email')
->setError($e_email)
->setDisabled($has_signed))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Address line 1'))
->setValue(idx($data, 'address_1', ''))
->setName('address_1')
->setError($e_address_1)
->setDisabled($has_signed))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Address line 2'))
->setValue(idx($data, 'address_2', ''))
->setName('address_2')
->setDisabled($has_signed))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Phone'))
->setValue(idx($data, 'phone', ''))
->setName('phone')
->setDisabled($has_signed))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'agree',
'agree',
pht('I agree to the terms laid forth above.'),
$has_signed)
->setDisabled($has_signed))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Sign and Agree'))
->setDisabled($has_signed)));
return $form;
}
}
diff --git a/src/applications/legalpad/controller/LegalpadDocumentViewController.php b/src/applications/legalpad/controller/LegalpadDocumentViewController.php
index 81597d3d4a..0fa35851d1 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentViewController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentViewController.php
@@ -1,226 +1,225 @@
<?php
/**
* @group legalpad
*/
final class LegalpadDocumentViewController extends LegalpadController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$document = id(new LegalpadDocumentQuery())
->setViewer($user)
->withIDs(array($this->id))
->needDocumentBodies(true)
->needContributors(true)
->executeOne();
if (!$document) {
return new Aphront404Response();
}
$xactions = id(new LegalpadTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($document->getPHID()))
->execute();
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$document->getPHID());
$document_body = $document->getDocumentBody();
$phids = array();
$phids[] = $document_body->getCreatorPHID();
foreach ($subscribers as $subscriber) {
$phids[] = $subscriber;
}
foreach ($document->getContributors() as $contributor) {
$phids[] = $contributor;
}
$this->loadHandles($phids);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$engine->addObject(
$document_body,
LegalpadDocumentBody::MARKUP_FIELD_TEXT);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$title = $document_body->getTitle();
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = $this->buildActionView($document);
$properties = $this->buildPropertyView($document, $engine);
$comment_form_id = celerity_generate_unique_node_id();
$xaction_view = id(new LegalpadTransactionView())
->setUser($this->getRequest()->getUser())
->setObjectPHID($document->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
$add_comment = $this->buildAddCommentView($document, $comment_form_id);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNav());
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('L'.$document->getID())
->setHref($this->getApplicationURI('view/'.$document->getID())));
$content = array(
$crumbs,
$header,
$actions,
$properties,
$this->buildDocument($engine, $document_body),
$xaction_view,
$add_comment,
);
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
'pageObjects' => array($document->getPHID()),
));
}
private function buildDocument(
PhabricatorMarkupEngine
$engine, LegalpadDocumentBody $body) {
require_celerity_resource('legalpad-documentbody-css');
return phutil_tag(
'div',
array(
'class' => 'legalpad-documentbody'
),
$engine->getOutput($body, LegalpadDocumentBody::MARKUP_FIELD_TEXT));
}
private function buildActionView(LegalpadDocument $document) {
$user = $this->getRequest()->getUser();
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($document);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$document,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Document'))
->setHref($this->getApplicationURI('/edit/'.$document->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $actions;
}
private function buildPropertyView(
LegalpadDocument $document,
PhabricatorMarkupEngine $engine) {
$user = $this->getRequest()->getUser();
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($document);
$properties->addProperty(
pht('Last Updated'),
phabricator_datetime($document->getDateModified(), $user));
$properties->addProperty(
pht('Updated By'),
$this->getHandle(
$document->getDocumentBody()->getCreatorPHID())->renderLink());
$properties->addProperty(
pht('Versions'),
$document->getVersions());
$contributor_view = array();
foreach ($document->getContributors() as $contributor) {
$contributor_view[] = $this->getHandle($contributor)->renderLink();
}
$contributor_view = phutil_implode_html(', ', $contributor_view);
$properties->addProperty(
pht('Contributors'),
$contributor_view);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$document);
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$properties->invokeWillRenderEvent();
return $properties;
}
private function buildAddCommentView(
LegalpadDocument $document,
$comment_form_id) {
$user = $this->getRequest()->getUser();
$draft = PhabricatorDraft::newFromUserAndKey($user, $document->getPHID());
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$title = $is_serious
? pht('Add Comment')
: pht('Debate Legislation');
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$button_name = $is_serious
? pht('Add Comment')
: pht('Commence Filibuster');
$form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setObjectPHID($document->getPHID())
->setFormID($comment_form_id)
->setDraft($draft)
->setSubmitButtonName($button_name)
->setAction($this->getApplicationURI('/comment/'.$document->getID().'/'))
->setRequestURI($this->getRequest()->getRequestURI());
return array(
$header,
$form,
);
}
}
diff --git a/src/applications/mailinglists/controller/PhabricatorMailingListsEditController.php b/src/applications/mailinglists/controller/PhabricatorMailingListsEditController.php
index e417d8b9a4..ef346d086e 100644
--- a/src/applications/mailinglists/controller/PhabricatorMailingListsEditController.php
+++ b/src/applications/mailinglists/controller/PhabricatorMailingListsEditController.php
@@ -1,140 +1,139 @@
<?php
final class PhabricatorMailingListsEditController
extends PhabricatorMailingListsController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
if ($this->id) {
$list = id(new PhabricatorMailingListQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$list) {
return new Aphront404Response();
}
} else {
$list = new PhabricatorMetaMTAMailingList();
}
$e_email = true;
$e_uri = null;
$e_name = true;
$errors = array();
$crumbs = $this->buildApplicationCrumbs();
if ($request->isFormPost()) {
$list->setName($request->getStr('name'));
$list->setEmail($request->getStr('email'));
$list->setURI($request->getStr('uri'));
$e_email = null;
$e_name = null;
if (!strlen($list->getEmail())) {
$e_email = pht('Required');
$errors[] = pht('Email is required.');
}
if (!strlen($list->getName())) {
$e_name = pht('Required');
$errors[] = pht('Name is required.');
} else if (preg_match('/[ ,]/', $list->getName())) {
$e_name = pht('Invalid');
$errors[] = pht('Name must not contain spaces or commas.');
}
if ($list->getURI()) {
if (!PhabricatorEnv::isValidWebResource($list->getURI())) {
$e_uri = pht('Invalid');
$errors[] = pht('Mailing list URI must point to a valid web page.');
}
}
if (!$errors) {
try {
$list->save();
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI());
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_email = pht('Duplicate');
$errors[] = pht('Another mailing list already uses that address.');
}
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
$form = new AphrontFormView();
$form->setUser($request->getUser());
if ($list->getID()) {
$form->setAction($this->getApplicationURI('/edit/'.$list->getID().'/'));
} else {
$form->setAction($this->getApplicationURI('/edit/'));
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($list->getEmail())
->setCaption(pht('Email will be delivered to this address.'))
->setError($e_email))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setError($e_name)
->setCaption(pht('Human-readable display and autocomplete name.'))
->setValue($list->getName()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('URI'))
->setName('uri')
->setError($e_uri)
->setCaption(pht('Optional link to mailing list archives or info.'))
->setValue($list->getURI()))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save'))
->addCancelButton($this->getApplicationURI()));
if ($list->getID()) {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Mailing List')));
} else {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Create Mailing List')));
}
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => pht('Edit Mailing List'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskListController.php b/src/applications/maniphest/controller/ManiphestTaskListController.php
index c7f2e8dff6..08d11af402 100644
--- a/src/applications/maniphest/controller/ManiphestTaskListController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskListController.php
@@ -1,978 +1,977 @@
<?php
/**
* @group maniphest
*/
final class ManiphestTaskListController extends ManiphestController {
const DEFAULT_PAGE_SIZE = 1000;
private $view;
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view');
}
private function getArrToStrList($key) {
$arr = $this->getRequest()->getArr($key);
$arr = implode(',', $arr);
return nonempty($arr, null);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($request->isFormPost()) {
// Redirect to GET so URIs can be copy/pasted.
$task_ids = $request->getStr('set_tasks');
$task_ids = nonempty($task_ids, null);
$search_text = $request->getStr('set_search');
$min_priority = $request->getInt('set_lpriority');
$max_priority = $request->getInt('set_hpriority');
$uri = $request->getRequestURI()
->alter('users', $this->getArrToStrList('set_users'))
->alter('projects', $this->getArrToStrList('set_projects'))
->alter('aprojects', $this->getArrToStrList('set_aprojects'))
->alter('useraprojects', $this->getArrToStrList('set_useraprojects'))
->alter('xprojects', $this->getArrToStrList('set_xprojects'))
->alter('owners', $this->getArrToStrList('set_owners'))
->alter('authors', $this->getArrToStrList('set_authors'))
->alter('lpriority', $min_priority)
->alter('hpriority', $max_priority)
->alter('tasks', $task_ids)
->alter('search', $search_text);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$nav = $this->buildBaseSideNav();
$has_filter = array(
'action' => true,
'created' => true,
'subscribed' => true,
'triage' => true,
'projecttriage' => true,
'projectall' => true,
);
$query = null;
$key = $request->getStr('key');
if (!$key && !$this->view) {
if ($this->getDefaultQuery()) {
$key = $this->getDefaultQuery()->getQueryKey();
}
}
if ($key) {
$query = id(new PhabricatorSearchQuery())->loadOneWhere(
'queryKey = %s',
$key);
}
// If the user is running a saved query, load query parameters from that
// query. Otherwise, build a new query object from the HTTP request.
if ($query) {
$nav->selectFilter('Q:'.$query->getQueryKey(), 'custom');
$this->view = 'custom';
} else {
$this->view = $nav->selectFilter($this->view, 'action');
$query = $this->buildQueryFromRequest();
}
// Execute the query.
list($tasks, $handles, $total_count) = self::loadTasks(
$query,
$user);
// Extract information we need to render the filters from the query.
$search_text = $query->getParameter('fullTextSearch');
$user_phids = $query->getParameter('userPHIDs', array());
$task_ids = $query->getParameter('taskIDs', array());
$owner_phids = $query->getParameter('ownerPHIDs', array());
$author_phids = $query->getParameter('authorPHIDs', array());
$project_phids = $query->getParameter('projectPHIDs', array());
$any_project_phids = $query->getParameter(
'anyProjectPHIDs',
array());
$any_user_project_phids = $query->getParameter(
'anyUserProjectPHIDs',
array());
$exclude_project_phids = $query->getParameter(
'excludeProjectPHIDs',
array());
$low_priority = $query->getParameter('lowPriority');
$high_priority = $query->getParameter('highPriority');
$page_size = $query->getParameter('limit');
$page = $query->getParameter('offset');
$q_status = $query->getParameter('status');
$q_group = $query->getParameter('group');
$q_order = $query->getParameter('order');
$form = id(new AphrontFormView())
->setUser($user)
->setNoShading(true)
->setAction(
$request->getRequestURI()
->alter('key', null)
->alter(
$this->getStatusRequestKey(),
$this->getStatusRequestValue($q_status))
->alter(
$this->getOrderRequestKey(),
$this->getOrderRequestValue($q_order))
->alter(
$this->getGroupRequestKey(),
$this->getGroupRequestValue($q_group)));
if (isset($has_filter[$this->view])) {
$tokens = array();
foreach ($user_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchowner/')
->setName('set_users')
->setLabel(pht('Users'))
->setValue($tokens));
}
if ($this->view == 'custom') {
$form->appendChild(
id(new AphrontFormTextControl())
->setName('set_search')
->setLabel(pht('Search'))
->setValue($search_text));
$form->appendChild(
id(new AphrontFormTextControl())
->setName('set_tasks')
->setLabel(pht('Task IDs'))
->setValue(join(',', $task_ids)));
$tokens = array();
foreach ($owner_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchowner/')
->setName('set_owners')
->setLabel(pht('Owners'))
->setValue($tokens));
$tokens = array();
foreach ($author_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/authors/')
->setName('set_authors')
->setLabel(pht('Authors'))
->setValue($tokens));
}
$tokens = array();
foreach ($project_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
if ($this->view != 'projectall' && $this->view != 'projecttriage') {
$caption = null;
if ($this->view == 'custom') {
$caption = pht('Find tasks in ALL of these projects ("AND" query).');
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/searchproject/')
->setName('set_projects')
->setLabel(pht('Projects'))
->setCaption($caption)
->setValue($tokens));
}
if ($this->view == 'custom') {
$atokens = array();
foreach ($any_project_phids as $phid) {
$atokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/projects/')
->setName('set_aprojects')
->setLabel(pht('Any Projects'))
->setCaption(pht('Find tasks in ANY of these projects ("OR" query).'))
->setValue($atokens));
$tokens = array();
foreach ($any_user_project_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/users/')
->setName('set_useraprojects')
->setLabel(pht('Any User Projects'))
->setCaption(
pht('Find tasks in ANY of these users\' projects ("OR" query).'))
->setValue($tokens));
$tokens = array();
foreach ($exclude_project_phids as $phid) {
$tokens[$phid] = $handles[$phid]->getFullName();
}
$form->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/projects/')
->setName('set_xprojects')
->setLabel(pht('Exclude Projects'))
->setCaption(pht('Find tasks NOT in any of these projects.'))
->setValue($tokens));
$priority = ManiphestTaskPriority::getLowestPriority();
if ($low_priority !== null) {
$priority = $low_priority;
}
$form->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Min Priority'))
->setName('set_lpriority')
->setValue($priority)
->setOptions(array_reverse(
ManiphestTaskPriority::getTaskPriorityMap(), true)));
$priority = ManiphestTaskPriority::getHighestPriority();
if ($high_priority !== null) {
$priority = $high_priority;
}
$form->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Max Priority'))
->setName('set_hpriority')
->setValue($priority)
->setOptions(ManiphestTaskPriority::getTaskPriorityMap()));
}
$form
->appendChild($this->renderStatusControl($q_status))
->appendChild($this->renderGroupControl($q_group))
->appendChild($this->renderOrderControl($q_order));
$submit = id(new AphrontFormSubmitControl())
->setValue(pht('Filter Tasks'));
// Only show "Save..." for novel queries which have some kind of query
// parameters set.
if ($this->view === 'custom'
&& empty($key)
&& $request->getRequestURI()->getQueryParams()) {
$submit->addCancelButton(
'/maniphest/custom/edit/?key='.$query->getQueryKey(),
pht('Save Custom Query...'));
}
$form->appendChild($submit);
$create_uri = new PhutilURI('/maniphest/task/create/');
if ($project_phids) {
// If we have project filters selected, use them as defaults for task
// creation.
$create_uri->setQueryParam('projects', implode(';', $project_phids));
}
$filter = new AphrontListFilterView();
if (empty($key)) {
$filter->appendChild($form);
}
$have_tasks = false;
foreach ($tasks as $group => $list) {
if (count($list)) {
$have_tasks = true;
break;
}
}
require_celerity_resource('maniphest-task-summary-css');
$list_container = new AphrontNullView();
$list_container->appendChild(hsprintf(
'<div class="maniphest-list-container">'));
if (!$have_tasks) {
$no_tasks = pht('No matching tasks.');
$list_container->appendChild(hsprintf(
'<h1 class="maniphest-task-group-header">'.
'%s'.
'</h1>',
$no_tasks));
$result_count = null;
} else {
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'offset');
$pager->setPageSize($page_size);
$pager->setOffset($page);
$pager->setCount($total_count);
$cur = ($pager->getOffset() + 1);
$max = min($pager->getOffset() + $page_size, $total_count);
$tot = $total_count;
$results = pht('Displaying tasks %s - %s of %s.',
number_format($cur),
number_format($max),
number_format($tot));
$result_count = phutil_tag(
'div',
array(
'class' => 'maniphest-total-result-count'
),
$results);
$selector = new AphrontNullView();
$group = $query->getParameter('group');
$order = $query->getParameter('order');
$is_draggable =
($order == 'priority') &&
($group == 'none' || $group == 'priority');
$lists = array();
foreach ($tasks as $group => $list) {
$task_list = new ManiphestTaskListView();
$task_list->setShowBatchControls(true);
if ($is_draggable) {
$task_list->setShowSubpriorityControls(true);
}
$task_list->setUser($user);
$task_list->setTasks($list);
$task_list->setHandles($handles);
$count = number_format(count($list));
$header =
javelin_tag(
'h1',
array(
'class' => 'maniphest-task-group-header',
'sigil' => 'task-group',
'meta' => array(
'priority' => head($list)->getPriority(),
),
),
$group.' ('.$count.')');
$lists[] =
phutil_tag(
'div',
array(
'class' => 'maniphest-task-group'
),
array(
$header,
$task_list,
));
}
$selector->appendChild($lists);
$selector->appendChild($this->renderBatchEditor($query));
$list_container->appendChild($selector);
$list_container->appendChild($pager);
Javelin::initBehavior(
'maniphest-subpriority-editor',
array(
'uri' => '/maniphest/subpriority/',
));
}
$nav->appendChild($filter);
$nav->appendChild($result_count);
$nav->appendChild($list_container);
$title = pht('Task List');
$crumbs = $this->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title))
->addAction(
id(new PHUIListItemView())
->setHref($this->getApplicationURI('/task/create/'))
->setName(pht('Create Task'))
->setIcon('create'));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
public static function loadTasks(
PhabricatorSearchQuery $search_query,
PhabricatorUser $viewer) {
$any_project = false;
$search_text = $search_query->getParameter('fullTextSearch');
$user_phids = $search_query->getParameter('userPHIDs', array());
$task_ids = $search_query->getParameter('taskIDs', array());
$project_phids = $search_query->getParameter('projectPHIDs', array());
$any_project_phids = $search_query->getParameter(
'anyProjectPHIDs',
array());
$any_user_project_phids = $search_query->getParameter(
'anyUserProjectPHIDs',
array());
$xproject_phids = $search_query->getParameter(
'excludeProjectPHIDs',
array());
$owner_phids = $search_query->getParameter('ownerPHIDs', array());
$author_phids = $search_query->getParameter('authorPHIDs', array());
$low_priority = $search_query->getParameter('lowPriority');
$low_priority = coalesce($low_priority,
ManiphestTaskPriority::getLowestPriority());
$high_priority = $search_query->getParameter('highPriority');
$high_priority = coalesce($high_priority,
ManiphestTaskPriority::getHighestPriority());
$query = new ManiphestTaskQuery();
$query->withTaskIDs($task_ids);
if ($project_phids) {
$query->withAllProjects($project_phids);
}
if ($xproject_phids) {
$query->withoutProjects($xproject_phids);
}
if ($any_project_phids) {
$query->withAnyProjects($any_project_phids);
}
if ($owner_phids) {
$query->withOwners($owner_phids);
}
if ($author_phids) {
$query->withAuthors($author_phids);
}
if ($any_user_project_phids) {
$query->setViewer($viewer);
$query->withAnyUserProjects($any_user_project_phids);
}
$status = $search_query->getParameter('status', 'all');
if (!empty($status['open']) && !empty($status['closed'])) {
$query->withStatus(ManiphestTaskQuery::STATUS_ANY);
} else if (!empty($status['open'])) {
$query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
} else {
$query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
}
switch ($search_query->getParameter('view')) {
case 'action':
$query->withOwners($user_phids);
break;
case 'created':
$query->withAuthors($user_phids);
break;
case 'subscribed':
$query->withSubscribers($user_phids);
break;
case 'triage':
$query->withOwners($user_phids);
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
break;
case 'alltriage':
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
break;
case 'all':
break;
case 'projecttriage':
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
break;
case 'projectall':
break;
case 'custom':
$query->withPrioritiesBetween($low_priority, $high_priority);
break;
}
$query->withFullTextSearch($search_text);
$order_map = array(
'priority' => ManiphestTaskQuery::ORDER_PRIORITY,
'created' => ManiphestTaskQuery::ORDER_CREATED,
'title' => ManiphestTaskQuery::ORDER_TITLE,
);
$query->setOrderBy(
idx(
$order_map,
$search_query->getParameter('order'),
ManiphestTaskQuery::ORDER_MODIFIED));
$group_map = array(
'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
'owner' => ManiphestTaskQuery::GROUP_OWNER,
'status' => ManiphestTaskQuery::GROUP_STATUS,
'project' => ManiphestTaskQuery::GROUP_PROJECT,
);
$query->setGroupBy(
idx(
$group_map,
$search_query->getParameter('group'),
ManiphestTaskQuery::GROUP_NONE));
$query->setCalculateRows(true);
$query->setLimit($search_query->getParameter('limit'));
$query->setOffset($search_query->getParameter('offset'));
$data = $query->execute();
$total_row_count = $query->getRowCount();
$project_group_phids = array();
if ($search_query->getParameter('group') == 'project') {
foreach ($data as $task) {
foreach ($task->getProjectPHIDs() as $phid) {
$project_group_phids[] = $phid;
}
}
}
$handle_phids = mpull($data, 'getOwnerPHID');
$handle_phids = array_merge(
$handle_phids,
$project_phids,
$user_phids,
$xproject_phids,
$owner_phids,
$author_phids,
$project_group_phids,
$any_project_phids,
$any_user_project_phids,
array_mergev(mpull($data, 'getProjectPHIDs')));
$handles = id(new PhabricatorObjectHandleData($handle_phids))
->setViewer($viewer)
->loadHandles();
switch ($search_query->getParameter('group')) {
case 'priority':
$data = mgroup($data, 'getPriority');
// If we have invalid priorities, they'll all map to "???". Merge
// arrays to prevent them from overwriting each other.
$out = array();
foreach ($data as $pri => $tasks) {
$out[ManiphestTaskPriority::getTaskPriorityName($pri)][] = $tasks;
}
foreach ($out as $pri => $tasks) {
$out[$pri] = array_mergev($tasks);
}
$data = $out;
break;
case 'status':
$data = mgroup($data, 'getStatus');
$out = array();
foreach ($data as $status => $tasks) {
$out[ManiphestTaskStatus::getTaskStatusFullName($status)] = $tasks;
}
$data = $out;
break;
case 'owner':
$data = mgroup($data, 'getOwnerPHID');
$out = array();
foreach ($data as $phid => $tasks) {
if ($phid) {
$out[$handles[$phid]->getFullName()] = $tasks;
} else {
$out['Unassigned'] = $tasks;
}
}
$data = $out;
ksort($data);
// Move "Unassigned" to the top of the list.
if (isset($data['Unassigned'])) {
$data = array('Unassigned' => $out['Unassigned']) + $out;
}
break;
case 'project':
$grouped = array();
foreach ($query->getGroupByProjectResults() as $project => $tasks) {
foreach ($tasks as $task) {
$group = $project ? $handles[$project]->getName() : 'No Project';
$grouped[$group][$task->getID()] = $task;
}
}
$data = $grouped;
ksort($data);
// Move "No Project" to the end of the list.
if (isset($data['No Project'])) {
$noproject = $data['No Project'];
unset($data['No Project']);
$data += array('No Project' => $noproject);
}
break;
default:
$data = array(
'Tasks' => $data,
);
break;
}
return array($data, $handles, $total_row_count);
}
private function renderBatchEditor(PhabricatorSearchQuery $search_query) {
$user = $this->getRequest()->getUser();
Javelin::initBehavior(
'maniphest-batch-selector',
array(
'selectAll' => 'batch-select-all',
'selectNone' => 'batch-select-none',
'submit' => 'batch-select-submit',
'status' => 'batch-select-status-cell',
'idContainer' => 'batch-select-id-container',
'formID' => 'batch-select-form',
));
$select_all = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'class' => 'grey button',
'id' => 'batch-select-all',
),
pht('Select All'));
$select_none = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'class' => 'grey button',
'id' => 'batch-select-none',
),
pht('Clear Selection'));
$submit = phutil_tag(
'button',
array(
'id' => 'batch-select-submit',
'disabled' => 'disabled',
'class' => 'disabled',
),
pht("Batch Edit Selected \xC2\xBB"));
$export = javelin_tag(
'a',
array(
'href' => '/maniphest/export/'.$search_query->getQueryKey().'/',
'class' => 'grey button',
),
pht('Export to Excel'));
$hidden = phutil_tag(
'div',
array(
'id' => 'batch-select-id-container',
),
'');
$editor = hsprintf(
'<div class="maniphest-batch-editor">'.
'<div class="batch-editor-header">%s</div>'.
'<table class="maniphest-batch-editor-layout">'.
'<tr>'.
'<td>%s%s</td>'.
'<td>%s</td>'.
'<td id="batch-select-status-cell">%s</td>'.
'<td class="batch-select-submit-cell">%s%s</td>'.
'</tr>'.
'</table>'.
'</div>',
pht('Batch Task Editor'),
$select_all,
$select_none,
$export,
'',
$submit,
$hidden);
$editor = phabricator_form(
$user,
array(
'method' => 'POST',
'action' => '/maniphest/batch/',
'id' => 'batch-select-form',
),
$editor);
return $editor;
}
private function buildQueryFromRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$status = $this->getStatusValueFromRequest();
$group = $this->getGroupValueFromRequest();
$order = $this->getOrderValueFromRequest();
$user_phids = $request->getStrList(
'users',
array($user->getPHID()));
if ($this->view == 'projecttriage' || $this->view == 'projectall') {
$projects = id(new PhabricatorProjectQuery())
->setViewer($user)
->withMemberPHIDs($user_phids)
->execute();
$any_project_phids = mpull($projects, 'getPHID');
$any_user_project_phids = array();
} else {
$any_project_phids = $request->getStrList('aprojects');
$any_user_project_phids = $request->getStrList('useraprojects');
}
$project_phids = $request->getStrList('projects');
$exclude_project_phids = $request->getStrList('xprojects');
$task_ids = $request->getStrList('tasks');
if ($task_ids) {
// We only need the integer portion of each task ID, so get rid of any
// non-numeric elements
$numeric_task_ids = array();
foreach ($task_ids as $task_id) {
$task_id = preg_replace('/\D+/', '', $task_id);
if (!empty($task_id)) {
$numeric_task_ids[] = $task_id;
}
}
if (empty($numeric_task_ids)) {
$numeric_task_ids = array(null);
}
$task_ids = $numeric_task_ids;
}
$owner_phids = $request->getStrList('owners');
$author_phids = $request->getStrList('authors');
$search_string = $request->getStr('search');
$low_priority = $request->getInt('lpriority');
$high_priority = $request->getInt('hpriority');
$page = $request->getInt('offset');
$page_size = self::DEFAULT_PAGE_SIZE;
$query = new PhabricatorSearchQuery();
$query->setQuery('<<maniphest>>');
$query->setParameters(
array(
'fullTextSearch' => $search_string,
'view' => $this->view,
'userPHIDs' => $user_phids,
'projectPHIDs' => $project_phids,
'anyProjectPHIDs' => $any_project_phids,
'anyUserProjectPHIDs' => $any_user_project_phids,
'excludeProjectPHIDs' => $exclude_project_phids,
'ownerPHIDs' => $owner_phids,
'authorPHIDs' => $author_phids,
'taskIDs' => $task_ids,
'lowPriority' => $low_priority,
'highPriority' => $high_priority,
'group' => $group,
'order' => $order,
'offset' => $page,
'limit' => $page_size,
'status' => $status,
));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$query->save();
unset($unguarded);
return $query;
}
/* -( Toggle Button Controls )---------------------------------------------
These are a giant mess since we have several different values: the request
key (GET param used in requests), the request value (short names used in
requests to keep URIs readable), and the query value (complex value stored in
the query).
*/
private function getStatusValueFromRequest() {
$map = $this->getStatusMap();
$val = $this->getRequest()->getStr($this->getStatusRequestKey());
return idx($map, $val, head($map));
}
private function getGroupValueFromRequest() {
$map = $this->getGroupMap();
$val = $this->getRequest()->getStr($this->getGroupRequestKey());
return idx($map, $val, head($map));
}
private function getOrderValueFromRequest() {
$map = $this->getOrderMap();
$val = $this->getRequest()->getStr($this->getOrderRequestKey());
return idx($map, $val, head($map));
}
private function getStatusRequestKey() {
return 's';
}
private function getGroupRequestKey() {
return 'g';
}
private function getOrderRequestKey() {
return 'o';
}
private function getStatusRequestValue($value) {
return array_search($value, $this->getStatusMap());
}
private function getGroupRequestValue($value) {
return array_search($value, $this->getGroupMap());
}
private function getOrderRequestValue($value) {
return array_search($value, $this->getOrderMap());
}
private function getStatusMap() {
return array(
'o' => array(
'open' => true,
),
'c' => array(
'closed' => true,
),
'oc' => array(
'open' => true,
'closed' => true,
),
);
}
private function getGroupMap() {
return array(
'p' => 'priority',
'o' => 'owner',
's' => 'status',
'j' => 'project',
'n' => 'none',
);
}
private function getOrderMap() {
return array(
'p' => 'priority',
'u' => 'updated',
'c' => 'created',
't' => 'title',
);
}
private function getStatusButtonMap() {
return array(
'o' => pht('Open'),
'c' => pht('Closed'),
'oc' => pht('All'),
);
}
private function getGroupButtonMap() {
return array(
'p' => pht('Priority'),
'o' => pht('Owner'),
's' => pht('Status'),
'j' => pht('Project'),
'n' => pht('None'),
);
}
private function getOrderButtonMap() {
return array(
'p' => pht('Priority'),
'u' => pht('Updated'),
'c' => pht('Created'),
't' => pht('Title'),
);
}
public function renderStatusControl($value) {
$request = $this->getRequest();
return id(new AphrontFormToggleButtonsControl())
->setLabel(pht('Status'))
->setValue($this->getStatusRequestValue($value))
->setBaseURI($request->getRequestURI(), $this->getStatusRequestKey())
->setButtons($this->getStatusButtonMap());
}
public function renderOrderControl($value) {
$request = $this->getRequest();
return id(new AphrontFormToggleButtonsControl())
->setLabel(pht('Order'))
->setValue($this->getOrderRequestValue($value))
->setBaseURI($request->getRequestURI(), $this->getOrderRequestKey())
->setButtons($this->getOrderButtonMap());
}
public function renderGroupControl($value) {
$request = $this->getRequest();
return id(new AphrontFormToggleButtonsControl())
->setLabel(pht('Group'))
->setValue($this->getGroupRequestValue($value))
->setBaseURI($request->getRequestURI(), $this->getGroupRequestKey())
->setButtons($this->getGroupButtonMap());
}
}
diff --git a/src/applications/meta/controller/PhabricatorApplicationsListController.php b/src/applications/meta/controller/PhabricatorApplicationsListController.php
index 0a136cea6a..e4e44cb506 100644
--- a/src/applications/meta/controller/PhabricatorApplicationsListController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationsListController.php
@@ -1,63 +1,62 @@
<?php
final class PhabricatorApplicationsListController
extends PhabricatorApplicationsController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$nav = $this->buildSideNavView();
$nav->selectFilter('/');
$applications = PhabricatorApplication::getAllApplications();
$list = $this->buildInstalledApplicationsList($applications);
$title = pht('Installed Applications');
$nav->appendChild($list);
$crumbs = $this
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Applications'))
->setHref($this->getApplicationURI()));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function buildInstalledApplicationsList(array $applications) {
$list = new PhabricatorObjectItemListView();
$applications = msort($applications, 'getName');
foreach ($applications as $application) {
$item = id(new PhabricatorObjectItemView())
->setHeader($application->getName())
->setHref('/applications/view/'.get_class($application).'/')
->addAttribute($application->getShortDescription());
if (!$application->isInstalled()) {
$item->addIcon('delete', pht('Uninstalled'));
}
if ($application->isBeta()) {
$item->addIcon('lint-warning', pht('Beta'));
}
$list->addItem($item);
}
return $list;
}
}
diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php
index d930b0cc3b..545c565e27 100644
--- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php
+++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php
@@ -1,237 +1,236 @@
<?php
final class PhabricatorOwnersDetailController
extends PhabricatorOwnersController {
private $id;
private $package;
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();
}
$this->package = $package;
$paths = $package->loadPaths();
$owners = $package->loadOwners();
$repository_phids = array();
foreach ($paths as $path) {
$repository_phids[$path->getRepositoryPHID()] = true;
}
if ($repository_phids) {
$repositories = id(new PhabricatorRepository())->loadAllWhere(
'phid in (%Ls)',
array_keys($repository_phids));
$repositories = mpull($repositories, null, 'getPHID');
} else {
$repositories = array();
}
$phids = array();
foreach ($owners as $owner) {
$phids[$owner->getUserPHID()] = true;
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$rows = array();
$rows[] = array(pht('Name'), $package->getName());
$rows[] = array(pht('Description'), $package->getDescription());
$primary_owner = null;
$primary_phid = $package->getPrimaryOwnerPHID();
if ($primary_phid && isset($handles[$primary_phid])) {
$primary_owner = phutil_tag(
'strong',
array(),
$handles[$primary_phid]->renderLink());
}
$rows[] = array(pht('Primary Owner'), $primary_owner);
$owner_links = array();
foreach ($owners as $owner) {
$owner_links[] = $handles[$owner->getUserPHID()]->renderLink();
}
$owner_links = phutil_implode_html(phutil_tag('br'), $owner_links);
$rows[] = array(pht('Owners'), $owner_links);
$rows[] = array(
pht('Auditing'),
$package->getAuditingEnabled() ?
pht('Enabled') :
pht('Disabled'),
);
$path_links = array();
foreach ($paths as $path) {
$repo = idx($repositories, $path->getRepositoryPHID());
if (!$repo) {
continue;
}
$href = DiffusionRequest::generateDiffusionURI(
array(
'callsign' => $repo->getCallsign(),
'branch' => $repo->getDefaultBranch(),
'path' => $path->getPath(),
'action' => 'browse'
));
$repo_name = phutil_tag('strong', array(), $repo->getName());
$path_link = phutil_tag(
'a',
array(
'href' => (string) $href,
),
$path->getPath());
$path_links[] = hsprintf(
'%s %s %s',
($path->getExcluded() ? "\xE2\x80\x93" : '+'),
$repo_name,
$path_link);
}
$path_links = phutil_implode_html(phutil_tag('br'), $path_links);
$rows[] = array(pht('Paths'), $path_links);
$table = new AphrontTableView($rows);
$table->setColumnClasses(
array(
'header',
'wide',
));
$panel = new AphrontPanelView();
$panel->setNoBackground();
$panel->setHeader(
pht('Package Details for "%s"', $package->getName()));
$panel->addButton(
javelin_tag(
'a',
array(
'href' => '/owners/delete/'.$package->getID().'/',
'class' => 'button grey',
'sigil' => 'workflow',
),
pht('Delete Package')));
$panel->addButton(
phutil_tag(
'a',
array(
'href' => '/owners/edit/'.$package->getID().'/',
'class' => 'button',
),
pht('Edit Package')));
$panel->appendChild($table);
$key = 'package/'.$package->getID();
$this->setSideNavFilter($key);
$commit_views = array();
$commit_uri = id(new PhutilURI('/audit/view/packagecommits/'))
->setQueryParams(
array(
'phid' => $package->getPHID(),
));
$attention_query = id(new PhabricatorAuditCommitQuery())
->withPackagePHIDs(array($package->getPHID()))
->withStatus(PhabricatorAuditCommitQuery::STATUS_CONCERN)
->needCommitData(true)
->needAudits(true)
->setLimit(10);
$attention_commits = $attention_query->execute();
if ($attention_commits) {
$view = new PhabricatorAuditCommitListView();
$view->setUser($user);
$view->setCommits($attention_commits);
$commit_views[] = array(
'view' => $view,
'header' => pht('Commits in this Package that Need Attention'),
'button' => phutil_tag(
'a',
array(
'href' => $commit_uri->alter('status', 'open'),
'class' => 'button grey',
),
pht('View All Problem Commits')),
);
}
$all_query = id(new PhabricatorAuditCommitQuery())
->withPackagePHIDs(array($package->getPHID()))
->needCommitData(true)
->needAudits(true)
->setLimit(100);
$all_commits = $all_query->execute();
$view = new PhabricatorAuditCommitListView();
$view->setUser($user);
$view->setCommits($all_commits);
$view->setNoDataString(pht('No commits in this package.'));
$commit_views[] = array(
'view' => $view,
'header' => pht('Recent Commits in Package'),
'button' => phutil_tag(
'a',
array(
'href' => $commit_uri,
'class' => 'button grey',
),
pht('View All Package Commits')),
);
$phids = array();
foreach ($commit_views as $commit_view) {
$phids[] = $commit_view['view']->getRequiredHandlePHIDs();
}
$phids = array_mergev($phids);
$handles = $this->loadViewerHandles($phids);
$commit_panels = array();
foreach ($commit_views as $commit_view) {
$commit_panel = new AphrontPanelView();
$commit_panel->setNoBackground();
$commit_panel->setHeader($commit_view['header']);
if (isset($commit_view['button'])) {
$commit_panel->addButton($commit_view['button']);
}
$commit_view['view']->setHandles($handles);
$commit_panel->appendChild($commit_view['view']);
$commit_panels[] = $commit_panel;
}
$nav = $this->buildSideNavView();
$nav->appendChild($panel);
$nav->appendChild($commit_panels);
return $this->buildApplicationPage(
array(
$nav,
),
array(
'title' => pht("Package %s", $package->getName()),
- 'dust' => true,
'device' => true,
));
}
protected function getExtraPackageViews(AphrontSideNavFilterView $view) {
$package = $this->package;
$view->addFilter('package/'.$package->getID(), pht('Details'));
}
}
diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php
index 2fc745b198..a87b20fbd4 100644
--- a/src/applications/owners/controller/PhabricatorOwnersEditController.php
+++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php
@@ -1,281 +1,280 @@
<?php
final class PhabricatorOwnersEditController
extends PhabricatorOwnersController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->id) {
$package = id(new PhabricatorOwnersPackage())->load($this->id);
if (!$package) {
return new Aphront404Response();
}
} else {
$package = new PhabricatorOwnersPackage();
$package->setPrimaryOwnerPHID($user->getPHID());
}
$e_name = true;
$e_primary = true;
$errors = array();
if ($request->isFormPost()) {
$package->setName($request->getStr('name'));
$package->setDescription($request->getStr('description'));
$old_auditing_enabled = $package->getAuditingEnabled();
$package->setAuditingEnabled(
($request->getStr('auditing') === 'enabled')
? 1
: 0);
$primary = $request->getArr('primary');
$primary = reset($primary);
$old_primary = $package->getPrimaryOwnerPHID();
$package->setPrimaryOwnerPHID($primary);
$owners = $request->getArr('owners');
if ($primary) {
array_unshift($owners, $primary);
}
$owners = array_unique($owners);
$paths = $request->getArr('path');
$repos = $request->getArr('repo');
$excludes = $request->getArr('exclude');
$path_refs = array();
for ($ii = 0; $ii < count($paths); $ii++) {
if (empty($paths[$ii]) || empty($repos[$ii])) {
continue;
}
$path_refs[] = array(
'repositoryPHID' => $repos[$ii],
'path' => $paths[$ii],
'excluded' => $excludes[$ii],
);
}
if (!strlen($package->getName())) {
$e_name = pht('Required');
$errors[] = pht('Package name is required.');
} else {
$e_name = null;
}
if (!$package->getPrimaryOwnerPHID()) {
$e_primary = pht('Required');
$errors[] = pht('Package must have a primary owner.');
} else {
$e_primary = null;
}
if (!$path_refs) {
$errors[] = pht('Package must include at least one path.');
}
if (!$errors) {
$package->attachUnsavedOwners($owners);
$package->attachUnsavedPaths($path_refs);
$package->attachOldAuditingEnabled($old_auditing_enabled);
$package->attachOldPrimaryOwnerPHID($old_primary);
$package->attachActorPHID($user->getPHID());
try {
$package->save();
return id(new AphrontRedirectResponse())
->setURI('/owners/package/'.$package->getID().'/');
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_name = pht('Duplicate');
$errors[] = pht('Package name must be unique.');
}
}
} else {
$owners = $package->loadOwners();
$owners = mpull($owners, 'getUserPHID');
$paths = $package->loadPaths();
$path_refs = array();
foreach ($paths as $path) {
$path_refs[] = array(
'repositoryPHID' => $path->getRepositoryPHID(),
'path' => $path->getPath(),
'excluded' => $path->getExcluded(),
);
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Package Errors'));
$error_view->setErrors($errors);
}
$handles = $this->loadViewerHandles($owners);
$primary = $package->getPrimaryOwnerPHID();
if ($primary && isset($handles[$primary])) {
$token_primary_owner = array(
$primary => $handles[$primary]->getFullName(),
);
} else {
$token_primary_owner = array();
}
$token_all_owners = array_select_keys($handles, $owners);
$token_all_owners = mpull($token_all_owners, 'getFullName');
if ($package->getID()) {
$title = pht('Edit Package');
$side_nav_filter = 'edit/'.$this->id;
} else {
$title = pht('New Package');
$side_nav_filter = 'new';
}
$this->setSideNavFilter($side_nav_filter);
$repos = id(new PhabricatorRepository())->loadAll();
$default_paths = array();
foreach ($repos as $repo) {
$default_path = $repo->getDetail('default-owners-path');
if ($default_path) {
$default_paths[$repo->getPHID()] = $default_path;
}
}
$repos = mpull($repos, 'getCallsign', 'getPHID');
$template = new AphrontTypeaheadTemplateView();
$template = $template->render();
Javelin::initBehavior(
'owners-path-editor',
array(
'root' => 'path-editor',
'table' => 'paths',
'add_button' => 'addpath',
'repositories' => $repos,
'input_template' => $template,
'pathRefs' => $path_refs,
'completeURI' => '/diffusion/services/path/complete/',
'validateURI' => '/diffusion/services/path/validate/',
'repositoryDefaultPaths' => $default_paths,
));
require_celerity_resource('owners-path-editor-css');
$cancel_uri = $package->getID()
? '/owners/package/'.$package->getID().'/'
: '/owners/';
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($package->getName())
->setError($e_name))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/usersorprojects/')
->setLabel(pht('Primary Owner'))
->setName('primary')
->setLimit(1)
->setValue($token_primary_owner)
->setError($e_primary))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/usersorprojects/')
->setLabel(pht('Owners'))
->setName('owners')
->setValue($token_all_owners))
->appendChild(
id(new AphrontFormSelectControl())
->setName('auditing')
->setLabel(pht('Auditing'))
->setCaption(
pht('With auditing enabled, all future commits that touch '.
'this package will be reviewed to make sure an owner '.
'of the package is involved and the commit message has '.
'a valid revision, reviewed by, and author.'))
->setOptions(array(
'disabled' => pht('Disabled'),
'enabled' => pht('Enabled'),
))
->setValue(
$package->getAuditingEnabled()
? 'enabled'
: 'disabled'))
->appendChild(
id(new AphrontFormInsetView())
->setTitle(pht('Paths'))
->addDivAttributes(array('id' => 'path-editor'))
->setRightButton(javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'addpath',
'mustcapture' => true,
),
pht('Add New Path')))
->setDescription(
pht('Specify the files and directories which comprise '.
'this package.'))
->setContent(javelin_tag(
'table',
array(
'class' => 'owners-path-editor-table',
'sigil' => 'paths',
),
'')))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Description'))
->setName('description')
->setValue($package->getDescription()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue(pht('Save Package')));
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$nav = $this->buildSideNavView();
$nav->appendChild($error_view);
$nav->appendChild($header);
$nav->appendChild($form);
return $this->buildApplicationPage(
array(
$nav,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
protected function getExtraPackageViews(AphrontSideNavFilterView $view) {
if ($this->id) {
$view->addFilter('edit/'.$this->id, pht('Edit'));
} else {
$view->addFilter('new', pht('New'));
}
}
}
diff --git a/src/applications/owners/controller/PhabricatorOwnersListController.php b/src/applications/owners/controller/PhabricatorOwnersListController.php
index 3191623c55..7783d141d9 100644
--- a/src/applications/owners/controller/PhabricatorOwnersListController.php
+++ b/src/applications/owners/controller/PhabricatorOwnersListController.php
@@ -1,346 +1,345 @@
<?php
final class PhabricatorOwnersListController
extends PhabricatorOwnersController {
protected $view;
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view', 'owned');
$this->setSideNavFilter('view/'.$this->view);
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$package = new PhabricatorOwnersPackage();
$owner = new PhabricatorOwnersOwner();
$path = new PhabricatorOwnersPath();
$repository_phid = '';
if ($request->getStr('repository') != '') {
$repository_phid = id(new PhabricatorRepository())
->loadOneWhere('callsign = %s', $request->getStr('repository'))
->getPHID();
}
switch ($this->view) {
case 'search':
$packages = array();
$conn_r = $package->establishConnection('r');
$where = array('1 = 1');
$join = array();
$having = '';
if ($request->getStr('name')) {
$where[] = qsprintf(
$conn_r,
'p.name LIKE %~',
$request->getStr('name'));
}
if ($repository_phid || $request->getStr('path')) {
$join[] = qsprintf(
$conn_r,
'JOIN %T path ON path.packageID = p.id',
$path->getTableName());
if ($repository_phid) {
$where[] = qsprintf(
$conn_r,
'path.repositoryPHID = %s',
$repository_phid);
}
if ($request->getStr('path')) {
$where[] = qsprintf(
$conn_r,
'(path.path LIKE %~ AND NOT path.excluded) OR
%s LIKE CONCAT(REPLACE(path.path, %s, %s), %s)',
$request->getStr('path'),
$request->getStr('path'),
'_',
'\_',
'%');
$having = 'HAVING MAX(path.excluded) = 0';
}
}
if ($request->getArr('owner')) {
$join[] = qsprintf(
$conn_r,
'JOIN %T o ON o.packageID = p.id',
$owner->getTableName());
$where[] = qsprintf(
$conn_r,
'o.userPHID IN (%Ls)',
$request->getArr('owner'));
}
$data = queryfx_all(
$conn_r,
'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id %Q',
$package->getTableName(),
implode(' ', $join),
'('.implode(') AND (', $where).')',
$having);
$packages = $package->loadAllFromArray($data);
$header = pht('Search Results');
$nodata = pht('No packages match your query.');
break;
case 'owned':
$data = queryfx_all(
$package->establishConnection('r'),
'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID
WHERE o.userPHID = %s GROUP BY p.id',
$package->getTableName(),
$owner->getTableName(),
$user->getPHID());
$packages = $package->loadAllFromArray($data);
$header = pht('Owned Packages');
$nodata = pht('No owned packages');
break;
case 'projects':
$projects = id(new PhabricatorProjectQuery())
->setViewer($user)
->withMemberPHIDs(array($user->getPHID()))
->withStatus(PhabricatorProjectQuery::STATUS_ANY)
->execute();
$owner_phids = mpull($projects, 'getPHID');
if ($owner_phids) {
$data = queryfx_all(
$package->establishConnection('r'),
'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID
WHERE o.userPHID IN (%Ls) GROUP BY p.id',
$package->getTableName(),
$owner->getTableName(),
$owner_phids);
} else {
$data = array();
}
$packages = $package->loadAllFromArray($data);
$header = pht('Owned Packages');
$nodata = pht('No owned packages');
break;
case 'all':
$packages = $package->loadAll();
$header = pht('All Packages');
$nodata = pht('There are no defined packages.');
break;
}
$content = $this->renderPackageTable(
$packages,
$header,
$nodata);
$filter = new AphrontListFilterView();
$owners_search_value = array();
if ($request->getArr('owner')) {
$phids = $request->getArr('owner');
$phid = reset($phids);
$handles = $this->loadViewerHandles(array($phid));
$owners_search_value = array(
$phid => $handles[$phid]->getFullName(),
);
}
$callsigns = array('' => pht('(Any Repository)'));
$repositories = id(new PhabricatorRepository())
->loadAllWhere('1 = 1 ORDER BY callsign');
foreach ($repositories as $repository) {
$callsigns[$repository->getCallsign()] =
$repository->getCallsign().': '.$repository->getName();
}
$form = id(new AphrontFormView())
->setUser($user)
->setAction('/owners/view/search/')
->setMethod('GET')
->setNoShading(true)
->appendChild(
id(new AphrontFormTextControl())
->setName('name')
->setLabel(pht('Name'))
->setValue($request->getStr('name')))
->appendChild(
id(new AphrontFormTokenizerControl())
->setDatasource('/typeahead/common/usersorprojects/')
->setLimit(1)
->setName('owner')
->setLabel(pht('Owner'))
->setValue($owners_search_value))
->appendChild(
id(new AphrontFormSelectControl())
->setName('repository')
->setLabel(pht('Repository'))
->setOptions($callsigns)
->setValue($request->getStr('repository')))
->appendChild(
id(new AphrontFormTextControl())
->setName('path')
->setLabel(pht('Path'))
->setValue($request->getStr('path')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Search for Packages')));
$filter->appendChild($form);
$nav = $this->buildSideNavView();
$nav->appendChild($filter);
$nav->appendChild($content);
return $this->buildApplicationPage(
array(
$nav,
),
array(
'title' => pht('Package Index'),
- 'dust' => true,
'device' => true,
));
}
private function renderPackageTable(array $packages, $header, $nodata) {
assert_instances_of($packages, 'PhabricatorOwnersPackage');
if ($packages) {
$package_ids = mpull($packages, 'getID');
$owners = id(new PhabricatorOwnersOwner())->loadAllWhere(
'packageID IN (%Ld)',
$package_ids);
$paths = id(new PhabricatorOwnersPath())->loadAllWhere(
'packageID in (%Ld)',
$package_ids);
$phids = array();
foreach ($owners as $owner) {
$phids[$owner->getUserPHID()] = true;
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$repository_phids = array();
foreach ($paths as $path) {
$repository_phids[$path->getRepositoryPHID()] = true;
}
if ($repository_phids) {
$repositories = id(new PhabricatorRepository())->loadAllWhere(
'phid in (%Ls)',
array_keys($repository_phids));
} else {
$repositories = array();
}
$repositories = mpull($repositories, null, 'getPHID');
$owners = mgroup($owners, 'getPackageID');
$paths = mgroup($paths, 'getPackageID');
} else {
$handles = array();
$repositories = array();
$owners = array();
$paths = array();
}
$rows = array();
foreach ($packages as $package) {
$pkg_owners = idx($owners, $package->getID(), array());
foreach ($pkg_owners as $key => $owner) {
$pkg_owners[$key] = $handles[$owner->getUserPHID()]->renderLink();
if ($owner->getUserPHID() == $package->getPrimaryOwnerPHID()) {
$pkg_owners[$key] = phutil_tag('strong', array(), $pkg_owners[$key]);
}
}
$pkg_owners = phutil_implode_html(phutil_tag('br'), $pkg_owners);
$pkg_paths = idx($paths, $package->getID(), array());
foreach ($pkg_paths as $key => $path) {
$repo = idx($repositories, $path->getRepositoryPHID());
if ($repo) {
$href = DiffusionRequest::generateDiffusionURI(
array(
'callsign' => $repo->getCallsign(),
'branch' => $repo->getDefaultBranch(),
'path' => $path->getPath(),
'action' => 'browse',
));
$pkg_paths[$key] = hsprintf(
'%s %s%s',
($path->getExcluded() ? "\xE2\x80\x93" : '+'),
phutil_tag('strong', array(), $repo->getName()),
phutil_tag(
'a',
array(
'href' => (string) $href,
),
$path->getPath()));
} else {
$pkg_paths[$key] = $path->getPath();
}
}
$pkg_paths = phutil_implode_html(phutil_tag('br'), $pkg_paths);
$rows[] = array(
phutil_tag(
'a',
array(
'href' => '/owners/package/'.$package->getID().'/',
),
$package->getName()),
$pkg_owners,
$pkg_paths,
phutil_tag(
'a',
array(
'href' => '/audit/view/packagecommits/?phid='.$package->getPHID(),
),
pht('Related Commits'))
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Name'),
pht('Owners'),
pht('Paths'),
pht('Related Commits'),
));
$table->setColumnClasses(
array(
'pri',
'',
'wide wrap',
'narrow',
));
$panel = new AphrontPanelView();
$panel->setHeader($header);
$panel->appendChild($table);
$panel->setNoBackground();
return $panel;
}
protected function getExtraPackageViews(AphrontSideNavFilterView $view) {
if ($this->view == 'search') {
$view->addFilter('view/search', pht('Search Results'));
}
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleEditController.php b/src/applications/people/controller/PhabricatorPeopleEditController.php
index f23a57f4e7..4b060b2488 100644
--- a/src/applications/people/controller/PhabricatorPeopleEditController.php
+++ b/src/applications/people/controller/PhabricatorPeopleEditController.php
@@ -1,823 +1,822 @@
<?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,
- 'dust' => 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')));
$header = new PhabricatorHeaderView();
if ($user->getID()) {
$header->setHeader(pht('Edit User'));
} else {
$header->setHeader(pht('Create New User'));
}
return array($error_view, $form);
}
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($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')));
}
$header = new PhabricatorHeaderView();
$header->setHeader(pht('Edit Role'));
return array($error_view, $header, $form);
}
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));
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.')));
}
$header = new PhabricatorHeaderView();
$header->setHeader(pht('Conduit Certificate'));
return array($header, $form);
}
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')));
$header = new PhabricatorHeaderView();
$header->setHeader(pht('Change Username'));
return array($errors, $header, $form);
}
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')));
$header = new PhabricatorHeaderView();
$header->setHeader(pht('Delete User'));
return array($errors, $header, $form);
}
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);
}
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/PhabricatorPeopleLdapController.php b/src/applications/people/controller/PhabricatorPeopleLdapController.php
index a2f02fc687..1bef0912be 100644
--- a/src/applications/people/controller/PhabricatorPeopleLdapController.php
+++ b/src/applications/people/controller/PhabricatorPeopleLdapController.php
@@ -1,220 +1,219 @@
<?php
final class PhabricatorPeopleLdapController
extends PhabricatorPeopleController {
public function processRequest() {
$request = $this->getRequest();
$admin = $request->getUser();
$content = array();
$form = id(new AphrontFormView())
->setAction($request->getRequestURI()
->alter('search', 'true')->alter('import', null))
->setUser($admin)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('LDAP username'))
->setName('username'))
->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('Password'))
->setName('password'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('LDAP query'))
->setCaption(pht('A filter such as (objectClass=*)'))
->setName('query'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Search')));
$panel = id(new AphrontPanelView())
->setHeader(pht('Import LDAP Users'))
->setNoBackground()
->setWidth(AphrontPanelView::WIDTH_FORM)
->appendChild($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Import Ldap Users'))
->setHref($this->getApplicationURI('/ldap/')));
$nav = $this->buildSideNavView();
$nav->setCrumbs($crumbs);
$nav->selectFilter('ldap');
$nav->appendChild($content);
if ($request->getStr('import')) {
$nav->appendChild($this->processImportRequest($request));
}
$nav->appendChild($panel);
if ($request->getStr('search')) {
$nav->appendChild($this->processSearchRequest($request));
}
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Import Ldap Users'),
'device' => true,
- 'dust' => true,
));
}
private function processImportRequest($request) {
$admin = $request->getUser();
$usernames = $request->getArr('usernames');
$emails = $request->getArr('email');
$names = $request->getArr('name');
$notice_view = new AphrontErrorView();
$notice_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice_view->setTitle(pht("Import Successful"));
$notice_view->setErrors(array(
pht("Successfully imported users from LDAP"),
));
$list = new PhabricatorObjectItemListView();
$list->setNoDataString(pht("No users imported?"));
foreach ($usernames as $username) {
$user = new PhabricatorUser();
$user->setUsername($username);
$user->setRealname($names[$username]);
$email_obj = id(new PhabricatorUserEmail())
->setAddress($emails[$username])
->setIsVerified(1);
try {
id(new PhabricatorUserEditor())
->setActor($admin)
->createNewUser($user, $email_obj);
id(new PhabricatorExternalAccount())
->setUserPHID($user->getPHID())
->setAccountType('ldap')
->setAccountDomain('self')
->setAccountID($username)
->save();
$header = pht('Successfully added %s', $username);
$attribute = null;
$color = 'green';
} catch (Exception $ex) {
$header = pht('Failed to add %s', $username);
$attribute = $ex->getMessage();
$color = 'red';
}
$item = id(new PhabricatorObjectItemView())
->setHeader($header)
->addAttribute($attribute)
->setBarColor($color);
$list->addItem($item);
}
return array(
$notice_view,
$list,
);
}
private function processSearchRequest($request) {
$panel = new AphrontPanelView();
$admin = $request->getUser();
$search = $request->getStr('query');
$ldap_provider = PhabricatorAuthProviderLDAP::getLDAPProvider();
if (!$ldap_provider) {
throw new Exception("No LDAP provider enabled!");
}
$ldap_adapter = $ldap_provider->getAdapter();
$ldap_adapter->setLoginUsername($request->getStr('username'));
$ldap_adapter->setLoginPassword(
new PhutilOpaqueEnvelope($request->getStr('password')));
// This causes us to connect and bind.
// TODO: Clean up this discard mode stuff.
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
$ldap_adapter->getAccountID();
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
$results = $ldap_adapter->searchLDAP('%Q', $search);
foreach ($results as $key => $record) {
$account_id = $ldap_adapter->readLDAPRecordAccountID($record);
if (!$account_id) {
unset($results[$key]);
continue;
}
$info = array(
$account_id,
$ldap_adapter->readLDAPRecordEmail($record),
$ldap_adapter->readLDAPRecordRealName($record),
);
$results[$key] = $info;
$results[$key][] = $this->renderUserInputs($info);
}
$form = id(new AphrontFormView())
->setUser($admin);
$table = new AphrontTableView($results);
$table->setHeaders(
array(
pht('Username'),
pht('Email'),
pht('Real Name'),
pht('Import?'),
));
$form->appendChild($table);
$form->setAction($request->getRequestURI()
->alter('import', 'true')->alter('search', null))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Import')));
$panel->appendChild($form);
return $panel;
}
private function renderUserInputs($user) {
$username = $user[0];
return hsprintf(
'%s%s%s',
phutil_tag(
'input',
array(
'type' => 'checkbox',
'name' => 'usernames[]',
'value' => $username,
)),
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => "email[$username]",
'value' => $user[1],
)),
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => "name[$username]",
'value' => $user[2],
)));
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php
index 432fbd5b44..97dd6bfb35 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php
@@ -1,131 +1,130 @@
<?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))
->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 PhabricatorHeaderView())
->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);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($user->getUsername()));
$feed = $this->renderUserFeed($user);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$feed,
),
array(
'title' => $user->getUsername(),
'device' => true,
- 'dust' => true,
));
}
private function buildPropertyView(PhabricatorUser $user) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($user);
$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>',
$view->render());
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php
index cf75625047..30efa91078 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php
@@ -1,88 +1,87 @@
<?php
final class PhabricatorPeopleProfileEditController
extends PhabricatorPeopleController {
private $id;
public function shouldRequireAdmin() {
return false;
}
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$profile_uri = '/p/'.$user->getUsername().'/';
$field_list = PhabricatorCustomField::getObjectFields(
$user,
PhabricatorCustomField::ROLE_EDIT);
$field_list->readFieldsFromStorage($user);
if ($request->isFormPost()) {
$xactions = $field_list->buildFieldTransactionsFromRequest(
new PhabricatorUserTransaction(),
$request);
$editor = id(new PhabricatorUserProfileEditor())
->setActor($viewer)
->setContentSource(
PhabricatorContentSource::newFromRequest($request))
->setContinueOnNoEffect(true);
$editor->applyTransactions($user, $xactions);
return id(new AphrontRedirectResponse())->setURI($profile_uri);
}
$title = pht('Edit Profile');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($user->getUsername())
->setHref($profile_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
$form = id(new AphrontFormView())
->setUser($viewer);
$field_list->appendFieldsToForm($form);
$form
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($profile_uri)
->setValue(pht('Save Profile')));
return $this->buildApplicationPage(
array(
$crumbs,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
index bd899961d2..0631e5fe13 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php
@@ -1,300 +1,299 @@
<?php
final class PhabricatorPeopleProfilePictureController
extends PhabricatorPeopleController {
private $id;
public function shouldRequireAdmin() {
return false;
}
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$profile_uri = '/p/'.$user->getUsername().'/';
$supported_formats = PhabricatorFile::getTransformableImageFormats();
$e_file = true;
$errors = array();
if ($request->isFormPost()) {
$phid = $request->getStr('phid');
$is_default = false;
if ($phid == PhabricatorPHIDConstants::PHID_VOID) {
$phid = null;
$is_default = true;
} else if ($phid) {
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($phid))
->executeOne();
} else {
if ($request->getFileExists('picture')) {
$file = PhabricatorFile::newFromPHPUpload(
$_FILES['picture'],
array(
'authorPHID' => $viewer->getPHID(),
));
} else {
$e_file = pht('Required');
$errors[] = pht(
'You must choose a file when uploading a new profile picture.');
}
}
if (!$errors && !$is_default) {
if (!$file->isTransformableImage()) {
$e_file = pht('Not Supported');
$errors[] = pht(
'This server only supports these image formats: %s.',
implode(', ', $supported_formats));
} else {
$xformer = new PhabricatorImageTransformer();
$xformed = $xformer->executeProfileTransform(
$file,
$width = 50,
$min_height = 50,
$max_height = 50);
}
}
if (!$errors) {
if ($is_default) {
$user->setProfileImagePHID(null);
} else {
$user->setProfileImagePHID($xformed->getPHID());
}
$user->save();
return id(new AphrontRedirectResponse())->setURI($profile_uri);
}
}
$title = pht('Edit Profile Picture');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($user->getUsername())
->setHref($profile_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
$form = id(new AphrontFormLayoutView())
->setUser($viewer);
$default_image = PhabricatorFile::loadBuiltin($viewer, 'profile.png');
$images = array();
$current = $user->getProfileImagePHID();
$has_current = false;
if ($current) {
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($current))
->execute();
if ($files) {
$file = head($files);
if ($file->isTransformableImage()) {
$has_current = true;
$images[$current] = array(
'uri' => $file->getBestURI(),
'tip' => pht('Current Picture'),
);
}
}
}
// Try to add external account images for any associated external accounts.
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($user->getPHID()))
->needImages(true)
->execute();
foreach ($accounts as $account) {
$file = $account->getProfileImageFile();
if ($account->getProfileImagePHID() != $file->getPHID()) {
// This is a default image, just skip it.
continue;
}
$provider = PhabricatorAuthProvider::getEnabledProviderByKey(
$account->getProviderKey());
if ($provider) {
$tip = pht('Picture From %s', $provider->getProviderName());
} else {
$tip = pht('Picture From External Account');
}
if ($file->isTransformableImage()) {
$images[$file->getPHID()] = array(
'uri' => $file->getBestURI(),
'tip' => $tip,
);
}
}
// Try to add Gravatar images for any email addresses associated with the
// account.
if (PhabricatorEnv::getEnvConfig('security.allow-outbound-http')) {
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
'userPHID = %s ORDER BY address',
$viewer->getPHID());
$futures = array();
foreach ($emails as $email_object) {
$email = $email_object->getAddress();
$hash = md5(strtolower(trim($email)));
$uri = id(new PhutilURI("https://secure.gravatar.com/avatar/{$hash}"))
->setQueryParams(
array(
'size' => 200,
'default' => '404',
'rating' => 'x',
));
$futures[$email] = new HTTPSFuture($uri);
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
foreach (Futures($futures) as $email => $future) {
try {
list($body) = $future->resolvex();
$file = PhabricatorFile::newFromFileData(
$body,
array(
'name' => 'profile-gravatar',
'ttl' => (60 * 60 * 4),
));
if ($file->isTransformableImage()) {
$images[$file->getPHID()] = array(
'uri' => $file->getBestURI(),
'tip' => pht('Gravatar for %s', $email),
);
}
} catch (Exception $ex) {
// Just continue.
}
}
unset($unguarded);
}
$images[PhabricatorPHIDConstants::PHID_VOID] = array(
'uri' => $default_image->getBestURI(),
'tip' => pht('Default Picture'),
);
require_celerity_resource('people-profile-css');
Javelin::initBehavior('phabricator-tooltips', array());
$buttons = array();
foreach ($images as $phid => $spec) {
$button = javelin_tag(
'button',
array(
'class' => 'grey profile-image-button',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $spec['tip'],
'size' => 300,
),
),
phutil_tag(
'img',
array(
'height' => 50,
'width' => 50,
'src' => $spec['uri'],
)));
$button = array(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'phid',
'value' => $phid,
)),
$button);
$button = phabricator_form(
$viewer,
array(
'class' => 'profile-image-form',
'method' => 'POST',
),
$button);
$buttons[] = $button;
}
if ($has_current) {
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Current Picture'))
->setValue(array_shift($buttons)));
}
$form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Use Picture'))
->setValue($buttons));
$upload_head = id(new PhabricatorHeaderView())
->setHeader(pht('Upload New Picture'));
$upload_form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->setEncType('multipart/form-data')
->appendChild(
id(new AphrontFormFileControl())
->setName('picture')
->setLabel(pht('Upload Picture'))
->setError($e_file)
->setCaption(
pht('Supported formats: %s', implode(', ', $supported_formats))))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($profile_uri)
->setValue(pht('Upload Picture')));
if ($errors) {
$errors = id(new AphrontErrorView())->setErrors($errors);
}
return $this->buildApplicationPage(
array(
$crumbs,
$errors,
$form,
$upload_head,
$upload_form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phame/controller/blog/PhameBlogEditController.php b/src/applications/phame/controller/blog/PhameBlogEditController.php
index fa23ff1c42..133632b458 100644
--- a/src/applications/phame/controller/blog/PhameBlogEditController.php
+++ b/src/applications/phame/controller/blog/PhameBlogEditController.php
@@ -1,197 +1,196 @@
<?php
/**
* @group phame
*/
final class PhameBlogEditController
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) {
$blog = id(new PhameBlogQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_EDIT
))
->executeOne();
if (!$blog) {
return new Aphront404Response();
}
$submit_button = pht('Save Changes');
$page_title = pht('Edit Blog');
$cancel_uri = $this->getApplicationURI('blog/view/'.$blog->getID().'/');
} else {
$blog = id(new PhameBlog())
->setCreatorPHID($user->getPHID());
$blog->setViewPolicy(PhabricatorPolicies::POLICY_USER);
$blog->setEditPolicy(PhabricatorPolicies::POLICY_USER);
$blog->setJoinPolicy(PhabricatorPolicies::POLICY_USER);
$submit_button = pht('Create Blog');
$page_title = pht('Create Blog');
$cancel_uri = $this->getApplicationURI();
}
$e_name = true;
$e_custom_domain = null;
$errors = array();
if ($request->isFormPost()) {
$name = $request->getStr('name');
$description = $request->getStr('description');
$custom_domain = $request->getStr('custom_domain');
$skin = $request->getStr('skin');
if (empty($name)) {
$errors[] = pht('You must give the blog a name.');
$e_name = pht('Required');
} else {
$e_name = null;
}
$blog->setName($name);
$blog->setDescription($description);
$blog->setDomain(nonempty($custom_domain, null));
$blog->setSkin($skin);
if (!empty($custom_domain)) {
$error = $blog->validateCustomDomain($custom_domain);
if ($error) {
$errors[] = $error;
$e_custom_domain = pht('Invalid');
}
}
$blog->setViewPolicy($request->getStr('can_view'));
$blog->setEditPolicy($request->getStr('can_edit'));
$blog->setJoinPolicy($request->getStr('can_join'));
// Don't let users remove their ability to edit blogs.
PhabricatorPolicyFilter::mustRetainCapability(
$user,
$blog,
PhabricatorPolicyCapability::CAN_EDIT);
if (!$errors) {
try {
$blog->save();
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI('blog/view/'.$blog->getID().'/'));
} catch (AphrontQueryDuplicateKeyException $ex) {
$errors[] = pht('Domain must be unique.');
$e_custom_domain = pht('Not Unique');
}
}
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($blog)
->execute();
$skins = PhameSkinSpecification::loadAllSkinSpecifications();
$skins = mpull($skins, 'getName');
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($blog->getName())
->setID('blog-name')
->setError($e_name))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Description'))
->setName('description')
->setValue($blog->getDescription())
->setID('blog-description')
->setUser($user)
->setDisableMacros(true))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($blog)
->setPolicies($policies)
->setName('can_view'))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($blog)
->setPolicies($policies)
->setName('can_edit'))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setCapability(PhabricatorPolicyCapability::CAN_JOIN)
->setPolicyObject($blog)
->setPolicies($policies)
->setName('can_join'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Custom Domain'))
->setName('custom_domain')
->setValue($blog->getDomain())
->setCaption(
pht('Must include at least one dot (.), e.g. blog.example.com'))
->setError($e_custom_domain))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Skin'))
->setName('skin')
->setValue($blog->getSkin())
->setOptions($skins))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($submit_button));
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
} else {
$error_view = null;
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($page_title)
->setHref($this->getApplicationURI('blog/new')));
$nav = $this->renderSideNavFilterView();
$nav->selectFilter($this->id ? null : 'blog/new');
$nav->appendChild(
array(
$crumbs,
$error_view,
$form,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $page_title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phame/controller/blog/PhameBlogListController.php b/src/applications/phame/controller/blog/PhameBlogListController.php
index 4a1f5b7da8..0508b04382 100644
--- a/src/applications/phame/controller/blog/PhameBlogListController.php
+++ b/src/applications/phame/controller/blog/PhameBlogListController.php
@@ -1,92 +1,91 @@
<?php
/**
* @group phame
*/
final class PhameBlogListController extends PhameController {
private $filter;
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$nav = $this->renderSideNavFilterView(null);
$filter = $nav->selectFilter('blog/'.$this->filter, 'blog/user');
$query = id(new PhameBlogQuery())
->setViewer($user);
switch ($filter) {
case 'blog/all':
$title = pht('All Blogs');
$nodata = pht('No blogs have been created.');
break;
case 'blog/user':
$title = pht('Joinable Blogs');
$nodata = pht('There are no blogs you can contribute to.');
$query->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_JOIN,
));
break;
default:
throw new Exception("Unknown filter '{$filter}'!");
}
$pager = id(new AphrontPagerView())
->setURI($request->getRequestURI(), 'offset')
->setOffset($request->getInt('offset'));
$blogs = $query->executeWithOffsetPager($pager);
$blog_list = $this->renderBlogList($blogs, $user, $nodata);
$blog_list->setPager($pager);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($this->getApplicationURI()));
$nav->appendChild(
array(
$crumbs,
$blog_list,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function renderBlogList(
array $blogs,
PhabricatorUser $user,
$nodata) {
$view = new PhabricatorObjectItemListView();
$view->setNoDataString($nodata);
$view->setUser($user);
foreach ($blogs as $blog) {
$item = id(new PhabricatorObjectItemView())
->setHeader($blog->getName())
->setHref($this->getApplicationURI('blog/view/'.$blog->getID().'/'))
->setObject($blog);
$view->addItem($item);
}
return $view;
}
}
diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php
index 8d6342e23b..6b2c4f0360 100644
--- a/src/applications/phame/controller/blog/PhameBlogViewController.php
+++ b/src/applications/phame/controller/blog/PhameBlogViewController.php
@@ -1,197 +1,196 @@
<?php
/**
* @group phame
*/
final class PhameBlogViewController extends PhameController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$blog = id(new PhameBlogQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$blog) {
return new Aphront404Response();
}
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request);
$posts = id(new PhamePostQuery())
->setViewer($user)
->withBlogPHIDs(array($blog->getPHID()))
->executeWithCursorPager($pager);
$nav = $this->renderSideNavFilterView(null);
$header = id(new PhabricatorHeaderView())
->setHeader($blog->getName());
$handle_phids = array_merge(
mpull($posts, 'getBloggerPHID'),
mpull($posts, 'getBlogPHID'));
$this->loadHandles($handle_phids);
$actions = $this->renderActions($blog, $user);
$properties = $this->renderProperties($blog, $user);
$post_list = $this->renderPostList(
$posts,
$user,
pht('This blog has no visible posts.'));
require_celerity_resource('phame-css');
$post_list = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE)
->addClass('phame-post-list')
->appendChild($post_list);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($blog->getName())
->setHref($this->getApplicationURI()));
$nav->appendChild(
array(
$crumbs,
$header,
$actions,
$properties,
$post_list,
));
return $this->buildApplicationPage(
$nav,
array(
'device' => true,
'title' => $blog->getName(),
- 'dust' => true,
));
}
private function renderProperties(PhameBlog $blog, PhabricatorUser $user) {
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips');
$properties = new PhabricatorPropertyListView();
$properties->addProperty(
pht('Skin'),
$blog->getSkin());
$properties->addProperty(
pht('Domain'),
$blog->getDomain());
$feed_uri = PhabricatorEnv::getProductionURI(
$this->getApplicationURI('blog/feed/'.$blog->getID().'/'));
$properties->addProperty(
pht('Atom URI'),
javelin_tag('a',
array(
'href' => $feed_uri,
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => pht('Atom URI does not support custom domains.'),
'size' => 320,
)
),
$feed_uri));
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$blog);
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$properties->addProperty(
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
$properties->addProperty(
pht('Joinable By'),
$descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user)
->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)
->process();
$properties->addTextContent(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)));
return $properties;
}
private function renderActions(PhameBlog $blog, PhabricatorUser $user) {
$actions = id(new PhabricatorActionListView())
->setObject($blog)
->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$blog,
PhabricatorPolicyCapability::CAN_EDIT);
$can_join = PhabricatorPolicyFilter::hasCapability(
$user,
$blog,
PhabricatorPolicyCapability::CAN_JOIN);
$must_use_form = $blog->getDomain();
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('new')
->setHref($this->getApplicationURI('post/edit/?blog='.$blog->getID()))
->setName(pht('Write Post'))
->setDisabled(!$can_join)
->setWorkflow(!$can_join));
$actions->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setIcon('world')
->setHref($this->getApplicationURI('live/'.$blog->getID().'/'))
->setRenderAsForm($must_use_form)
->setName(pht('View Live')));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/'))
->setName('Edit Blog')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('delete')
->setHref($this->getApplicationURI('blog/delete/'.$blog->getID().'/'))
->setName('Delete Blog')
->setDisabled(!$can_edit)
->setWorkflow(true));
return $actions;
}
}
diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php
index e72a949834..835b630ed0 100644
--- a/src/applications/phame/controller/post/PhamePostEditController.php
+++ b/src/applications/phame/controller/post/PhamePostEditController.php
@@ -1,201 +1,200 @@
<?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 = PhabricatorObjectHandleData::loadOneHandle(
$post->getBlogPHID(),
$user);
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->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>');
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;
}
$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,
$error_view,
$form,
$preview_panel,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $page_title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phame/controller/post/PhamePostListController.php b/src/applications/phame/controller/post/PhamePostListController.php
index 8009333f7a..5153c1dd33 100644
--- a/src/applications/phame/controller/post/PhamePostListController.php
+++ b/src/applications/phame/controller/post/PhamePostListController.php
@@ -1,102 +1,101 @@
<?php
/**
* @group phame
*/
final class PhamePostListController extends PhameController {
private $bloggername;
private $filter;
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter', 'blogger');
$this->bloggername = idx($data, 'bloggername');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$query = id(new PhamePostQuery())
->setViewer($user);
$nav = $this->renderSideNavFilterView();
switch ($this->filter) {
case 'draft':
$query->withBloggerPHIDs(array($user->getPHID()));
$query->withVisibility(PhamePost::VISIBILITY_DRAFT);
$nodata = pht('You have no unpublished drafts.');
$title = pht('Unpublished Drafts');
$nav->selectFilter('post/draft');
break;
case 'blogger':
if ($this->bloggername) {
$blogger = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$this->bloggername);
if (!$blogger) {
return new Aphront404Response();
}
} else {
$blogger = $user;
}
$query->withBloggerPHIDs(array($blogger->getPHID()));
if ($blogger->getPHID() == $user->getPHID()) {
$nav->selectFilter('post');
$nodata = pht('You have not written any posts.');
} else {
$nodata = pht('%s has not written any posts.', $blogger);
}
$title = pht('Posts By %s', $blogger);
break;
case 'all':
$nodata = pht('There are no visible posts.');
$title = pht('Posts');
$nav->selectFilter('post/all');
break;
default:
throw new Exception("Unknown filter '{$this->filter}'!");
}
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request);
$posts = $query->executeWithCursorPager($pager);
$handle_phids = array_merge(
mpull($posts, 'getBloggerPHID'),
mpull($posts, 'getBlogPHID'));
$this->loadHandles($handle_phids);
require_celerity_resource('phame-css');
$post_list = $this->renderPostList($posts, $user, $nodata);
$post_list = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE)
->addClass('phame-post-list')
->appendChild($post_list);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($this->getApplicationURI()));
$nav->appendChild(
array(
$crumbs,
$post_list,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phame/controller/post/PhamePostNewController.php b/src/applications/phame/controller/post/PhamePostNewController.php
index 407d52a8a2..e335f36190 100644
--- a/src/applications/phame/controller/post/PhamePostNewController.php
+++ b/src/applications/phame/controller/post/PhamePostNewController.php
@@ -1,131 +1,130 @@
<?php
/**
* @group phame
*/
final class PhamePostNewController extends PhameController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$post = null;
$view_uri = null;
if ($this->id) {
$post = id(new PhamePostQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$post) {
return new Aphront404Response();
}
$view_uri = '/post/view/'.$post->getID().'/';
$view_uri = $this->getApplicationURI($view_uri);
if ($request->isFormPost()) {
$blog = id(new PhameBlogQuery())
->setViewer($user)
->withIDs(array($request->getInt('blog')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_JOIN,
))
->executeOne();
if ($blog) {
$post->setBlogPHID($blog->getPHID());
$post->save();
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
}
$title = pht('Move Post');
} else {
$title = pht('Create Post');
$view_uri = $this->getApplicationURI('/post/new');
}
$blogs = id(new PhameBlogQuery())
->setViewer($user)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_JOIN,
))
->execute();
$nav = $this->renderSideNavFilterView();
$nav->selectFilter('post/new');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($view_uri));
$nav->appendChild($crumbs);
if (!$blogs) {
$notification = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NODATA)
->appendChild(
pht('You do not have permission to join any blogs. Create a blog '.
'first, then you can post to it.'));
$nav->appendChild($notification);
} else {
$options = mpull($blogs, 'getName', 'getID');
asort($options);
$selected_value = null;
if ($post && $post->getBlog()) {
$selected_value = $post->getBlog()->getID();
}
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Blog'))
->setName('blog')
->setOptions($options)
->setValue($selected_value));
if ($post) {
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Move Post'))
->addCancelButton($view_uri));
} else {
$form
->setAction($this->getApplicationURI('post/edit/'))
->setMethod('GET')
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Continue')));
}
$nav->appendChild($form);
}
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php
index 6adf240b91..bf61f75d08 100644
--- a/src/applications/phame/controller/post/PhamePostViewController.php
+++ b/src/applications/phame/controller/post/PhamePostViewController.php
@@ -1,212 +1,211 @@
<?php
/**
* @group phame
*/
final class PhamePostViewController extends PhameController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$post = id(new PhamePostQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$post) {
return new Aphront404Response();
}
$nav = $this->renderSideNavFilterView();
$this->loadHandles(
array(
$post->getBlogPHID(),
$post->getBloggerPHID(),
));
$actions = $this->renderActions($post, $user);
$properties = $this->renderProperties($post, $user);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($post->getTitle())
->setHref($this->getApplicationURI('post/view/'.$post->getID().'/')));
$nav->appendChild($crumbs);
$nav->appendChild(
id(new PhabricatorHeaderView())
->setHeader($post->getTitle()));
if ($post->isDraft()) {
$nav->appendChild(
id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Draft Post'))
->appendChild(
pht('Only you can see this draft until you publish it. '.
'Use "Preview / Publish" to publish this post.')));
}
if (!$post->getBlog()) {
$nav->appendChild(
id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle(pht('Not On A Blog'))
->appendChild(
pht('This post is not associated with a blog (the blog may have '.
'been deleted). Use "Move Post" to move it to a new blog.')));
}
$nav->appendChild(
array(
$actions,
$properties,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $post->getTitle(),
'device' => true,
- 'dust' => true,
));
}
private function renderActions(
PhamePost $post,
PhabricatorUser $user) {
$actions = id(new PhabricatorActionListView())
->setObject($post)
->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$post,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $post->getID();
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setHref($this->getApplicationURI('post/edit/'.$id.'/'))
->setName(pht('Edit Post'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('move')
->setHref($this->getApplicationURI('post/move/'.$id.'/'))
->setName(pht('Move Post'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($post->isDraft()) {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('preview')
->setHref($this->getApplicationURI('post/publish/'.$id.'/'))
->setName(pht('Preview / Publish')));
} else {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('unpublish')
->setHref($this->getApplicationURI('post/unpublish/'.$id.'/'))
->setName(pht('Unpublish'))
->setWorkflow(true));
}
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('delete')
->setHref($this->getApplicationURI('post/delete/'.$id.'/'))
->setName(pht('Delete Post'))
->setDisabled(!$can_edit)
->setWorkflow(true));
$blog = $post->getBlog();
$can_view_live = $blog && !$post->isDraft();
$must_use_form = $blog && $blog->getDomain();
if ($can_view_live) {
$live_uri = 'live/'.$blog->getID().'/post/'.$post->getPhameTitle();
} else {
$live_uri = 'post/notlive/'.$post->getID().'/';
}
$live_uri = $this->getApplicationURI($live_uri);
$actions->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setIcon('world')
->setHref($live_uri)
->setName(pht('View Live'))
->setRenderAsForm($must_use_form)
->setDisabled(!$can_view_live)
->setWorkflow(!$can_view_live));
return $actions;
}
private function renderProperties(
PhamePost $post,
PhabricatorUser $user) {
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($post);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$post);
$properties->addProperty(
pht('Blog'),
$post->getBlogPHID()
? $this->getHandle($post->getBlogPHID())->renderLink()
: null);
$properties->addProperty(
pht('Blogger'),
$this->getHandle($post->getBloggerPHID())->renderLink());
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$properties->addProperty(
pht('Published'),
$post->isDraft()
? pht('Draft')
: phabricator_datetime($post->getDatePublished(), $user));
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user)
->addObject($post, PhamePost::MARKUP_FIELD_BODY)
->process();
$properties->invokeWillRenderEvent();
$properties->addTextContent(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY)));
return $properties;
}
}
diff --git a/src/applications/phlux/controller/PhluxEditController.php b/src/applications/phlux/controller/PhluxEditController.php
index 93f5afa7bc..38ab9da6ff 100644
--- a/src/applications/phlux/controller/PhluxEditController.php
+++ b/src/applications/phlux/controller/PhluxEditController.php
@@ -1,196 +1,195 @@
<?php
final class PhluxEditController extends PhluxController {
private $key;
public function willProcessRequest(array $data) {
$this->key = idx($data, 'key');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$is_new = ($this->key === null);
if ($is_new) {
$var = new PhluxVariable();
$var->setViewPolicy(PhabricatorPolicies::POLICY_USER);
$var->setEditPolicy(PhabricatorPolicies::POLICY_USER);
} else {
$var = id(new PhluxVariableQuery())
->setViewer($user)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withKeys(array($this->key))
->executeOne();
if (!$var) {
return new Aphront404Response();
}
$view_uri = $this->getApplicationURI('/view/'.$this->key.'/');
}
$e_key = ($is_new ? true : null);
$e_value = true;
$errors = array();
$key = $var->getVariableKey();
$display_value = null;
$value = $var->getVariableValue();
if ($request->isFormPost()) {
if ($is_new) {
$key = $request->getStr('key');
if (!strlen($key)) {
$errors[] = pht('Variable key is required.');
$e_key = pht('Required');
} else if (!preg_match('/^[a-z0-9.-]+$/', $key)) {
$errors[] = pht(
'Variable key "%s" must contain only lowercase letters, digits, '.
'period, and hyphen.',
$key);
$e_key = pht('Invalid');
}
}
$raw_value = $request->getStr('value');
$value = json_decode($raw_value, true);
if ($value === null && strtolower($raw_value) !== 'null') {
$e_value = pht('Invalid');
$errors[] = pht('Variable value must be valid JSON.');
$display_value = $raw_value;
}
if (!$errors) {
$editor = id(new PhluxVariableEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
$xactions = array();
$xactions[] = id(new PhluxTransaction())
->setTransactionType(PhluxTransaction::TYPE_EDIT_KEY)
->setNewValue($key);
$xactions[] = id(new PhluxTransaction())
->setTransactionType(PhluxTransaction::TYPE_EDIT_VALUE)
->setNewValue($value);
$xactions[] = id(new PhluxTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($request->getStr('viewPolicy'));
$xactions[] = id(new PhluxTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($request->getStr('editPolicy'));
try {
$editor->applyTransactions($var, $xactions);
$view_uri = $this->getApplicationURI('/view/'.$key.'/');
return id(new AphrontRedirectResponse())->setURI($view_uri);
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_key = pht('Not Unique');
$errors[] = pht('Variable key must be unique.');
}
}
}
if ($display_value === null) {
if (is_array($value) &&
(array_keys($value) !== array_keys(array_values($value)))) {
$json = new PhutilJSON();
$display_value = $json->encodeFormatted($value);
} else {
$display_value = json_encode($value);
}
}
if ($errors) {
$errors = id(new AphrontErrorView())
->setErrors($errors);
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($var)
->execute();
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setValue($var->getVariableKey())
->setLabel(pht('Key'))
->setName('key')
->setError($e_key)
->setCaption(pht('Lowercase letters, digits, dot and hyphen only.'))
->setDisabled(!$is_new))
->appendChild(
id(new AphrontFormTextAreaControl())
->setValue($display_value)
->setLabel(pht('Value'))
->setName('value')
->setCaption(pht('Enter value as JSON.'))
->setError($e_value))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($var)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($var)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies));
if ($is_new) {
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Create Variable')));
} else {
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Update Variable'))
->addCancelButton($view_uri));
}
$crumbs = $this->buildApplicationCrumbs();
if ($is_new) {
$title = pht('Create Variable');
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($request->getRequestURI()));
} else {
$title = pht('Edit %s', $this->key);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($request->getRequestURI()));
}
$header = id(new PhabricatorHeaderView())
->setHeader($title);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$errors,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phlux/controller/PhluxListController.php b/src/applications/phlux/controller/PhluxListController.php
index 949d79b86a..f019f65ede 100644
--- a/src/applications/phlux/controller/PhluxListController.php
+++ b/src/applications/phlux/controller/PhluxListController.php
@@ -1,52 +1,51 @@
<?php
final class PhluxListController extends PhluxController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$pager = new AphrontCursorPagerView();
$pager->readFromRequest($request);
$query = id(new PhluxVariableQuery())
->setViewer($user);
$vars = $query->executeWithCursorPager($pager);
$view = new PhabricatorObjectItemListView();
foreach ($vars as $var) {
$key = $var->getVariableKey();
$item = new PhabricatorObjectItemView();
$item->setHeader($key);
$item->setHref($this->getApplicationURI('/view/'.$key.'/'));
$item->addIcon(
'none',
phabricator_datetime($var->getDateModified(), $user));
$view->addItem($item);
}
$crumbs = $this->buildApplicationCrumbs();
$title = pht('Variable List');
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($this->getApplicationURI()));
return $this->buildApplicationPage(
array(
$crumbs,
$view,
$pager,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phlux/controller/PhluxViewController.php b/src/applications/phlux/controller/PhluxViewController.php
index 845e7a18f0..3d130f2b7e 100644
--- a/src/applications/phlux/controller/PhluxViewController.php
+++ b/src/applications/phlux/controller/PhluxViewController.php
@@ -1,101 +1,100 @@
<?php
final class PhluxViewController extends PhluxController {
private $key;
public function willProcessRequest(array $data) {
$this->key = $data['key'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$var = id(new PhluxVariableQuery())
->setViewer($user)
->withKeys(array($this->key))
->executeOne();
if (!$var) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs();
$title = $var->getVariableKey();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($request->getRequestURI()));
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($request->getRequestURI())
->setObject($var);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$var,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Variable'))
->setHref($this->getApplicationURI('/edit/'.$var->getVariableKey().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$display_value = json_encode($var->getVariableValue());
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$var);
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($var)
->addProperty(pht('Value'), $display_value)
->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW])
->addProperty(
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
$xactions = id(new PhluxTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($var->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setObjectPHID($var->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$xaction_view,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phortune/controller/PhortuneAccountBuyController.php b/src/applications/phortune/controller/PhortuneAccountBuyController.php
index 06f6ac04ee..3937edbb0e 100644
--- a/src/applications/phortune/controller/PhortuneAccountBuyController.php
+++ b/src/applications/phortune/controller/PhortuneAccountBuyController.php
@@ -1,186 +1,185 @@
<?php
final class PhortuneAccountBuyController
extends PhortuneController {
private $accountID;
private $id;
public function willProcessRequest(array $data) {
$this->accountID = $data['accountID'];
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$account = id(new PhortuneAccountQuery())
->setViewer($user)
->withIDs(array($this->accountID))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$account_uri = $this->getApplicationURI($account->getID().'/');
$product = id(new PhortuneProductQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$product) {
return new Aphront404Response();
}
$purchase = new PhortunePurchase();
$purchase->setProductPHID($product->getPHID());
$purchase->setAccountPHID($account->getPHID());
$purchase->setPurchaseName($product->getProductName());
$purchase->setBasePriceInCents($product->getPriceInCents());
$purchase->setQuantity(1);
$purchase->setTotalPriceInCents(
$purchase->getBasePriceInCents() * $purchase->getQuantity());
$purchase->setStatus(PhortunePurchase::STATUS_PENDING);
$cart = new PhortuneCart();
$cart->setAccountPHID($account->getPHID());
$cart->setOwnerPHID($user->getPHID());
$cart->attachPurchases(
array(
$purchase,
));
$rows = array();
$total = 0;
foreach ($cart->getPurchases() as $purchase) {
$rows[] = array(
$purchase->getPurchaseName(),
PhortuneCurrency::newFromUSDCents($purchase->getBasePriceInCents())
->formatForDisplay(),
$purchase->getQuantity(),
PhortuneCurrency::newFromUSDCents($purchase->getTotalPriceInCents())
->formatForDisplay(),
);
$total += $purchase->getTotalPriceInCents();
}
$rows[] = array(
phutil_tag('strong', array(), pht('Total')),
'',
'',
phutil_tag('strong', array(),
PhortuneCurrency::newFromUSDCents($total)->formatForDisplay()),
);
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Item'),
pht('Price'),
pht('Qty.'),
pht('Total'),
));
$panel = new AphrontPanelView();
$panel->setNoBackground(true);
$panel->appendChild($table);
$title = pht('Buy Stuff');
$methods = id(new PhortunePaymentMethodQuery())
->setViewer($user)
->withAccountPHIDs(array($account->getPHID()))
->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN)
->execute();
$method_control = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Payment Method'));
if (!$methods) {
$method_control = id(new AphrontFormStaticControl())
->setLabel(pht('Payment Method'))
->setValue(
phutil_tag('em', array(), pht('No payment methods configured.')));
} else {
$method_control = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Payment Method'))
->setName('paymentMethodID')
->setValue($request->getInt('paymentMethodID'));
foreach ($methods as $method) {
$method_control->addButton(
$method->getID(),
$method->getBrand().' / '.$method->getLastFourDigits(),
$method->getDescription());
}
}
$payment_method_uri = $this->getApplicationURI(
$account->getID().'/paymentmethod/edit/');
$form = id(new AphrontFormView())
->setUser($user)
->appendChild($method_control);
$add_providers = PhortunePaymentProvider::getProvidersForAddPaymentMethod();
if ($add_providers) {
$new_method = phutil_tag(
'a',
array(
'class' => 'button grey',
'href' => $payment_method_uri,
'sigil' => 'workflow',
),
pht('Add New Payment Method'));
$form->appendChild(
id(new AphrontFormMarkupControl())
->setValue($new_method));
}
if ($methods || $add_providers) {
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht("Submit Payment"))
->setDisabled(!$methods));
}
$provider_form = null;
$pay_providers = PhortunePaymentProvider::getProvidersForOneTimePayment();
if ($pay_providers) {
$one_time_options = array();
foreach ($pay_providers as $provider) {
$one_time_options[] = $provider->renderOneTimePaymentButton(
$account,
$cart,
$user);
}
$provider_form = id(new AphrontFormLayoutView())
->setPadded(true)
->setBackgroundShading(true);
$provider_form->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Pay With')
->setValue($one_time_options));
}
return $this->buildApplicationPage(
array(
$panel,
$form,
phutil_tag('br', array()),
$provider_form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php
index de2d6a50cd..b43862425d 100644
--- a/src/applications/phortune/controller/PhortuneAccountViewController.php
+++ b/src/applications/phortune/controller/PhortuneAccountViewController.php
@@ -1,181 +1,180 @@
<?php
final class PhortuneAccountViewController extends PhortuneController {
private $accountID;
public function willProcessRequest(array $data) {
$this->accountID = $data['accountID'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$account = id(new PhortuneAccountQuery())
->setViewer($user)
->withIDs(array($this->accountID))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Account'))
->setHref($request->getRequestURI()));
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($request->getRequestURI())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Account'))
->setIcon('edit')
->setHref('#')
->setDisabled(true))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Members'))
->setIcon('transcript')
->setHref('#')
->setDisabled(true));
$crumbs->setActionList($actions);
$properties = id(new PhabricatorPropertyListView())
->setObject($account)
->setUser($user);
$properties->addProperty(pht('Balance'), $account->getBalanceInCents());
$payment_methods = $this->buildPaymentMethodsSection($account);
$purchase_history = $this->buildPurchaseHistorySection($account);
$account_history = $this->buildAccountHistorySection($account);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$payment_methods,
$purchase_history,
$account_history,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function buildPaymentMethodsSection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Payment Methods'));
$id = $account->getID();
$add_uri = $this->getApplicationURI($id.'/paymentmethod/edit/');
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($request->getRequestURI())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Add Payment Method'))
->setIcon('new')
->setHref($add_uri));
$list = id(new PhabricatorObjectItemListView())
->setUser($user)
->setNoDataString(
pht('No payment methods associated with this account.'));
$methods = id(new PhortunePaymentMethodQuery())
->setViewer($user)
->withAccountPHIDs(array($account->getPHID()))
->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN)
->execute();
if ($methods) {
$this->loadHandles(mpull($methods, 'getAuthorPHID'));
}
foreach ($methods as $method) {
$item = new PhabricatorObjectItemView();
$item->setHeader($method->getBrand().' / '.$method->getLastFourDigits());
switch ($method->getStatus()) {
case PhortunePaymentMethod::STATUS_ACTIVE:
$item->addAttribute(pht('Active'));
$item->setBarColor('green');
break;
}
$item->addAttribute(
pht(
'Added %s by %s',
phabricator_datetime($method->getDateCreated(), $user),
$this->getHandle($method->getAuthorPHID())->renderLink()));
$list->addItem($item);
}
return array(
$header,
$actions,
$list,
);
}
private function buildPurchaseHistorySection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Purchase History'));
return array(
$header,
);
}
private function buildAccountHistorySection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Account History'));
$xactions = id(new PhortuneAccountTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($account->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setObjectPHID($account->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
return array(
$header,
$xaction_view,
);
}
}
diff --git a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php
index 387fd0b0e0..dc805378d2 100644
--- a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php
+++ b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php
@@ -1,241 +1,240 @@
<?php
final class PhortunePaymentMethodEditController
extends PhortuneController {
private $accountID;
public function willProcessRequest(array $data) {
$this->accountID = $data['accountID'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$account = id(new PhortuneAccountQuery())
->setViewer($user)
->withIDs(array($this->accountID))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$cancel_uri = $this->getApplicationURI($account->getID().'/');
$account_uri = $this->getApplicationURI($account->getID().'/');
$providers = PhortunePaymentProvider::getProvidersForAddPaymentMethod();
if (!$providers) {
throw new Exception(
"There are no payment providers enabled that can add payment ".
"methods.");
}
$provider_key = $request->getStr('providerKey');
if (empty($providers[$provider_key])) {
$choices = array();
foreach ($providers as $provider) {
$choices[] = $this->renderSelectProvider($provider);
}
return $this->buildResponse($choices, $account_uri);
}
$provider = $providers[$provider_key];
$errors = array();
if ($request->isFormPost() && $request->getBool('isProviderForm')) {
$method = id(new PhortunePaymentMethod())
->setAccountPHID($account->getPHID())
->setAuthorPHID($user->getPHID())
->setStatus(PhortunePaymentMethod::STATUS_ACTIVE)
->setProviderType($provider->getProviderType())
->setProviderDomain($provider->getProviderDomain());
if (!$errors) {
$errors = $this->processClientErrors(
$provider,
$request->getStr('errors'));
}
if (!$errors) {
$client_token_raw = $request->getStr('token');
$client_token = json_decode($client_token_raw, true);
if (!is_array($client_token)) {
$errors[] = pht(
'There was an error decoding token information submitted by the '.
'client. Expected a JSON-encoded token dictionary, received: %s.',
nonempty($client_token_raw, pht('nothing')));
} else {
if (!$provider->validateCreatePaymentMethodToken($client_token)) {
$errors[] = pht(
'There was an error with the payment token submitted by the '.
'client. Expected a valid dictionary, received: %s.',
$client_token_raw);
}
}
if (!$errors) {
$errors = $provider->createPaymentMethodFromRequest(
$request,
$method,
$client_token);
}
}
if (!$errors) {
$method->save();
$save_uri = new PhutilURI($account_uri);
$save_uri->setFragment('payment');
return id(new AphrontRedirectResponse())->setURI($save_uri);
} else {
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle(pht('Error Adding Payment Method'))
->appendChild(id(new AphrontErrorView())->setErrors($errors))
->addCancelButton($request->getRequestURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
$form = $provider->renderCreatePaymentMethodForm($request, $errors);
$form
->setUser($user)
->setAction($request->getRequestURI())
->setWorkflow(true)
->addHiddenInput('providerKey', $provider_key)
->addHiddenInput('isProviderForm', true)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Add Payment Method'))
->addCancelButton($account_uri));
if ($errors) {
$errors = id(new AphrontErrorView())
->setErrors($errors);
}
return $this->buildResponse(
array($errors, $form),
$account_uri);
}
private function renderSelectProvider(
PhortunePaymentProvider $provider) {
$request = $this->getRequest();
$user = $request->getUser();
$description = $provider->getPaymentMethodDescription();
$icon = $provider->getPaymentMethodIcon();
$details = $provider->getPaymentMethodProviderDescription();
$button = phutil_tag(
'button',
array(
'class' => 'grey',
),
array(
$description,
phutil_tag('br'),
$icon,
$details,
));
$form = id(new AphrontFormView())
->setUser($user)
->addHiddenInput('providerKey', $provider->getProviderKey())
->appendChild($button);
return $form;
}
private function buildResponse($content, $account_uri) {
$request = $this->getRequest();
$title = pht('Add Payment Method');
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Account'))
->setHref($account_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Payment Methods'))
->setHref($request->getRequestURI()));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$content,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function processClientErrors(
PhortunePaymentProvider $provider,
$client_errors_raw) {
$errors = array();
$client_errors = json_decode($client_errors_raw, true);
if (!is_array($client_errors)) {
$errors[] = pht(
'There was an error decoding error information submitted by the '.
'client. Expected a JSON-encoded list of error codes, received: %s.',
nonempty($client_errors_raw, pht('nothing')));
}
foreach (array_unique($client_errors) as $key => $client_error) {
$client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode(
$client_error);
}
foreach (array_unique($client_errors) as $client_error) {
switch ($client_error) {
case PhortuneErrCode::ERR_CC_INVALID_NUMBER:
$message = pht(
'The card number you entered is not a valid card number. Check '.
'that you entered it correctly.');
break;
case PhortuneErrCode::ERR_CC_INVALID_CVC:
$message = pht(
'The CVC code you entered is not a valid CVC code. Check that '.
'you entered it correctly. The CVC code is a 3-digit or 4-digit '.
'numeric code which usually appears on the back of the card.');
break;
case PhortuneErrCode::ERR_CC_INVALID_EXPIRY:
$message = pht(
'The card expiration date is not a valid expiration date. Check '.
'that you entered it correctly. You can not add an expired card '.
'as a payment method.');
break;
default:
$message = $provider->getCreatePaymentErrorMessage($client_error);
if (!$message) {
$message = pht(
"There was an unexpected error ('%s') processing payment ".
"information.",
$client_error);
phlog($message);
}
break;
}
$errors[$client_error] = $message;
}
return $errors;
}
}
diff --git a/src/applications/phortune/controller/PhortunePaymentMethodListController.php b/src/applications/phortune/controller/PhortunePaymentMethodListController.php
index 4e4f003890..b2f3a5a4a1 100644
--- a/src/applications/phortune/controller/PhortunePaymentMethodListController.php
+++ b/src/applications/phortune/controller/PhortunePaymentMethodListController.php
@@ -1,23 +1,22 @@
<?php
final class PhortunePaymentMethodListController extends PhabricatorController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$title = pht('Payment Methods');
$crumbs = $this->buildApplicationCrumbs();
return $this->buildApplicationPage(
array(
$crumbs,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phortune/controller/PhortunePaymentMethodViewController.php b/src/applications/phortune/controller/PhortunePaymentMethodViewController.php
index 167c152745..dd6d563cd9 100644
--- a/src/applications/phortune/controller/PhortunePaymentMethodViewController.php
+++ b/src/applications/phortune/controller/PhortunePaymentMethodViewController.php
@@ -1,21 +1,20 @@
<?php
final class PhortunePaymentMethodViewController extends PhabricatorController {
public function processRequest() {
$title = '...';
$crumbs = $this->buildApplicationCrumbs();
return $this->buildApplicationPage(
array(
$crumbs,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phortune/controller/PhortuneProductEditController.php b/src/applications/phortune/controller/PhortuneProductEditController.php
index 2a0b875b03..1717096d2c 100644
--- a/src/applications/phortune/controller/PhortuneProductEditController.php
+++ b/src/applications/phortune/controller/PhortuneProductEditController.php
@@ -1,164 +1,163 @@
<?php
final class PhortuneProductEditController extends PhabricatorController {
private $productID;
public function willProcessRequest(array $data) {
$this->productID = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->productID) {
$product = id(new PhortuneProductQuery())
->setViewer($user)
->withIDs(array($this->productID))
->executeOne();
if (!$product) {
return new Aphront404Response();
}
$is_create = false;
$cancel_uri = $this->getApplicationURI(
'product/view/'.$this->productID.'/');
} else {
$product = new PhortuneProduct();
$is_create = true;
$cancel_uri = $this->getApplicationURI('product/');
}
$v_name = $product->getProductName();
$v_type = $product->getProductType();
$v_price = (int)$product->getPriceInCents();
$display_price = PhortuneCurrency::newFromUSDCents($v_price)
->formatForDisplay();
$e_name = true;
$e_type = null;
$e_price = true;
$errors = array();
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
if (!strlen($v_name)) {
$e_name = pht('Required');
$errors[] = pht('Product must have a name.');
} else {
$e_name = null;
}
if ($is_create) {
$v_type = $request->getStr('type');
$type_map = PhortuneProduct::getTypeMap();
if (empty($type_map[$v_type])) {
$e_type = pht('Invalid');
$errors[] = pht('Product type is invalid.');
} else {
$e_type = null;
}
}
$display_price = $request->getStr('price');
try {
$v_price = PhortuneCurrency::newFromUserInput($user, $display_price)
->getValue();
$e_price = null;
} catch (Exception $ex) {
$errors[] = pht('Price should be formatted as: $1.23');
$e_price = pht('Invalid');
}
if (!$errors) {
$xactions = array();
$xactions[] = id(new PhortuneProductTransaction())
->setTransactionType(PhortuneProductTransaction::TYPE_NAME)
->setNewValue($v_name);
$xactions[] = id(new PhortuneProductTransaction())
->setTransactionType(PhortuneProductTransaction::TYPE_TYPE)
->setNewValue($v_type);
$xactions[] = id(new PhortuneProductTransaction())
->setTransactionType(PhortuneProductTransaction::TYPE_PRICE)
->setNewValue($v_price);
$editor = id(new PhortuneProductEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
$editor->applyTransactions($product, $xactions);
return id(new AphrontRedirectResponse())->setURI(
$this->getApplicationURI('product/view/'.$product->getID().'/'));
}
}
if ($errors) {
$errors = id(new AphrontErrorView())
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Type'))
->setName('type')
->setValue($v_type)
->setError($e_type)
->setOptions(PhortuneProduct::getTypeMap())
->setDisabled(!$is_create))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Price'))
->setName('price')
->setValue($display_price)
->setError($e_price))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(
$is_create
? pht('Create Product')
: pht('Save Product'))
->addCancelButton($cancel_uri));
$title = pht('Edit Product');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Products'))
->setHref($this->getApplicationURI('product/')));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($is_create ? pht('Create') : pht('Edit'))
->setHref($request->getRequestURI()));
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Edit Product'));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$errors,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phortune/controller/PhortuneProductListController.php b/src/applications/phortune/controller/PhortuneProductListController.php
index e401e9337e..e9bd770eb0 100644
--- a/src/applications/phortune/controller/PhortuneProductListController.php
+++ b/src/applications/phortune/controller/PhortuneProductListController.php
@@ -1,68 +1,67 @@
<?php
final class PhortuneProductListController extends PhabricatorController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$pager = new AphrontCursorPagerView();
$pager->readFromRequest($request);
$query = id(new PhortuneProductQuery())
->setViewer($user);
$products = $query->executeWithCursorPager($pager);
$title = pht('Product List');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('Products')
->setHref($this->getApplicationURI('product/')));
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Product'))
->setHref($this->getApplicationURI('product/edit/'))
->setIcon('create'));
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Product List'));
$product_list = id(new PhabricatorObjectItemListView())
->setUser($user)
->setNoDataString(pht('No products.'));
foreach ($products as $product) {
$view_uri = $this->getApplicationURI(
'product/view/'.$product->getID().'/');
$price = $product->getPriceInCents();
$item = id(new PhabricatorObjectItemView())
->setObjectName($product->getID())
->setHeader($product->getProductName())
->setHref($view_uri)
->addAttribute(
PhortuneCurrency::newFromUSDCents($price)->formatForDisplay())
->addAttribute($product->getTypeName());
$product_list->addItem($item);
}
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$product_list,
$pager,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php
index 2e9be65229..42d321591d 100644
--- a/src/applications/phortune/controller/PhortuneProductViewController.php
+++ b/src/applications/phortune/controller/PhortuneProductViewController.php
@@ -1,99 +1,98 @@
<?php
final class PhortuneProductViewController extends PhortuneController {
private $productID;
public function willProcessRequest(array $data) {
$this->productID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$product = id(new PhortuneProductQuery())
->setViewer($user)
->withIDs(array($this->productID))
->executeOne();
if (!$product) {
return new Aphront404Response();
}
$title = pht('Product: %s', $product->getProductName());
$header = id(new PhabricatorHeaderView())
->setHeader($product->getProductName());
$account = $this->loadActiveAccount($user);
$edit_uri = $this->getApplicationURI('product/edit/'.$product->getID().'/');
$cart_uri = $this->getApplicationURI(
$account->getID().'/buy/'.$product->getID().'/');
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($request->getRequestURI())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Product'))
->setHref($edit_uri)
->setIcon('edit'))
->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setName(pht('Purchase'))
->setHref($cart_uri)
->setIcon('new')
->setRenderAsForm(true));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Products'))
->setHref($this->getApplicationURI('product/')));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('#%d', $product->getID()))
->setHref($request->getRequestURI()));
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->addProperty(pht('Type'), $product->getTypeName())
->addProperty(
pht('Price'),
PhortuneCurrency::newFromUSDCents($product->getPriceInCents())
->formatForDisplay());
$xactions = id(new PhortuneProductTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($product->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setObjectPHID($product->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$xaction_view,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phortune/controller/PhortuneProviderController.php b/src/applications/phortune/controller/PhortuneProviderController.php
index 15d56f485b..b59a6831c4 100644
--- a/src/applications/phortune/controller/PhortuneProviderController.php
+++ b/src/applications/phortune/controller/PhortuneProviderController.php
@@ -1,64 +1,63 @@
<?php
final class PhortuneProviderController extends PhortuneController {
private $digest;
private $action;
public function willProcessRequest(array $data) {
$this->digest = $data['digest'];
$this->setAction($data['action']);
}
public function setAction($action) {
$this->action = $action;
return $this;
}
public function getAction() {
return $this->action;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
// NOTE: This use of digests to identify payment providers is because
// payment provider keys don't necessarily have restrictions on what they
// contain (so they might have stuff that's not safe to put in URIs), and
// using digests prevents errors with URI encoding.
$provider = PhortunePaymentProvider::getProviderByDigest($this->digest);
if (!$provider) {
throw new Exception("Invalid payment provider digest!");
}
if (!$provider->canRespondToControllerAction($this->getAction())) {
return new Aphront404Response();
}
$response = $provider->processControllerRequest($this, $request);
if ($response instanceof AphrontResponse) {
return $response;
}
$title = 'Phortune';
return $this->buildApplicationPage(
$response,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
public function loadCart($id) {
return id(new PhortuneCart());
}
}
diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php
index 22eb0eb2f6..bb938745f4 100644
--- a/src/applications/phriction/controller/PhrictionDocumentController.php
+++ b/src/applications/phriction/controller/PhrictionDocumentController.php
@@ -1,452 +1,451 @@
<?php
/**
* @group phriction
*/
final class PhrictionDocumentController
extends PhrictionController {
private $slug;
public function willProcessRequest(array $data) {
$this->slug = $data['slug'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$slug = PhabricatorSlug::normalize($this->slug);
if ($slug != $this->slug) {
$uri = PhrictionDocument::getSlugURI($slug);
// Canonicalize pages to their one true URI.
return id(new AphrontRedirectResponse())->setURI($uri);
}
require_celerity_resource('phriction-document-css');
$document = id(new PhrictionDocumentQuery())
->setViewer($user)
->withSlugs(array($slug))
->executeOne();
$version_note = null;
$core_content = '';
$move_notice = '';
$properties = null;
if (!$document) {
$document = new PhrictionDocument();
if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProject())->loadOneWhere(
'phrictionSlug = %s',
PhrictionDocument::getProjectSlugIdentifier($slug));
if (!$project) {
return new Aphront404Response();
}
}
$create_uri = '/phriction/edit/?slug='.$slug;
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NODATA);
$notice->setTitle(pht('No content here!'));
$notice->appendChild(
pht(
'No document found at %s. You can <strong>'.
'<a href="%s">create a new document here</a></strong>.',
phutil_tag('tt', array(), $slug),
$create_uri));
$core_content = $notice;
$page_title = pht('Page Not Found');
} else {
$version = $request->getInt('v');
if ($version) {
$content = id(new PhrictionContent())->loadOneWhere(
'documentID = %d AND version = %d',
$document->getID(),
$version);
if (!$content) {
return new Aphront404Response();
}
if ($content->getID() != $document->getContentID()) {
$vdate = phabricator_datetime($content->getDateCreated(), $user);
$version_note = new AphrontErrorView();
$version_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$version_note->setTitle('Older Version');
$version_note->appendChild(
pht('You are viewing an older version of this document, as it '.
'appeared on %s.', $vdate));
}
} else {
$content = id(new PhrictionContent())->load($document->getContentID());
}
$page_title = $content->getTitle();
$properties = $this
->buildPropertyListView($document, $content, $slug);
$doc_status = $document->getStatus();
$current_status = $content->getChangeType();
if ($current_status == PhrictionChangeType::CHANGE_EDIT ||
$current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
$core_content = $content->renderContent($user);
} else if ($current_status == PhrictionChangeType::CHANGE_DELETE) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Document Deleted'));
$notice->appendChild(
pht('This document has been deleted. You can edit it to put new '.
'content here, or use history to revert to an earlier version.'));
$core_content = $notice->render();
} else if ($current_status == PhrictionChangeType::CHANGE_STUB) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Empty Document'));
$notice->appendChild(
pht('This document is empty. You can edit it to put some proper '.
'content here.'));
$core_content = $notice->render();
} else if ($current_status == PhrictionChangeType::CHANGE_MOVE_AWAY) {
$new_doc_id = $content->getChangeRef();
$new_doc = new PhrictionDocument();
$new_doc->load($new_doc_id);
$slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug());
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Document Moved'));
$notice->appendChild(phutil_tag('p', array(),
pht('This document has been moved to %s. You can edit it to put new '.
'content here, or use history to revert to an earlier version.',
phutil_tag('a', array('href' => $slug_uri), $slug_uri))));
$core_content = $notice->render();
} else {
throw new Exception("Unknown document status '{$doc_status}'!");
}
$move_notice = null;
if ($current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
$from_doc_id = $content->getChangeRef();
$from_doc = id(new PhrictionDocument())->load($from_doc_id);
$slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug());
$move_notice = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->appendChild(pht('This document was moved from %s',
phutil_tag('a', array('href' => $slug_uri), $slug_uri)))
->render();
}
}
if ($version_note) {
$version_note = $version_note->render();
}
$children = $this->renderDocumentChildren($slug);
$actions = $this->buildActionView($user, $document);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumb_views = $this->renderBreadcrumbs($slug);
foreach ($crumb_views as $view) {
$crumbs->addCrumb($view);
}
$header = id(new PhabricatorHeaderView())
->setHeader($page_title);
$page_content = id(new PHUIDocumentView())
->setOffset(true)
->appendChild(
array(
$header,
$actions,
$properties,
$move_notice,
$core_content,
));
$core_page = phutil_tag(
'div',
array(
'class' => 'phriction-offset'
),
array(
$page_content,
$children,
));
return $this->buildApplicationPage(
array(
$crumbs->render(),
$core_page,
),
array(
'pageObjects' => array($document->getPHID()),
'title' => $page_title,
'device' => true,
- 'dust' => true,
));
}
private function buildPropertyListView(
PhrictionDocument $document,
PhrictionContent $content,
$slug) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($document);
$project_phid = null;
if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProject())->loadOneWhere(
'phrictionSlug = %s',
PhrictionDocument::getProjectSlugIdentifier($slug));
if ($project) {
$project_phid = $project->getPHID();
}
}
$phids = array_filter(
array(
$content->getAuthorPHID(),
$project_phid,
));
$this->loadHandles($phids);
$project_info = null;
if ($project_phid) {
$view->addProperty(
pht('Project Info'),
$this->getHandle($project_phid)->renderLink());
}
$view->addProperty(
pht('Last Author'),
$this->getHandle($content->getAuthorPHID())->renderLink());
$age = time() - $content->getDateCreated();
$age = floor($age / (60 * 60 * 24));
if ($age < 1) {
$when = pht('Today');
} else if ($age == 1) {
$when = pht('Yesterday');
} else {
$when = pht("%d Days Ago", $age);
}
$view->addProperty(pht('Last Updated'), $when);
return $view;
}
private function buildActionView(
PhabricatorUser $user,
PhrictionDocument $document) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$document,
PhabricatorPolicyCapability::CAN_EDIT);
$slug = PhabricatorSlug::normalize($this->slug);
$action_view = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($document);
if (!$document->getID()) {
return $action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Create This Document'))
->setIcon('create')
->setHref('/phriction/edit/?slug='.$slug));
}
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Document'))
->setIcon('edit')
->setHref('/phriction/edit/'.$document->getID().'/'));
if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) {
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Move Document'))
->setIcon('move')
->setHref('/phriction/move/'.$document->getID().'/')
->setWorkflow(true));
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete Document'))
->setIcon('delete')
->setHref('/phriction/delete/'.$document->getID().'/')
->setWorkflow(true));
}
return
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('View History'))
->setIcon('history')
->setHref(PhrictionDocument::getSlugURI($slug, 'history')));
}
private function renderDocumentChildren($slug) {
$document_dao = new PhrictionDocument();
$content_dao = new PhrictionContent();
$conn = $document_dao->establishConnection('r');
$limit = 250;
$d_child = PhabricatorSlug::getDepth($slug) + 1;
$d_grandchild = PhabricatorSlug::getDepth($slug) + 2;
// Select children and grandchildren.
$children = queryfx_all(
$conn,
'SELECT d.slug, d.depth, c.title FROM %T d JOIN %T c
ON d.contentID = c.id
WHERE d.slug LIKE %> AND d.depth IN (%d, %d)
AND d.status IN (%Ld)
ORDER BY d.depth, c.title LIMIT %d',
$document_dao->getTableName(),
$content_dao->getTableName(),
($slug == '/' ? '' : $slug),
$d_child,
$d_grandchild,
array(
PhrictionDocumentStatus::STATUS_EXISTS,
PhrictionDocumentStatus::STATUS_STUB,
),
$limit);
if (!$children) {
return;
}
// We're going to render in one of three modes to try to accommodate
// different information scales:
//
// - If we found fewer than $limit rows, we know we have all the children
// and grandchildren and there aren't all that many. We can just render
// everything.
// - If we found $limit rows but the results included some grandchildren,
// we just throw them out and render only the children, as we know we
// have them all.
// - If we found $limit rows and the results have no grandchildren, we
// have a ton of children. Render them and then let the user know that
// this is not an exhaustive list.
if (count($children) == $limit) {
$more_children = true;
foreach ($children as $child) {
if ($child['depth'] == $d_grandchild) {
$more_children = false;
}
}
$show_grandchildren = false;
} else {
$show_grandchildren = true;
$more_children = false;
}
$grandchildren = array();
foreach ($children as $key => $child) {
if ($child['depth'] == $d_child) {
continue;
} else {
unset($children[$key]);
if ($show_grandchildren) {
$ancestors = PhabricatorSlug::getAncestry($child['slug']);
$grandchildren[end($ancestors)][] = $child;
}
}
}
// Fill in any missing children.
$known_slugs = ipull($children, null, 'slug');
foreach ($grandchildren as $slug => $ignored) {
if (empty($known_slugs[$slug])) {
$children[] = array(
'slug' => $slug,
'depth' => $d_child,
'title' => PhabricatorSlug::getDefaultTitle($slug),
'empty' => true,
);
}
}
$children = isort($children, 'title');
$list = array();
foreach ($children as $child) {
$list[] = hsprintf('<li>');
$list[] = $this->renderChildDocumentLink($child);
$grand = idx($grandchildren, $child['slug'], array());
if ($grand) {
$list[] = hsprintf('<ul>');
foreach ($grand as $grandchild) {
$list[] = hsprintf('<li>');
$list[] = $this->renderChildDocumentLink($grandchild);
$list[] = hsprintf('</li>');
}
$list[] = hsprintf('</ul>');
}
$list[] = hsprintf('</li>');
}
if ($more_children) {
$list[] = phutil_tag('li', array(), pht('More...'));
}
$content = array(
phutil_tag(
'div',
array(
'class' => 'phriction-children-header',
),
pht('Document Hierarchy')),
phutil_tag(
'div',
array(
'class' => 'phriction-children',
),
phutil_tag('ul', array(), $list)),
);
return id(new PHUIDocumentView())
->setOffset(true)
->appendChild($content);
}
private function renderChildDocumentLink(array $info) {
$title = nonempty($info['title'], pht('(Untitled Document)'));
$item = phutil_tag(
'a',
array(
'href' => PhrictionDocument::getSlugURI($info['slug']),
),
$title);
if (isset($info['empty'])) {
$item = phutil_tag('em', array(), $item);
}
return $item;
}
protected function getDocumentSlug() {
return $this->slug;
}
}
diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php
index 5437cfec98..7ff2bd3916 100644
--- a/src/applications/phriction/controller/PhrictionEditController.php
+++ b/src/applications/phriction/controller/PhrictionEditController.php
@@ -1,268 +1,267 @@
<?php
/**
* @group phriction
*/
final class PhrictionEditController
extends PhrictionController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->id) {
$document = id(new PhrictionDocument())->load($this->id);
if (!$document) {
return new Aphront404Response();
}
$revert = $request->getInt('revert');
if ($revert) {
$content = id(new PhrictionContent())->loadOneWhere(
'documentID = %d AND version = %d',
$document->getID(),
$revert);
if (!$content) {
return new Aphront404Response();
}
} else {
$content = id(new PhrictionContent())->load($document->getContentID());
}
} else {
$slug = $request->getStr('slug');
$slug = PhabricatorSlug::normalize($slug);
if (!$slug) {
return new Aphront404Response();
}
$document = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
$slug);
if ($document) {
$content = id(new PhrictionContent())->load($document->getContentID());
} else {
if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProject())->loadOneWhere(
'phrictionSlug = %s',
PhrictionDocument::getProjectSlugIdentifier($slug));
if (!$project) {
return new Aphront404Response();
}
}
$document = new PhrictionDocument();
$document->setSlug($slug);
$content = new PhrictionContent();
$content->setSlug($slug);
$default_title = PhabricatorSlug::getDefaultTitle($slug);
$content->setTitle($default_title);
}
}
if ($request->getBool('nodraft')) {
$draft = null;
$draft_key = null;
} else {
if ($document->getPHID()) {
$draft_key = $document->getPHID().':'.$content->getVersion();
} else {
$draft_key = 'phriction:'.$content->getSlug();
}
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
$draft_key);
}
require_celerity_resource('phriction-document-css');
$e_title = true;
$notes = null;
$errors = array();
if ($request->isFormPost()) {
$title = $request->getStr('title');
$notes = $request->getStr('description');
if (!strlen($title)) {
$e_title = pht('Required');
$errors[] = pht('Document title is required.');
} else {
$e_title = null;
}
if ($document->getID()) {
if ($content->getTitle() == $title &&
$content->getContent() == $request->getStr('content')) {
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle(pht('No Edits'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'You did not make any changes to the document.')));
$dialog->addCancelButton($request->getRequestURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
} else if (!strlen($request->getStr('content'))) {
// We trigger this only for new pages. For existing pages, deleting
// all the content counts as deleting the page.
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle(pht('Empty Page'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'You can not create an empty document.')));
$dialog->addCancelButton($request->getRequestURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if (!count($errors)) {
$editor = id(PhrictionDocumentEditor::newForSlug($document->getSlug()))
->setActor($user)
->setTitle($title)
->setContent($request->getStr('content'))
->setDescription($notes);
$editor->save();
if ($draft) {
$draft->delete();
}
$uri = PhrictionDocument::getSlugURI($document->getSlug());
return id(new AphrontRedirectResponse())->setURI($uri);
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
if ($document->getID()) {
$panel_header = pht('Edit Phriction Document');
$submit_button = pht('Save Changes');
} else {
$panel_header = pht('Create New Phriction Document');
$submit_button = pht('Create Document');
}
$uri = $document->getSlug();
$uri = PhrictionDocument::getSlugURI($uri);
$uri = PhabricatorEnv::getProductionURI($uri);
$cancel_uri = PhrictionDocument::getSlugURI($document->getSlug());
if ($draft &&
strlen($draft->getDraft()) &&
($draft->getDraft() != $content->getContent())) {
$content_text = $draft->getDraft();
$discard = phutil_tag(
'a',
array(
'href' => $request->getRequestURI()->alter('nodraft', true),
),
pht('discard this draft'));
$draft_note = new AphrontErrorView();
$draft_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$draft_note->setTitle('Recovered Draft');
$draft_note->appendChild(hsprintf(
'<p>Showing a saved draft of your edits, you can %s.</p>',
$discard));
} else {
$content_text = $content->getContent();
$draft_note = null;
}
$form = id(new AphrontFormView())
->setUser($user)
->setWorkflow(true)
->setAction($request->getRequestURI()->getPath())
->addHiddenInput('slug', $document->getSlug())
->addHiddenInput('nodraft', $request->getBool('nodraft'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Title'))
->setValue($content->getTitle())
->setError($e_title)
->setName('title'))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('URI'))
->setValue($uri))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Content'))
->setValue($content_text)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setName('content')
->setID('document-textarea')
->setUser($user))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Edit Notes'))
->setValue($notes)
->setError(null)
->setName('description'))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($submit_button));
$header = id(new PhabricatorHeaderView())
->setHeader($panel_header);
$preview = id(new PHUIRemarkupPreviewPanel())
->setHeader(pht('Document Preview'))
->setPreviewURI('/phriction/preview/')
->setControlID('document-textarea')
->setSkin('document');
$crumbs = $this->buildApplicationCrumbs();
if ($document->getID()) {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($content->getTitle())
->setHref(PhrictionDocument::getSlugURI($document->getSlug())));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit')));
} else {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Create')));
}
return $this->buildApplicationPage(
array(
$crumbs,
$draft_note,
$error_view,
$form,
$preview,
),
array(
'title' => pht('Edit Document'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/phriction/controller/PhrictionHistoryController.php b/src/applications/phriction/controller/PhrictionHistoryController.php
index bf3cea207e..c8ae6e93e0 100644
--- a/src/applications/phriction/controller/PhrictionHistoryController.php
+++ b/src/applications/phriction/controller/PhrictionHistoryController.php
@@ -1,169 +1,168 @@
<?php
/**
* @group phriction
*/
final class PhrictionHistoryController
extends PhrictionController {
private $slug;
public function willProcessRequest(array $data) {
$this->slug = $data['slug'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$document = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
PhabricatorSlug::normalize($this->slug));
if (!$document) {
return new Aphront404Response();
}
$current = id(new PhrictionContent())->load($document->getContentID());
$pager = new AphrontPagerView();
$pager->setOffset($request->getInt('page'));
$pager->setURI($request->getRequestURI(), 'page');
$history = id(new PhrictionContent())->loadAllWhere(
'documentID = %d ORDER BY version DESC LIMIT %d, %d',
$document->getID(),
$pager->getOffset(),
$pager->getPageSize() + 1);
$history = $pager->sliceResults($history);
$author_phids = mpull($history, 'getAuthorPHID');
$handles = $this->loadViewerHandles($author_phids);
$list = new PhabricatorObjectItemListView();
foreach ($history as $content) {
$author = $handles[$content->getAuthorPHID()]->renderLink();
$slug_uri = PhrictionDocument::getSlugURI($document->getSlug());
$version = $content->getVersion();
$diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/');
$vs_previous = null;
if ($content->getVersion() != 1) {
$vs_previous = $diff_uri
->alter('l', $content->getVersion() - 1)
->alter('r', $content->getVersion());
}
$vs_head = null;
if ($content->getID() != $document->getContentID()) {
$vs_head = $diff_uri
->alter('l', $content->getVersion())
->alter('r', $current->getVersion());
}
$change_type = PhrictionChangeType::getChangeTypeLabel(
$content->getChangeType());
switch ($content->getChangeType()) {
case PhrictionChangeType::CHANGE_DELETE:
$color = 'red';
break;
case PhrictionChangeType::CHANGE_EDIT:
$color = 'blue';
break;
case PhrictionChangeType::CHANGE_MOVE_HERE:
$color = 'yellow';
break;
case PhrictionChangeType::CHANGE_MOVE_AWAY:
$color = 'orange';
break;
case PhrictionChangeType::CHANGE_STUB:
$color = 'green';
break;
default:
throw new Exception("Unknown change type!");
break;
}
$item = id(new PhabricatorObjectItemView())
->setHeader(pht('%s by %s', $change_type, $author))
->setBarColor($color)
->addAttribute(
phutil_tag(
'a',
array(
'href' => $slug_uri.'?v='.$version,
),
pht('Version %s', $version)))
->addAttribute(pht('%s %s',
phabricator_date($content->getDateCreated(), $user),
phabricator_time($content->getDateCreated(), $user)));
if ($content->getDescription()) {
$item->addAttribute($content->getDescription());
}
if ($vs_previous) {
$item->addIcon(
'arrow_left',
pht('Show Change'),
array(
'href' => $vs_previous,
));
} else {
$item->addIcon('arrow_left-grey',
phutil_tag('em', array(), pht('No previous change')));
}
if ($vs_head) {
$item->addIcon(
'merge',
pht('Show Later Changes'),
array(
'href' => $vs_head,
));
} else {
$item->addIcon('merge-grey',
phutil_tag('em', array(), pht('No later changes')));
}
$list->addItem($item);
}
$crumbs = $this->buildApplicationCrumbs();
$crumb_views = $this->renderBreadcrumbs($document->getSlug());
foreach ($crumb_views as $view) {
$crumbs->addCrumb($view);
}
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('History'))
->setHref(
PhrictionDocument::getSlugURI($document->getSlug(), 'history')));
$header = new PhabricatorHeaderView();
$header->setHeader(pht('Document History for %s',
phutil_tag(
'a',
array('href' => PhrictionDocument::getSlugURI($document->getSlug())),
head($history)->getTitle())));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$list,
$pager,
),
array(
'title' => pht('Document History'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/ponder/controller/PonderAnswerEditController.php b/src/applications/ponder/controller/PonderAnswerEditController.php
index cc23658431..b25252fdd9 100644
--- a/src/applications/ponder/controller/PonderAnswerEditController.php
+++ b/src/applications/ponder/controller/PonderAnswerEditController.php
@@ -1,117 +1,116 @@
<?php
final class PonderAnswerEditController extends PonderController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$answer = id(new PonderAnswerQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$answer) {
return new Aphront404Response();
}
$v_content = $answer->getContent();
$e_content = true;
$question = $answer->getQuestion();
$qid = $question->getID();
$aid = $answer->getID();
$question_uri = "/Q{$qid}#A{$aid}";
$errors = array();
if ($request->isFormPost()) {
$v_content = $request->getStr('content');
if (!strlen($v_content)) {
$errors[] = pht('You must provide some substance in your answer.');
$e_content = pht('Required');
}
if (!$errors) {
$xactions = array();
$xactions[] = id(new PonderAnswerTransaction())
->setTransactionType(PonderAnswerTransaction::TYPE_CONTENT)
->setNewValue($v_content);
$editor = id(new PonderAnswerEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
$editor->applyTransactions($answer, $xactions);
return id(new AphrontRedirectResponse())
->setURI($question_uri);
}
}
if ($errors) {
$errors = id(new AphrontErrorView())->setErrors($errors);
}
$answer_content_id = celerity_generate_unique_node_id();
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Question'))
->setValue($question->getTitle()))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Answer'))
->setName('content')
->setID($answer_content_id)
->setValue($v_content)
->setError($e_content))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Update Answer'))
->addCancelButton($question_uri));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName("Q{$qid}")
->setHref($question_uri));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Answer')));
$preview = id(new PHUIRemarkupPreviewPanel())
->setHeader(pht('Answer Preview'))
->setControlID($answer_content_id)
->setPreviewURI($this->getApplicationURI('preview/'));
return $this->buildApplicationPage(
array(
$crumbs,
$errors,
$form,
$preview,
),
array(
'title' => pht('Edit Answer'),
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/ponder/controller/PonderAnswerHistoryController.php b/src/applications/ponder/controller/PonderAnswerHistoryController.php
index 39e69a9484..324809ec27 100644
--- a/src/applications/ponder/controller/PonderAnswerHistoryController.php
+++ b/src/applications/ponder/controller/PonderAnswerHistoryController.php
@@ -1,73 +1,72 @@
<?php
final class PonderAnswerHistoryController extends PonderController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$answer = id(new PonderAnswerQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$answer) {
return new Aphront404Response();
}
$xactions = id(new PonderAnswerTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($answer->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($answer->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
$qid = $answer->getQuestion()->getID();
$aid = $answer->getID();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName("Q{$qid}")
->setHref("/Q{$qid}"));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName("A{$aid}")
->setHref("/Q{$qid}#{$aid}"));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('History')));
return $this->buildApplicationPage(
array(
$crumbs,
$timeline,
),
array(
'title' => pht('Answer History'),
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/ponder/controller/PonderQuestionEditController.php b/src/applications/ponder/controller/PonderQuestionEditController.php
index c9999fa4c0..2a7a8c759d 100644
--- a/src/applications/ponder/controller/PonderQuestionEditController.php
+++ b/src/applications/ponder/controller/PonderQuestionEditController.php
@@ -1,143 +1,142 @@
<?php
final class PonderQuestionEditController extends PonderController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->id) {
$question = id(new PonderQuestionQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$question) {
return new Aphront404Response();
}
} else {
$question = id(new PonderQuestion())
->setStatus(PonderQuestionStatus::STATUS_OPEN)
->setAuthorPHID($user->getPHID())
->setVoteCount(0)
->setAnswerCount(0)
->setHeat(0.0);
}
$v_title = $question->getTitle();
$v_content = $question->getContent();
$errors = array();
$e_title = true;
if ($request->isFormPost()) {
$v_title = $request->getStr('title');
$v_content = $request->getStr('content');
$len = phutil_utf8_strlen($v_title);
if ($len < 1) {
$errors[] = pht('Title must not be empty.');
$e_title = pht('Required');
} else if ($len > 255) {
$errors[] = pht('Title is too long.');
$e_title = pht('Too Long');
}
if (!$errors) {
$template = id(new PonderQuestionTransaction());
$xactions = array();
$xactions[] = id(clone $template)
->setTransactionType(PonderQuestionTransaction::TYPE_TITLE)
->setNewValue($v_title);
$xactions[] = id(clone $template)
->setTransactionType(PonderQuestionTransaction::TYPE_CONTENT)
->setNewValue($v_content);
$editor = id(new PonderQuestionEditor())
->setActor($user)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
$editor->applyTransactions($question, $xactions);
return id(new AphrontRedirectResponse())
->setURI('/Q'.$question->getID());
}
}
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Form Errors'))
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Question'))
->setName('title')
->setValue($v_title)
->setError($e_title))
->appendChild(
id(new PhabricatorRemarkupControl())
->setName('content')
->setID('content')
->setValue($v_content)
->setLabel(pht('Description'))
->setUser($user))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI())
->setValue(pht('Ask Away!')));
$preview = id(new PHUIRemarkupPreviewPanel())
->setHeader(pht('Question Preview'))
->setControlID('content')
->setPreviewURI($this->getApplicationURI('preview/'));
$crumbs = $this->buildApplicationCrumbs();
$id = $question->getID();
if ($id) {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName("Q{$id}")
->setHref("/Q{$id}"));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit')));
} else {
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Ask Question')));
}
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
$preview,
),
array(
'title' => pht('Ask a Question'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/ponder/controller/PonderQuestionHistoryController.php b/src/applications/ponder/controller/PonderQuestionHistoryController.php
index 85c76b1a7d..4c8c382f8e 100644
--- a/src/applications/ponder/controller/PonderQuestionHistoryController.php
+++ b/src/applications/ponder/controller/PonderQuestionHistoryController.php
@@ -1,68 +1,67 @@
<?php
final class PonderQuestionHistoryController extends PonderController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$question = id(new PonderQuestionQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->executeOne();
if (!$question) {
return new Aphront404Response();
}
$xactions = id(new PonderQuestionTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($question->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($question->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
$qid = $question->getID();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName("Q{$qid}")
->setHref("/Q{$qid}"));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('History')));
return $this->buildApplicationPage(
array(
$crumbs,
$timeline,
),
array(
'title' => pht('Question History'),
- 'dust' => true,
'device' => true,
));
}
}
diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php
index 1caedaab90..b2dfb00493 100644
--- a/src/applications/ponder/controller/PonderQuestionViewController.php
+++ b/src/applications/ponder/controller/PonderQuestionViewController.php
@@ -1,398 +1,397 @@
<?php
final class PonderQuestionViewController extends PonderController {
private $questionID;
public function willProcessRequest(array $data) {
$this->questionID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$question = id(new PonderQuestionQuery())
->setViewer($user)
->withIDs(array($this->questionID))
->needAnswers(true)
->needViewerVotes(true)
->executeOne();
if (!$question) {
return new Aphront404Response();
}
$question->attachVotes($user->getPHID());
$question_xactions = $this->buildQuestionTransactions($question);
$answers = $this->buildAnswers($question->getAnswers());
$authors = mpull($question->getAnswers(), null, 'getAuthorPHID');
if (isset($authors[$user->getPHID()])) {
$answer_add_panel = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NODATA)
->appendChild(
pht(
'You have already answered this question. You can not answer '.
'twice, but you can edit your existing answer.'));
} else {
$answer_add_panel = new PonderAddAnswerView();
$answer_add_panel
->setQuestion($question)
->setUser($user)
->setActionURI("/ponder/answer/add/");
}
$header = id(new PhabricatorHeaderView())
->setHeader($question->getTitle());
$actions = $this->buildActionListView($question);
$properties = $this->buildPropertyListView($question);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('Q'.$this->questionID)
->setHref('/Q'.$this->questionID));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$question_xactions,
$answers,
$answer_add_panel
),
array(
'device' => true,
'title' => 'Q'.$question->getID().' '.$question->getTitle(),
- 'dust' => true,
'pageObjects' => array($question->getPHID()),
));
}
private function buildActionListView(PonderQuestion $question) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $question->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$question,
PhabricatorPolicyCapability::CAN_EDIT);
$view = id(new PhabricatorActionListView())
->setUser($request->getUser())
->setObject($question)
->setObjectURI($request->getRequestURI());
$view->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Question'))
->setHref($this->getApplicationURI("/question/edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) {
$name = pht("Close Question");
$icon = "delete";
$href = "close";
} else {
$name = pht("Reopen Question");
$icon = "enable";
$href = "open";
}
$view->addAction(
id(new PhabricatorActionView())
->setName($name)
->setIcon($icon)
->setRenderAsForm($can_edit)
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit)
->setHref($this->getApplicationURI("/question/{$href}/{$id}/")));
$view->addAction(
id(new PhabricatorActionView())
->setIcon('transcript')
->setName(pht('View History'))
->setHref($this->getApplicationURI("/question/history/{$id}/")));
return $view;
}
private function buildPropertyListView(
PonderQuestion $question) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($question);
$this->loadHandles(array($question->getAuthorPHID()));
$view->addProperty(
pht('Status'),
PonderQuestionStatus::getQuestionStatusFullName($question->getStatus()));
$view->addProperty(
pht('Author'),
$this->getHandle($question->getAuthorPHID())->renderLink());
$view->addProperty(
pht('Created'),
phabricator_datetime($question->getDateCreated(), $viewer));
$view->invokeWillRenderEvent();
$votable = id(new PonderVotableView())
->setPHID($question->getPHID())
->setURI($this->getApplicationURI('vote/'))
->setCount($question->getVoteCount())
->setVote($question->getUserVote());
$view->addTextContent(
array(
$votable,
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
PhabricatorMarkupEngine::renderOneObject(
$question,
$question->getMarkupField(),
$viewer)),
));
return $view;
}
private function buildQuestionTransactions(PonderQuestion $question) {
$viewer = $this->getRequest()->getUser();
$id = $question->getID();
$xactions = id(new PonderQuestionTransactionQuery())
->setViewer($viewer)
->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))
->withObjectPHIDs(array($question->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($question->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
$add_comment = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($question->getPHID())
->setShowPreview(false)
->setAction($this->getApplicationURI("/question/comment/{$id}/"))
->setSubmitButtonName(pht('Comment'));
return $this->wrapComments(
count($xactions),
array(
$timeline,
$add_comment,
));
}
private function buildAnswers(array $answers) {
$request = $this->getRequest();
$viewer = $request->getUser();
$out = array();
$phids = mpull($answers, 'getAuthorPHID');
$this->loadHandles($phids);
$xactions = id(new PonderAnswerTransactionQuery())
->setViewer($viewer)
->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))
->withObjectPHIDs(mpull($answers, 'getPHID'))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$xaction_groups = mgroup($xactions, 'getObjectPHID');
foreach ($answers as $answer) {
$author_phid = $answer->getAuthorPHID();
$xactions = idx($xaction_groups, $answer->getPHID(), array());
$id = $answer->getID();
$out[] = phutil_tag('br');
$out[] = phutil_tag('br');
$out[] = id(new PhabricatorHeaderView())
->setHeader($this->getHandle($author_phid)->getFullName())
->setImage($this->getHandle($author_phid)->getImageURI());
$out[] = $this->buildAnswerActions($answer);
$out[] = $this->buildAnswerProperties($answer);
$details = array();
$details[] = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($answer->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
$details[] = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($answer->getPHID())
->setShowPreview(false)
->setAction($this->getApplicationURI("/answer/comment/{$id}/"))
->setSubmitButtonName(pht('Comment'));
$out[] = $this->wrapComments(
count($xactions),
$details);
}
$out[] = phutil_tag('br');
$out[] = phutil_tag('br');
return $out;
}
private function buildAnswerActions(PonderAnswer $answer) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $answer->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$answer,
PhabricatorPolicyCapability::CAN_EDIT);
$view = id(new PhabricatorActionListView())
->setUser($request->getUser())
->setObject($answer)
->setObjectURI($request->getRequestURI());
$view->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Answer'))
->setHref($this->getApplicationURI("/answer/edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$view->addAction(
id(new PhabricatorActionView())
->setIcon('transcript')
->setName(pht('View History'))
->setHref($this->getApplicationURI("/answer/history/{$id}/")));
return $view;
}
private function buildAnswerProperties(PonderAnswer $answer) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($answer);
$view->addProperty(
pht('Created'),
phabricator_datetime($answer->getDateCreated(), $viewer));
$view->invokeWillRenderEvent();
$votable = id(new PonderVotableView())
->setPHID($answer->getPHID())
->setURI($this->getApplicationURI('vote/'))
->setCount($answer->getVoteCount())
->setVote($answer->getUserVote());
$view->addTextContent(
array(
$votable,
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
PhabricatorMarkupEngine::renderOneObject(
$answer,
$answer->getMarkupField(),
$viewer)),
));
return $view;
}
private function wrapComments($n, $stuff) {
if ($n == 0) {
$text = pht('Add a Comment');
} else {
$text = pht('Show %s Comments', new PhutilNumber($n));
}
$show_id = celerity_generate_unique_node_id();
$hide_id = celerity_generate_unique_node_id();
Javelin::initBehavior('phabricator-reveal-content');
require_celerity_resource('ponder-comment-table-css');
$show = phutil_tag(
'div',
array(
'id' => $show_id,
'class' => 'ponder-show-comments',
),
javelin_tag(
'a',
array(
'href' => '#',
'sigil' => 'reveal-content',
'meta' => array(
'showIDs' => array($hide_id),
'hideIDs' => array($show_id),
),
),
$text));
$hide = phutil_tag(
'div',
array(
'id' => $hide_id,
'style' => 'display: none',
),
$stuff);
return array($show, $hide);
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectCreateController.php b/src/applications/project/controller/PhabricatorProjectCreateController.php
index 2cfc972faa..899d94eb27 100644
--- a/src/applications/project/controller/PhabricatorProjectCreateController.php
+++ b/src/applications/project/controller/PhabricatorProjectCreateController.php
@@ -1,129 +1,128 @@
<?php
final class PhabricatorProjectCreateController
extends PhabricatorProjectController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = new PhabricatorProject();
$project->setAuthorPHID($user->getPHID());
$profile = new PhabricatorProjectProfile();
$e_name = true;
$errors = array();
if ($request->isFormPost()) {
try {
$xactions = array();
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_NAME);
$xaction->setNewValue($request->getStr('name'));
$xactions[] = $xaction;
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_MEMBERS);
$xaction->setNewValue(array($user->getPHID()));
$xactions[] = $xaction;
$editor = new PhabricatorProjectEditor($project);
$editor->setActor($user);
$editor->applyTransactions($xactions);
} catch (PhabricatorProjectNameCollisionException $ex) {
$e_name = 'Not Unique';
$errors[] = $ex->getMessage();
}
$profile->setBlurb($request->getStr('blurb'));
if (!$errors) {
$project->save();
$profile->setProjectPHID($project->getPHID());
$profile->save();
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(array(
'phid' => $project->getPHID(),
'name' => $project->getName(),
));
} else {
return id(new AphrontRedirectResponse())
->setURI('/project/view/'.$project->getID().'/');
}
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Form Errors'));
$error_view->setErrors($errors);
}
if ($request->isAjax()) {
$form = new AphrontFormLayoutView();
} else {
$form = new AphrontFormView();
$form->setUser($user);
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($project->getName())
->setError($e_name))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Blurb'))
->setName('blurb')
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setValue($profile->getBlurb()));
if ($request->isAjax()) {
$dialog = id(new AphrontDialogView())
->setUser($user)
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Create a New Project'))
->appendChild($error_view)
->appendChild($form)
->addSubmitButton(pht('Create Project'))
->addCancelButton('/project/');
return id(new AphrontDialogResponse())->setDialog($dialog);
} else {
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Create'))
->addCancelButton('/project/'));
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Create Project'))
->setHref($this->getApplicationURI().'create/'));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => pht('Create New Project'),
'device' => true,
- 'dust' => true,
));
}
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
index 795c684658..f4448877a7 100644
--- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectMembersEditController.php
@@ -1,175 +1,174 @@
<?php
final class PhabricatorProjectMembersEditController
extends PhabricatorProjectController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (empty($profile)) {
$profile = new PhabricatorProjectProfile();
}
$member_phids = $project->loadMemberPHIDs();
$errors = array();
if ($request->isFormPost()) {
$changed_something = false;
$member_map = array_fill_keys($member_phids, true);
$remove = $request->getStr('remove');
if ($remove) {
if (isset($member_map[$remove])) {
unset($member_map[$remove]);
$changed_something = true;
}
} else {
$new_members = $request->getArr('phids');
foreach ($new_members as $member) {
if (empty($member_map[$member])) {
$member_map[$member] = true;
$changed_something = true;
}
}
}
$xactions = array();
if ($changed_something) {
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_MEMBERS);
$xaction->setNewValue(array_keys($member_map));
$xactions[] = $xaction;
}
if ($xactions) {
$editor = new PhabricatorProjectEditor($project);
$editor->setActor($user);
$editor->applyTransactions($xactions);
}
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI());
}
$member_phids = array_reverse($member_phids);
$handles = $this->loadViewerHandles($member_phids);
$state = array();
foreach ($handles as $handle) {
$state[] = array(
'phid' => $handle->getPHID(),
'name' => $handle->getFullName(),
);
}
$header_name = pht('Edit Members');
$title = pht('Edit Members');
$list = $this->renderMemberList($handles);
$form = new AphrontFormView();
$form
->setUser($user)
->setFlexible(true)
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('phids')
->setLabel(pht('Add Members'))
->setDatasource('/typeahead/common/users/'))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton('/project/view/'.$project->getID().'/')
->setValue(pht('Add Members')));
$faux_form = id(new AphrontFormLayoutView())
->appendChild(
id(new AphrontFormInsetView())
->setTitle(pht('Current Members (%d)', count($handles)))
->appendChild($list));
$box = new PHUIBoxView();
$box->appendChild($faux_form);
$box->setShadow(true);
$box->addPadding(PHUI::PADDING_LARGE);
$box->addMargin(PHUI::MARGIN_LARGE);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($project->getName())
->setHref('/project/view/'.$project->getID().'/'));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Members'))
->setHref($this->getApplicationURI()));
return $this->buildApplicationPage(
array(
$crumbs,
$form,
$box,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function renderMemberList(array $handles) {
$request = $this->getRequest();
$user = $request->getUser();
$list = id(new PhabricatorObjectListView())
->setHandles($handles);
foreach ($handles as $handle) {
$hidden_input = phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'remove',
'value' => $handle->getPHID(),
),
'');
$button = javelin_tag(
'button',
array(
'class' => 'grey',
),
pht('Remove'));
$list->addButton(
$handle,
phabricator_form(
$user,
array(
'method' => 'POST',
'action' => $request->getRequestURI(),
),
array($hidden_input, $button)));
}
return $list;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index 12a27cf725..39b3144fcc 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -1,277 +1,276 @@
<?php
final class PhabricatorProjectProfileController
extends PhabricatorProjectController {
private $id;
private $page;
private $project;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$query = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
->needMembers(true);
$project = $query->executeOne();
$this->project = $project;
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (!$profile) {
$profile = new PhabricatorProjectProfile();
}
$picture = $profile->loadProfileImageURI();
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);
$header = id(new PhabricatorHeaderView())
->setHeader($project->getName())
->setSubheader(phutil_utf8_shorten($profile->getBlurb(), 1024))
->setImage($picture);
$actions = $this->buildActionListView($project);
$properties = $this->buildPropertyListView($project);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($project->getName()));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$content,
),
array(
'title' => $project->getName(),
'device' => true,
- 'dust' => 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.'));
}
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);
}
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>',
$view->render());
}
private function renderTasksPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$user = $this->getRequest()->getUser();
$query = id(new ManiphestTaskQuery())
->withAnyProjects(array($project->getPHID()))
->withStatus(ManiphestTaskQuery::STATUS_OPEN)
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
->setLimit(10)
->setCalculateRows(true);
$tasks = $query->execute();
$count = $query->getRowCount();
$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);
$open = number_format($count);
$more_link = phutil_tag(
'a',
array(
'href' => '/maniphest/view/all/?projects='.$project->getPHID(),
),
pht("View All Open Tasks \xC2\xBB"));
$content = 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 class="phabricator-profile-info-pane-more-link">%s</div>'.
'</div>
</div>',
pht('Open Tasks (%s)', $open),
$task_list,
$more_link);
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));
$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);
return $view;
}
private function buildPropertyListView(PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $request->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($project);
$view->addProperty(
pht('Created'),
phabricator_datetime($project->getDateCreated(), $viewer));
return $view;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileEditController.php b/src/applications/project/controller/PhabricatorProjectProfileEditController.php
index eec98ff974..0e3e13a84c 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileEditController.php
@@ -1,246 +1,245 @@
<?php
final class PhabricatorProjectProfileEditController
extends PhabricatorProjectController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (empty($profile)) {
$profile = new PhabricatorProjectProfile();
}
$img_src = $profile->loadProfileImageURI();
$options = PhabricatorProjectStatus::getStatusMap();
$supported_formats = PhabricatorFile::getTransformableImageFormats();
$e_name = true;
$e_image = null;
$errors = array();
if ($request->isFormPost()) {
try {
$xactions = array();
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_NAME);
$xaction->setNewValue($request->getStr('name'));
$xactions[] = $xaction;
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_STATUS);
$xaction->setNewValue($request->getStr('status'));
$xactions[] = $xaction;
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_CAN_VIEW);
$xaction->setNewValue($request->getStr('can_view'));
$xactions[] = $xaction;
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_CAN_EDIT);
$xaction->setNewValue($request->getStr('can_edit'));
$xactions[] = $xaction;
$xaction = new PhabricatorProjectTransaction();
$xaction->setTransactionType(
PhabricatorProjectTransactionType::TYPE_CAN_JOIN);
$xaction->setNewValue($request->getStr('can_join'));
$xactions[] = $xaction;
$editor = new PhabricatorProjectEditor($project);
$editor->setActor($user);
$editor->applyTransactions($xactions);
} catch (PhabricatorProjectNameCollisionException $ex) {
$e_name = pht('Not Unique');
$errors[] = $ex->getMessage();
}
$profile->setBlurb($request->getStr('blurb'));
if (!strlen($project->getName())) {
$e_name = pht('Required');
$errors[] = pht('Project name is required.');
} else {
$e_name = null;
}
$default_image = $request->getExists('default_image');
if ($default_image) {
$profile->setProfileImagePHID(null);
} else if (!empty($_FILES['image'])) {
$err = idx($_FILES['image'], 'error');
if ($err != UPLOAD_ERR_NO_FILE) {
$file = PhabricatorFile::newFromPHPUpload(
$_FILES['image'],
array(
'authorPHID' => $user->getPHID(),
));
$okay = $file->isTransformableImage();
if ($okay) {
$xformer = new PhabricatorImageTransformer();
$xformed = $xformer->executeThumbTransform(
$file,
$x = 50,
$y = 50);
$profile->setProfileImagePHID($xformed->getPHID());
} else {
$e_image = pht('Not Supported');
$errors[] =
pht('This server only supports these image formats:').' '.
implode(', ', $supported_formats).'.';
}
}
}
if (!$errors) {
$project->save();
$profile->setProjectPHID($project->getPHID());
$profile->save();
return id(new AphrontRedirectResponse())
->setURI('/project/view/'.$project->getID().'/');
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Form Errors'));
$error_view->setErrors($errors);
}
$header_name = pht('Edit Project');
$title = pht('Edit Project');
$action = '/project/edit/'.$project->getID().'/';
$policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($project)
->execute();
$form = new AphrontFormView();
$form
->setID('project-edit-form')
->setUser($user)
->setAction($action)
->setEncType('multipart/form-data')
->setFlexible(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($project->getName())
->setError($e_name))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Project Status'))
->setName('status')
->setOptions($options)
->setValue($project->getStatus()))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Blurb'))
->setName('blurb')
->setValue($profile->getBlurb()))
->appendChild(hsprintf(
'<p class="aphront-form-instructions">%s</p>',
pht(
'NOTE: Policy settings are not yet fully implemented. '.
'Some interfaces still ignore these settings, '.
'particularly "Visible To".')))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setName('can_view')
->setCaption(pht('Members can always view a project.'))
->setPolicyObject($project)
->setPolicies($policies)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setName('can_edit')
->setPolicyObject($project)
->setPolicies($policies)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT))
->appendChild(
id(new AphrontFormPolicyControl())
->setUser($user)
->setName('can_join')
->setCaption(
pht('Users who can edit a project can always join a project.'))
->setPolicyObject($project)
->setPolicies($policies)
->setCapability(PhabricatorPolicyCapability::CAN_JOIN))
->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:').' '.implode(', ', $supported_formats)))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton('/project/view/'.$project->getID().'/')
->setValue(pht('Save')));
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($project->getName())
->setHref('/project/view/'.$project->getID().'/'));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit Project'))
->setHref($this->getApplicationURI()));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php
index 8e9391f68c..1309a3d55d 100644
--- a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php
+++ b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php
@@ -1,118 +1,117 @@
<?php
final class ReleephBranchCreateController extends ReleephProjectController {
public function processRequest() {
$releeph_project = $this->getReleephProject();
$request = $this->getRequest();
$cut_point = $request->getStr('cutPoint');
$symbolic_name = $request->getStr('symbolicName');
if (!$cut_point) {
$repository = $releeph_project->loadPhabricatorRepository();
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$cut_point = $releeph_project->getTrunkBranch();
break;
}
}
$e_cut = true;
$errors = array();
$branch_date_control = id(new AphrontFormDateControl())
->setUser($request->getUser())
->setName('templateDate')
->setLabel(pht('Date'))
->setCaption(pht('The date used for filling out the branch template.'))
->setInitialTime(AphrontFormDateControl::TIME_START_OF_DAY);
$branch_date = $branch_date_control->readValueFromRequest($request);
if ($request->isFormPost()) {
$cut_commit = null;
if (!$cut_point) {
$e_cut = pht('Required');
$errors[] = pht('You must give a branch cut point');
} else {
try {
$finder = id(new ReleephCommitFinder())
->setUser($request->getUser())
->setReleephProject($releeph_project);
$cut_commit = $finder->fromPartial($cut_point);
} catch (Exception $e) {
$e_cut = pht('Invalid');
$errors[] = $e->getMessage();
}
}
if (!$errors) {
$branch = id(new ReleephBranchEditor())
->setReleephProject($releeph_project)
->setActor($request->getUser())
->newBranchFromCommit(
$cut_commit,
$branch_date,
$symbolic_name);
return id(new AphrontRedirectResponse())
->setURI($branch->getURI());
}
}
$error_view = array();
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle(pht('Form Errors'));
}
$project_id = $releeph_project->getID();
$project_uri = $this->getApplicationURI("project/{$project_id}/");
$form = id(new AphrontFormView())
->setUser($request->getUser())
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Symbolic Name'))
->setName('symbolicName')
->setValue($symbolic_name)
->setCaption(pht('Mutable alternate name, for easy reference, '.
'(e.g. "LATEST")')))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Cut point'))
->setName('cutPoint')
->setValue($cut_point)
->setError($e_cut)
->setCaption(
pht('A commit ID for your repo type, or a '.
'Diffusion ID like "rE123"')))
->appendChild($branch_date_control)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Cut Branch'))
->addCancelButton($project_uri));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('New Branch')));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => pht('New Branch'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/releeph/controller/branch/ReleephBranchEditController.php b/src/applications/releeph/controller/branch/ReleephBranchEditController.php
index fe0b491688..4f8f5e50ce 100644
--- a/src/applications/releeph/controller/branch/ReleephBranchEditController.php
+++ b/src/applications/releeph/controller/branch/ReleephBranchEditController.php
@@ -1,104 +1,103 @@
<?php
final class ReleephBranchEditController extends ReleephProjectController {
public function processRequest() {
$request = $this->getRequest();
$releeph_branch = $this->getReleephBranch();
$symbolic_name = $request->getStr(
'symbolicName',
$releeph_branch->getSymbolicName());
$errors = array();
if ($request->isFormPost()) {
$existing_with_same_symbolic_name =
id(new ReleephBranch())
->loadOneWhere(
'id != %d AND releephProjectID = %d AND symbolicName = %s',
$releeph_branch->getID(),
$releeph_branch->getReleephProjectID(),
$symbolic_name);
$releeph_branch->openTransaction();
$releeph_branch
->setSymbolicName($symbolic_name);
if ($existing_with_same_symbolic_name) {
$existing_with_same_symbolic_name
->setSymbolicName(null)
->save();
}
$releeph_branch->save();
$releeph_branch->saveTransaction();
return id(new AphrontRedirectResponse())
->setURI('/releeph/project/'.$releeph_branch->getReleephProjectID());
}
$phids = array();
$phids[] = $creator_phid = $releeph_branch->getCreatedByUserPHID();
$phids[] = $cut_commit_phid = $releeph_branch->getCutPointCommitPHID();
$handles = id(new PhabricatorObjectHandleData($phids))
->setViewer($request->getUser())
->loadHandles();
$form = id(new AphrontFormView())
->setUser($request->getUser())
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Branch Name'))
->setValue($releeph_branch->getName()))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Cut Point'))
->setValue($handles[$cut_commit_phid]->renderLink()))
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel(pht('Created By'))
->setValue($handles[$creator_phid]->renderLink()))
->appendChild(
id(new AphrontFormTextControl)
->setLabel(pht('Symbolic Name'))
->setName('symbolicName')
->setValue($symbolic_name)
->setCaption(pht('Mutable alternate name, for easy reference, '.
'(e.g. "LATEST")')))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($releeph_branch->getURI())
->setValue(pht('Save')));
$error_view = null;
if ($errors) {
$error_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_ERROR)
->setErrors($errors)
->setTitle(pht('Errors'));
}
$title = pht(
'Edit Branch %s',
$releeph_branch->getDisplayNameWithDetail());
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit')));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/releeph/controller/project/ReleephProjectCreateController.php b/src/applications/releeph/controller/project/ReleephProjectCreateController.php
index cc423dda65..04c4567867 100644
--- a/src/applications/releeph/controller/project/ReleephProjectCreateController.php
+++ b/src/applications/releeph/controller/project/ReleephProjectCreateController.php
@@ -1,170 +1,169 @@
<?php
final class ReleephProjectCreateController extends ReleephProjectController {
public function processRequest() {
$request = $this->getRequest();
$name = trim($request->getStr('name'));
$trunk_branch = trim($request->getStr('trunkBranch'));
$arc_pr_id = $request->getInt('arcPrID');
$arc_projects = $this->loadArcProjects();
$e_name = true;
$e_trunk_branch = true;
$errors = array();
if ($request->isFormPost()) {
if (!$name) {
$e_name = pht('Required');
$errors[] = pht(
'Your Releeph project should have a simple descriptive name.');
}
if (!$trunk_branch) {
$e_trunk_branch = pht('Required');
$errors[] = pht(
'You must specify which branch you will be picking from.');
}
$arc_project = $arc_projects[$arc_pr_id];
$pr_repository = $arc_project->loadRepository();
if (!$errors) {
$releeph_project = id(new ReleephProject())
->setName($name)
->setTrunkBranch($trunk_branch)
->setRepositoryPHID($pr_repository->getPHID())
->setArcanistProjectID($arc_project->getID())
->setCreatedByUserPHID($request->getUser()->getPHID())
->setIsActive(1);
try {
$releeph_project->save();
return id(new AphrontRedirectResponse())
->setURI($releeph_project->getURI());
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_name = pht('Not Unique');
$errors[] = pht(
'Another project already uses this name.');
}
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
}
$arc_project_options = $this->getArcProjectSelectOptions($arc_projects);
$project_name_input = id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setDisableAutocomplete(true)
->setName('name')
->setValue($name)
->setError($e_name)
->setCaption(pht('A name like "Thrift" but not "Thrift releases".'));
$arc_project_input = id(new AphrontFormSelectControl())
->setLabel(pht('Arc Project'))
->setName('arcPrID')
->setValue($arc_pr_id)
->setCaption(pht(
'If your Arc project isn\'t listed, associate it with a repository %s',
phutil_tag(
'a',
array(
'href' => '/repository/',
'target' => '_blank',
),
'here')))
->setOptions($arc_project_options);
$branch_name_preview = id(new ReleephBranchPreviewView())
->setLabel(pht('Example Branch'))
->addControl('projectName', $project_name_input)
->addControl('arcProjectID', $arc_project_input)
->addStatic('template', '')
->addStatic('isSymbolic', false);
$form = id(new AphrontFormView())
->setUser($request->getUser())
->setFlexible(true)
->appendChild($project_name_input)
->appendChild($arc_project_input)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Trunk'))
->setName('trunkBranch')
->setValue($trunk_branch)
->setError($e_trunk_branch)
->setCaption(pht('The development branch, '.
'from which requests will be picked.')))
->appendChild($branch_name_preview)
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton('/releeph/project/')
->setValue(pht('Create')));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('New Project')));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => pht('Create New Project'),
- 'dust' => true,
'device' => true,
));
}
private function loadArcProjects() {
$viewer = $this->getRequest()->getUser();
$projects = id(new PhabricatorRepositoryArcanistProjectQuery())
->setViewer($viewer)
->needRepositories(true)
->execute();
$projects = mfilter($projects, 'getRepository');
$projects = msort($projects, 'getName');
return $projects;
}
private function getArcProjectSelectOptions(array $arc_projects) {
assert_instances_of($arc_projects, 'PhabricatorRepositoryArcanistProject');
$repos = mpull($arc_projects, 'getRepository');
$repos = mpull($repos, null, 'getID');
$groups = array();
foreach ($arc_projects as $arc_project) {
$id = $arc_project->getID();
$repo_id = $arc_project->getRepository()->getID();
$groups[$repo_id][$id] = $arc_project->getName();
}
$choices = array();
foreach ($groups as $repo_id => $group) {
$repo_name = $repos[$repo_id]->getName();
$callsign = $repos[$repo_id]->getCallsign();
$name = "r{$callsign} ({$repo_name})";
$choices[$name] = $group;
}
ksort($choices);
return $choices;
}
}
diff --git a/src/applications/repository/controller/PhabricatorRepositoryCreateController.php b/src/applications/repository/controller/PhabricatorRepositoryCreateController.php
index 3f39341e46..67bb810a6c 100644
--- a/src/applications/repository/controller/PhabricatorRepositoryCreateController.php
+++ b/src/applications/repository/controller/PhabricatorRepositoryCreateController.php
@@ -1,122 +1,121 @@
<?php
final class PhabricatorRepositoryCreateController
extends PhabricatorRepositoryController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$e_name = true;
$e_callsign = true;
$repository = new PhabricatorRepository();
$type_map = PhabricatorRepositoryType::getAllRepositoryTypes();
$errors = array();
if ($request->isFormPost()) {
$repository->setName($request->getStr('name'));
$repository->setCallsign($request->getStr('callsign'));
$repository->setVersionControlSystem($request->getStr('type'));
if (!strlen($repository->getName())) {
$e_name = 'Required';
$errors[] = 'Repository name is required.';
} else {
$e_name = null;
}
if (!strlen($repository->getCallsign())) {
$e_callsign = 'Required';
$errors[] = 'Callsign is required.';
} else if (!preg_match('/^[A-Z]+$/', $repository->getCallsign())) {
$e_callsign = 'Invalid';
$errors[] = 'Callsign must be ALL UPPERCASE LETTERS.';
} else {
$e_callsign = null;
}
if (empty($type_map[$repository->getVersionControlSystem()])) {
$errors[] = 'Invalid version control system.';
}
if (!$errors) {
try {
$repository->save();
return id(new AphrontRedirectResponse())
->setURI('/repository/edit/'.$repository->getID().'/');
} catch (AphrontQueryDuplicateKeyException $ex) {
$e_callsign = 'Duplicate';
$errors[] = 'Callsign must be unique. Another repository already '.
'uses that callsign.';
}
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle('Form Errors');
}
$form = new AphrontFormView();
$form
->setUser($user)
->setAction('/repository/create/')
->setFlexible(true)
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($repository->getName())
->setError($e_name)
->setCaption('Human-readable repository name.'))
->appendChild(hsprintf(
'<p class="aphront-form-instructions">Select a "Callsign" &mdash; a '.
'short, uppercase string to identify revisions in this repository. If '.
'you choose "EX", revisions in this repository will be identified '.
'with the prefix "rEX".</p>'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Callsign')
->setName('callsign')
->setValue($repository->getCallsign())
->setError($e_callsign)
->setCaption(
'Short, UPPERCASE identifier. Once set, it can not be changed.'))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Type')
->setName('type')
->setOptions($type_map)
->setValue($repository->getVersionControlSystem()))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Create Repository')
->addCancelButton('/repository/'));
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Create Repository'));
return $this->buildApplicationPage(
array(
$error_view,
$header,
$form,
),
array(
'title' => pht('Create Repository'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/repository/controller/PhabricatorRepositoryEditController.php b/src/applications/repository/controller/PhabricatorRepositoryEditController.php
index c3c3447a3d..11ec24fd63 100644
--- a/src/applications/repository/controller/PhabricatorRepositoryEditController.php
+++ b/src/applications/repository/controller/PhabricatorRepositoryEditController.php
@@ -1,694 +1,693 @@
<?php
final class PhabricatorRepositoryEditController
extends PhabricatorRepositoryController {
private $id;
private $view;
private $repository;
private $sideNav;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$this->view = idx($data, 'view');
}
public function processRequest() {
$request = $this->getRequest();
$repository = id(new PhabricatorRepository())->load($this->id);
if (!$repository) {
return new Aphront404Response();
}
$views = array(
'basic' => 'Basics',
'tracking' => 'Tracking',
);
$this->repository = $repository;
if (!isset($views[$this->view])) {
$this->view = head_key($views);
}
$nav = new AphrontSideNavFilterView();
$base_uri = new PhutilURI('/repository/edit/'.$repository->getID().'/');
$nav->setBaseURI($base_uri);
foreach ($views as $view => $name) {
$nav->addFilter($view, $name);
}
$nav->selectFilter($this->view, null);
$nav->appendChild($this->renderDaemonNotice());
$this->sideNav = $nav;
switch ($this->view) {
case 'basic':
return $this->processBasicRequest();
case 'tracking':
return $this->processTrackingRequest();
default:
throw new Exception("Unknown view.");
}
}
protected function processBasicRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$repository = $this->repository;
$repository_id = $repository->getID();
$errors = array();
$e_name = true;
if ($request->isFormPost()) {
$repository->setName($request->getStr('name'));
if (!strlen($repository->getName())) {
$e_name = 'Required';
$errors[] = 'Repository name is required.';
} else {
$e_name = null;
}
$repository->setDetail('description', $request->getStr('description'));
$repository->setDetail('encoding', $request->getStr('encoding'));
if (!$errors) {
$repository->save();
return id(new AphrontRedirectResponse())
->setURI('/repository/edit/'.$repository_id.'/basic/?saved=true');
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle('Form Errors');
} else if ($request->getStr('saved')) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$error_view->setTitle('Changes Saved');
$error_view->appendChild('Repository changes were saved.');
}
$encoding_doc_link = PhabricatorEnv::getDoclink(
'article/User_Guide_UTF-8_and_Character_Encoding.html');
$form = new AphrontFormView();
$form
->setUser($user)
->setAction('/repository/edit/'.$repository->getID().'/')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($repository->getName())
->setError($e_name)
->setCaption('Human-readable repository name.'))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Description')
->setName('description')
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setValue($repository->getDetail('description')))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Callsign')
->setName('callsign')
->setValue($repository->getCallsign()))
->appendChild(hsprintf('
<p class="aphront-form-instructions">'.
'If source code in this repository uses a character '.
'encoding other than UTF-8 (for example, ISO-8859-1), '.
'specify it here. You can usually leave this field blank. '.
'See User Guide: '.
'<a href="%s">UTF-8 and Character Encoding</a> for more information.'.
'</p>',
$encoding_doc_link))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Encoding')
->setName('encoding')
->setValue($repository->getDetail('encoding')))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('Type')
->setName('type')
->setValue($repository->getVersionControlSystem()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('ID')
->setValue($repository->getID()))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel('PHID')
->setValue($repository->getPHID()))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save'));
$nav = $this->sideNav;
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Edit Repository'));
$nav->appendChild($error_view);
$nav->appendChild($header);
$nav->appendChild($form);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Edit Repository'),
'device' => true,
- 'dust' => true,
));
}
private function processTrackingRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$repository = $this->repository;
$repository_id = $repository->getID();
$errors = array();
$e_uri = null;
$e_path = null;
$is_git = false;
$is_svn = false;
$is_mercurial = false;
$e_ssh_key = null;
$e_ssh_keyfile = null;
$e_branch = null;
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$is_git = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$is_svn = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$is_mercurial = true;
break;
default:
throw new Exception("Unsupported VCS!");
}
$has_branches = ($is_git || $is_mercurial);
$has_local = ($is_git || $is_mercurial);
$has_branch_filter = ($is_git);
$has_auth_support = $is_svn;
if ($request->isFormPost()) {
$tracking = ($request->getStr('tracking') == 'enabled' ? true : false);
$repository->setDetail('tracking-enabled', $tracking);
$repository->setDetail('remote-uri', $request->getStr('uri'));
if ($has_local) {
$repository->setDetail('local-path', $request->getStr('path'));
}
if ($has_branch_filter) {
$branch_filter = $request->getStrList('branch-filter');
$branch_filter = array_fill_keys($branch_filter, true);
$repository->setDetail('branch-filter', $branch_filter);
$close_commits_filter = $request->getStrList('close-commits-filter');
$close_commits_filter = array_fill_keys($close_commits_filter, true);
$repository->setDetail('close-commits-filter', $close_commits_filter);
}
$repository->setDetail(
'disable-autoclose',
$request->getStr('autoclose') == 'disabled' ? true : false);
$repository->setDetail(
'pull-frequency',
max(1, $request->getInt('frequency')));
if ($has_branches) {
$repository->setDetail(
'default-branch',
$request->getStr('default-branch'));
if ($is_git) {
$branch_name = $repository->getDetail('default-branch');
if (strpos($branch_name, '/') !== false) {
$e_branch = 'Invalid';
$errors[] = "Your branch name should not specify an explicit ".
"remote. For instance, use 'master', not ".
"'origin/master'.";
}
}
}
$repository->setDetail(
'default-owners-path',
$request->getStr(
'default-owners-path',
'/'));
$repository->setDetail('ssh-login', $request->getStr('ssh-login'));
$repository->setDetail('ssh-key', $request->getStr('ssh-key'));
$repository->setDetail('ssh-keyfile', $request->getStr('ssh-keyfile'));
$repository->setDetail('http-login', $request->getStr('http-login'));
$repository->setDetail('http-pass', $request->getStr('http-pass'));
$repository->setDetail('show-user', $request->getInt('show-user'));
if ($repository->getDetail('ssh-key') &&
$repository->getDetail('ssh-keyfile')) {
$errors[] =
"Specify only one of 'SSH Private Key' and 'SSH Private Key File', ".
"not both.";
$e_ssh_key = 'Choose Only One';
$e_ssh_keyfile = 'Choose Only One';
}
$repository->setDetail(
'herald-disabled',
$request->getInt('herald-disabled', 0));
if ($is_svn) {
$repository->setUUID($request->getStr('uuid'));
$subpath = ltrim($request->getStr('svn-subpath'), '/');
if ($subpath) {
$subpath = rtrim($subpath, '/').'/';
}
$repository->setDetail('svn-subpath', $subpath);
}
if ($tracking) {
if (!$repository->getDetail('remote-uri')) {
$e_uri = 'Required';
$errors[] = "Repository URI is required.";
} else if ($is_svn &&
!preg_match('@/$@', $repository->getDetail('remote-uri'))) {
$e_uri = 'Invalid';
$errors[] = 'Subversion Repository Root must end in a slash ("/").';
} else {
$e_uri = null;
}
if ($has_local) {
if (!$repository->getDetail('local-path')) {
$e_path = 'Required';
$errors[] = "Local path is required.";
} else {
$e_path = null;
}
}
}
if (!$errors) {
$repository->save();
return id(new AphrontRedirectResponse())
->setURI('/repository/edit/'.$repository_id.'/tracking/?saved=true');
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setErrors($errors);
$error_view->setTitle('Form Errors');
} else if ($request->getStr('saved')) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$error_view->setTitle('Changes Saved');
$error_view->appendChild('Tracking changes were saved.');
} else if (!$repository->isTracked()) {
$error_view = new AphrontErrorView();
$error_view->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$error_view->setTitle('Repository Not Tracked');
$error_view->appendChild(
'Tracking is currently "Disabled" for this repository, so it will '.
'not be imported into Phabricator. You can enable it below.');
}
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$is_git = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$is_svn = true;
break;
}
$doc_href = PhabricatorEnv::getDoclink('article/Diffusion_User_Guide.html');
$user_guide_link = phutil_tag(
'a',
array(
'href' => $doc_href,
),
'Diffusion User Guide');
$form = new AphrontFormView();
$form
->setUser($user)
->setAction('/repository/edit/'.$repository->getID().'/tracking/')
->setFlexible(true)
->appendChild(hsprintf(
'<p class="aphront-form-instructions">Phabricator can track '.
'repositories, importing commits as they happen and notifying '.
'Differential, Diffusion, Herald, and other services. To enable '.
'tracking for a repository, configure it here and then start (or '.
'restart) the daemons. More information is available in the '.
'<strong>%s</strong>.</p>',
$user_guide_link));
$form
->appendChild(
id(new AphrontFormInsetView())
->setTitle('Basics')
->appendChild(id(new AphrontFormStaticControl())
->setLabel('Repository Name')
->setValue($repository->getName()))
->appendChild(id(new AphrontFormSelectControl())
->setName('tracking')
->setLabel('Tracking')
->setOptions(array(
'disabled' => 'Disabled',
'enabled' => 'Enabled',
))
->setValue(
$repository->isTracked()
? 'enabled'
: 'disabled')));
$inset = new AphrontFormInsetView();
$inset->setTitle('Remote URI');
$clone_command = null;
$fetch_command = null;
if ($is_git) {
$clone_command = 'git clone';
$fetch_command = 'git fetch';
} else if ($is_mercurial) {
$clone_command = 'hg clone';
$fetch_command = 'hg pull';
}
$uri_label = 'Repository URI';
if ($has_local) {
if ($is_git) {
$instructions = hsprintf(
'Enter the URI to clone this repository from. It should look like '.
'<tt>git@github.com:example/example.git</tt>, '.
'<tt>ssh://user@host.com/git/example.git</tt>, or '.
'<tt>file:///local/path/to/repo</tt>');
} else if ($is_mercurial) {
$instructions = hsprintf(
'Enter the URI to clone this repository from. It should look '.
'something like <tt>ssh://user@host.com/hg/example</tt>');
}
$inset->appendChild(hsprintf(
'<p class="aphront-form-instructions">%s</p>',
$instructions));
} else if ($is_svn) {
$instructions = hsprintf(
'Enter the <strong>Repository Root</strong> for this SVN repository. '.
'You can figure this out by running <tt>svn info</tt> and looking at '.
'the value in the <tt>Repository Root</tt> field. It should be a URI '.
'and look like <tt>http://svn.example.org/svn/</tt>, '.
'<tt>svn+ssh://svn.example.com/svnroot/</tt>, or '.
'<tt>svn://svn.example.net/svn/</tt>');
$inset->appendChild(hsprintf(
'<p class="aphront-form-instructions">%s</p>',
$instructions));
$uri_label = 'Repository Root';
}
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('uri')
->setLabel($uri_label)
->setID('remote-uri')
->setValue($repository->getDetail('remote-uri'))
->setError($e_uri));
$inset->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'show-user',
1,
pht('Permit users to view the username of this connection.'),
$repository->getDetail('show-user') == 1));
$inset->appendChild(hsprintf(
'<div class="aphront-form-instructions">'.
'If you want to connect to this repository over SSH, enter the '.
'username and private key to use. You can leave these fields blank if '.
'the repository does not use SSH.'.
'</div>'));
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('ssh-login')
->setLabel('SSH User')
->setValue($repository->getDetail('ssh-login')))
->appendChild(
id(new AphrontFormTextAreaControl())
->setName('ssh-key')
->setLabel('SSH Private Key')
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setValue($repository->getDetail('ssh-key'))
->setError($e_ssh_key)
->setCaption(
hsprintf('Specify the entire private key, <em>or</em>...')))
->appendChild(
id(new AphrontFormTextControl())
->setName('ssh-keyfile')
->setLabel('SSH Private Key File')
->setValue($repository->getDetail('ssh-keyfile'))
->setError($e_ssh_keyfile)
->setCaption(
'...specify a path on disk where the daemon should '.
'look for a private key.'));
if ($has_auth_support) {
$inset
->appendChild(hsprintf(
'<div class="aphront-form-instructions">'.
'If you want to connect to this repository with a username and '.
'password, such as over HTTP Basic Auth or SVN with SASL, '.
'enter the username and password to use. You can leave these '.
'fields blank if the repository does not use a username and '.
'password for authentication.'.
'</div>'))
->appendChild(
id(new AphrontFormTextControl())
->setName('http-login')
->setLabel('Username')
->setValue($repository->getDetail('http-login')))
->appendChild(
id(new AphrontFormPasswordControl())
->setName('http-pass')
->setLabel('Password')
->setValue($repository->getDetail('http-pass')));
}
$inset
->appendChild(hsprintf(
'<div class="aphront-form-important">'.
'To test your authentication configuration, <strong>save this '.
'form</strong> and then run this script:'.
'<code>'.
'phabricator/ $ ./scripts/repository/test_connection.php %s'.
'</code>'.
'This will verify that your configuration is correct and the '.
'daemons can connect to the remote repository and pull changes '.
'from it.'.
'</div>',
$repository->getCallsign()));
$form->appendChild($inset);
$inset = new AphrontFormInsetView();
$inset->setTitle('Repository Information');
if ($has_local) {
$default_local_path = '';
$default =
PhabricatorEnv::getEnvConfig('repository.default-local-path');
if (!$repository->getDetail('remote-uri') && $default) {
$default_local_path = $default.strtolower($repository->getCallsign());
}
$inset->appendChild(hsprintf(
'<p class="aphront-form-instructions">Select a path on local disk '.
'which the daemons should <tt>%s</tt> the repository into. This must '.
'be readable and writable by the daemons, and readable by the '.
'webserver. The daemons will <tt>%s</tt> and keep this repository up '.
'to date.</p>',
$clone_command,
$fetch_command));
$inset->appendChild(
id(new AphrontFormTextControl())
->setName('path')
->setLabel('Local Path')
->setValue($repository->getDetail('local-path', $default_local_path))
->setError($e_path));
} else if ($is_svn) {
$inset->appendChild(hsprintf(
'<p class="aphront-form-instructions">If you only want to parse one '.
'subpath of the repository, specify it here, relative to the '.
'repository root (e.g., <tt>trunk/</tt> or <tt>projects/wheel/</tt>). '.
'If you want to parse multiple subdirectories, create a separate '.
'Phabricator repository for each one.</p>'));
$inset->appendChild(
id(new AphrontFormTextControl())
->setName('svn-subpath')
->setLabel('Subpath')
->setValue($repository->getDetail('svn-subpath'))
->setError($e_path));
}
if ($has_branch_filter) {
$branch_filter_str = implode(
', ',
array_keys($repository->getDetail('branch-filter', array())));
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('branch-filter')
->setLabel('Track Only')
->setValue($branch_filter_str)
->setCaption(hsprintf(
'Optional list of branches to track. Other branches will be '.
'completely ignored. If left empty, all branches are tracked. '.
'Example: <tt>master, release</tt>')));
}
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('frequency')
->setLabel('Pull Frequency')
->setValue($repository->getDetail('pull-frequency', 15))
->setCaption(
'Number of seconds daemon should sleep between requests. Larger '.
'numbers reduce load but also decrease responsiveness.'));
$form->appendChild($inset);
$inset = new AphrontFormInsetView();
$inset->setTitle('Application Configuration');
if ($has_branches) {
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('default-branch')
->setLabel('Default Branch')
->setValue($repository->getDefaultBranch())
->setError($e_branch)
->setCaption(
'Default branch to show in Diffusion.'));
}
$inset
->appendChild(id(new AphrontFormSelectControl())
->setName('autoclose')
->setLabel('Autoclose')
->setOptions(array(
'enabled' => 'Enabled: Automatically Close Pushed Revisions',
'disabled' => 'Disabled: Ignore Pushed Revisions',
))
->setCaption(
"Automatically close Differential revisions when associated commits ".
"are pushed to this repository.")
->setValue(
$repository->getDetail('disable-autoclose', false)
? 'disabled'
: 'enabled'));
if ($has_branch_filter) {
$close_commits_filter_str = implode(
', ',
array_keys($repository->getDetail('close-commits-filter', array())));
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('close-commits-filter')
->setLabel('Autoclose Branches')
->setValue($close_commits_filter_str)
->setCaption(
'Optional list of branches which can trigger autoclose. '.
'If left empty, all branches trigger autoclose.'));
}
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('default-owners-path')
->setLabel('Default Owners Path')
->setValue(
$repository->getDetail(
'default-owners-path',
'/'))
->setCaption('Default path in Owners tool.'));
$inset
->appendChild(
id(new AphrontFormSelectControl())
->setName('herald-disabled')
->setLabel('Herald/Feed Enabled')
->setValue($repository->getDetail('herald-disabled', 0))
->setOptions(
array(
0 => 'Enabled - Send Email and Publish Stories',
1 => 'Disabled - Do Not Send Email or Publish Stories',
))
->setCaption(
'You can disable Herald commit notifications and feed stories '.
'for this repository. This can be useful when initially importing '.
'a repository. Feed stories are never published about commits '.
'that are more than 24 hours old.'));
if ($is_svn) {
$inset
->appendChild(
id(new AphrontFormTextControl())
->setName('uuid')
->setLabel('UUID')
->setValue($repository->getUUID())
->setCaption(hsprintf('Repository UUID from <tt>svn info</tt>.')));
}
$form->appendChild($inset);
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save Configuration'));
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Edit Repository Tracking'));
$nav = $this->sideNav;
$nav->appendChild($error_view);
$nav->appendChild($header);
$nav->appendChild($form);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Edit Repository Tracking'),
));
}
}
diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php
index 058d71ffc8..e79c14afef 100644
--- a/src/applications/search/controller/PhabricatorApplicationSearchController.php
+++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php
@@ -1,324 +1,322 @@
<?php
final class PhabricatorApplicationSearchController
extends PhabricatorSearchBaseController {
private $searchEngine;
private $navigation;
private $queryKey;
public function setQueryKey($query_key) {
$this->queryKey = $query_key;
return $this;
}
protected function getQueryKey() {
return $this->queryKey;
}
public function setNavigation(AphrontSideNavFilterView $navigation) {
$this->navigation = $navigation;
return $this;
}
protected function getNavigation() {
return $this->navigation;
}
public function setSearchEngine(
PhabricatorApplicationSearchEngine $search_engine) {
$this->searchEngine = $search_engine;
return $this;
}
protected function getSearchEngine() {
return $this->searchEngine;
}
protected function validateDelegatingController() {
$parent = $this->getDelegatingController();
if (!$parent) {
throw new Exception(
"You must delegate to this controller, not invoke it directly.");
}
$engine = $this->getSearchEngine();
if (!$engine) {
throw new Exception(
"Call setEngine() before delegating to this controller!");
}
$nav = $this->getNavigation();
if (!$nav) {
throw new Exception(
"Call setNavigation() before delegating to this controller!");
}
$engine->setViewer($this->getRequest()->getUser());
$parent = $this->getDelegatingController();
$interface = 'PhabricatorApplicationSearchResultsControllerInterface';
if (!$parent instanceof $interface) {
throw new Exception(
"Delegating controller must implement '{$interface}'.");
}
}
public function processRequest() {
$this->validateDelegatingController();
$key = $this->getQueryKey();
if ($key == 'edit') {
return $this->processEditRequest();
} else {
return $this->processSearchRequest();
}
}
private function processSearchRequest() {
$parent = $this->getDelegatingController();
$request = $this->getRequest();
$user = $request->getUser();
$engine = $this->getSearchEngine();
$nav = $this->getNavigation();
if ($request->isFormPost()) {
$saved_query = $engine->buildSavedQueryFromRequest($request);
$this->saveQuery($saved_query);
return id(new AphrontRedirectResponse())->setURI(
$engine->getQueryResultsPageURI($saved_query->getQueryKey()));
}
$named_query = null;
$run_query = true;
$query_key = $this->queryKey;
if ($this->queryKey == 'advanced') {
$run_query = false;
$query_key = $request->getStr('query');
} else if (!strlen($this->queryKey)) {
$query_key = head_key($engine->loadEnabledNamedQueries());
}
if ($engine->isBuiltinQuery($query_key)) {
$saved_query = $engine->buildSavedQueryFromBuiltin($query_key);
$named_query = idx($engine->loadEnabledNamedQueries(), $query_key);
} else if ($query_key) {
$saved_query = id(new PhabricatorSavedQueryQuery())
->setViewer($user)
->withQueryKeys(array($query_key))
->executeOne();
if (!$saved_query) {
return new Aphront404Response();
}
$named_query = idx($engine->loadEnabledNamedQueries(), $query_key);
} else {
$saved_query = $engine->buildSavedQueryFromRequest($request);
}
$nav->selectFilter(
'query/'.$saved_query->getQueryKey(),
'query/advanced');
$form = id(new AphrontFormView())
->setNoShading(true)
->setUser($user);
$engine->buildSearchForm($form, $saved_query);
$errors = $engine->getErrors();
if ($errors) {
$run_query = false;
$errors = id(new AphrontErrorView())
->setTitle(pht('Query Errors'))
->setErrors($errors);
}
$submit = id(new AphrontFormSubmitControl())
->setValue(pht('Execute Query'));
if ($run_query && !$named_query && $user->isLoggedIn()) {
$submit->addCancelButton(
'/search/edit/'.$saved_query->getQueryKey().'/',
pht('Save Custom Query...'));
}
$form->appendChild($submit);
$filter_view = id(new AphrontListFilterView())->appendChild($form);
if ($run_query && $named_query) {
if ($named_query->getIsBuiltin()) {
$description = pht(
'Showing results for query "%s".',
$named_query->getQueryName());
} else {
$description = pht(
'Showing results for saved query "%s".',
$named_query->getQueryName());
}
$filter_view->setCollapsed(
pht('Edit Query...'),
pht('Hide Query'),
$description,
$this->getApplicationURI('query/advanced/?query='.$query_key));
}
$nav->appendChild($filter_view);
if ($run_query) {
$query = $engine->buildQueryFromSavedQuery($saved_query);
$pager = new AphrontCursorPagerView();
$pager->readFromRequest($request);
$pager->setPageSize($engine->getPageSize($saved_query));
$objects = $query->setViewer($request->getUser())
->executeWithCursorPager($pager);
$list = $parent->renderResultsList($objects, $saved_query);
$nav->appendChild($list);
// TODO: This is a bit hacky.
if ($list instanceof PhabricatorObjectItemListView) {
$list->setNoDataString(pht("No results found for this query."));
$list->setPager($pager);
} else {
$nav->appendChild($pager);
}
}
if ($errors) {
$nav->appendChild($errors);
}
if ($named_query) {
$title = pht('Query: %s', $named_query->getQueryName());
} else {
$title = pht('Advanced Search');
}
$crumbs = $parent
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht("Search")));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
private function processEditRequest() {
$parent = $this->getDelegatingController();
$request = $this->getRequest();
$user = $request->getUser();
$engine = $this->getSearchEngine();
$nav = $this->getNavigation();
$named_queries = $engine->loadAllNamedQueries();
$list_id = celerity_generate_unique_node_id();
$list = new PhabricatorObjectItemListView();
$list->setUser($user);
$list->setID($list_id);
Javelin::initBehavior(
'search-reorder-queries',
array(
'listID' => $list_id,
'orderURI' => '/search/order/'.get_class($engine).'/',
));
foreach ($named_queries as $named_query) {
$class = get_class($engine);
$key = $named_query->getQueryKey();
$item = id(new PhabricatorObjectItemView())
->setHeader($named_query->getQueryName())
->setHref($engine->getQueryResultsPageURI($key));
if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) {
$icon = 'new';
} else {
$icon = 'delete';
}
$item->addAction(
id(new PHUIListItemView())
->setIcon($icon)
->setHref('/search/delete/'.$key.'/'.$class.'/')
->setWorkflow(true));
if ($named_query->getIsBuiltin()) {
if ($named_query->getIsDisabled()) {
$item->addIcon('delete-grey', pht('Disabled'));
$item->setDisabled(true);
} else {
$item->addIcon('lock-grey', pht('Builtin'));
}
} else {
$item->addAction(
id(new PHUIListItemView())
->setIcon('edit')
->setHref('/search/edit/'.$key.'/'));
}
$item->setGrippable(true);
$item->addSigil('named-query');
$item->setMetadata(
array(
'queryKey' => $named_query->getQueryKey(),
));
$list->addItem($item);
}
$list->setNoDataString(pht('No saved queries.'));
$crumbs = $parent
->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht("Saved Queries"))
->setHref($engine->getQueryManagementURI()));
$nav->selectFilter('query/edit');
$nav->setCrumbs($crumbs);
$nav->appendChild($list);
return $parent->buildApplicationPage(
$nav,
array(
'title' => pht("Saved Queries"),
'device' => true,
- 'dust' => true,
));
}
private function saveQuery(PhabricatorSavedQuery $query) {
$query->setEngineClassName(get_class($this->getSearchEngine()));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
try {
$query->save();
} catch (AphrontQueryDuplicateKeyException $ex) {
// Ignore, this is just a repeated search.
}
unset($unguarded);
}
protected function buildApplicationMenu() {
return $this->getDelegatingController()->buildApplicationMenu();
}
}
diff --git a/src/applications/search/controller/PhabricatorSearchController.php b/src/applications/search/controller/PhabricatorSearchController.php
index 7312ade9b3..62b7eca832 100644
--- a/src/applications/search/controller/PhabricatorSearchController.php
+++ b/src/applications/search/controller/PhabricatorSearchController.php
@@ -1,288 +1,287 @@
<?php
/**
* @group search
*/
final class PhabricatorSearchController
extends PhabricatorSearchBaseController {
private $key;
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::jumpPostResponse($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()));
$author_value = mpull($author_value, 'getFullName', 'getPHID');
$owner_value = array_select_keys(
$handles,
$query->getParameter('owner', array()));
$owner_value = mpull($owner_value, 'getFullName', 'getPHID');
$subscribers_value = array_select_keys(
$handles,
$query->getParameter('subscribers', array()));
$subscribers_value = mpull($subscribers_value, 'getFullName', 'getPHID');
$project_value = array_select_keys(
$handles,
$query->getParameter('project', array()));
$project_value = mpull($project_value, 'getFullName', 'getPHID');
$search_form = new AphrontFormView();
$search_form
->setUser($user)
->setAction('/search/')
->setNoShading(true)
->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) {
$loader = id(new PhabricatorObjectHandleData($results))
->setViewer($user);
$handles = $loader->loadHandles();
$objects = $loader->loadObjects();
$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>',
phutil_implode_html("\n", $results),
$pager->render());
} else {
$results = hsprintf(
'<div class="phabricator-search-result-list">'.
'<p class="phabricator-search-no-results">No search results.</p>'.
'</div>');
}
$results = id(new PHUIBoxView())
->addMargin(PHUI::MARGIN_LARGE)
->addPadding(PHUI::PADDING_LARGE)
->setShadow(true)
->appendChild($results)
->addClass('phabricator-search-result-box');
} else {
$results = null;
}
return $this->buildApplicationPage(
array(
$search_panel,
$results,
),
array(
'title' => pht('Search Results'),
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/search/controller/PhabricatorSearchEditController.php b/src/applications/search/controller/PhabricatorSearchEditController.php
index e9753d314a..920a9ec33c 100644
--- a/src/applications/search/controller/PhabricatorSearchEditController.php
+++ b/src/applications/search/controller/PhabricatorSearchEditController.php
@@ -1,114 +1,113 @@
<?php
/**
* @group search
*/
final class PhabricatorSearchEditController
extends PhabricatorSearchBaseController {
private $queryKey;
public function willProcessRequest(array $data) {
$this->queryKey = idx($data, 'queryKey');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$saved_query = id(new PhabricatorSavedQueryQuery())
->setViewer($user)
->withQueryKeys(array($this->queryKey))
->executeOne();
if (!$saved_query) {
return new Aphront404Response();
}
$engine = $saved_query->newEngine()->setViewer($user);
$complete_uri = $engine->getQueryManagementURI();
$cancel_uri = $complete_uri;
$named_query = id(new PhabricatorNamedQueryQuery())
->setViewer($user)
->withQueryKeys(array($saved_query->getQueryKey()))
->withUserPHIDs(array($user->getPHID()))
->executeOne();
if (!$named_query) {
$named_query = id(new PhabricatorNamedQuery())
->setUserPHID($user->getPHID())
->setQueryKey($saved_query->getQueryKey())
->setEngineClassName($saved_query->getEngineClassName());
// If we haven't saved the query yet, this is a "Save..." operation, so
// take the user back to the query if they cancel instead of back to the
// management interface.
$cancel_uri = $engine->getQueryResultsPageURI(
$saved_query->getQueryKey());
}
$e_name = true;
$errors = array();
if ($request->isFormPost()) {
$named_query->setQueryName($request->getStr('name'));
if (!strlen($named_query->getQueryName())) {
$e_name = pht('Required');
$errors[] = pht('You must name the query.');
} else {
$e_name = null;
}
if (!$errors) {
$named_query->save();
return id(new AphrontRedirectResponse())->setURI($complete_uri);
}
}
if ($errors) {
$errors = id(new AphrontErrorView())
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($user);
$form->appendChild(
id(new AphrontFormTextControl())
->setName('name')
->setLabel(pht('Query Name'))
->setValue($named_query->getQueryName())
->setError($e_name));
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Query'))
->addCancelButton($cancel_uri));
if ($named_query->getID()) {
$title = pht('Edit Saved Query');
} else {
$title = pht('Save Query');
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$errors,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/search/controller/PhabricatorSearchHovercardController.php b/src/applications/search/controller/PhabricatorSearchHovercardController.php
index 7a9fa27f90..8bac125718 100644
--- a/src/applications/search/controller/PhabricatorSearchHovercardController.php
+++ b/src/applications/search/controller/PhabricatorSearchHovercardController.php
@@ -1,65 +1,64 @@
<?php
/**
* @group search
*/
final class PhabricatorSearchHovercardController
extends PhabricatorSearchBaseController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = $request->getArr('phids');
$handle_data = new PhabricatorObjectHandleData($phids);
$handle_data->setViewer($user);
$handles = $handle_data->loadHandles();
$objects = $handle_data->loadObjects();
$cards = array();
foreach ($phids as $phid) {
$handle = $handles[$phid];
$hovercard = new PhabricatorHovercardView();
$hovercard->setObjectHandle($handle);
// Send it to the other side of the world, thanks to PhutilEventEngine
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_UI_DIDRENDERHOVERCARD,
array(
'hovercard' => $hovercard,
'handle' => $handle,
'object' => idx($objects, $phid),
));
$event->setUser($user);
PhutilEventEngine::dispatchEvent($event);
$cards[$phid] = $hovercard;
}
// Browser-friendly for non-Ajax requests
if (!$request->isAjax()) {
foreach ($cards as $key => $hovercard) {
$cards[$key] = phutil_tag('div',
array(
'class' => 'ml',
),
$hovercard);
}
return $this->buildApplicationPage(
$cards,
array(
- 'dust' => true,
));
} else {
return id(new AphrontAjaxResponse())->setContent(
array(
'cards' => $cards,
));
}
}
}
diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php
index 57dabf112d..2b20285b73 100644
--- a/src/applications/settings/controller/PhabricatorSettingsMainController.php
+++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php
@@ -1,95 +1,94 @@
<?php
final class PhabricatorSettingsMainController
extends PhabricatorController {
private $key;
public function willProcessRequest(array $data) {
$this->key = idx($data, 'key');
}
public function processRequest() {
$request = $this->getRequest();
$panels = $this->buildPanels();
$nav = $this->renderSideNav($panels);
$key = $nav->selectFilter($this->key, head($panels)->getPanelKey());
$panel = $panels[$key];
$response = $panel->processRequest($request);
if ($response instanceof AphrontResponse) {
return $response;
}
$nav->appendChild($response);
return $this->buildApplicationPage(
$nav,
array(
'title' => $panel->getPanelName(),
'device' => true,
- 'dust' => true,
));
}
private function buildPanels() {
$panel_specs = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorSettingsPanel')
->setConcreteOnly(true)
->selectAndLoadSymbols();
$panels = array();
foreach ($panel_specs as $spec) {
$class = newv($spec['name'], array());
$panels[] = $class->buildPanels();
}
$panels = array_mergev($panels);
$panels = mpull($panels, null, 'getPanelKey');
$result = array();
foreach ($panels as $key => $panel) {
if (!$panel->isEnabled()) {
continue;
}
if (!empty($result[$key])) {
throw new Exception(pht(
"Two settings panels share the same panel key ('%s'): %s, %s.",
$key,
get_class($panel),
get_class($result[$key])));
}
$result[$key] = $panel;
}
$result = msort($result, 'getPanelSortKey');
return $result;
}
private function renderSideNav(array $panels) {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI('/panel/')));
$group = null;
foreach ($panels as $panel) {
if ($panel->getPanelGroup() != $group) {
$group = $panel->getPanelGroup();
$nav->addLabel($group);
}
$nav->addFilter($panel->getPanelKey(), $panel->getPanelName());
}
return $nav;
}
public function buildApplicationMenu() {
$panels = $this->buildPanels();
return $this->renderSideNav($panels)->getMenu();
}
}
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
index 75c7740dcf..1c732c88a0 100644
--- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php
@@ -1,248 +1,247 @@
<?php
/**
* @group slowvote
*/
final class PhabricatorSlowvoteEditController
extends PhabricatorSlowvoteController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->id) {
$poll = id(new PhabricatorSlowvoteQuery())
->setViewer($user)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$poll) {
return new Aphront404Response();
}
$is_new = false;
} else {
$poll = id(new PhabricatorSlowvotePoll())
->setAuthorPHID($user->getPHID())
->setViewPolicy(PhabricatorPolicies::POLICY_USER);
$is_new = true;
}
$e_question = true;
$e_response = true;
$errors = array();
$v_question = $poll->getQuestion();
$v_description = $poll->getDescription();
$v_responses = $poll->getResponseVisibility();
$v_shuffle = $poll->getShuffle();
$responses = $request->getArr('response');
if ($request->isFormPost()) {
$v_question = $request->getStr('question');
$v_description = $request->getStr('description');
$v_responses = (int)$request->getInt('responses');
$v_shuffle = (int)$request->getBool('shuffle');
if ($is_new) {
$poll->setMethod($request->getInt('method'));
}
if (!strlen($v_question)) {
$e_question = pht('Required');
$errors[] = pht('You must ask a poll question.');
} else {
$e_question = null;
}
if ($is_new) {
$responses = array_filter($responses);
if (empty($responses)) {
$errors[] = pht('You must offer at least one response.');
$e_response = pht('Required');
} else {
$e_response = null;
}
}
$xactions = array();
$template = id(new PhabricatorSlowvoteTransaction());
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_QUESTION)
->setNewValue($v_question);
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION)
->setNewValue($v_description);
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_RESPONSES)
->setNewValue($v_responses);
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_SHUFFLE)
->setNewValue($v_shuffle);
if (empty($errors)) {
$editor = id(new PhabricatorSlowvoteEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
$xactions = $editor->applyTransactions($poll, $xactions);
if ($is_new) {
$poll->save();
foreach ($responses as $response) {
$option = new PhabricatorSlowvoteOption();
$option->setName($response);
$option->setPollID($poll->getID());
$option->save();
}
}
return id(new AphrontRedirectResponse())
->setURI('/V'.$poll->getID());
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Form Errors'));
$error_view->setErrors($errors);
}
$instructions =
phutil_tag(
'p',
array(
'class' => 'aphront-form-instructions',
),
pht('Resolve issues and build consensus through '.
'protracted deliberation.'));
$form = id(new AphrontFormView())
->setUser($user)
->setFlexible(true)
->appendChild($instructions)
->appendChild(
id(new AphrontFormTextAreaControl())
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
->setLabel(pht('Question'))
->setName('question')
->setValue($v_question)
->setError($e_question))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Description'))
->setName('description')
->setValue($v_description));
if ($is_new) {
for ($ii = 0; $ii < 10; $ii++) {
$n = ($ii + 1);
$response = id(new AphrontFormTextControl())
->setLabel(pht("Response %d", $n))
->setName('response[]')
->setValue(idx($responses, $ii, ''));
if ($ii == 0) {
$response->setError($e_response);
}
$form->appendChild($response);
}
}
$poll_type_options = array(
PhabricatorSlowvotePoll::METHOD_PLURALITY =>
pht('Plurality (Single Choice)'),
PhabricatorSlowvotePoll::METHOD_APPROVAL =>
pht('Approval (Multiple Choice)'),
);
$response_type_options = array(
PhabricatorSlowvotePoll::RESPONSES_VISIBLE
=> pht('Allow anyone to see the responses'),
PhabricatorSlowvotePoll::RESPONSES_VOTERS
=> pht('Require a vote to see the responses'),
PhabricatorSlowvotePoll::RESPONSES_OWNER
=> pht('Only I can see the responses'),
);
if ($is_new) {
$form->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Vote Type'))
->setName('method')
->setValue($poll->getMethod())
->setOptions($poll_type_options));
} else {
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Vote Type'))
->setValue(idx($poll_type_options, $poll->getMethod())));
}
if ($is_new) {
$title = pht('Create Slowvote');
$button = pht('Create');
$cancel_uri = $this->getApplicationURI();
} else {
$title = pht('Edit %s', 'V'.$poll->getID());
$button = pht('Save Changes');
$cancel_uri = '/V'.$poll->getID();
}
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Responses'))
->setName('responses')
->setValue($v_responses)
->setOptions($response_type_options))
->appendChild(
id(new AphrontFormCheckboxControl())
->setLabel(pht('Shuffle'))
->addCheckbox(
'shuffle',
1,
pht('Show choices in random order.'),
$v_shuffle))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($button)
->addCancelButton($cancel_uri));
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$error_view,
$form,
),
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
index 03e60cf050..27817151c7 100644
--- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
+++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php
@@ -1,196 +1,195 @@
<?php
/**
* @group slowvote
*/
final class PhabricatorSlowvotePollController
extends PhabricatorSlowvoteController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$poll = id(new PhabricatorSlowvoteQuery())
->setViewer($user)
->withIDs(array($this->id))
->needOptions(true)
->needChoices(true)
->needViewerChoices(true)
->executeOne();
if (!$poll) {
return new Aphront404Response();
}
$poll_view = id(new SlowvoteEmbedView())
->setHeadless(true)
->setUser($user)
->setPoll($poll);
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'pollID' => $poll->getID(),
'contentHTML' => $poll_view->render(),
));
}
$header = id(new PhabricatorHeaderView())
->setHeader($poll->getQuestion());
$xaction_header = id(new PhabricatorHeaderView())
->setHeader(pht('Ongoing Deliberations'));
$actions = $this->buildActionView($poll);
$properties = $this->buildPropertyView($poll);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('V'.$poll->getID()));
$xactions = $this->buildTransactions($poll);
$add_comment = $this->buildCommentForm($poll);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
phutil_tag(
'div',
array(
'class' => 'ml',
),
$poll_view),
$xaction_header,
$xactions,
$add_comment,
),
array(
'title' => 'V'.$poll->getID().' '.$poll->getQuestion(),
'device' => true,
- 'dust' => true,
'pageObjects' => array($poll->getPHID()),
));
}
private function buildActionView(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($poll);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$poll,
PhabricatorPolicyCapability::CAN_EDIT);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Poll'))
->setIcon('edit')
->setHref($this->getApplicationURI('edit/'.$poll->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $view;
}
private function buildPropertyView(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($poll);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$poll);
$view->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$view->invokeWillRenderEvent();
if (strlen($poll->getDescription())) {
$view->addTextContent(
$output = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent(
$poll->getDescription()),
'default',
$viewer));
}
return $view;
}
private function buildTransactions(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$xactions = id(new PhabricatorSlowvoteTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($poll->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($viewer)
->setObjectPHID($poll->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
return $timeline;
}
private function buildCommentForm(PhabricatorSlowvotePoll $poll) {
$viewer = $this->getRequest()->getUser();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$add_comment_header = id(new PhabricatorHeaderView())
->setHeader(
$is_serious
? pht('Add Comment')
: pht('Enter Deliberations'));
$submit_button_name = $is_serious
? pht('Add Comment')
: pht('Perhaps');
$draft = PhabricatorDraft::newFromUserAndKey($viewer, $poll->getPHID());
$add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($poll->getPHID())
->setDraft($draft)
->setAction($this->getApplicationURI('/comment/'.$poll->getID().'/'))
->setSubmitButtonName($submit_button_name);
return array(
$add_comment_header,
$add_comment_form,
);
}
}
diff --git a/src/applications/tokens/controller/PhabricatorTokenGivenController.php b/src/applications/tokens/controller/PhabricatorTokenGivenController.php
index 7968a0563c..9c68a835ed 100644
--- a/src/applications/tokens/controller/PhabricatorTokenGivenController.php
+++ b/src/applications/tokens/controller/PhabricatorTokenGivenController.php
@@ -1,79 +1,78 @@
<?php
final class PhabricatorTokenGivenController extends PhabricatorTokenController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request);
$tokens_given = id(new PhabricatorTokenGivenQuery())
->setViewer($user)
->executeWithCursorPager($pager);
$handles = array();
if ($tokens_given) {
$object_phids = mpull($tokens_given, 'getObjectPHID');
$user_phids = mpull($tokens_given, 'getAuthorPHID');
$handle_phids = array_merge($object_phids, $user_phids);
$handles = id(new PhabricatorObjectHandleData($handle_phids))
->setViewer($user)
->loadHandles();
}
$tokens = array();
if ($tokens_given) {
$token_phids = mpull($tokens_given, 'getTokenPHID');
$tokens = id(new PhabricatorTokenQuery())
->setViewer($user)
->withPHIDs($token_phids)
->execute();
$tokens = mpull($tokens, null, 'getPHID');
}
$list = new PhabricatorObjectItemListView();
foreach ($tokens_given as $token_given) {
$handle = $handles[$token_given->getObjectPHID()];
$token = idx($tokens, $token_given->getTokenPHID());
$item = id(new PhabricatorObjectItemView());
$item->setHeader($handle->getFullName());
$item->setHref($handle->getURI());
$item->addAttribute($token->renderIcon());
$item->addAttribute(
pht(
'Given by %s on %s',
$handles[$token_given->getAuthorPHID()]->renderLink(),
phabricator_date($token_given->getDateCreated(), $user)));
$list->addItem($item);
}
$list->setPager($pager);
$title = pht('Tokens Given');
$nav = $this->buildSideNav();
$nav->setCrumbs(
$this->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)));
$nav->selectFilter('given/');
$nav->appendChild($list);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/tokens/controller/PhabricatorTokenLeaderController.php b/src/applications/tokens/controller/PhabricatorTokenLeaderController.php
index fe2b7e5eb3..9c33244294 100644
--- a/src/applications/tokens/controller/PhabricatorTokenLeaderController.php
+++ b/src/applications/tokens/controller/PhabricatorTokenLeaderController.php
@@ -1,61 +1,60 @@
<?php
final class PhabricatorTokenLeaderController
extends PhabricatorTokenController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'page');
$pager->setOffset($request->getInt('page'));
$query = id(new PhabricatorTokenReceiverQuery());
$objects = $query->setViewer($user)->executeWithOffsetPager($pager);
$counts = $query->getTokenCounts();
$handles = array();
$phids = array();
if ($counts) {
$phids = mpull($objects, 'getPHID');
$handles = id(new PhabricatorObjectHandleData($phids))
->setViewer($user)
->loadHandles();
}
$list = new PhabricatorObjectItemListView();
foreach ($phids as $object) {
$count = idx($counts, $object, 0);
$item = id(new PhabricatorObjectItemView());
$handle = $handles[$object];
$item->setHeader($handle->getFullName());
$item->setHref($handle->getURI());
$item->addAttribute(pht('Tokens: %s', $count));
$list->addItem($item);
}
$title = pht('Token Leader Board');
$nav = $this->buildSideNav();
$nav->setCrumbs(
$this->buildApplicationCrumbs()
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)));
$nav->selectFilter('leaders/');
$nav->appendChild($list);
$nav->appendChild($pager);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
- 'dust' => true
));
}
}
diff --git a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
index e1fe0f0b8c..f386982964 100644
--- a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
+++ b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
@@ -1,66 +1,65 @@
<?php
final class PhabricatorUIExampleRenderController extends PhabricatorController {
private $class;
public function willProcessRequest(array $data) {
$this->class = idx($data, 'class');
}
public function processRequest() {
$classes = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorUIExample')
->setConcreteOnly(true)
->selectAndLoadSymbols();
$classes = ipull($classes, 'name', 'name');
foreach ($classes as $class => $ignored) {
$classes[$class] = newv($class, array());
}
$classes = msort($classes, 'getName');
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI('view/')));
foreach ($classes as $class => $obj) {
$name = $obj->getName();
$nav->addFilter($class, $name);
}
$selected = $nav->selectFilter($this->class, head_key($classes));
$example = $classes[$selected];
$example->setRequest($this->getRequest());
$result = $example->renderExample();
if ($result instanceof AphrontResponse) {
// This allows examples to generate dialogs, etc., for demonstration.
return $result;
}
require_celerity_resource('phabricator-ui-example-css');
$nav->appendChild(hsprintf(
'<div class="phabricator-ui-example-header">'.
'<h1 class="phabricator-ui-example-name">%s (%s)</h1>'.
'<p class="phabricator-ui-example-description">%s</p>'.
'</div>',
$example->getName(),
get_class($example),
$example->getDescription()));
$nav->appendChild($result);
return $this->buildApplicationPage(
$nav,
array(
'title' => 'UI Example',
'device' => true,
- 'dust' => true,
));
}
}
diff --git a/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php b/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php
index facd9d2c4c..03a158cd3a 100644
--- a/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php
+++ b/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php
@@ -1,93 +1,92 @@
<?php
final class PhabricatorXHProfSampleListController
extends PhabricatorXHProfController {
private $view;
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view', 'all');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$pager = new AphrontPagerView();
$pager->setOffset($request->getInt('page'));
switch ($this->view) {
case 'sampled':
$clause = '`sampleRate` > 0';
$show_type = false;
break;
case 'my-runs':
$clause = qsprintf(
id(new PhabricatorXHProfSample())->establishConnection('r'),
'`sampleRate` = 0 AND `userPHID` = %s',
$request->getUser()->getPHID());
$show_type = false;
break;
case 'manual':
$clause = '`sampleRate` = 0';
$show_type = false;
break;
case 'all':
default:
$clause = '1 = 1';
$show_type = true;
break;
}
$samples = id(new PhabricatorXHProfSample())->loadAllWhere(
'%Q ORDER BY id DESC LIMIT %d, %d',
$clause,
$pager->getOffset(),
$pager->getPageSize() + 1);
$samples = $pager->sliceResults($samples);
$pager->setURI($request->getRequestURI(), 'page');
$list = new PhabricatorObjectItemListView();
foreach ($samples as $sample) {
$file_phid = $sample->getFilePHID();
$item = id(new PhabricatorObjectItemView())
->setObjectName($sample->getID())
->setHeader($sample->getRequestPath())
->setHref($this->getApplicationURI('profile/'.$file_phid.'/'))
->addAttribute(
number_format($sample->getUsTotal())." \xCE\xBCs");
if ($sample->getController()) {
$item->addAttribute($sample->getController());
}
$item->addAttribute($sample->getHostName());
$rate = $sample->getSampleRate();
if ($rate == 0) {
$item->addIcon('flag-6', pht('Manual Run'));
} else {
$item->addIcon('flag-7', pht('Sampled (1/%d)', $rate));
}
$item->addIcon(
'none',
phabricator_datetime($sample->getDateCreated(), $user));
$list->addItem($item);
}
$list->setPager($pager);
return $this->buildStandardPageResponse(
$list,
array(
'title' => pht('XHProf Samples'),
'device' => true,
- 'dust' => true,
));
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Mar 16, 10:04 PM (10 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
963391
Default Alt Text
(731 KB)

Event Timeline