Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php
index fc89d310e1..3da467e4f4 100644
--- a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php
+++ b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php
@@ -1,116 +1,117 @@
<?php
final class PhabricatorConduitTokensSettingsPanel
extends PhabricatorSettingsPanel {
public function isManagementPanel() {
if ($this->getUser()->getIsMailingList()) {
return false;
}
return true;
}
public function getPanelKey() {
return 'apitokens';
}
public function getPanelName() {
return pht('Conduit API Tokens');
}
public function getPanelGroupKey() {
return PhabricatorSettingsLogsPanelGroup::PANELGROUPKEY;
}
public function isEnabled() {
return true;
}
public function processRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$user = $this->getUser();
$tokens = id(new PhabricatorConduitTokenQuery())
->setViewer($viewer)
->withObjectPHIDs(array($user->getPHID()))
->withExpired(false)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
$rows = array();
foreach ($tokens as $token) {
$rows[] = array(
javelin_tag(
'a',
array(
'href' => '/conduit/token/edit/'.$token->getID().'/',
'sigil' => 'workflow',
),
$token->getPublicTokenName()),
PhabricatorConduitToken::getTokenTypeName($token->getTokenType()),
phabricator_datetime($token->getDateCreated(), $viewer),
($token->getExpires()
? phabricator_datetime($token->getExpires(), $viewer)
: pht('Never')),
javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => '/conduit/token/terminate/'.$token->getID().'/',
'sigil' => 'workflow',
),
pht('Terminate')),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(pht("You don't have any active API tokens."));
$table->setHeaders(
array(
pht('Token'),
pht('Type'),
pht('Created'),
pht('Expires'),
null,
));
$table->setColumnClasses(
array(
'wide pri',
'',
'right',
'right',
'action',
));
$generate_button = id(new PHUIButtonView())
->setText(pht('Generate API Token'))
->setHref('/conduit/token/edit/?objectPHID='.$user->getPHID())
->setTag('a')
->setWorkflow(true)
->setIcon('fa-plus');
$terminate_button = id(new PHUIButtonView())
->setText(pht('Terminate All Tokens'))
->setHref('/conduit/token/terminate/?objectPHID='.$user->getPHID())
->setTag('a')
->setWorkflow(true)
->setIcon('fa-exclamation-triangle');
$header = id(new PHUIHeaderView())
->setHeader(pht('Active API Tokens'))
->addActionLink($generate_button)
->addActionLink($terminate_button);
$panel = id(new PHUIObjectBoxView())
->setHeader($header)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
return $panel;
}
}
diff --git a/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php b/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php
index 596f3decc9..cfc0bc91a8 100644
--- a/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php
+++ b/src/applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php
@@ -1,142 +1,143 @@
<?php
final class PhabricatorOAuthServerAuthorizationsSettingsPanel
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'oauthorizations';
}
public function getPanelName() {
return pht('OAuth Authorizations');
}
public function getPanelGroupKey() {
return PhabricatorSettingsLogsPanelGroup::PANELGROUPKEY;
}
public function isEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorOAuthServerApplication');
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
// TODO: It would be nice to simply disable this panel, but we can't do
// viewer-based checks for enabled panels right now.
$app_class = 'PhabricatorOAuthServerApplication';
$installed = PhabricatorApplication::isClassInstalledForViewer(
$app_class,
$viewer);
if (!$installed) {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('OAuth Not Available'))
->appendParagraph(
pht('You do not have access to OAuth authorizations.'))
->addCancelButton('/settings/');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$authorizations = id(new PhabricatorOAuthClientAuthorizationQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->execute();
$authorizations = mpull($authorizations, null, 'getID');
$panel_uri = $this->getPanelURI();
$revoke = $request->getInt('revoke');
if ($revoke) {
if (empty($authorizations[$revoke])) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$authorizations[$revoke]->delete();
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Revoke Authorization?'))
->appendParagraph(
pht(
'This application will no longer be able to access Phabricator '.
'on your behalf.'))
->addSubmitButton(pht('Revoke Authorization'))
->addCancelButton($panel_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
$highlight = $request->getInt('id');
$rows = array();
$rowc = array();
foreach ($authorizations as $authorization) {
if ($highlight == $authorization->getID()) {
$rowc[] = 'highlighted';
} else {
$rowc[] = null;
}
$button = javelin_tag(
'a',
array(
'href' => $this->getPanelURI('?revoke='.$authorization->getID()),
'class' => 'small button button-grey',
'sigil' => 'workflow',
),
pht('Revoke'));
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $authorization->getClient()->getViewURI(),
),
$authorization->getClient()->getName()),
$authorization->getScopeString(),
phabricator_datetime($authorization->getDateCreated(), $viewer),
phabricator_datetime($authorization->getDateModified(), $viewer),
$button,
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(
pht("You haven't authorized any OAuth applications."));
$table->setRowClasses($rowc);
$table->setHeaders(
array(
pht('Application'),
pht('Scope'),
pht('Created'),
pht('Updated'),
null,
));
$table->setColumnClasses(
array(
'pri',
'wide',
'right',
'right',
'action',
));
$header = id(new PHUIHeaderView())
->setHeader(pht('OAuth Application Authorizations'));
$panel = id(new PHUIObjectBoxView())
->setHeader($header)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
return $panel;
}
}
diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php
index 841529a051..4c1bfab6f1 100644
--- a/src/applications/settings/controller/PhabricatorSettingsMainController.php
+++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php
@@ -1,225 +1,237 @@
<?php
final class PhabricatorSettingsMainController
extends PhabricatorController {
private $user;
private $builtinKey;
private $preferences;
private function getUser() {
return $this->user;
}
private function isSelf() {
$user = $this->getUser();
if (!$user) {
return false;
}
$user_phid = $user->getPHID();
$viewer_phid = $this->getViewer()->getPHID();
return ($viewer_phid == $user_phid);
}
private function isTemplate() {
return ($this->builtinKey !== null);
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
// Redirect "/panel/XYZ/" to the viewer's personal settings panel. This
// was the primary URI before global settings were introduced and allows
// generation of viewer-agnostic URIs for email and logged-out users.
$panel = $request->getURIData('panel');
if ($panel) {
$panel = phutil_escape_uri($panel);
$username = $viewer->getUsername();
$panel_uri = "/user/{$username}/page/{$panel}/";
$panel_uri = $this->getApplicationURI($panel_uri);
return id(new AphrontRedirectResponse())->setURI($panel_uri);
}
$username = $request->getURIData('username');
$builtin = $request->getURIData('builtin');
$key = $request->getURIData('pageKey');
if ($builtin) {
$this->builtinKey = $builtin;
$preferences = id(new PhabricatorUserPreferencesQuery())
->setViewer($viewer)
->withBuiltinKeys(array($builtin))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$preferences) {
$preferences = id(new PhabricatorUserPreferences())
->attachUser(null)
->setBuiltinKey($builtin);
}
} else {
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withUsernames(array($username))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$preferences = PhabricatorUserPreferences::loadUserPreferences($user);
$this->user = $user;
}
if (!$preferences) {
return new Aphront404Response();
}
PhabricatorPolicyFilter::requireCapability(
$viewer,
$preferences,
PhabricatorPolicyCapability::CAN_EDIT);
$this->preferences = $preferences;
$panels = $this->buildPanels($preferences);
$nav = $this->renderSideNav($panels);
$key = $nav->selectFilter($key, head($panels)->getPanelKey());
$panel = $panels[$key]
->setController($this)
->setNavigation($nav);
$response = $panel->processRequest($request);
if (($response instanceof AphrontResponse) ||
($response instanceof AphrontResponseProducerInterface)) {
return $response;
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($panel->getPanelName());
+ $crumbs->setBorder(true);
+
+ if ($this->user) {
+ $header_text = pht('Edit Settings (%s)', $user->getUserName());
+ } else {
+ $header_text = pht('Edit Global Settings');
+ }
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader($header_text)
+ ->setHeaderIcon('fa-pencil');
$title = $panel->getPanelName();
$view = id(new PHUITwoColumnView())
- ->setNavigation($nav)
- ->setMainColumn($response);
+ ->setHeader($header)
+ ->setFooter($response);
return $this->newPage()
->setTitle($title)
+ ->setNavigation($nav)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildPanels(PhabricatorUserPreferences $preferences) {
$viewer = $this->getViewer();
$panels = PhabricatorSettingsPanel::getAllDisplayPanels();
$result = array();
foreach ($panels as $key => $panel) {
$panel
->setPreferences($preferences)
->setViewer($viewer);
if ($this->user) {
$panel->setUser($this->user);
}
if (!$panel->isEnabled()) {
continue;
}
if ($this->isTemplate()) {
if (!$panel->isTemplatePanel()) {
continue;
}
} else {
if (!$this->isSelf() && !$panel->isManagementPanel()) {
continue;
}
if ($this->isSelf() && !$panel->isUserPanel()) {
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;
}
if (!$result) {
throw new Exception(pht('No settings panels are available.'));
}
return $result;
}
private function renderSideNav(array $panels) {
$nav = new AphrontSideNavFilterView();
if ($this->isTemplate()) {
$base_uri = 'builtin/'.$this->builtinKey.'/page/';
} else {
$user = $this->getUser();
$base_uri = 'user/'.$user->getUsername().'/page/';
}
$nav->setBaseURI(new PhutilURI($this->getApplicationURI($base_uri)));
$group_key = null;
foreach ($panels as $panel) {
if ($panel->getPanelGroupKey() != $group_key) {
$group_key = $panel->getPanelGroupKey();
$group = $panel->getPanelGroup();
$nav->addLabel($group->getPanelGroupName());
}
$nav->addFilter($panel->getPanelKey(), $panel->getPanelName());
}
return $nav;
}
public function buildApplicationMenu() {
if ($this->preferences) {
$panels = $this->buildPanels($this->preferences);
return $this->renderSideNav($panels)->getMenu();
}
return parent::buildApplicationMenu();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$user = $this->getUser();
if (!$this->isSelf() && $user) {
$username = $user->getUsername();
$crumbs->addTextCrumb($username, "/p/{$username}/");
}
return $crumbs;
}
}
diff --git a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php
index 4bcfa08d1d..e12654e1a4 100644
--- a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php
+++ b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php
@@ -1,236 +1,241 @@
<?php
final class PhabricatorSettingsEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'settings.settings';
private $isSelfEdit;
private $profileURI;
public function setIsSelfEdit($is_self_edit) {
$this->isSelfEdit = $is_self_edit;
return $this;
}
public function getIsSelfEdit() {
return $this->isSelfEdit;
}
public function setProfileURI($profile_uri) {
$this->profileURI = $profile_uri;
return $this;
}
public function getProfileURI() {
return $this->profileURI;
}
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Settings');
}
public function getSummaryHeader() {
return pht('Edit Settings Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit settings.');
}
public function getEngineApplicationClass() {
return 'PhabricatorSettingsApplication';
}
protected function newEditableObject() {
return new PhabricatorUserPreferences();
}
protected function newObjectQuery() {
return new PhabricatorUserPreferencesQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Settings');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Settings');
}
protected function getObjectEditTitleText($object) {
- return pht('Edit Settings');
+ $user = $object->getUser();
+ if ($user) {
+ return pht('Edit Settings (%s)', $user->getUserName());
+ } else {
+ return pht('Edit Global Settings');
+ }
}
protected function getObjectEditShortText($object) {
if (!$object->getUser()) {
return pht('Global Defaults');
} else {
if ($this->getIsSelfEdit()) {
return pht('Personal Settings');
} else {
return pht('Account Settings');
}
}
}
protected function getObjectCreateShortText() {
return pht('Create Settings');
}
protected function getObjectName() {
$page = $this->getSelectedPage();
if ($page) {
return $page->getLabel();
}
return pht('Settings');
}
protected function getEditorURI() {
throw new PhutilMethodNotImplementedException();
}
protected function getObjectCreateCancelURI($object) {
return '/settings/';
}
protected function getObjectViewURI($object) {
return $object->getEditURI();
}
protected function getCreateNewObjectPolicy() {
return PhabricatorPolicies::POLICY_ADMIN;
}
public function getEffectiveObjectEditDoneURI($object) {
return parent::getEffectiveObjectViewURI($object).'saved/';
}
public function getEffectiveObjectEditCancelURI($object) {
if (!$object->getUser()) {
return '/settings/';
}
if ($this->getIsSelfEdit()) {
return null;
}
if ($this->getProfileURI()) {
return $this->getProfileURI();
}
return parent::getEffectiveObjectEditCancelURI($object);
}
protected function newPages($object) {
$viewer = $this->getViewer();
$user = $object->getUser();
$panels = PhabricatorSettingsPanel::getAllPanels();
foreach ($panels as $key => $panel) {
if (!($panel instanceof PhabricatorEditEngineSettingsPanel)) {
unset($panels[$key]);
continue;
}
$panel->setViewer($viewer);
if ($user) {
$panel->setUser($user);
}
}
$pages = array();
$uris = array();
foreach ($panels as $key => $panel) {
$uris[$key] = $panel->getPanelURI();
$page = $panel->newEditEnginePage();
if (!$page) {
continue;
}
$pages[] = $page;
}
$more_pages = array(
id(new PhabricatorEditPage())
->setKey('extra')
->setLabel(pht('Extra Settings'))
->setIsDefault(true),
);
foreach ($more_pages as $page) {
$pages[] = $page;
}
return $pages;
}
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
$settings = PhabricatorSetting::getAllEnabledSettings($viewer);
foreach ($settings as $key => $setting) {
$setting = clone $setting;
$setting->setViewer($viewer);
$settings[$key] = $setting;
}
$settings = msortv($settings, 'getSettingOrderVector');
$fields = array();
foreach ($settings as $setting) {
foreach ($setting->newCustomEditFields($object) as $field) {
$fields[] = $field;
}
}
return $fields;
}
protected function getValidationExceptionShortMessage(
PhabricatorApplicationTransactionValidationException $ex,
PhabricatorEditField $field) {
// Settings fields all have the same transaction type so we need to make
// sure the transaction is changing the same setting before matching an
// error to a given field.
$xaction_type = $field->getTransactionType();
if ($xaction_type == PhabricatorUserPreferencesTransaction::TYPE_SETTING) {
$property = PhabricatorUserPreferencesTransaction::PROPERTY_SETTING;
$field_setting = idx($field->getMetadata(), $property);
foreach ($ex->getErrors() as $error) {
if ($error->getType() !== $xaction_type) {
continue;
}
$xaction = $error->getTransaction();
if (!$xaction) {
continue;
}
$xaction_setting = $xaction->getMetadataValue($property);
if ($xaction_setting != $field_setting) {
continue;
}
$short_message = $error->getShortMessage();
if ($short_message !== null) {
return $short_message;
}
}
return null;
}
return parent::getValidationExceptionShortMessage($ex, $field);
}
}
diff --git a/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php b/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php
index a16984c140..0b3533f287 100644
--- a/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorActivitySettingsPanel.php
@@ -1,64 +1,65 @@
<?php
final class PhabricatorActivitySettingsPanel extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'activity';
}
public function getPanelName() {
return pht('Activity Logs');
}
public function getPanelGroupKey() {
return PhabricatorSettingsLogsPanelGroup::PANELGROUPKEY;
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request);
$logs = id(new PhabricatorPeopleLogQuery())
->setViewer($viewer)
->withRelatedPHIDs(array($user->getPHID()))
->executeWithCursorPager($pager);
$phids = array();
foreach ($logs as $log) {
$phids[] = $log->getUserPHID();
$phids[] = $log->getActorPHID();
}
if ($phids) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
} else {
$handles = array();
}
$table = id(new PhabricatorUserLogView())
->setUser($viewer)
->setLogs($logs)
->setHandles($handles);
$panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Account Activity Logs'))
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
$pager_box = id(new PHUIBoxView())
->addMargin(PHUI::MARGIN_LARGE)
->appendChild($pager);
return array($panel, $pager_box);
}
public function isManagementPanel() {
return true;
}
}
diff --git a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php b/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php
index c08a833f1b..f94de959cd 100644
--- a/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php
@@ -1,175 +1,176 @@
<?php
final class PhabricatorDesktopNotificationsSettingsPanel
extends PhabricatorSettingsPanel {
public function isEnabled() {
$servers = PhabricatorNotificationServerRef::getEnabledAdminServers();
if (!$servers) {
return false;
}
return PhabricatorApplication::isClassInstalled(
'PhabricatorNotificationsApplication');
}
public function getPanelKey() {
return 'desktopnotifications';
}
public function getPanelName() {
return pht('Desktop Notifications');
}
public function getPanelGroupKey() {
return PhabricatorSettingsApplicationsPanelGroup::PANELGROUPKEY;
}
public function processRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$preferences = $this->getPreferences();
$notifications_key = PhabricatorDesktopNotificationsSetting::SETTINGKEY;
$notifications_value = $preferences->getSettingValue($notifications_key);
if ($request->isFormPost()) {
$this->writeSetting(
$preferences,
$notifications_key,
$request->getInt($notifications_key));
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$title = pht('Desktop Notifications');
$control_id = celerity_generate_unique_node_id();
$status_id = celerity_generate_unique_node_id();
$browser_status_id = celerity_generate_unique_node_id();
$cancel_ask = pht(
'The dialog asking for permission to send desktop notifications was '.
'closed without granting permission. Only application notifications '.
'will be sent.');
$accept_ask = pht(
'Click "Save Preference" to persist these changes.');
$reject_ask = pht(
'Permission for desktop notifications was denied. Only application '.
'notifications will be sent.');
$no_support = pht(
'This web browser does not support desktop notifications. Only '.
'application notifications will be sent for this browser regardless of '.
'this preference.');
$default_status = phutil_tag(
'span',
array(),
array(
pht('This browser has not yet granted permission to send desktop '.
'notifications for this Phabricator instance.'),
phutil_tag('br'),
phutil_tag('br'),
javelin_tag(
'button',
array(
'sigil' => 'desktop-notifications-permission-button',
'class' => 'green',
),
pht('Grant Permission')),
));
$granted_status = phutil_tag(
'span',
array(),
pht('This browser has been granted permission to send desktop '.
'notifications for this Phabricator instance.'));
$denied_status = phutil_tag(
'span',
array(),
pht('This browser has denied permission to send desktop notifications '.
'for this Phabricator instance. Consult your browser settings / '.
'documentation to figure out how to clear this setting, do so, '.
'and then re-visit this page to grant permission.'));
$message_id = celerity_generate_unique_node_id();
$message_container = phutil_tag(
'span',
array(
'id' => $message_id,
));
$status_box = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setID($status_id)
->setIsHidden(true)
->appendChild($message_container);
$control_config = array(
'controlID' => $control_id,
'statusID' => $status_id,
'messageID' => $message_id,
'browserStatusID' => $browser_status_id,
'defaultMode' => 0,
'desktopMode' => 1,
'cancelAsk' => $cancel_ask,
'grantedAsk' => $accept_ask,
'deniedAsk' => $reject_ask,
'defaultStatus' => $default_status,
'deniedStatus' => $denied_status,
'grantedStatus' => $granted_status,
'noSupport' => $no_support,
);
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormSelectControl())
->setLabel($title)
->setControlID($control_id)
->setName($notifications_key)
->setValue($notifications_value)
->setOptions(
array(
1 => pht('Send Desktop Notifications Too'),
0 => pht('Send Application Notifications Only'),
))
->setCaption(
pht(
'Should Phabricator send desktop notifications? These are sent '.
'in addition to the notifications within the Phabricator '.
'application.'))
->initBehavior(
'desktop-notifications-control',
$control_config))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preference')));
$test_button = id(new PHUIButtonView())
->setTag('a')
->setWorkflow(true)
->setText(pht('Send Test Notification'))
->setHref('/notification/test/')
->setIcon('fa-exclamation-triangle');
$form_box = id(new PHUIObjectBoxView())
->setHeader(
id(new PHUIHeaderView())
->setHeader(pht('Desktop Notifications'))
->addActionLink($test_button))
->setForm($form)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setInfoView($status_box)
->setFormSaved($request->getBool('saved'));
$browser_status_box = id(new PHUIInfoView())
->setID($browser_status_id)
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->setIsHidden(true)
->appendChild($default_status);
return array(
$form_box,
$browser_status_box,
);
}
}
diff --git a/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php b/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php
index 27161218d1..5aad71785e 100644
--- a/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php
@@ -1,75 +1,74 @@
<?php
abstract class PhabricatorEditEngineSettingsPanel
extends PhabricatorSettingsPanel {
final public function processRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$user = $this->getUser();
if ($user && ($user->getPHID() === $viewer->getPHID())) {
$is_self = true;
} else {
$is_self = false;
}
if ($user && $user->getPHID()) {
$profile_uri = '/people/manage/'.$user->getID().'/';
} else {
$profile_uri = null;
}
$engine = id(new PhabricatorSettingsEditEngine())
->setController($this->getController())
->setNavigation($this->getNavigation())
- ->setHideHeader(true)
->setIsSelfEdit($is_self)
->setProfileURI($profile_uri);
$preferences = $this->getPreferences();
$engine->setTargetObject($preferences);
return $engine->buildResponse();
}
final public function isEnabled() {
// Only enable the panel if it has any fields.
$field_keys = $this->getPanelSettingsKeys();
return (bool)$field_keys;
}
final public function newEditEnginePage() {
$field_keys = $this->getPanelSettingsKeys();
if (!$field_keys) {
return null;
}
$key = $this->getPanelKey();
$label = $this->getPanelName();
$panel_uri = $this->getPanelURI();
return id(new PhabricatorEditPage())
->setKey($key)
->setLabel($label)
->setViewURI($panel_uri)
->setFieldKeys($field_keys);
}
final public function getPanelSettingsKeys() {
$viewer = $this->getViewer();
$settings = PhabricatorSetting::getAllEnabledSettings($viewer);
$this_key = $this->getPanelKey();
$panel_settings = array();
foreach ($settings as $setting) {
if ($setting->getSettingPanelKey() == $this_key) {
$panel_settings[] = $setting;
}
}
return mpull($panel_settings, 'getSettingKey');
}
}
diff --git a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php
index d628341dea..66fd0396ad 100644
--- a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php
@@ -1,416 +1,417 @@
<?php
final class PhabricatorEmailAddressesSettingsPanel
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'email';
}
public function getPanelName() {
return pht('Email Addresses');
}
public function getPanelGroupKey() {
return PhabricatorSettingsEmailPanelGroup::PANELGROUPKEY;
}
public function isEditableByAdministrators() {
if ($this->getUser()->getIsMailingList()) {
return true;
}
return false;
}
public function processRequest(AphrontRequest $request) {
$user = $this->getUser();
$editable = PhabricatorEnv::getEnvConfig('account.editable');
$uri = $request->getRequestURI();
$uri->setQueryParams(array());
if ($editable) {
$new = $request->getStr('new');
if ($new) {
return $this->returnNewAddressResponse($request, $uri, $new);
}
$delete = $request->getInt('delete');
if ($delete) {
return $this->returnDeleteAddressResponse($request, $uri, $delete);
}
}
$verify = $request->getInt('verify');
if ($verify) {
return $this->returnVerifyAddressResponse($request, $uri, $verify);
}
$primary = $request->getInt('primary');
if ($primary) {
return $this->returnPrimaryAddressResponse($request, $uri, $primary);
}
$emails = id(new PhabricatorUserEmail())->loadAllWhere(
'userPHID = %s ORDER BY address',
$user->getPHID());
$rowc = array();
$rows = array();
foreach ($emails as $email) {
$button_verify = javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => $uri->alter('verify', $email->getID()),
'sigil' => 'workflow',
),
pht('Verify'));
$button_make_primary = javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => $uri->alter('primary', $email->getID()),
'sigil' => 'workflow',
),
pht('Make Primary'));
$button_remove = javelin_tag(
'a',
array(
'class' => 'button small button-grey',
'href' => $uri->alter('delete', $email->getID()),
'sigil' => 'workflow',
),
pht('Remove'));
$button_primary = phutil_tag(
'a',
array(
'class' => 'button small disabled',
),
pht('Primary'));
if (!$email->getIsVerified()) {
$action = $button_verify;
} else if ($email->getIsPrimary()) {
$action = $button_primary;
} else {
$action = $button_make_primary;
}
if ($email->getIsPrimary()) {
$remove = $button_primary;
$rowc[] = 'highlighted';
} else {
$remove = $button_remove;
$rowc[] = null;
}
$rows[] = array(
$email->getAddress(),
$action,
$remove,
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Email'),
pht('Status'),
pht('Remove'),
));
$table->setColumnClasses(
array(
'wide',
'action',
'action',
));
$table->setRowClasses($rowc);
$table->setColumnVisibility(
array(
true,
true,
$editable,
));
$view = new PHUIObjectBoxView();
$header = new PHUIHeaderView();
$header->setHeader(pht('Email Addresses'));
if ($editable) {
$button = new PHUIButtonView();
$button->setText(pht('Add New Address'));
$button->setTag('a');
$button->setHref($uri->alter('new', 'true'));
$button->setIcon('fa-plus');
$button->addSigil('workflow');
$header->addActionLink($button);
}
$view->setHeader($header);
$view->setTable($table);
+ $view->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
return $view;
}
private function returnNewAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$new) {
$user = $this->getUser();
$viewer = $this->getViewer();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
$e_email = true;
$email = null;
$errors = array();
if ($request->isDialogFormPost()) {
$email = trim($request->getStr('email'));
if ($new == 'verify') {
// The user clicked "Done" from the "an email has been sent" dialog.
return id(new AphrontReloadResponse())->setURI($uri);
}
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhabricatorSettingsAddEmailAction(),
1);
if (!strlen($email)) {
$e_email = pht('Required');
$errors[] = pht('Email is required.');
} else if (!PhabricatorUserEmail::isValidAddress($email)) {
$e_email = pht('Invalid');
$errors[] = PhabricatorUserEmail::describeValidAddresses();
} else if (!PhabricatorUserEmail::isAllowedAddress($email)) {
$e_email = pht('Disallowed');
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
}
if ($e_email === true) {
$application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withAddresses(array($email))
->executeOne();
if ($application_email) {
$e_email = pht('In Use');
$errors[] = $application_email->getInUseMessage();
}
}
if (!$errors) {
$object = id(new PhabricatorUserEmail())
->setAddress($email)
->setIsVerified(0);
// If an administrator is editing a mailing list, automatically verify
// the address.
if ($viewer->getPHID() != $user->getPHID()) {
if ($viewer->getIsAdmin()) {
$object->setIsVerified(1);
}
}
try {
id(new PhabricatorUserEditor())
->setActor($viewer)
->addEmail($user, $object);
if ($object->getIsVerified()) {
// If we autoverified the address, just reload the page.
return id(new AphrontReloadResponse())->setURI($uri);
}
$object->sendVerificationEmail($user);
$dialog = $this->newDialog()
->addHiddenInput('new', 'verify')
->setTitle(pht('Verification Email Sent'))
->appendChild(phutil_tag('p', array(), pht(
'A verification email has been sent. Click the link in the '.
'email to verify your address.')))
->setSubmitURI($uri)
->addSubmitButton(pht('Done'));
return id(new AphrontDialogResponse())->setDialog($dialog);
} catch (AphrontDuplicateKeyQueryException $ex) {
$e_email = pht('Duplicate');
$errors[] = pht('Another user already has this email.');
}
}
}
if ($errors) {
$errors = id(new PHUIInfoView())
->setErrors($errors);
}
$form = id(new PHUIFormLayoutView())
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Email'))
->setName('email')
->setValue($email)
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
->setError($e_email));
$dialog = $this->newDialog()
->addHiddenInput('new', 'true')
->setTitle(pht('New Address'))
->appendChild($errors)
->appendChild($form)
->addSubmitButton(pht('Save'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnDeleteAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $this->getUser();
$viewer = $this->getViewer();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
// NOTE: You can only delete your own email addresses, and you can not
// delete your primary address.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isPrimary = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($viewer)
->removeEmail($user, $email);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('delete', $email_id)
->setTitle(pht("Really delete address '%s'?", $address))
->appendParagraph(
pht(
'Are you sure you want to delete this address? You will no '.
'longer be able to use it to login.'))
->appendParagraph(
pht(
'Note: Removing an email address from your account will invalidate '.
'any outstanding password reset links.'))
->addSubmitButton(pht('Delete'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnVerifyAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $this->getUser();
$viewer = $this->getViewer();
// NOTE: You can only send more email for your unverified addresses.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isVerified = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$email->sendVerificationEmail($user);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('verify', $email_id)
->setTitle(pht('Send Another Verification Email?'))
->appendChild(phutil_tag('p', array(), pht(
'Send another copy of the verification email to %s?',
$address)))
->addSubmitButton(pht('Send Email'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
private function returnPrimaryAddressResponse(
AphrontRequest $request,
PhutilURI $uri,
$email_id) {
$user = $this->getUser();
$viewer = $this->getViewer();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
// NOTE: You can only make your own verified addresses primary.
$email = id(new PhabricatorUserEmail())->loadOneWhere(
'id = %d AND userPHID = %s AND isVerified = 1 AND isPrimary = 0',
$email_id,
$user->getPHID());
if (!$email) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
id(new PhabricatorUserEditor())
->setActor($viewer)
->changePrimaryEmail($user, $email);
return id(new AphrontRedirectResponse())->setURI($uri);
}
$address = $email->getAddress();
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('primary', $email_id)
->setTitle(pht('Change primary email address?'))
->appendParagraph(
pht(
'If you change your primary address, Phabricator will send all '.
'email to %s.',
$address))
->appendParagraph(
pht(
'Note: Changing your primary email address will invalidate any '.
'outstanding password reset links.'))
->addSubmitButton(pht('Change Primary Address'))
->addCancelButton($uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}
diff --git a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php
index 0c775c5a4d..77364e0aa0 100644
--- a/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php
@@ -1,212 +1,213 @@
<?php
final class PhabricatorEmailPreferencesSettingsPanel
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'emailpreferences';
}
public function getPanelName() {
return pht('Email Preferences');
}
public function getPanelGroupKey() {
return PhabricatorSettingsEmailPanelGroup::PANELGROUPKEY;
}
public function isManagementPanel() {
if ($this->getUser()->getIsMailingList()) {
return true;
}
return false;
}
public function isTemplatePanel() {
return true;
}
public function processRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$user = $this->getUser();
$preferences = $this->getPreferences();
$value_email = PhabricatorEmailTagsSetting::VALUE_EMAIL;
$errors = array();
if ($request->isFormPost()) {
$new_tags = $request->getArr('mailtags');
$mailtags = $preferences->getPreference('mailtags', array());
$all_tags = $this->getAllTags($user);
foreach ($all_tags as $key => $label) {
$mailtags[$key] = (int)idx($new_tags, $key, $value_email);
}
$this->writeSetting(
$preferences,
PhabricatorEmailTagsSetting::SETTINGKEY,
$mailtags);
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?saved=true'));
}
$mailtags = $preferences->getSettingValue(
PhabricatorEmailTagsSetting::SETTINGKEY);
$form = id(new AphrontFormView())
->setUser($viewer);
$form->appendRemarkupInstructions(
pht(
'You can adjust **Application Settings** here to customize when '.
'you are emailed and notified.'.
"\n\n".
"| Setting | Effect\n".
"| ------- | -------\n".
"| Email | You will receive an email and a notification, but the ".
"notification will be marked \"read\".\n".
"| Notify | You will receive an unread notification only.\n".
"| Ignore | You will receive nothing.\n".
"\n\n".
'If an update makes several changes (like adding CCs to a task, '.
'closing it, and adding a comment) you will receive the strongest '.
'notification any of the changes is configured to deliver.'.
"\n\n".
'These preferences **only** apply to objects you are connected to '.
'(for example, Revisions where you are a reviewer or tasks you are '.
'CC\'d on). To receive email alerts when other objects are created, '.
'configure [[ /herald/ | Herald Rules ]].'));
$editors = $this->getAllEditorsWithTags($user);
// Find all the tags shared by more than one application, and put them
// in a "common" group.
$all_tags = array();
foreach ($editors as $editor) {
foreach ($editor->getMailTagsMap() as $tag => $name) {
if (empty($all_tags[$tag])) {
$all_tags[$tag] = array(
'count' => 0,
'name' => $name,
);
}
$all_tags[$tag]['count'];
}
}
$common_tags = array();
foreach ($all_tags as $tag => $info) {
if ($info['count'] > 1) {
$common_tags[$tag] = $info['name'];
}
}
// Build up the groups of application-specific options.
$tag_groups = array();
foreach ($editors as $editor) {
$tag_groups[] = array(
$editor->getEditorObjectsDescription(),
array_diff_key($editor->getMailTagsMap(), $common_tags),
);
}
// Sort them, then put "Common" at the top.
$tag_groups = isort($tag_groups, 0);
if ($common_tags) {
array_unshift($tag_groups, array(pht('Common'), $common_tags));
}
// Finally, build the controls.
foreach ($tag_groups as $spec) {
list($label, $map) = $spec;
$control = $this->buildMailTagControl($label, $map, $mailtags);
$form->appendChild($control);
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Preferences')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Email Preferences'))
->setFormSaved($request->getStr('saved'))
->setFormErrors($errors)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
return $form_box;
}
private function getAllEditorsWithTags(PhabricatorUser $user = null) {
$editors = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorApplicationTransactionEditor')
->setFilterMethod('getMailTagsMap')
->execute();
foreach ($editors as $key => $editor) {
// Remove editors for applications which are not installed.
$app = $editor->getEditorApplicationClass();
if ($app !== null && $user !== null) {
if (!PhabricatorApplication::isClassInstalledForViewer($app, $user)) {
unset($editors[$key]);
}
}
}
return $editors;
}
private function getAllTags(PhabricatorUser $user = null) {
$tags = array();
foreach ($this->getAllEditorsWithTags($user) as $editor) {
$tags += $editor->getMailTagsMap();
}
return $tags;
}
private function buildMailTagControl(
$control_label,
array $tags,
array $prefs) {
$value_email = PhabricatorEmailTagsSetting::VALUE_EMAIL;
$value_notify = PhabricatorEmailTagsSetting::VALUE_NOTIFY;
$value_ignore = PhabricatorEmailTagsSetting::VALUE_IGNORE;
$content = array();
foreach ($tags as $key => $label) {
$select = AphrontFormSelectControl::renderSelectTag(
(int)idx($prefs, $key, $value_email),
array(
$value_email => pht("\xE2\x9A\xAB Email"),
$value_notify => pht("\xE2\x97\x90 Notify"),
$value_ignore => pht("\xE2\x9A\xAA Ignore"),
),
array(
'name' => 'mailtags['.$key.']',
));
$content[] = phutil_tag(
'div',
array(
'class' => 'psb',
),
array(
$select,
' ',
$label,
));
}
$control = new AphrontFormStaticControl();
$control->setLabel($control_label);
$control->setValue($content);
return $control;
}
}
diff --git a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php
index 662d4e6cf6..068c58d549 100644
--- a/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php
@@ -1,146 +1,148 @@
<?php
final class PhabricatorExternalAccountsSettingsPanel
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'external';
}
public function getPanelName() {
return pht('External Accounts');
}
public function getPanelGroupKey() {
return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$providers = PhabricatorAuthProvider::getAllProviders();
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->needImages(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
$linked_head = id(new PHUIHeaderView())
->setHeader(pht('Linked Accounts and Authentication'));
$linked = id(new PHUIObjectItemListView())
->setUser($viewer)
->setFlush(true)
->setNoDataString(pht('You have no linked accounts.'));
$login_accounts = 0;
foreach ($accounts as $account) {
if ($account->isUsableForLogin()) {
$login_accounts++;
}
}
foreach ($accounts as $account) {
$item = id(new PHUIObjectItemView());
$provider = idx($providers, $account->getProviderKey());
if ($provider) {
$item->setHeader($provider->getProviderName());
$can_unlink = $provider->shouldAllowAccountUnlink();
if (!$can_unlink) {
$item->addAttribute(pht('Permanently Linked'));
}
} else {
$item->setHeader(
pht('Unknown Account ("%s")', $account->getProviderKey()));
$can_unlink = true;
}
$can_login = $account->isUsableForLogin();
if (!$can_login) {
$item->addAttribute(
pht(
'Disabled (an administrator has disabled login for this '.
'account provider).'));
}
$can_unlink = $can_unlink && (!$can_login || ($login_accounts > 1));
$can_refresh = $provider && $provider->shouldAllowAccountRefresh();
if ($can_refresh) {
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-refresh')
->setHref('/auth/refresh/'.$account->getProviderKey().'/'));
}
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->setWorkflow(true)
->setDisabled(!$can_unlink)
->setHref('/auth/unlink/'.$account->getProviderKey().'/'));
if ($provider) {
$provider->willRenderLinkedAccount($viewer, $item, $account);
}
$linked->addItem($item);
}
$linkable_head = id(new PHUIHeaderView())
->setHeader(pht('Add External Account'));
$linkable = id(new PHUIObjectItemListView())
->setUser($viewer)
->setFlush(true)
->setNoDataString(
pht('Your account is linked with all available providers.'));
$accounts = mpull($accounts, null, 'getProviderKey');
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
$providers = msort($providers, 'getProviderName');
foreach ($providers as $key => $provider) {
if (isset($accounts[$key])) {
continue;
}
if (!$provider->shouldAllowAccountLink()) {
continue;
}
$link_uri = '/auth/link/'.$provider->getProviderKey().'/';
$item = id(new PHUIObjectItemView());
$item->setHeader($provider->getProviderName());
$item->setHref($link_uri);
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-link')
->setHref($link_uri));
$linkable->addItem($item);
}
$linked_box = id(new PHUIObjectBoxView())
->setHeader($linked_head)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($linked);
$linkable_box = id(new PHUIObjectBoxView())
->setHeader($linkable_head)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($linkable);
return array(
$linked_box,
$linkable_box,
);
}
}
diff --git a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
index 8f1a5c643c..68d1812616 100644
--- a/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
@@ -1,325 +1,326 @@
<?php
final class PhabricatorMultiFactorSettingsPanel
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'multifactor';
}
public function getPanelName() {
return pht('Multi-Factor Auth');
}
public function getPanelGroupKey() {
return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
}
public function processRequest(AphrontRequest $request) {
if ($request->getExists('new')) {
return $this->processNew($request);
}
if ($request->getExists('edit')) {
return $this->processEdit($request);
}
if ($request->getExists('delete')) {
return $this->processDelete($request);
}
$user = $this->getUser();
$viewer = $request->getUser();
$factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere(
'userPHID = %s',
$user->getPHID());
$rows = array();
$rowc = array();
$highlight_id = $request->getInt('id');
foreach ($factors as $factor) {
$impl = $factor->getImplementation();
if ($impl) {
$type = $impl->getFactorName();
} else {
$type = $factor->getFactorKey();
}
if ($factor->getID() == $highlight_id) {
$rowc[] = 'highlighted';
} else {
$rowc[] = null;
}
$rows[] = array(
javelin_tag(
'a',
array(
'href' => $this->getPanelURI('?edit='.$factor->getID()),
'sigil' => 'workflow',
),
$factor->getFactorName()),
$type,
phabricator_datetime($factor->getDateCreated(), $viewer),
javelin_tag(
'a',
array(
'href' => $this->getPanelURI('?delete='.$factor->getID()),
'sigil' => 'workflow',
'class' => 'small button button-grey',
),
pht('Remove')),
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(
pht("You haven't added any authentication factors to your account yet."));
$table->setHeaders(
array(
pht('Name'),
pht('Type'),
pht('Created'),
'',
));
$table->setColumnClasses(
array(
'wide pri',
'',
'right',
'action',
));
$table->setRowClasses($rowc);
$table->setDeviceVisibility(
array(
true,
false,
false,
true,
));
$panel = new PHUIObjectBoxView();
$header = new PHUIHeaderView();
$help_uri = PhabricatorEnv::getDoclink(
'User Guide: Multi-Factor Authentication');
$help_button = id(new PHUIButtonView())
->setText(pht('Help'))
->setHref($help_uri)
->setTag('a')
->setIcon('fa-info-circle');
$create_button = id(new PHUIButtonView())
->setText(pht('Add Authentication Factor'))
->setHref($this->getPanelURI('?new=true'))
->setTag('a')
->setWorkflow(true)
->setIcon('fa-plus');
$header->setHeader(pht('Authentication Factors'));
$header->addActionLink($help_button);
$header->addActionLink($create_button);
$panel->setHeader($header);
$panel->setTable($table);
+ $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
return $panel;
}
private function processNew(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
$factors = PhabricatorAuthFactor::getAllFactors();
$form = id(new AphrontFormView())
->setUser($viewer);
$type = $request->getStr('type');
if (empty($factors[$type]) || !$request->isFormPost()) {
$factor = null;
} else {
$factor = $factors[$type];
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('new', true);
if ($factor === null) {
$choice_control = id(new AphrontFormRadioButtonControl())
->setName('type')
->setValue(key($factors));
foreach ($factors as $available_factor) {
$choice_control->addButton(
$available_factor->getFactorKey(),
$available_factor->getFactorName(),
$available_factor->getFactorDescription());
}
$dialog->appendParagraph(
pht(
'Adding an additional authentication factor improves the security '.
'of your account. Choose the type of factor to add:'));
$form
->appendChild($choice_control);
} else {
$dialog->addHiddenInput('type', $type);
$config = $factor->processAddFactorForm(
$form,
$request,
$user);
if ($config) {
$config->save();
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$user->getPHID(),
PhabricatorUserLog::ACTION_MULTI_ADD);
$log->save();
$user->updateMultiFactorEnrollment();
// Terminate other sessions so they must log in and survive the
// multi-factor auth check.
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
$user,
$request->getCookie(PhabricatorCookies::COOKIE_SESSION));
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?id='.$config->getID()));
}
}
$dialog
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Add Authentication Factor'))
->appendChild($form->buildLayoutView())
->addSubmitButton(pht('Continue'))
->addCancelButton($this->getPanelURI());
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
private function processEdit(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$factor = id(new PhabricatorAuthFactorConfig())->loadOneWhere(
'id = %d AND userPHID = %s',
$request->getInt('edit'),
$user->getPHID());
if (!$factor) {
return new Aphront404Response();
}
$e_name = true;
$errors = array();
if ($request->isFormPost()) {
$name = $request->getStr('name');
if (!strlen($name)) {
$e_name = pht('Required');
$errors[] = pht(
'Authentication factors must have a name to identify them.');
}
if (!$errors) {
$factor->setFactorName($name);
$factor->save();
$user->updateMultiFactorEnrollment();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI('?id='.$factor->getID()));
}
} else {
$name = $factor->getFactorName();
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setName('name')
->setLabel(pht('Name'))
->setValue($name)
->setError($e_name));
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('edit', $factor->getID())
->setTitle(pht('Edit Authentication Factor'))
->setErrors($errors)
->appendChild($form->buildLayoutView())
->addSubmitButton(pht('Save'))
->addCancelButton($this->getPanelURI());
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
private function processDelete(AphrontRequest $request) {
$viewer = $request->getUser();
$user = $this->getUser();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$this->getPanelURI());
$factor = id(new PhabricatorAuthFactorConfig())->loadOneWhere(
'id = %d AND userPHID = %s',
$request->getInt('delete'),
$user->getPHID());
if (!$factor) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
$factor->delete();
$log = PhabricatorUserLog::initializeNewLog(
$viewer,
$user->getPHID(),
PhabricatorUserLog::ACTION_MULTI_REMOVE);
$log->save();
$user->updateMultiFactorEnrollment();
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI());
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->addHiddenInput('delete', $factor->getID())
->setTitle(pht('Delete Authentication Factor'))
->appendParagraph(
pht(
'Really remove the authentication factor %s from your account?',
phutil_tag('strong', array(), $factor->getFactorName())))
->addSubmitButton(pht('Remove Factor'))
->addCancelButton($this->getPanelURI());
return id(new AphrontDialogResponse())
->setDialog($dialog);
}
}
diff --git a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php
index 17b8cdde95..2a1b482c80 100644
--- a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php
@@ -1,217 +1,218 @@
<?php
final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'password';
}
public function getPanelName() {
return pht('Password');
}
public function getPanelGroupKey() {
return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
}
public function isEnabled() {
// There's no sense in showing a change password panel if this install
// doesn't support password authentication.
if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
return false;
}
return true;
}
public function processRequest(AphrontRequest $request) {
$user = $request->getUser();
$token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$user,
$request,
'/settings/');
$min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');
$min_len = (int)$min_len;
// NOTE: To change your password, you need to prove you own the account,
// either by providing the old password or by carrying a token to
// the workflow from a password reset email.
$key = $request->getStr('key');
$password_type = PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE;
$token = null;
if ($key) {
$token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($user)
->withTokenResources(array($user->getPHID()))
->withTokenTypes(array($password_type))
->withTokenCodes(array(PhabricatorHash::weakDigest($key)))
->withExpired(false)
->executeOne();
}
$e_old = true;
$e_new = true;
$e_conf = true;
$errors = array();
if ($request->isFormPost()) {
if (!$token) {
$envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw'));
if (!$user->comparePassword($envelope)) {
$errors[] = pht('The old password you entered is incorrect.');
$e_old = pht('Invalid');
}
}
$pass = $request->getStr('new_pw');
$conf = $request->getStr('conf_pw');
if (strlen($pass) < $min_len) {
$errors[] = pht('Your new password is too short.');
$e_new = pht('Too Short');
} else if ($pass !== $conf) {
$errors[] = pht('New password and confirmation do not match.');
$e_conf = pht('Invalid');
} else if (PhabricatorCommonPasswords::isCommonPassword($pass)) {
$e_new = pht('Very Weak');
$e_conf = pht('Very Weak');
$errors[] = pht(
'Your new password is very weak: it is one of the most common '.
'passwords in use. Choose a stronger password.');
}
if (!$errors) {
// This write is unguarded because the CSRF token has already
// been checked in the call to $request->isFormPost() and
// the CSRF token depends on the password hash, so when it
// is changed here the CSRF token check will fail.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$envelope = new PhutilOpaqueEnvelope($pass);
id(new PhabricatorUserEditor())
->setActor($user)
->changePassword($user, $envelope);
unset($unguarded);
if ($token) {
// Destroy the token.
$token->delete();
// If this is a password set/reset, kick the user to the home page
// after we update their account.
$next = '/';
} else {
$next = $this->getPanelURI('?saved=true');
}
id(new PhabricatorAuthSessionEngine())->terminateLoginSessions(
$user,
$request->getCookie(PhabricatorCookies::COOKIE_SESSION));
return id(new AphrontRedirectResponse())->setURI($next);
}
}
$hash_envelope = new PhutilOpaqueEnvelope($user->getPasswordHash());
if (strlen($hash_envelope->openEnvelope())) {
try {
$can_upgrade = PhabricatorPasswordHasher::canUpgradeHash(
$hash_envelope);
} catch (PhabricatorPasswordHasherUnavailableException $ex) {
$can_upgrade = false;
// Only show this stuff if we aren't on the reset workflow. We can
// do resets regardless of the old hasher's availability.
if (!$token) {
$errors[] = pht(
'Your password is currently hashed using an algorithm which is '.
'no longer available on this install.');
$errors[] = pht(
'Because the algorithm implementation is missing, your password '.
'can not be used or updated.');
$errors[] = pht(
'To set a new password, request a password reset link from the '.
'login screen and then follow the instructions.');
}
}
if ($can_upgrade) {
$errors[] = pht(
'The strength of your stored password hash can be upgraded. '.
'To upgrade, either: log out and log in using your password; or '.
'change your password.');
}
}
$len_caption = null;
if ($min_len) {
$len_caption = pht('Minimum password length: %d characters.', $min_len);
}
$form = new AphrontFormView();
$form
->setUser($user)
->addHiddenInput('key', $key);
if (!$token) {
$form->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('Old Password'))
->setError($e_old)
->setName('old_pw'));
}
$form
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setLabel(pht('New Password'))
->setError($e_new)
->setName('new_pw'));
$form
->appendChild(
id(new AphrontFormPasswordControl())
->setDisableAutocomplete(true)
->setLabel(pht('Confirm Password'))
->setCaption($len_caption)
->setError($e_conf)
->setName('conf_pw'));
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Change Password')));
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Current Algorithm'))
->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName(
new PhutilOpaqueEnvelope($user->getPasswordHash()))));
$form->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Best Available Algorithm'))
->setValue(PhabricatorPasswordHasher::getBestAlgorithmName()));
$form->appendRemarkupInstructions(
pht(
'NOTE: Changing your password will terminate any other outstanding '.
'login sessions.'));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Change Password'))
->setFormSaved($request->getStr('saved'))
->setFormErrors($errors)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
return array(
$form_box,
);
}
}
diff --git a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php
index 54a217300d..3e339e9145 100644
--- a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php
@@ -1,57 +1,58 @@
<?php
final class PhabricatorSSHKeysSettingsPanel extends PhabricatorSettingsPanel {
public function isManagementPanel() {
if ($this->getUser()->getIsMailingList()) {
return false;
}
return true;
}
public function getPanelKey() {
return 'ssh';
}
public function getPanelName() {
return pht('SSH Public Keys');
}
public function getPanelGroupKey() {
return PhabricatorSettingsAuthenticationPanelGroup::PANELGROUPKEY;
}
public function processRequest(AphrontRequest $request) {
$user = $this->getUser();
$viewer = $request->getUser();
$keys = id(new PhabricatorAuthSSHKeyQuery())
->setViewer($viewer)
->withObjectPHIDs(array($user->getPHID()))
->withIsActive(true)
->execute();
$table = id(new PhabricatorAuthSSHKeyTableView())
->setUser($viewer)
->setKeys($keys)
->setCanEdit(true)
->setNoDataString(pht("You haven't added any SSH Public Keys."));
$panel = new PHUIObjectBoxView();
$header = new PHUIHeaderView();
$ssh_actions = PhabricatorAuthSSHKeyTableView::newKeyActionsMenu(
$viewer,
$user);
$header->setHeader(pht('SSH Public Keys'));
$header->addActionLink($ssh_actions);
$panel->setHeader($header);
$panel->setTable($table);
+ $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
return $panel;
}
}
diff --git a/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php b/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php
index e643f2ee08..fb60e40d81 100644
--- a/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php
@@ -1,144 +1,145 @@
<?php
final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'sessions';
}
public function getPanelName() {
return pht('Sessions');
}
public function getPanelGroupKey() {
return PhabricatorSettingsLogsPanelGroup::PANELGROUPKEY;
}
public function isEnabled() {
return true;
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$accounts = id(new PhabricatorExternalAccountQuery())
->setViewer($viewer)
->withUserPHIDs(array($viewer->getPHID()))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
$identity_phids = mpull($accounts, 'getPHID');
$identity_phids[] = $viewer->getPHID();
$sessions = id(new PhabricatorAuthSessionQuery())
->setViewer($viewer)
->withIdentityPHIDs($identity_phids)
->execute();
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($identity_phids)
->execute();
$current_key = PhabricatorHash::weakDigest(
$request->getCookie(PhabricatorCookies::COOKIE_SESSION));
$rows = array();
$rowc = array();
foreach ($sessions as $session) {
$is_current = phutil_hashes_are_identical(
$session->getSessionKey(),
$current_key);
if ($is_current) {
$rowc[] = 'highlighted';
$button = phutil_tag(
'a',
array(
'class' => 'small button button-grey disabled',
),
pht('Current'));
} else {
$rowc[] = null;
$button = javelin_tag(
'a',
array(
'href' => '/auth/session/terminate/'.$session->getID().'/',
'class' => 'small button button-grey',
'sigil' => 'workflow',
),
pht('Terminate'));
}
$hisec = ($session->getHighSecurityUntil() - time());
$rows[] = array(
$handles[$session->getUserPHID()]->renderLink(),
substr($session->getSessionKey(), 0, 6),
$session->getType(),
($hisec > 0)
? phutil_format_relative_time($hisec)
: null,
phabricator_datetime($session->getSessionStart(), $viewer),
phabricator_date($session->getSessionExpires(), $viewer),
$button,
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(pht("You don't have any active sessions."));
$table->setRowClasses($rowc);
$table->setHeaders(
array(
pht('Identity'),
pht('Session'),
pht('Type'),
pht('HiSec'),
pht('Created'),
pht('Expires'),
pht(''),
));
$table->setColumnClasses(
array(
'wide',
'n',
'',
'right',
'right',
'right',
'action',
));
$terminate_button = id(new PHUIButtonView())
->setText(pht('Terminate All Sessions'))
->setHref('/auth/session/terminate/all/')
->setTag('a')
->setWorkflow(true)
->setIcon('fa-exclamation-triangle');
$header = id(new PHUIHeaderView())
->setHeader(pht('Active Login Sessions'))
->addActionLink($terminate_button);
$hisec = ($viewer->getSession()->getHighSecurityUntil() - time());
if ($hisec > 0) {
$hisec_button = id(new PHUIButtonView())
->setText(pht('Leave High Security'))
->setHref('/auth/session/downgrade/')
->setTag('a')
->setWorkflow(true)
->setIcon('fa-lock');
$header->addActionLink($hisec_button);
}
$panel = id(new PHUIObjectBoxView())
->setHeader($header)
- ->setTable($table);
+ ->setTable($table)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
return $panel;
}
}
diff --git a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php
index c2659d5226..d2cc0dedb6 100644
--- a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php
@@ -1,92 +1,93 @@
<?php
final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'tokens';
}
public function getPanelName() {
return pht('Temporary Tokens');
}
public function getPanelGroupKey() {
return PhabricatorSettingsLogsPanelGroup::PANELGROUPKEY;
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$tokens = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($viewer)
->withTokenResources(array($viewer->getPHID()))
->execute();
$rows = array();
foreach ($tokens as $token) {
if ($token->isRevocable()) {
$button = javelin_tag(
'a',
array(
'href' => '/auth/token/revoke/'.$token->getID().'/',
'class' => 'small button button-grey',
'sigil' => 'workflow',
),
pht('Revoke'));
} else {
$button = javelin_tag(
'a',
array(
'class' => 'small button button-grey disabled',
),
pht('Revoke'));
}
if ($token->getTokenExpires() >= time()) {
$expiry = phabricator_datetime($token->getTokenExpires(), $viewer);
} else {
$expiry = pht('Expired');
}
$rows[] = array(
$token->getTokenReadableTypeName(),
$expiry,
$button,
);
}
$table = new AphrontTableView($rows);
$table->setNoDataString(pht("You don't have any active tokens."));
$table->setHeaders(
array(
pht('Type'),
pht('Expires'),
pht(''),
));
$table->setColumnClasses(
array(
'wide',
'right',
'action',
));
$terminate_button = id(new PHUIButtonView())
->setText(pht('Revoke All'))
->setHref('/auth/token/revoke/all/')
->setTag('a')
->setWorkflow(true)
->setIcon('fa-exclamation-triangle');
$header = id(new PHUIHeaderView())
->setHeader(pht('Temporary Tokens'))
->addActionLink($terminate_button);
$panel = id(new PHUIObjectBoxView())
->setHeader($header)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
return $panel;
}
}
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php
index 1b891cddee..c9d8a6887f 100644
--- a/src/applications/transactions/editengine/PhabricatorEditEngine.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php
@@ -1,2423 +1,2408 @@
<?php
/**
* @task fields Managing Fields
* @task text Display Text
* @task config Edit Engine Configuration
* @task uri Managing URIs
* @task load Creating and Loading Objects
* @task web Responding to Web Requests
* @task edit Responding to Edit Requests
* @task http Responding to HTTP Parameter Requests
* @task conduit Responding to Conduit Requests
*/
abstract class PhabricatorEditEngine
extends Phobject
implements PhabricatorPolicyInterface {
const EDITENGINECONFIG_DEFAULT = 'default';
const SUBTYPE_DEFAULT = 'default';
private $viewer;
private $controller;
private $isCreate;
private $editEngineConfiguration;
private $contextParameters = array();
private $targetObject;
private $page;
private $pages;
private $navigation;
- private $hideHeader;
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function setController(PhabricatorController $controller) {
$this->controller = $controller;
$this->setViewer($controller->getViewer());
return $this;
}
final public function getController() {
return $this->controller;
}
final public function getEngineKey() {
$key = $this->getPhobjectClassConstant('ENGINECONST', 64);
if (strpos($key, '/') !== false) {
throw new Exception(
pht(
'EditEngine ("%s") contains an invalid key character "/".',
get_class($this)));
}
return $key;
}
final public function getApplication() {
$app_class = $this->getEngineApplicationClass();
return PhabricatorApplication::getByClass($app_class);
}
final public function addContextParameter($key) {
$this->contextParameters[] = $key;
return $this;
}
public function isEngineConfigurable() {
return true;
}
public function isEngineExtensible() {
return true;
}
public function isDefaultQuickCreateEngine() {
return false;
}
public function getDefaultQuickCreateFormKeys() {
$keys = array();
if ($this->isDefaultQuickCreateEngine()) {
$keys[] = self::EDITENGINECONFIG_DEFAULT;
}
foreach ($keys as $idx => $key) {
$keys[$idx] = $this->getEngineKey().'/'.$key;
}
return $keys;
}
public static function splitFullKey($full_key) {
return explode('/', $full_key, 2);
}
public function getQuickCreateOrderVector() {
return id(new PhutilSortVector())
->addString($this->getObjectCreateShortText());
}
/**
* Force the engine to edit a particular object.
*/
public function setTargetObject($target_object) {
$this->targetObject = $target_object;
return $this;
}
public function getTargetObject() {
return $this->targetObject;
}
public function setNavigation(AphrontSideNavFilterView $navigation) {
$this->navigation = $navigation;
return $this;
}
public function getNavigation() {
return $this->navigation;
}
- public function setHideHeader($hide_header) {
- $this->hideHeader = $hide_header;
- return $this;
- }
-
- public function getHideHeader() {
- return $this->hideHeader;
- }
-
/* -( Managing Fields )---------------------------------------------------- */
abstract public function getEngineApplicationClass();
abstract protected function buildCustomEditFields($object);
public function getFieldsForConfig(
PhabricatorEditEngineConfiguration $config) {
$object = $this->newEditableObject();
$this->editEngineConfiguration = $config;
// This is mostly making sure that we fill in default values.
$this->setIsCreate(true);
return $this->buildEditFields($object);
}
final protected function buildEditFields($object) {
$viewer = $this->getViewer();
$fields = $this->buildCustomEditFields($object);
foreach ($fields as $field) {
$field
->setViewer($viewer)
->setObject($object);
}
$fields = mpull($fields, null, 'getKey');
if ($this->isEngineExtensible()) {
$extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions();
} else {
$extensions = array();
}
foreach ($extensions as $extension) {
$extension->setViewer($viewer);
if (!$extension->supportsObject($this, $object)) {
continue;
}
$extension_fields = $extension->buildCustomEditFields($this, $object);
// TODO: Validate this in more detail with a more tailored error.
assert_instances_of($extension_fields, 'PhabricatorEditField');
foreach ($extension_fields as $field) {
$field
->setViewer($viewer)
->setObject($object);
}
$extension_fields = mpull($extension_fields, null, 'getKey');
foreach ($extension_fields as $key => $field) {
$fields[$key] = $field;
}
}
$config = $this->getEditEngineConfiguration();
$fields = $this->willConfigureFields($object, $fields);
$fields = $config->applyConfigurationToFields($this, $object, $fields);
$fields = $this->applyPageToFields($object, $fields);
return $fields;
}
protected function willConfigureFields($object, array $fields) {
return $fields;
}
final public function supportsSubtypes() {
try {
$object = $this->newEditableObject();
} catch (Exception $ex) {
return false;
}
return ($object instanceof PhabricatorEditEngineSubtypeInterface);
}
final public function newSubtypeMap() {
return $this->newEditableObject()->newEditEngineSubtypeMap();
}
/* -( Display Text )------------------------------------------------------- */
/**
* @task text
*/
abstract public function getEngineName();
/**
* @task text
*/
abstract protected function getObjectCreateTitleText($object);
/**
* @task text
*/
protected function getFormHeaderText($object) {
$config = $this->getEditEngineConfiguration();
return $config->getName();
}
/**
* @task text
*/
abstract protected function getObjectEditTitleText($object);
/**
* @task text
*/
abstract protected function getObjectCreateShortText();
/**
* @task text
*/
abstract protected function getObjectName();
/**
* @task text
*/
abstract protected function getObjectEditShortText($object);
/**
* @task text
*/
protected function getObjectCreateButtonText($object) {
return $this->getObjectCreateTitleText($object);
}
/**
* @task text
*/
protected function getObjectEditButtonText($object) {
return pht('Save Changes');
}
/**
* @task text
*/
protected function getCommentViewSeriousHeaderText($object) {
return pht('Take Action');
}
/**
* @task text
*/
protected function getCommentViewSeriousButtonText($object) {
return pht('Submit');
}
/**
* @task text
*/
protected function getCommentViewHeaderText($object) {
return $this->getCommentViewSeriousHeaderText($object);
}
/**
* @task text
*/
protected function getCommentViewButtonText($object) {
return $this->getCommentViewSeriousButtonText($object);
}
/**
* Return a human-readable header describing what this engine is used to do,
* like "Configure Maniphest Task Forms".
*
* @return string Human-readable description of the engine.
* @task text
*/
abstract public function getSummaryHeader();
/**
* Return a human-readable summary of what this engine is used to do.
*
* @return string Human-readable description of the engine.
* @task text
*/
abstract public function getSummaryText();
/* -( Edit Engine Configuration )------------------------------------------ */
protected function supportsEditEngineConfiguration() {
return true;
}
final protected function getEditEngineConfiguration() {
return $this->editEngineConfiguration;
}
private function newConfigurationQuery() {
return id(new PhabricatorEditEngineConfigurationQuery())
->setViewer($this->getViewer())
->withEngineKeys(array($this->getEngineKey()));
}
private function loadEditEngineConfigurationWithQuery(
PhabricatorEditEngineConfigurationQuery $query,
$sort_method) {
if ($sort_method) {
$results = $query->execute();
$results = msort($results, $sort_method);
$result = head($results);
} else {
$result = $query->executeOne();
}
if (!$result) {
return null;
}
$this->editEngineConfiguration = $result;
return $result;
}
private function loadEditEngineConfigurationWithIdentifier($identifier) {
$query = $this->newConfigurationQuery()
->withIdentifiers(array($identifier));
return $this->loadEditEngineConfigurationWithQuery($query, null);
}
private function loadDefaultConfiguration() {
$query = $this->newConfigurationQuery()
->withIdentifiers(
array(
self::EDITENGINECONFIG_DEFAULT,
))
->withIgnoreDatabaseConfigurations(true);
return $this->loadEditEngineConfigurationWithQuery($query, null);
}
private function loadDefaultCreateConfiguration() {
$query = $this->newConfigurationQuery()
->withIsDefault(true)
->withIsDisabled(false);
return $this->loadEditEngineConfigurationWithQuery(
$query,
'getCreateSortKey');
}
public function loadDefaultEditConfiguration($object) {
$query = $this->newConfigurationQuery()
->withIsEdit(true)
->withIsDisabled(false);
// If this object supports subtyping, we edit it with a form of the same
// subtype: so "bug" tasks get edited with "bug" forms.
if ($object instanceof PhabricatorEditEngineSubtypeInterface) {
$query->withSubtypes(
array(
$object->getEditEngineSubtype(),
));
}
return $this->loadEditEngineConfigurationWithQuery(
$query,
'getEditSortKey');
}
final public function getBuiltinEngineConfigurations() {
$configurations = $this->newBuiltinEngineConfigurations();
if (!$configurations) {
throw new Exception(
pht(
'EditEngine ("%s") returned no builtin engine configurations, but '.
'an edit engine must have at least one configuration.',
get_class($this)));
}
assert_instances_of($configurations, 'PhabricatorEditEngineConfiguration');
$has_default = false;
foreach ($configurations as $config) {
if ($config->getBuiltinKey() == self::EDITENGINECONFIG_DEFAULT) {
$has_default = true;
}
}
if (!$has_default) {
$first = head($configurations);
if (!$first->getBuiltinKey()) {
$first
->setBuiltinKey(self::EDITENGINECONFIG_DEFAULT)
->setIsDefault(true)
->setIsEdit(true);
if (!strlen($first->getName())) {
$first->setName($this->getObjectCreateShortText());
}
} else {
throw new Exception(
pht(
'EditEngine ("%s") returned builtin engine configurations, '.
'but none are marked as default and the first configuration has '.
'a different builtin key already. Mark a builtin as default or '.
'omit the key from the first configuration',
get_class($this)));
}
}
$builtins = array();
foreach ($configurations as $key => $config) {
$builtin_key = $config->getBuiltinKey();
if ($builtin_key === null) {
throw new Exception(
pht(
'EditEngine ("%s") returned builtin engine configurations, '.
'but one (with key "%s") is missing a builtin key. Provide a '.
'builtin key for each configuration (you can omit it from the '.
'first configuration in the list to automatically assign the '.
'default key).',
get_class($this),
$key));
}
if (isset($builtins[$builtin_key])) {
throw new Exception(
pht(
'EditEngine ("%s") returned builtin engine configurations, '.
'but at least two specify the same builtin key ("%s"). Engines '.
'must have unique builtin keys.',
get_class($this),
$builtin_key));
}
$builtins[$builtin_key] = $config;
}
return $builtins;
}
protected function newBuiltinEngineConfigurations() {
return array(
$this->newConfiguration(),
);
}
final protected function newConfiguration() {
return PhabricatorEditEngineConfiguration::initializeNewConfiguration(
$this->getViewer(),
$this);
}
/* -( Managing URIs )------------------------------------------------------ */
/**
* @task uri
*/
abstract protected function getObjectViewURI($object);
/**
* @task uri
*/
protected function getObjectCreateCancelURI($object) {
return $this->getApplication()->getApplicationURI();
}
/**
* @task uri
*/
protected function getEditorURI() {
return $this->getApplication()->getApplicationURI('edit/');
}
/**
* @task uri
*/
protected function getObjectEditCancelURI($object) {
return $this->getObjectViewURI($object);
}
/**
* @task uri
*/
public function getEditURI($object = null, $path = null) {
$parts = array();
$parts[] = $this->getEditorURI();
if ($object && $object->getID()) {
$parts[] = $object->getID().'/';
}
if ($path !== null) {
$parts[] = $path;
}
return implode('', $parts);
}
public function getEffectiveObjectViewURI($object) {
if ($this->getIsCreate()) {
return $this->getObjectViewURI($object);
}
$page = $this->getSelectedPage();
if ($page) {
$view_uri = $page->getViewURI();
if ($view_uri !== null) {
return $view_uri;
}
}
return $this->getObjectViewURI($object);
}
public function getEffectiveObjectEditDoneURI($object) {
return $this->getEffectiveObjectViewURI($object);
}
public function getEffectiveObjectEditCancelURI($object) {
$page = $this->getSelectedPage();
if ($page) {
$view_uri = $page->getViewURI();
if ($view_uri !== null) {
return $view_uri;
}
}
return $this->getObjectEditCancelURI($object);
}
/* -( Creating and Loading Objects )--------------------------------------- */
/**
* Initialize a new object for creation.
*
* @return object Newly initialized object.
* @task load
*/
abstract protected function newEditableObject();
/**
* Build an empty query for objects.
*
* @return PhabricatorPolicyAwareQuery Query.
* @task load
*/
abstract protected function newObjectQuery();
/**
* Test if this workflow is creating a new object or editing an existing one.
*
* @return bool True if a new object is being created.
* @task load
*/
final public function getIsCreate() {
return $this->isCreate;
}
/**
* Flag this workflow as a create or edit.
*
* @param bool True if this is a create workflow.
* @return this
* @task load
*/
private function setIsCreate($is_create) {
$this->isCreate = $is_create;
return $this;
}
/**
* Try to load an object by ID, PHID, or monogram. This is done primarily
* to make Conduit a little easier to use.
*
* @param wild ID, PHID, or monogram.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object Corresponding editable object.
* @task load
*/
private function newObjectFromIdentifier(
$identifier,
array $capabilities = array()) {
if (is_int($identifier) || ctype_digit($identifier)) {
$object = $this->newObjectFromID($identifier, $capabilities);
if (!$object) {
throw new Exception(
pht(
'No object exists with ID "%s".',
$identifier));
}
return $object;
}
$type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN;
if (phid_get_type($identifier) != $type_unknown) {
$object = $this->newObjectFromPHID($identifier, $capabilities);
if (!$object) {
throw new Exception(
pht(
'No object exists with PHID "%s".',
$identifier));
}
return $object;
}
$target = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withNames(array($identifier))
->executeOne();
if (!$target) {
throw new Exception(
pht(
'Monogram "%s" does not identify a valid object.',
$identifier));
}
$expect = $this->newEditableObject();
$expect_class = get_class($expect);
$target_class = get_class($target);
if ($expect_class !== $target_class) {
throw new Exception(
pht(
'Monogram "%s" identifies an object of the wrong type. Loaded '.
'object has class "%s", but this editor operates on objects of '.
'type "%s".',
$identifier,
$target_class,
$expect_class));
}
// Load the object by PHID using this engine's standard query. This makes
// sure it's really valid, goes through standard policy check logic, and
// picks up any `need...()` clauses we want it to load with.
$object = $this->newObjectFromPHID($target->getPHID(), $capabilities);
if (!$object) {
throw new Exception(
pht(
'Failed to reload object identified by monogram "%s" when '.
'querying by PHID.',
$identifier));
}
return $object;
}
/**
* Load an object by ID.
*
* @param int Object ID.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object|null Object, or null if no such object exists.
* @task load
*/
private function newObjectFromID($id, array $capabilities = array()) {
$query = $this->newObjectQuery()
->withIDs(array($id));
return $this->newObjectFromQuery($query, $capabilities);
}
/**
* Load an object by PHID.
*
* @param phid Object PHID.
* @param list<const> List of required capability constants, or omit for
* defaults.
* @return object|null Object, or null if no such object exists.
* @task load
*/
private function newObjectFromPHID($phid, array $capabilities = array()) {
$query = $this->newObjectQuery()
->withPHIDs(array($phid));
return $this->newObjectFromQuery($query, $capabilities);
}
/**
* Load an object given a configured query.
*
* @param PhabricatorPolicyAwareQuery Configured query.
* @param list<const> List of required capabilitiy constants, or omit for
* defaults.
* @return object|null Object, or null if no such object exists.
* @task load
*/
private function newObjectFromQuery(
PhabricatorPolicyAwareQuery $query,
array $capabilities = array()) {
$viewer = $this->getViewer();
if (!$capabilities) {
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
$object = $query
->setViewer($viewer)
->requireCapabilities($capabilities)
->executeOne();
if (!$object) {
return null;
}
return $object;
}
/**
* Verify that an object is appropriate for editing.
*
* @param wild Loaded value.
* @return void
* @task load
*/
private function validateObject($object) {
if (!$object || !is_object($object)) {
throw new Exception(
pht(
'EditEngine "%s" created or loaded an invalid object: object must '.
'actually be an object, but is of some other type ("%s").',
get_class($this),
gettype($object)));
}
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
throw new Exception(
pht(
'EditEngine "%s" created or loaded an invalid object: object (of '.
'class "%s") must implement "%s", but does not.',
get_class($this),
get_class($object),
'PhabricatorApplicationTransactionInterface'));
}
}
/* -( Responding to Web Requests )----------------------------------------- */
final public function buildResponse() {
$viewer = $this->getViewer();
$controller = $this->getController();
$request = $controller->getRequest();
$action = $this->getEditAction();
$capabilities = array();
$use_default = false;
$require_create = true;
switch ($action) {
case 'comment':
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
);
$use_default = true;
break;
case 'parameters':
$use_default = true;
break;
case 'nodefault':
case 'nocreate':
case 'nomanage':
$require_create = false;
break;
default:
break;
}
$object = $this->getTargetObject();
if (!$object) {
$id = $request->getURIData('id');
if ($id) {
$this->setIsCreate(false);
$object = $this->newObjectFromID($id, $capabilities);
if (!$object) {
return new Aphront404Response();
}
} else {
// Make sure the viewer has permission to create new objects of
// this type if we're going to create a new object.
if ($require_create) {
$this->requireCreateCapability();
}
$this->setIsCreate(true);
$object = $this->newEditableObject();
}
} else {
$id = $object->getID();
}
$this->validateObject($object);
if ($use_default) {
$config = $this->loadDefaultConfiguration();
if (!$config) {
return new Aphront404Response();
}
} else {
$form_key = $request->getURIData('formKey');
if (strlen($form_key)) {
$config = $this->loadEditEngineConfigurationWithIdentifier($form_key);
if (!$config) {
return new Aphront404Response();
}
if ($id && !$config->getIsEdit()) {
return $this->buildNotEditFormRespose($object, $config);
}
} else {
if ($id) {
$config = $this->loadDefaultEditConfiguration($object);
if (!$config) {
return $this->buildNoEditResponse($object);
}
} else {
$config = $this->loadDefaultCreateConfiguration();
if (!$config) {
return $this->buildNoCreateResponse($object);
}
}
}
}
if ($config->getIsDisabled()) {
return $this->buildDisabledFormResponse($object, $config);
}
$page_key = $request->getURIData('pageKey');
if (!strlen($page_key)) {
$pages = $this->getPages($object);
if ($pages) {
$page_key = head_key($pages);
}
}
if (strlen($page_key)) {
$page = $this->selectPage($object, $page_key);
if (!$page) {
return new Aphront404Response();
}
}
switch ($action) {
case 'parameters':
return $this->buildParametersResponse($object);
case 'nodefault':
return $this->buildNoDefaultResponse($object);
case 'nocreate':
return $this->buildNoCreateResponse($object);
case 'nomanage':
return $this->buildNoManageResponse($object);
case 'comment':
return $this->buildCommentResponse($object);
default:
return $this->buildEditResponse($object);
}
}
private function buildCrumbs($object, $final = false) {
$controller = $this->getController();
$crumbs = $controller->buildApplicationCrumbsForEditEngine();
if ($this->getIsCreate()) {
$create_text = $this->getObjectCreateShortText();
if ($final) {
$crumbs->addTextCrumb($create_text);
} else {
$edit_uri = $this->getEditURI($object);
$crumbs->addTextCrumb($create_text, $edit_uri);
}
} else {
$crumbs->addTextCrumb(
$this->getObjectEditShortText($object),
$this->getEffectiveObjectViewURI($object));
$edit_text = pht('Edit');
if ($final) {
$crumbs->addTextCrumb($edit_text);
} else {
$edit_uri = $this->getEditURI($object);
$crumbs->addTextCrumb($edit_text, $edit_uri);
}
}
return $crumbs;
}
private function buildEditResponse($object) {
$viewer = $this->getViewer();
$controller = $this->getController();
$request = $controller->getRequest();
$fields = $this->buildEditFields($object);
$template = $object->getApplicationTransactionTemplate();
if ($this->getIsCreate()) {
$cancel_uri = $this->getObjectCreateCancelURI($object);
$submit_button = $this->getObjectCreateButtonText($object);
} else {
$cancel_uri = $this->getEffectiveObjectEditCancelURI($object);
$submit_button = $this->getObjectEditButtonText($object);
}
$config = $this->getEditEngineConfiguration()
->attachEngine($this);
// NOTE: Don't prompt users to override locks when creating objects,
// even if the default settings would create a locked object.
$can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
if (!$can_interact &&
!$this->getIsCreate() &&
!$request->getBool('editEngine') &&
!$request->getBool('overrideLock')) {
$lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
$dialog = $this->getController()
->newDialog()
->addHiddenInput('overrideLock', true)
->setDisableWorkflowOnSubmit(true)
->addCancelButton($cancel_uri);
return $lock->willPromptUserForLockOverrideWithDialog($dialog);
}
$validation_exception = null;
if ($request->isFormPost() && $request->getBool('editEngine')) {
$submit_fields = $fields;
foreach ($submit_fields as $key => $field) {
if (!$field->shouldGenerateTransactionsFromSubmit()) {
unset($submit_fields[$key]);
continue;
}
}
// Before we read the submitted values, store a copy of what we would
// use if the form was empty so we can figure out which transactions are
// just setting things to their default values for the current form.
$defaults = array();
foreach ($submit_fields as $key => $field) {
$defaults[$key] = $field->getValueForTransaction();
}
foreach ($submit_fields as $key => $field) {
$field->setIsSubmittedForm(true);
if (!$field->shouldReadValueFromSubmit()) {
continue;
}
$field->readValueFromSubmit($request);
}
$xactions = array();
if ($this->getIsCreate()) {
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
if ($this->supportsSubtypes()) {
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_SUBTYPE)
->setNewValue($config->getSubtype());
}
}
foreach ($submit_fields as $key => $field) {
$field_value = $field->getValueForTransaction();
$type_xactions = $field->generateTransactions(
clone $template,
array(
'value' => $field_value,
));
foreach ($type_xactions as $type_xaction) {
$default = $defaults[$key];
if ($default === $field->getValueForTransaction()) {
$type_xaction->setIsDefaultTransaction(true);
}
$xactions[] = $type_xaction;
}
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$xactions = $this->willApplyTransactions($object, $xactions);
$editor->applyTransactions($object, $xactions);
$this->didApplyTransactions($object, $xactions);
return $this->newEditResponse($request, $object, $xactions);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
foreach ($fields as $field) {
$message = $this->getValidationExceptionShortMessage($ex, $field);
if ($message === null) {
continue;
}
$field->setControlError($message);
}
}
} else {
if ($this->getIsCreate()) {
$template = $request->getStr('template');
if (strlen($template)) {
$template_object = $this->newObjectFromIdentifier(
$template,
array(
PhabricatorPolicyCapability::CAN_VIEW,
));
if (!$template_object) {
return new Aphront404Response();
}
} else {
$template_object = null;
}
if ($template_object) {
$copy_fields = $this->buildEditFields($template_object);
$copy_fields = mpull($copy_fields, null, 'getKey');
foreach ($copy_fields as $copy_key => $copy_field) {
if (!$copy_field->getIsCopyable()) {
unset($copy_fields[$copy_key]);
}
}
} else {
$copy_fields = array();
}
foreach ($fields as $field) {
if (!$field->shouldReadValueFromRequest()) {
continue;
}
$field_key = $field->getKey();
if (isset($copy_fields[$field_key])) {
$field->readValueFromField($copy_fields[$field_key]);
}
$field->readValueFromRequest($request);
}
}
}
$action_button = $this->buildEditFormActionButton($object);
if ($this->getIsCreate()) {
$header_text = $this->getFormHeaderText($object);
$header_icon = 'fa-plus-square';
} else {
$header_text = $this->getObjectEditTitleText($object);
$header_icon = 'fa-pencil';
}
$show_preview = !$request->isAjax();
if ($show_preview) {
$previews = array();
foreach ($fields as $field) {
$preview = $field->getPreviewPanel();
if (!$preview) {
continue;
}
$control_id = $field->getControlID();
$preview
->setControlID($control_id)
->setPreviewURI('/transactions/remarkuppreview/');
$previews[] = $preview;
}
} else {
$previews = array();
}
$form = $this->buildEditForm($object, $fields);
if ($request->isAjax()) {
return $this->getController()
->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle($header_text)
->setValidationException($validation_exception)
->appendForm($form)
->addCancelButton($cancel_uri)
->addSubmitButton($submit_button);
}
$crumbs = $this->buildCrumbs($object, $final = true);
- if ($this->getHideHeader()) {
- $header = null;
- $crumbs->setBorder(false);
- } else {
- $header = id(new PHUIHeaderView())
- ->setHeader($header_text)
- ->setHeaderIcon($header_icon);
- $crumbs->setBorder(true);
- }
+ $header = id(new PHUIHeaderView())
+ ->setHeader($header_text)
+ ->setHeaderIcon($header_icon);
+ $crumbs->setBorder(true);
if ($action_button) {
$header->addActionLink($action_button);
}
$box = id(new PHUIObjectBoxView())
->setUser($viewer)
->setHeaderText($this->getObjectName())
->setValidationException($validation_exception)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($form);
// This is fairly questionable, but in use by Settings.
if ($request->getURIData('formSaved')) {
$box->setFormSaved(true);
}
$content = array(
$box,
$previews,
);
$view = new PHUITwoColumnView();
if ($header) {
$view->setHeader($header);
}
- $navigation = $this->getNavigation();
- if ($navigation) {
- $view
- ->setNavigation($navigation)
- ->setMainColumn($content);
- } else {
- $view->setFooter($content);
- }
+ $view->setFooter($content);
- return $controller->newPage()
+ $page = $controller->newPage()
->setTitle($header_text)
->setCrumbs($crumbs)
->appendChild($view);
+
+ $navigation = $this->getNavigation();
+ if ($navigation) {
+ $page->setNavigation($navigation);
+ }
+
+ return $page;
}
protected function newEditResponse(
AphrontRequest $request,
$object,
array $xactions) {
return id(new AphrontRedirectResponse())
->setURI($this->getEffectiveObjectEditDoneURI($object));
}
private function buildEditForm($object, array $fields) {
$viewer = $this->getViewer();
$controller = $this->getController();
$request = $controller->getRequest();
$fields = $this->willBuildEditForm($object, $fields);
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('editEngine', 'true');
foreach ($this->contextParameters as $param) {
$form->addHiddenInput($param, $request->getStr($param));
}
foreach ($fields as $field) {
$field->appendToForm($form);
}
if ($this->getIsCreate()) {
$cancel_uri = $this->getObjectCreateCancelURI($object);
$submit_button = $this->getObjectCreateButtonText($object);
} else {
$cancel_uri = $this->getEffectiveObjectEditCancelURI($object);
$submit_button = $this->getObjectEditButtonText($object);
}
if (!$request->isAjax()) {
$buttons = id(new AphrontFormSubmitControl())
->setValue($submit_button);
if ($cancel_uri) {
$buttons->addCancelButton($cancel_uri);
}
$form->appendControl($buttons);
}
return $form;
}
protected function willBuildEditForm($object, array $fields) {
return $fields;
}
private function buildEditFormActionButton($object) {
if (!$this->isEngineConfigurable()) {
return null;
}
$viewer = $this->getViewer();
$action_view = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($this->buildEditFormActions($object) as $action) {
$action_view->addAction($action);
}
$action_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Configure Form'))
->setHref('#')
->setIcon('fa-gear')
->setDropdownMenu($action_view);
return $action_button;
}
private function buildEditFormActions($object) {
$actions = array();
if ($this->supportsEditEngineConfiguration()) {
$engine_key = $this->getEngineKey();
$config = $this->getEditEngineConfiguration();
$can_manage = PhabricatorPolicyFilter::hasCapability(
$this->getViewer(),
$config,
PhabricatorPolicyCapability::CAN_EDIT);
if ($can_manage) {
$manage_uri = $config->getURI();
} else {
$manage_uri = $this->getEditURI(null, 'nomanage/');
}
$view_uri = "/transactions/editengine/{$engine_key}/";
$actions[] = id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Configuration'));
$actions[] = id(new PhabricatorActionView())
->setName(pht('View Form Configurations'))
->setIcon('fa-list-ul')
->setHref($view_uri);
$actions[] = id(new PhabricatorActionView())
->setName(pht('Edit Form Configuration'))
->setIcon('fa-pencil')
->setHref($manage_uri)
->setDisabled(!$can_manage)
->setWorkflow(!$can_manage);
}
$actions[] = id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Documentation'));
$actions[] = id(new PhabricatorActionView())
->setName(pht('Using HTTP Parameters'))
->setIcon('fa-book')
->setHref($this->getEditURI($object, 'parameters/'));
$doc_href = PhabricatorEnv::getDoclink('User Guide: Customizing Forms');
$actions[] = id(new PhabricatorActionView())
->setName(pht('User Guide: Customizing Forms'))
->setIcon('fa-book')
->setHref($doc_href);
return $actions;
}
/**
* Test if the viewer could apply a certain type of change by using the
* normal "Edit" form.
*
* This method returns `true` if the user has access to an edit form and
* that edit form has a field which applied the specified transaction type,
* and that field is visible and editable for the user.
*
* For example, you can use it to test if a user is able to reassign tasks
* or not, prior to rendering dedicated UI for task reassingment.
*
* Note that this method does NOT test if the user can actually edit the
* current object, just if they have access to the related field.
*
* @param const Transaction type to test for.
* @return bool True if the user could "Edit" to apply the transaction type.
*/
final public function hasEditAccessToTransaction($xaction_type) {
$viewer = $this->getViewer();
$object = $this->getTargetObject();
if (!$object) {
$object = $this->newEditableObject();
}
$config = $this->loadDefaultEditConfiguration($object);
if (!$config) {
return false;
}
$fields = $this->buildEditFields($object);
$field = null;
foreach ($fields as $form_field) {
$field_xaction_type = $form_field->getTransactionType();
if ($field_xaction_type === $xaction_type) {
$field = $form_field;
break;
}
}
if (!$field) {
return false;
}
if (!$field->shouldReadValueFromSubmit()) {
return false;
}
return true;
}
public function newNUXButton($text) {
$specs = $this->newCreateActionSpecifications(array());
$head = head($specs);
return id(new PHUIButtonView())
->setTag('a')
->setText($text)
->setHref($head['uri'])
->setDisabled($head['disabled'])
->setWorkflow($head['workflow'])
->setColor(PHUIButtonView::GREEN);
}
final public function addActionToCrumbs(
PHUICrumbsView $crumbs,
array $parameters = array()) {
$viewer = $this->getViewer();
$specs = $this->newCreateActionSpecifications($parameters);
$head = head($specs);
$menu_uri = $head['uri'];
$dropdown = null;
if (count($specs) > 1) {
$menu_icon = 'fa-caret-square-o-down';
$menu_name = $this->getObjectCreateShortText();
$workflow = false;
$disabled = false;
$dropdown = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($specs as $spec) {
$dropdown->addAction(
id(new PhabricatorActionView())
->setName($spec['name'])
->setIcon($spec['icon'])
->setHref($spec['uri'])
->setDisabled($head['disabled'])
->setWorkflow($head['workflow']));
}
} else {
$menu_icon = $head['icon'];
$menu_name = $head['name'];
$workflow = $head['workflow'];
$disabled = $head['disabled'];
}
$action = id(new PHUIListItemView())
->setName($menu_name)
->setHref($menu_uri)
->setIcon($menu_icon)
->setWorkflow($workflow)
->setDisabled($disabled);
if ($dropdown) {
$action->setDropdownMenu($dropdown);
}
$crumbs->addAction($action);
}
/**
* Build a raw description of available "Create New Object" UI options so
* other methods can build menus or buttons.
*/
public function newCreateActionSpecifications(array $parameters) {
$viewer = $this->getViewer();
$can_create = $this->hasCreateCapability();
if ($can_create) {
$configs = $this->loadUsableConfigurationsForCreate();
} else {
$configs = array();
}
$disabled = false;
$workflow = false;
$menu_icon = 'fa-plus-square';
$specs = array();
if (!$configs) {
if ($viewer->isLoggedIn()) {
$disabled = true;
} else {
// If the viewer isn't logged in, assume they'll get hit with a login
// dialog and are likely able to create objects after they log in.
$disabled = false;
}
$workflow = true;
if ($can_create) {
$create_uri = $this->getEditURI(null, 'nodefault/');
} else {
$create_uri = $this->getEditURI(null, 'nocreate/');
}
$specs[] = array(
'name' => $this->getObjectCreateShortText(),
'uri' => $create_uri,
'icon' => $menu_icon,
'disabled' => $disabled,
'workflow' => $workflow,
);
} else {
foreach ($configs as $config) {
$config_uri = $config->getCreateURI();
if ($parameters) {
$config_uri = (string)id(new PhutilURI($config_uri))
->setQueryParams($parameters);
}
$specs[] = array(
'name' => $config->getDisplayName(),
'uri' => $config_uri,
'icon' => 'fa-plus',
'disabled' => false,
'workflow' => false,
);
}
}
return $specs;
}
final public function buildEditEngineCommentView($object) {
$config = $this->loadDefaultEditConfiguration($object);
if (!$config) {
// TODO: This just nukes the entire comment form if you don't have access
// to any edit forms. We might want to tailor this UX a bit.
return id(new PhabricatorApplicationTransactionCommentView())
->setNoPermission(true);
}
$viewer = $this->getViewer();
$can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
if (!$can_interact) {
$lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
return id(new PhabricatorApplicationTransactionCommentView())
->setEditEngineLock($lock);
}
$object_phid = $object->getPHID();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
$header_text = $this->getCommentViewSeriousHeaderText($object);
$button_text = $this->getCommentViewSeriousButtonText($object);
} else {
$header_text = $this->getCommentViewHeaderText($object);
$button_text = $this->getCommentViewButtonText($object);
}
$comment_uri = $this->getEditURI($object, 'comment/');
$view = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setObjectPHID($object_phid)
->setHeaderText($header_text)
->setAction($comment_uri)
->setSubmitButtonName($button_text);
$draft = PhabricatorVersionedDraft::loadDraft(
$object_phid,
$viewer->getPHID());
if ($draft) {
$view->setVersionedDraft($draft);
}
$view->setCurrentVersion($this->loadDraftVersion($object));
$fields = $this->buildEditFields($object);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$comment_actions = array();
foreach ($fields as $field) {
if (!$field->shouldGenerateTransactionsFromComment()) {
continue;
}
if (!$can_edit) {
if (!$field->getCanApplyWithoutEditCapability()) {
continue;
}
}
$comment_action = $field->getCommentAction();
if (!$comment_action) {
continue;
}
$key = $comment_action->getKey();
// TODO: Validate these better.
$comment_actions[$key] = $comment_action;
}
$comment_actions = msortv($comment_actions, 'getSortVector');
$view->setCommentActions($comment_actions);
$comment_groups = $this->newCommentActionGroups();
$view->setCommentActionGroups($comment_groups);
return $view;
}
protected function loadDraftVersion($object) {
$viewer = $this->getViewer();
if (!$viewer->isLoggedIn()) {
return null;
}
$template = $object->getApplicationTransactionTemplate();
$conn_r = $template->establishConnection('r');
// Find the most recent transaction the user has written. We'll use this
// as a version number to make sure that out-of-date drafts get discarded.
$result = queryfx_one(
$conn_r,
'SELECT id AS version FROM %T
WHERE objectPHID = %s AND authorPHID = %s
ORDER BY id DESC LIMIT 1',
$template->getTableName(),
$object->getPHID(),
$viewer->getPHID());
if ($result) {
return (int)$result['version'];
} else {
return null;
}
}
/* -( Responding to HTTP Parameter Requests )------------------------------ */
/**
* Respond to a request for documentation on HTTP parameters.
*
* @param object Editable object.
* @return AphrontResponse Response object.
* @task http
*/
private function buildParametersResponse($object) {
$controller = $this->getController();
$viewer = $this->getViewer();
$request = $controller->getRequest();
$fields = $this->buildEditFields($object);
$crumbs = $this->buildCrumbs($object);
$crumbs->addTextCrumb(pht('HTTP Parameters'));
$crumbs->setBorder(true);
$header_text = pht(
'HTTP Parameters: %s',
$this->getObjectCreateShortText());
$header = id(new PHUIHeaderView())
->setHeader($header_text);
$help_view = id(new PhabricatorApplicationEditHTTPParameterHelpView())
->setUser($viewer)
->setFields($fields);
$document = id(new PHUIDocumentViewPro())
->setUser($viewer)
->setHeader($header)
->appendChild($help_view);
return $controller->newPage()
->setTitle(pht('HTTP Parameters'))
->setCrumbs($crumbs)
->appendChild($document);
}
private function buildError($object, $title, $body) {
$cancel_uri = $this->getObjectCreateCancelURI($object);
$dialog = $this->getController()
->newDialog()
->addCancelButton($cancel_uri);
if ($title !== null) {
$dialog->setTitle($title);
}
if ($body !== null) {
$dialog->appendParagraph($body);
}
return $dialog;
}
private function buildNoDefaultResponse($object) {
return $this->buildError(
$object,
pht('No Default Create Forms'),
pht(
'This application is not configured with any forms for creating '.
'objects that are visible to you and enabled.'));
}
private function buildNoCreateResponse($object) {
return $this->buildError(
$object,
pht('No Create Permission'),
pht('You do not have permission to create these objects.'));
}
private function buildNoManageResponse($object) {
return $this->buildError(
$object,
pht('No Manage Permission'),
pht(
'You do not have permission to configure forms for this '.
'application.'));
}
private function buildNoEditResponse($object) {
return $this->buildError(
$object,
pht('No Edit Forms'),
pht(
'You do not have access to any forms which are enabled and marked '.
'as edit forms.'));
}
private function buildNotEditFormRespose($object, $config) {
return $this->buildError(
$object,
pht('Not an Edit Form'),
pht(
'This form ("%s") is not marked as an edit form, so '.
'it can not be used to edit objects.',
$config->getName()));
}
private function buildDisabledFormResponse($object, $config) {
return $this->buildError(
$object,
pht('Form Disabled'),
pht(
'This form ("%s") has been disabled, so it can not be used.',
$config->getName()));
}
private function buildLockedObjectResponse($object) {
$dialog = $this->buildError($object, null, null);
$viewer = $this->getViewer();
$lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
return $lock->willBlockUserInteractionWithDialog($dialog);
}
private function buildCommentResponse($object) {
$viewer = $this->getViewer();
if ($this->getIsCreate()) {
return new Aphront404Response();
}
$controller = $this->getController();
$request = $controller->getRequest();
if (!$request->isFormPost()) {
return new Aphront400Response();
}
$can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object);
if (!$can_interact) {
return $this->buildLockedObjectResponse($object);
}
$config = $this->loadDefaultEditConfiguration($object);
if (!$config) {
return new Aphront404Response();
}
$fields = $this->buildEditFields($object);
$is_preview = $request->isPreviewRequest();
$view_uri = $this->getEffectiveObjectViewURI($object);
$template = $object->getApplicationTransactionTemplate();
$comment_template = $template->getApplicationTransactionCommentObject();
$comment_text = $request->getStr('comment');
$actions = $request->getStr('editengine.actions');
if ($actions) {
$actions = phutil_json_decode($actions);
}
if ($is_preview) {
$version_key = PhabricatorVersionedDraft::KEY_VERSION;
$request_version = $request->getInt($version_key);
$current_version = $this->loadDraftVersion($object);
if ($request_version >= $current_version) {
$draft = PhabricatorVersionedDraft::loadOrCreateDraft(
$object->getPHID(),
$viewer->getPHID(),
$current_version);
$is_empty = (!strlen($comment_text) && !$actions);
$draft
->setProperty('comment', $comment_text)
->setProperty('actions', $actions)
->save();
$draft_engine = $this->newDraftEngine($object);
if ($draft_engine) {
$draft_engine
->setVersionedDraft($draft)
->synchronize();
}
}
}
$xactions = array();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
if ($actions) {
$action_map = array();
foreach ($actions as $action) {
$type = idx($action, 'type');
if (!$type) {
continue;
}
if (empty($fields[$type])) {
continue;
}
$action_map[$type] = $action;
}
foreach ($action_map as $type => $action) {
$field = $fields[$type];
if (!$field->shouldGenerateTransactionsFromComment()) {
continue;
}
// If you don't have edit permission on the object, you're limited in
// which actions you can take via the comment form. Most actions
// need edit permission, but some actions (like "Accept Revision")
// can be applied by anyone with view permission.
if (!$can_edit) {
if (!$field->getCanApplyWithoutEditCapability()) {
// We know the user doesn't have the capability, so this will
// raise a policy exception.
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
}
}
if (array_key_exists('initialValue', $action)) {
$field->setInitialValue($action['initialValue']);
}
$field->readValueFromComment(idx($action, 'value'));
$type_xactions = $field->generateTransactions(
clone $template,
array(
'value' => $field->getValueForTransaction(),
));
foreach ($type_xactions as $type_xaction) {
$xactions[] = $type_xaction;
}
}
}
$auto_xactions = $this->newAutomaticCommentTransactions($object);
foreach ($auto_xactions as $xaction) {
$xactions[] = $xaction;
}
if (strlen($comment_text) || !$xactions) {
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(clone $comment_template)
->setContent($comment_text));
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContinueOnNoEffect($request->isContinueRequest())
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request)
->setIsPreview($is_preview);
try {
$xactions = $editor->applyTransactions($object, $xactions);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
return id(new PhabricatorApplicationTransactionValidationResponse())
->setCancelURI($view_uri)
->setException($ex);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($view_uri)
->setException($ex);
}
if (!$is_preview) {
PhabricatorVersionedDraft::purgeDrafts(
$object->getPHID(),
$viewer->getPHID(),
$this->loadDraftVersion($object));
$draft_engine = $this->newDraftEngine($object);
if ($draft_engine) {
$draft_engine
->setVersionedDraft(null)
->synchronize();
}
}
if ($request->isAjax() && $is_preview) {
$preview_content = $this->newCommentPreviewContent($object, $xactions);
return id(new PhabricatorApplicationTransactionResponse())
->setViewer($viewer)
->setTransactions($xactions)
->setIsPreview($is_preview)
->setPreviewContent($preview_content);
} else {
return id(new AphrontRedirectResponse())
->setURI($view_uri);
}
}
protected function newDraftEngine($object) {
$viewer = $this->getViewer();
if ($object instanceof PhabricatorDraftInterface) {
$engine = $object->newDraftEngine();
} else {
$engine = new PhabricatorBuiltinDraftEngine();
}
return $engine
->setObject($object)
->setViewer($viewer);
}
/* -( Conduit )------------------------------------------------------------ */
/**
* Respond to a Conduit edit request.
*
* This method accepts a list of transactions to apply to an object, and
* either edits an existing object or creates a new one.
*
* @task conduit
*/
final public function buildConduitResponse(ConduitAPIRequest $request) {
$viewer = $this->getViewer();
$config = $this->loadDefaultConfiguration();
if (!$config) {
throw new Exception(
pht(
'Unable to load configuration for this EditEngine ("%s").',
get_class($this)));
}
$identifier = $request->getValue('objectIdentifier');
if ($identifier) {
$this->setIsCreate(false);
$object = $this->newObjectFromIdentifier($identifier);
} else {
$this->requireCreateCapability();
$this->setIsCreate(true);
$object = $this->newEditableObject();
}
$this->validateObject($object);
$fields = $this->buildEditFields($object);
$types = $this->getConduitEditTypesFromFields($fields);
$template = $object->getApplicationTransactionTemplate();
$xactions = $this->getConduitTransactions($request, $types, $template);
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSource($request->newContentSource())
->setContinueOnNoEffect(true);
if (!$this->getIsCreate()) {
$editor->setContinueOnMissingFields(true);
}
$xactions = $editor->applyTransactions($object, $xactions);
$xactions_struct = array();
foreach ($xactions as $xaction) {
$xactions_struct[] = array(
'phid' => $xaction->getPHID(),
);
}
return array(
'object' => array(
'id' => $object->getID(),
'phid' => $object->getPHID(),
),
'transactions' => $xactions_struct,
);
}
/**
* Generate transactions which can be applied from edit actions in a Conduit
* request.
*
* @param ConduitAPIRequest The request.
* @param list<PhabricatorEditType> Supported edit types.
* @param PhabricatorApplicationTransaction Template transaction.
* @return list<PhabricatorApplicationTransaction> Generated transactions.
* @task conduit
*/
private function getConduitTransactions(
ConduitAPIRequest $request,
array $types,
PhabricatorApplicationTransaction $template) {
$viewer = $request->getUser();
$transactions_key = 'transactions';
$xactions = $request->getValue($transactions_key);
if (!is_array($xactions)) {
throw new Exception(
pht(
'Parameter "%s" is not a list of transactions.',
$transactions_key));
}
foreach ($xactions as $key => $xaction) {
if (!is_array($xaction)) {
throw new Exception(
pht(
'Parameter "%s" must contain a list of transaction descriptions, '.
'but item with key "%s" is not a dictionary.',
$transactions_key,
$key));
}
if (!array_key_exists('type', $xaction)) {
throw new Exception(
pht(
'Parameter "%s" must contain a list of transaction descriptions, '.
'but item with key "%s" is missing a "type" field. Each '.
'transaction must have a type field.',
$transactions_key,
$key));
}
$type = $xaction['type'];
if (empty($types[$type])) {
throw new Exception(
pht(
'Transaction with key "%s" has invalid type "%s". This type is '.
'not recognized. Valid types are: %s.',
$key,
$type,
implode(', ', array_keys($types))));
}
}
$results = array();
if ($this->getIsCreate()) {
$results[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_CREATE);
}
foreach ($xactions as $xaction) {
$type = $types[$xaction['type']];
// Let the parameter type interpret the value. This allows you to
// use usernames in list<user> fields, for example.
$parameter_type = $type->getConduitParameterType();
$parameter_type->setViewer($viewer);
try {
$xaction['value'] = $parameter_type->getValue(
$xaction,
'value',
$request->getIsStrictlyTyped());
} catch (Exception $ex) {
throw new PhutilProxyException(
pht(
'Exception when processing transaction of type "%s": %s',
$xaction['type'],
$ex->getMessage()),
$ex);
}
$type_xactions = $type->generateTransactions(
clone $template,
$xaction);
foreach ($type_xactions as $type_xaction) {
$results[] = $type_xaction;
}
}
return $results;
}
/**
* @return map<string, PhabricatorEditType>
* @task conduit
*/
private function getConduitEditTypesFromFields(array $fields) {
$types = array();
foreach ($fields as $field) {
$field_types = $field->getConduitEditTypes();
if ($field_types === null) {
continue;
}
foreach ($field_types as $field_type) {
$field_type->setField($field);
$types[$field_type->getEditType()] = $field_type;
}
}
return $types;
}
public function getConduitEditTypes() {
$config = $this->loadDefaultConfiguration();
if (!$config) {
return array();
}
$object = $this->newEditableObject();
$fields = $this->buildEditFields($object);
return $this->getConduitEditTypesFromFields($fields);
}
final public static function getAllEditEngines() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getEngineKey')
->execute();
}
final public static function getByKey(PhabricatorUser $viewer, $key) {
return id(new PhabricatorEditEngineQuery())
->setViewer($viewer)
->withEngineKeys(array($key))
->executeOne();
}
public function getIcon() {
$application = $this->getApplication();
return $application->getIcon();
}
private function loadUsableConfigurationsForCreate() {
$viewer = $this->getViewer();
$configs = id(new PhabricatorEditEngineConfigurationQuery())
->setViewer($viewer)
->withEngineKeys(array($this->getEngineKey()))
->withIsDefault(true)
->withIsDisabled(false)
->execute();
$configs = msort($configs, 'getCreateSortKey');
// Attach this specific engine to configurations we load so they can access
// any runtime configuration. For example, this allows us to generate the
// correct "Create Form" buttons when editing forms, see T12301.
foreach ($configs as $config) {
$config->attachEngine($this);
}
return $configs;
}
protected function getValidationExceptionShortMessage(
PhabricatorApplicationTransactionValidationException $ex,
PhabricatorEditField $field) {
$xaction_type = $field->getTransactionType();
if ($xaction_type === null) {
return null;
}
return $ex->getShortMessage($xaction_type);
}
protected function getCreateNewObjectPolicy() {
return PhabricatorPolicies::POLICY_USER;
}
private function requireCreateCapability() {
PhabricatorPolicyFilter::requireCapability(
$this->getViewer(),
$this,
PhabricatorPolicyCapability::CAN_EDIT);
}
private function hasCreateCapability() {
return PhabricatorPolicyFilter::hasCapability(
$this->getViewer(),
$this,
PhabricatorPolicyCapability::CAN_EDIT);
}
public function isCommentAction() {
return ($this->getEditAction() == 'comment');
}
public function getEditAction() {
$controller = $this->getController();
$request = $controller->getRequest();
return $request->getURIData('editAction');
}
protected function newCommentActionGroups() {
return array();
}
protected function newAutomaticCommentTransactions($object) {
return array();
}
protected function newCommentPreviewContent($object, array $xactions) {
return null;
}
/* -( Form Pages )--------------------------------------------------------- */
public function getSelectedPage() {
return $this->page;
}
private function selectPage($object, $page_key) {
$pages = $this->getPages($object);
if (empty($pages[$page_key])) {
return null;
}
$this->page = $pages[$page_key];
return $this->page;
}
protected function newPages($object) {
return array();
}
protected function getPages($object) {
if ($this->pages === null) {
$pages = $this->newPages($object);
assert_instances_of($pages, 'PhabricatorEditPage');
$pages = mpull($pages, null, 'getKey');
$this->pages = $pages;
}
return $this->pages;
}
private function applyPageToFields($object, array $fields) {
$pages = $this->getPages($object);
if (!$pages) {
return $fields;
}
if (!$this->getSelectedPage()) {
return $fields;
}
$page_picks = array();
$default_key = head($pages)->getKey();
foreach ($pages as $page_key => $page) {
foreach ($page->getFieldKeys() as $field_key) {
$page_picks[$field_key] = $page_key;
}
if ($page->getIsDefault()) {
$default_key = $page_key;
}
}
$page_map = array_fill_keys(array_keys($pages), array());
foreach ($fields as $field_key => $field) {
if (isset($page_picks[$field_key])) {
$page_map[$page_picks[$field_key]][$field_key] = $field;
continue;
}
// TODO: Maybe let the field pick a page to associate itself with so
// extensions can force themselves onto a particular page?
$page_map[$default_key][$field_key] = $field;
}
$page = $this->getSelectedPage();
if (!$page) {
$page = head($pages);
}
$selected_key = $page->getKey();
return $page_map[$selected_key];
}
protected function willApplyTransactions($object, array $xactions) {
return $xactions;
}
protected function didApplyTransactions($object, array $xactions) {
return;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getPHID() {
return get_class($this);
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::getMostOpenPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getCreateNewObjectPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 24, 7:11 AM (19 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
182584
Default Alt Text
(145 KB)

Event Timeline