Page MenuHome GnuPG

No OneTemporary

diff --git a/src/applications/people/profilepanel/PhabricatorPeopleManageProfilePanel.php b/src/applications/people/profilepanel/PhabricatorPeopleManageProfilePanel.php
index ce0bd98d8..f010016d3 100644
--- a/src/applications/people/profilepanel/PhabricatorPeopleManageProfilePanel.php
+++ b/src/applications/people/profilepanel/PhabricatorPeopleManageProfilePanel.php
@@ -1,54 +1,59 @@
<?php
final class PhabricatorPeopleManageProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'people.manage';
public function getPanelTypeName() {
return pht('Mangage User');
}
private function getDefaultName() {
return pht('Manage');
}
+ public function canHidePanel(
+ PhabricatorProfilePanelConfiguration $config) {
+ return false;
+ }
+
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
if (strlen($name)) {
return $name;
}
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setPlaceholder($this->getDefaultName())
->setValue($config->getPanelProperty('name')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$user = $config->getProfileObject();
$id = $user->getID();
$item = $this->newItem()
->setHref("/people/manage/{$id}/")
->setName($this->getDisplayName($config))
->setIcon('fa-gears');
return array(
$item,
);
}
}
diff --git a/src/applications/project/profilepanel/PhabricatorProjectManageProfilePanel.php b/src/applications/project/profilepanel/PhabricatorProjectManageProfilePanel.php
index 16985890d..ae66c4cc3 100644
--- a/src/applications/project/profilepanel/PhabricatorProjectManageProfilePanel.php
+++ b/src/applications/project/profilepanel/PhabricatorProjectManageProfilePanel.php
@@ -1,64 +1,69 @@
<?php
final class PhabricatorProjectManageProfilePanel
extends PhabricatorProfilePanel {
const PANELKEY = 'project.manage';
public function getPanelTypeName() {
return pht('Manage Project');
}
private function getDefaultName() {
return pht('Manage');
}
+ public function canHidePanel(
+ PhabricatorProfilePanelConfiguration $config) {
+ return false;
+ }
+
public function canMakeDefault(
PhabricatorProfilePanelConfiguration $config) {
return true;
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
$name = $config->getPanelProperty('name');
if (strlen($name)) {
return $name;
}
return $this->getDefaultName();
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setPlaceholder($this->getDefaultName())
->setValue($config->getPanelProperty('name')),
);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$project = $config->getProfileObject();
$id = $project->getID();
$name = $this->getDisplayName($config);
$icon = 'fa-gears';
$href = "/project/manage/{$id}/";
$item = $this->newItem()
->setHref($href)
->setName($name)
->setIcon($icon);
return array(
$item,
);
}
}
diff --git a/src/applications/search/engine/PhabricatorProfilePanelEngine.php b/src/applications/search/engine/PhabricatorProfilePanelEngine.php
index 658cf5c26..6500676bd 100644
--- a/src/applications/search/engine/PhabricatorProfilePanelEngine.php
+++ b/src/applications/search/engine/PhabricatorProfilePanelEngine.php
@@ -1,946 +1,957 @@
<?php
abstract class PhabricatorProfilePanelEngine extends Phobject {
private $viewer;
private $profileObject;
private $panels;
private $defaultPanel;
private $controller;
private $navigation;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setProfileObject($profile_object) {
$this->profileObject = $profile_object;
return $this;
}
public function getProfileObject() {
return $this->profileObject;
}
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
private function setDefaultPanel(
PhabricatorProfilePanelConfiguration $default_panel) {
$this->defaultPanel = $default_panel;
return $this;
}
public function getDefaultPanel() {
$this->loadPanels();
return $this->defaultPanel;
}
abstract protected function getPanelURI($path);
protected function isPanelEngineConfigurable() {
return PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
}
public function buildResponse() {
$controller = $this->getController();
$viewer = $controller->getViewer();
$this->setViewer($viewer);
$request = $controller->getRequest();
$panel_action = $request->getURIData('panelAction');
// If the engine is not configurable, don't respond to any of the editing
// or configuration routes.
if (!$this->isPanelEngineConfigurable()) {
switch ($panel_action) {
case 'view':
break;
default:
return new Aphront404Response();
}
}
$panel_id = $request->getURIData('panelID');
$panel_list = $this->loadPanels();
$selected_panel = null;
if (strlen($panel_id)) {
$panel_id_int = (int)$panel_id;
foreach ($panel_list as $panel) {
if ($panel_id_int) {
if ((int)$panel->getID() === $panel_id_int) {
$selected_panel = $panel;
break;
}
}
$builtin_key = $panel->getBuiltinKey();
if ($builtin_key === (string)$panel_id) {
$selected_panel = $panel;
break;
}
}
}
switch ($panel_action) {
case 'view':
case 'info':
case 'hide':
case 'default':
case 'builtin':
if (!$selected_panel) {
return new Aphront404Response();
}
break;
}
$navigation = $this->buildNavigation();
$navigation->selectFilter('panel.configure');
$crumbs = $controller->buildApplicationCrumbsForEditEngine();
switch ($panel_action) {
case 'view':
$content = $this->buildPanelViewContent($selected_panel);
break;
case 'configure':
$content = $this->buildPanelConfigureContent($panel_list);
$crumbs->addTextCrumb(pht('Configure Menu'));
break;
case 'reorder':
$content = $this->buildPanelReorderContent($panel_list);
break;
case 'new':
$panel_key = $request->getURIData('panelKey');
$content = $this->buildPanelNewContent($panel_key);
break;
case 'builtin':
$content = $this->buildPanelBuiltinContent($selected_panel);
break;
case 'hide':
$content = $this->buildPanelHideContent($selected_panel);
break;
case 'default':
$content = $this->buildPanelDefaultContent(
$selected_panel,
$panel_list);
break;
case 'edit':
$content = $this->buildPanelEditContent();
break;
default:
throw new Exception(
pht(
'Unsupported panel action "%s".',
$panel_action));
}
if ($content instanceof AphrontResponse) {
return $content;
}
if ($content instanceof AphrontResponseProducerInterface) {
return $content;
}
return $controller->newPage()
->setTitle(pht('Profile Stuff'))
->setNavigation($navigation)
->setCrumbs($crumbs)
->appendChild($content);
}
public function buildNavigation() {
if ($this->navigation) {
return $this->navigation;
}
$nav = id(new AphrontSideNavFilterView())
->setIsProfileMenu(true)
->setBaseURI(new PhutilURI($this->getPanelURI('')));
$panels = $this->getPanels();
foreach ($panels as $panel) {
if ($panel->isDisabled()) {
continue;
}
$items = $panel->buildNavigationMenuItems();
foreach ($items as $item) {
$this->validateNavigationMenuItem($item);
}
// If the panel produced only a single item which does not otherwise
// have a key, try to automatically assign it a reasonable key. This
// makes selecting the correct item simpler.
if (count($items) == 1) {
$item = head($items);
if ($item->getKey() === null) {
$builtin_key = $panel->getBuiltinKey();
$panel_phid = $panel->getPHID();
if ($builtin_key !== null) {
$item->setKey($builtin_key);
} else if ($panel_phid !== null) {
$item->setKey($panel_phid);
}
}
}
foreach ($items as $item) {
$nav->addMenuItem($item);
}
}
$more_items = $this->newAutomaticMenuItems($nav);
foreach ($more_items as $item) {
$nav->addMenuItem($item);
}
$nav->selectFilter(null);
$this->navigation = $nav;
return $this->navigation;
}
private function getPanels() {
if ($this->panels === null) {
$this->panels = $this->loadPanels();
}
return $this->panels;
}
private function loadPanels() {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$panels = $this->loadBuiltinProfilePanels();
$stored_panels = id(new PhabricatorProfilePanelConfigurationQuery())
->setViewer($viewer)
->withProfilePHIDs(array($object->getPHID()))
->execute();
// Merge the stored panels into the builtin panels. If a builtin panel has
// a stored version, replace the defaults with the stored changes.
foreach ($stored_panels as $stored_panel) {
$builtin_key = $stored_panel->getBuiltinKey();
if ($builtin_key !== null) {
// If this builtin actually exists, replace the builtin with the
// stored configuration. Otherwise, we're just going to drop the
// stored config: it corresponds to an out-of-date or uninstalled
// panel.
if (isset($panels[$builtin_key])) {
$panels[$builtin_key] = $stored_panel;
} else {
continue;
}
} else {
$panels[] = $stored_panel;
}
}
foreach ($panels as $panel) {
$impl = $panel->getPanel();
$impl->setViewer($viewer);
}
$panels = msort($panels, 'getSortKey');
// Normalize keys since callers shouldn't rely on this array being
// partially keyed.
$panels = array_values($panels);
// Make sure exactly one valid panel is marked as default.
$default = null;
$first = null;
foreach ($panels as $panel) {
if (!$panel->canMakeDefault()) {
continue;
}
if ($panel->isDefault()) {
$default = $panel;
break;
}
if ($first === null) {
$first = $panel;
}
}
if (!$default) {
$default = $first;
}
if ($default) {
$this->setDefaultPanel($default);
}
return $panels;
}
private function loadBuiltinProfilePanels() {
$object = $this->getProfileObject();
$builtins = $this->getBuiltinProfilePanels($object);
$panels = PhabricatorProfilePanel::getAllPanels();
$order = 1;
$map = array();
foreach ($builtins as $builtin) {
$builtin_key = $builtin->getBuiltinKey();
if (!$builtin_key) {
throw new Exception(
pht(
'Object produced a builtin panel with no builtin panel key! '.
'Builtin panels must have a unique key.'));
}
if (isset($map[$builtin_key])) {
throw new Exception(
pht(
'Object produced two panels with the same builtin key ("%s"). '.
'Each panel must have a unique builtin key.',
$builtin_key));
}
$panel_key = $builtin->getPanelKey();
$panel = idx($panels, $panel_key);
if (!$panel) {
throw new Exception(
pht(
'Builtin panel ("%s") specifies a bad panel key ("%s"); there '.
'is no corresponding panel implementation available.',
$builtin_key,
$panel_key));
}
$builtin
->setProfilePHID($object->getPHID())
->attachPanel($panel)
->attachProfileObject($object)
->setPanelOrder($order);
$map[$builtin_key] = $builtin;
$order++;
}
return $map;
}
private function validateNavigationMenuItem($item) {
if (!($item instanceof PHUIListItemView)) {
throw new Exception(
pht(
'Expected buildNavigationMenuItems() to return a list of '.
'PHUIListItemView objects, but got a surprise.'));
}
}
private function newAutomaticMenuItems(AphrontSideNavFilterView $nav) {
$items = array();
// NOTE: We're adding a spacer item for the fixed footer, so that if the
// menu taller than the page content you can still scroll down the page far
// enough to access the last item without the content being obscured by the
// fixed items.
$items[] = id(new PHUIListItemView())
->setHideInApplicationMenu(true)
->addClass('phui-profile-menu-spacer');
$collapse_id = celerity_generate_unique_node_id();
$viewer = $this->getViewer();
$collapse_key =
PhabricatorUserPreferences::PREFERENCE_PROFILE_MENU_COLLAPSED;
$preferences = $viewer->loadPreferences();
$is_collapsed = $preferences->getPreference($collapse_key, false);
if ($is_collapsed) {
$nav->addClass('phui-profile-menu-collapsed');
} else {
$nav->addClass('phui-profile-menu-expanded');
}
if ($viewer->isLoggedIn()) {
$settings_uri = '/settings/adjust/?key='.$collapse_key;
} else {
$settings_uri = null;
}
Javelin::initBehavior(
'phui-profile-menu',
array(
'menuID' => $nav->getMainID(),
'collapseID' => $collapse_id,
'isCollapsed' => (bool)$is_collapsed,
'settingsURI' => $settings_uri,
));
$collapse_icon = id(new PHUIIconCircleView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-expanded')
->setIconFont('fa-chevron-left');
$expand_icon = id(new PHUIIconCircleView())
->addClass('phui-list-item-icon')
->addClass('phui-profile-menu-visible-when-collapsed')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Expand'),
'align' => 'E',
))
->setIconFont('fa-chevron-right');
$items[] = id(new PHUIListItemView())
->setName('Collapse')
->addIcon($collapse_icon)
->addIcon($expand_icon)
->setID($collapse_id)
->addClass('phui-profile-menu-footer')
->addClass('phui-profile-menu-footer-1')
->setHideInApplicationMenu(true)
->setHref('#');
return $items;
}
public function getConfigureURI() {
return $this->getPanelURI('configure/');
}
private function buildPanelReorderContent(array $panels) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$controller = $this->getController();
$request = $controller->getRequest();
$request->validateCSRF();
$order = $request->getStrList('order');
$by_builtin = array();
$by_id = array();
foreach ($panels as $key => $panel) {
$id = $panel->getID();
if ($id) {
$by_id[$id] = $key;
continue;
}
$builtin_key = $panel->getBuiltinKey();
if ($builtin_key) {
$by_builtin[$builtin_key] = $key;
continue;
}
}
$key_order = array();
foreach ($order as $order_item) {
if (isset($by_id[$order_item])) {
$key_order[] = $by_id[$order_item];
continue;
}
if (isset($by_builtin[$order_item])) {
$key_order[] = $by_builtin[$order_item];
continue;
}
}
$panels = array_select_keys($panels, $key_order) + $panels;
$type_order =
PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER;
$order = 1;
foreach ($panels as $panel) {
$xactions = array();
$xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
->setTransactionType($type_order)
->setNewValue($order);
$editor = id(new PhabricatorProfilePanelEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($panel, $xactions);
$order++;
}
return id(new AphrontRedirectResponse())
->setURI($this->getConfigureURI());
}
private function buildPanelConfigureContent(array $panels) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$list_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'reorder-profile-menu-items',
array(
'listID' => $list_id,
'orderURI' => $this->getPanelURI('reorder/'),
));
$list = id(new PHUIObjectItemListView())
->setID($list_id);
foreach ($panels as $panel) {
$id = $panel->getID();
$builtin_key = $panel->getBuiltinKey();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$panel,
PhabricatorPolicyCapability::CAN_EDIT);
$item = id(new PHUIObjectItemView());
$name = $panel->getDisplayName();
$type = $panel->getPanelTypeName();
if (!strlen(trim($name))) {
$name = pht('Untitled "%s" Item', $type);
}
$item->setHeader($name);
$item->addAttribute($type);
if ($can_edit) {
$item
->setGrippable(true)
->addSigil('profile-menu-item')
->setMetadata(
array(
'key' => nonempty($id, $builtin_key),
));
if ($id) {
$default_uri = $this->getPanelURI("default/{$id}/");
} else {
$default_uri = $this->getPanelURI("default/{$builtin_key}/");
}
if ($panel->isDefault()) {
$default_icon = 'fa-thumb-tack green';
$default_text = pht('Current Default');
} else if ($panel->canMakeDefault()) {
$default_icon = 'fa-thumb-tack';
$default_text = pht('Make Default');
} else {
$default_text = null;
}
if ($default_text !== null) {
$item->addAction(
id(new PHUIListItemView())
->setHref($default_uri)
->setWorkflow(true)
->setName($default_text)
->setIcon($default_icon));
}
if ($id) {
$item->setHref($this->getPanelURI("edit/{$id}/"));
$hide_uri = $this->getPanelURI("hide/{$id}/");
} else {
$item->setHref($this->getPanelURI("builtin/{$builtin_key}/"));
$hide_uri = $this->getPanelURI("hide/{$builtin_key}/");
}
if ($panel->isDisabled()) {
$hide_icon = 'fa-plus';
$hide_text = pht('Enable');
} else if ($panel->getBuiltinKey() !== null) {
$hide_icon = 'fa-times';
$hide_text = pht('Disable');
} else {
$hide_icon = 'fa-times';
$hide_text = pht('Delete');
}
+ $can_disable = $panel->canHidePanel();
+
$item->addAction(
id(new PHUIListItemView())
->setHref($hide_uri)
->setWorkflow(true)
+ ->setDisabled(!$can_disable)
->setName($hide_text)
->setIcon($hide_icon));
}
if ($panel->isDisabled()) {
$item->setDisabled(true);
}
$list->addItem($item);
}
$action_view = id(new PhabricatorActionListView())
->setUser($viewer);
$panel_types = PhabricatorProfilePanel::getAllPanels();
$action_view->addAction(
id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Add New Menu Item...')));
foreach ($panel_types as $panel_type) {
if (!$panel_type->canAddToObject($object)) {
continue;
}
$panel_key = $panel_type->getPanelKey();
$action_view->addAction(
id(new PhabricatorActionView())
->setIcon($panel_type->getPanelTypeIcon())
->setName($panel_type->getPanelTypeName())
->setHref($this->getPanelURI("new/{$panel_key}/")));
}
$action_view->addAction(
id(new PhabricatorActionView())
->setLabel(true)
->setName(pht('Documentation')));
$doc_link = PhabricatorEnv::getDoclink('Profile Menu User Guide');
$doc_name = pht('Profile Menu User Guide');
$action_view->addAction(
id(new PhabricatorActionView())
->setIcon('fa-book')
->setHref($doc_link)
->setName($doc_name));
$action_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Configure Menu'))
->setHref('#')
->setIconFont('fa-gear')
->setDropdownMenu($action_view);
$header = id(new PHUIHeaderView())
->setHeader(pht('Profile Menu Items'))
->addActionLink($action_button);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->setObjectList($list);
return $box;
}
private function buildPanelNewContent($panel_key) {
$panel_types = PhabricatorProfilePanel::getAllPanels();
$panel_type = idx($panel_types, $panel_key);
if (!$panel_type) {
return new Aphront404Response();
}
$object = $this->getProfileObject();
if (!$panel_type->canAddToObject($object)) {
return new Aphront404Response();
}
$configuration =
PhabricatorProfilePanelConfiguration::initializeNewPanelConfiguration(
$object,
$panel_type);
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setPanelEngine($this)
->setProfileObject($object)
->setNewPanelConfiguration($configuration)
->setController($controller)
->buildResponse();
}
private function buildPanelEditContent() {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setPanelEngine($this)
->setProfileObject($object)
->setController($controller)
->buildResponse();
}
private function buildPanelBuiltinContent(
PhabricatorProfilePanelConfiguration $configuration) {
// If this builtin panel has already been persisted, redirect to the
// edit page.
$id = $configuration->getID();
if ($id) {
return id(new AphrontRedirectResponse())
->setURI($this->getPanelURI("edit/{$id}/"));
}
// Otherwise, act like we're creating a new panel, we're just starting
// with the builtin template.
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
$object = $this->getProfileObject();
$controller = $this->getController();
return id(new PhabricatorProfilePanelEditEngine())
->setIsBuiltin(true)
->setPanelEngine($this)
->setProfileObject($object)
->setNewPanelConfiguration($configuration)
->setController($controller)
->buildResponse();
}
private function buildPanelHideContent(
PhabricatorProfilePanelConfiguration $configuration) {
$controller = $this->getController();
$request = $controller->getRequest();
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
+ if (!$configuration->canHidePanel()) {
+ return $controller->newDialog()
+ ->setTitle(pht('Mandatory Panel'))
+ ->appendParagraph(
+ pht('This panel is very important, and can not be disabled.'))
+ ->addCancelButton($this->getConfigureURI());
+ }
+
if ($configuration->getBuiltinKey() === null) {
$new_value = null;
$title = pht('Delete Menu Item');
$body = pht('Delete this menu item?');
$button = pht('Delete Menu Item');
} else if ($configuration->isDisabled()) {
$new_value = PhabricatorProfilePanelConfiguration::VISIBILITY_VISIBLE;
$title = pht('Enable Menu Item');
$body = pht(
'Enable this menu item? It will appear in the menu again.');
$button = pht('Enable Menu Item');
} else {
$new_value = PhabricatorProfilePanelConfiguration::VISIBILITY_DISABLED;
$title = pht('Disable Menu Item');
$body = pht(
'Disable this menu item? It will no longer appear in the menu, but '.
'you can re-enable it later.');
$button = pht('Disable Menu Item');
}
$v_visibility = $configuration->getVisibility();
if ($request->isFormPost()) {
if ($new_value === null) {
$configuration->delete();
} else {
$type_visibility =
PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY;
$xactions = array();
$xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
->setTransactionType($type_visibility)
->setNewValue($new_value);
$editor = id(new PhabricatorProfilePanelEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($configuration, $xactions);
}
return id(new AphrontRedirectResponse())
->setURI($this->getConfigureURI());
}
return $controller->newDialog()
->setTitle($title)
->appendParagraph($body)
->addCancelButton($this->getConfigureURI())
->addSubmitButton($button);
}
private function buildPanelDefaultContent(
PhabricatorProfilePanelConfiguration $configuration,
array $panels) {
$controller = $this->getController();
$request = $controller->getRequest();
$viewer = $this->getViewer();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$configuration,
PhabricatorPolicyCapability::CAN_EDIT);
$done_uri = $this->getConfigureURI();
if (!$configuration->canMakeDefault()) {
return $controller->newDialog()
->setTitle(pht('Not Defaultable'))
->appendParagraph(
pht(
'This item can not be set as the default item. This is usually '.
'because the item has no page of its own, or links to an '.
'external page.'))
->addCancelButton($done_uri);
}
if ($configuration->isDefault()) {
return $controller->newDialog()
->setTitle(pht('Already Default'))
->appendParagraph(
pht(
'This item is already set as the default item for this menu.'))
->addCancelButton($done_uri);
}
if ($request->isFormPost()) {
$key = $configuration->getID();
if (!$key) {
$key = $configuration->getBuiltinKey();
}
$this->adjustDefault($key);
return id(new AphrontRedirectResponse())
->setURI($done_uri);
}
return $controller->newDialog()
->setTitle(pht('Make Default'))
->appendParagraph(
pht(
'Set this item as the default for this menu? Users arriving on '.
'this page will be shown the content of this item by default.'))
->addCancelButton($done_uri)
->addSubmitButton(pht('Make Default'));
}
protected function newPanel() {
return PhabricatorProfilePanelConfiguration::initializeNewBuiltin();
}
public function adjustDefault($key) {
$controller = $this->getController();
$request = $controller->getRequest();
$viewer = $request->getViewer();
$panels = $this->loadPanels();
// To adjust the default panel, we first change any existing panels that
// are marked as defaults to "visible", then make the new default panel
// the default.
$default = array();
$visible = array();
foreach ($panels as $panel) {
$builtin_key = $panel->getBuiltinKey();
$id = $panel->getID();
$is_target =
(($builtin_key !== null) && ($builtin_key === $key)) ||
(($id !== null) && ($id === (int)$key));
if ($is_target) {
if (!$panel->isDefault()) {
$default[] = $panel;
}
} else {
if ($panel->isDefault()) {
$visible[] = $panel;
}
}
}
$type_visibility =
PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY;
$v_visible = PhabricatorProfilePanelConfiguration::VISIBILITY_VISIBLE;
$v_default = PhabricatorProfilePanelConfiguration::VISIBILITY_DEFAULT;
$apply = array(
array($v_visible, $visible),
array($v_default, $default),
);
foreach ($apply as $group) {
list($value, $panels) = $group;
foreach ($panels as $panel) {
$xactions = array();
$xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction())
->setTransactionType($type_visibility)
->setNewValue($value);
$editor = id(new PhabricatorProfilePanelEditor())
->setContentSourceFromRequest($request)
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($panel, $xactions);
}
}
return $this;
}
}
diff --git a/src/applications/search/profilepanel/PhabricatorProfilePanel.php b/src/applications/search/profilepanel/PhabricatorProfilePanel.php
index 49159dbf8..8316d1346 100644
--- a/src/applications/search/profilepanel/PhabricatorProfilePanel.php
+++ b/src/applications/search/profilepanel/PhabricatorProfilePanel.php
@@ -1,62 +1,67 @@
<?php
abstract class PhabricatorProfilePanel extends Phobject {
private $viewer;
final public function buildNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
return $this->newNavigationMenuItems($config);
}
abstract protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config);
public function getPanelTypeIcon() {
return null;
}
abstract public function getPanelTypeName();
abstract public function getDisplayName(
PhabricatorProfilePanelConfiguration $config);
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array();
}
public function canAddToObject($object) {
return false;
}
+ public function canHidePanel(
+ PhabricatorProfilePanelConfiguration $config) {
+ return true;
+ }
+
public function canMakeDefault(
PhabricatorProfilePanelConfiguration $config) {
return false;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
final public function getPanelKey() {
return $this->getPhobjectClassConstant('PANELKEY');
}
final public static function getAllPanels() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getPanelKey')
->execute();
}
protected function newItem() {
return new PHUIListItemView();
}
}
diff --git a/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php b/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php
index 127ce6b68..faeaeb520 100644
--- a/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php
+++ b/src/applications/search/storage/PhabricatorProfilePanelConfiguration.php
@@ -1,194 +1,201 @@
<?php
final class PhabricatorProfilePanelConfiguration
extends PhabricatorSearchDAO
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
PhabricatorApplicationTransactionInterface {
protected $profilePHID;
protected $panelKey;
protected $builtinKey;
protected $panelOrder;
protected $visibility;
protected $panelProperties = array();
private $profileObject = self::ATTACHABLE;
private $panel = self::ATTACHABLE;
const VISIBILITY_DEFAULT = 'default';
const VISIBILITY_VISIBLE = 'visible';
const VISIBILITY_DISABLED = 'disabled';
public static function initializeNewBuiltin() {
return id(new self())
->setVisibility(self::VISIBILITY_VISIBLE);
}
public static function initializeNewPanelConfiguration(
$profile_object,
PhabricatorProfilePanel $panel) {
return self::initializeNewBuiltin()
->setProfilePHID($profile_object->getPHID())
->setPanelKey($panel->getPanelKey())
->attachPanel($panel)
->attachProfileObject($profile_object);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'panelProperties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'panelKey' => 'text64',
'builtinKey' => 'text64?',
'panelOrder' => 'uint32?',
'visibility' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_profile' => array(
'columns' => array('profilePHID', 'panelOrder'),
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorProfilePanelPHIDType::TYPECONST);
}
public function attachPanel(PhabricatorProfilePanel $panel) {
$this->panel = $panel;
return $this;
}
public function getPanel() {
return $this->assertAttached($this->panel);
}
public function attachProfileObject($profile_object) {
$this->profileObject = $profile_object;
return $this;
}
public function getProfileObject() {
return $this->assertAttached($this->profileObject);
}
public function setPanelProperty($key, $value) {
$this->panelProperties[$key] = $value;
return $this;
}
public function getPanelProperty($key, $default = null) {
return idx($this->panelProperties, $key, $default);
}
public function buildNavigationMenuItems() {
return $this->getPanel()->buildNavigationMenuItems($this);
}
public function getPanelTypeName() {
return $this->getPanel()->getPanelTypeName();
}
public function getDisplayName() {
return $this->getPanel()->getDisplayName($this);
}
public function canMakeDefault() {
return $this->getPanel()->canMakeDefault($this);
}
+ public function canHidePanel() {
+ return $this->getPanel()->canHidePanel($this);
+ }
+
public function getSortKey() {
$order = $this->getPanelOrder();
if ($order === null) {
$order = 'Z';
} else {
$order = sprintf('%020d', $order);
}
return sprintf(
'~%s%020d',
$order,
$this->getID());
}
public function isDisabled() {
+ if (!$this->canHidePanel()) {
+ return false;
+ }
return ($this->getVisibility() === self::VISIBILITY_DISABLED);
}
public function isDefault() {
return ($this->getVisibility() === self::VISIBILITY_DEFAULT);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getProfileObject()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
return array(
array(
$this->getProfileObject(),
$capability,
),
);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorProfilePanelEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorProfilePanelConfigurationTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
}
diff --git a/src/docs/user/userguide/profile_menu.diviner b/src/docs/user/userguide/profile_menu.diviner
index 51912dd86..bd278b270 100644
--- a/src/docs/user/userguide/profile_menu.diviner
+++ b/src/docs/user/userguide/profile_menu.diviner
@@ -1,143 +1,149 @@
@title Profile Menu User Guide
@group userguide
Master profile menus for projects and other objects.
Overview
========
Some objects, like projects, have customizable menus called "profile menus".
This guide discusses how to add, remove, reorder, configure and extend these
menus.
Supported Applications
======================
These applications currently support profile menus:
| Application | Support |
|-----|-----|
| Projects | Full |
| People | //Read-Only// |
Collapsing and Expanding
========================
To collapse a full-width profile menu, click
{nav icon="angle-left", name="Collapse"}. To expand a narrow menu, click
{nav icon="angle-right", name="Expand"}.
If you're logged in, this setting is sticky, and all menus will respect your
preference.
Editing Menus
=============
You can only edit an object's menu if you can edit the object. For example, you
must have permission to edit a project in order to reconfigure the menu for the
project.
-To edit a menu, click {nav icon="pencil", name="Edit Menu"}. This brings you to
-the menu configuration interface which allows you to add and remove items,
-reorder the menu, edit existing items, and choose a default item.
+To edit a menu, click {nav icon="cogs", name="Manage"} in the menu, then click
+{nav icon="th-list", name="Edit Menu"}. This brings you to the menu
+configuration interface which allows you to add and remove items, reorder the
+menu, edit existing items, and choose a default item.
Menus are comprised of a list of items. Some of the items are builtin
(for example, projects have builtin "Profile", "Workboard" and "Members"
items). You can also add custom items. Builtin and custom items mostly
behave in similar ways, but there are a few exceptions (for example, you can
not completely delete builtin items).
Adding Items
============
To add new items to a menu, use {nav icon="cog", name="Configure Menu"} and
choose a type of item to add. See below for more details on available items.
You can also find a link to this documentation in the same menu, if you want
to reference it later.
Reordering Items
================
To reorder items, drag and drop them to the desired position. Your changes
will be reflected in the item ordering in the menu.
Setting a Default
=================
The default item controls what content is shown when a user browses to the
object's main page. For example, the default item for a project controls where
the user ends up when they click a link to the project from another
application.
To choose a default item, click {nav icon="thumb-tack", name="Make Default"}.
Not all kinds of items can be set as the default item. For example, you can not
set a separator line as a default because the item can't be selected and has no
content.
If no default is explicitly selected, or a default is deleted or disabled, the
first item which is eligible to be a default is used as the default item.
Removing Items
==============
To remove items, click the {nav icon="times", name="Delete"} action.
Builtin items can not be deleted and have a
{nav icon="times", name="Disable"} action instead, which will hide them but
not delete them. You an re-enable a disabled item with the
{nav icon="plus', name="Enable"} action.
+A few items can not be hidden or deleted. For example, the
+{nav icon="cogs", name="Manage"} item must always be available in the menu
+because if you hid it by accident there would no longer be a way to access
+the configuration interface and fix the mistake.
+
Removing or hiding an item does not disable the underlying functionality.
For example, if you hide the "Members" item for a project, that just removes
it from the menu. The project still has members, and users can still navigate
to the members page by following a link to it from elsewhere in the application
or entering the URI manually.
Editing Items
=============
To edit an item, click the name of the item. This will show you available
configuration for the item and allow you to edit it.
Which properties are editable depends on what sort of item you are editing.
Most items can be renamed, and some items have more settings available. For
example, when editing a link, you can choose the link target and select an
icon for the item.
A few items have no configuration. For example, visual separator lines are
purely cosmetic and have no available settings.
Available Items
===============
When you add items, you can choose between different types of items to add.
Which item types are available depends on what sort of object you are editing
the menu for, but most objects support these items:
- {icon link} **Link**: Allows you to create an item which links to
somewhere else in Phabricator, or to an external site.
- {icon minus} **Divider**: Adds a visual separator to the menu. This is
purely cosmetic.
- {icon coffee} **Motivator**: Motivate your employees with inspirational
quotes. A new quote every day!
To learn more about how an item works, try adding it. You can always delete
it later if it doesn't do what you wanted.
Writing New Item Types
======================
IMPORTANT: This feature is not stable, and the API is subject to change.
To add new types of items, subclass @{class:PhabricatorProfilePanel}.

File Metadata

Mime Type
text/x-diff
Expires
Mon, May 12, 6:36 PM (1 d, 20 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
1d/cf/3555c1e52fd66e303bae1c5d950a

Event Timeline