1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 04:42:40 +01:00

Make "profile menu" configuration mostly work

Summary:
Ref T10054. This does a big chunk of the legwork to let users reconfigure profile menus (currently, just project menus).

This includes:

  - Editing builtin items (e.g., you can rename the default items).
  - Creating new items (for now, only links are available).

This does not yet include:

  - Hiding items.
  - Reordering items.
  - Lots of fancy types of items (dashboards, etc).
  - Any UI changes.
  - Documentation (does feature: TODO link for documentation).

Test Plan:
{F1060695}

{F1060696}

{F1060697}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10054

Differential Revision: https://secure.phabricator.com/D15010
This commit is contained in:
epriestley 2016-01-12 15:06:43 -08:00
parent bb6df2e5f3
commit f24318f308
26 changed files with 999 additions and 39 deletions

View file

@ -0,0 +1,13 @@
CREATE TABLE {$NAMESPACE}_search.search_profilepanelconfiguration (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
profilePHID VARBINARY(64) NOT NULL,
panelKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
builtinKey VARCHAR(64) COLLATE {$COLLATE_TEXT},
panelOrder INT UNSIGNED,
visibility VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
panelProperties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
KEY `key_profile` (profilePHID, panelOrder)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_search.search_profilepanelconfigurationtransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -2831,7 +2831,10 @@ phutil_register_library_map(array(
'PhabricatorProfilePanelConfiguration' => 'applications/search/storage/PhabricatorProfilePanelConfiguration.php',
'PhabricatorProfilePanelConfigurationQuery' => 'applications/search/query/PhabricatorProfilePanelConfigurationQuery.php',
'PhabricatorProfilePanelConfigurationTransaction' => 'applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php',
'PhabricatorProfilePanelEditEngine' => 'applications/search/editor/PhabricatorProfilePanelEditEngine.php',
'PhabricatorProfilePanelEditor' => 'applications/search/editor/PhabricatorProfilePanelEditor.php',
'PhabricatorProfilePanelEngine' => 'applications/search/engine/PhabricatorProfilePanelEngine.php',
'PhabricatorProfilePanelIconSet' => 'applications/search/profilepanel/PhabricatorProfilePanelIconSet.php',
'PhabricatorProfilePanelInterface' => 'applications/search/interface/PhabricatorProfilePanelInterface.php',
'PhabricatorProfilePanelPHIDType' => 'applications/search/phidtype/PhabricatorProfilePanelPHIDType.php',
'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php',
@ -2896,6 +2899,7 @@ phutil_register_library_map(array(
'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php',
'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php',
'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php',
'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php',
'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php',
'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php',
'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php',
@ -7188,10 +7192,14 @@ phutil_register_library_map(array(
'PhabricatorSearchDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhabricatorProfilePanelConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProfilePanelConfigurationTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorProfilePanelEditEngine' => 'PhabricatorEditEngine',
'PhabricatorProfilePanelEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorProfilePanelEngine' => 'Phobject',
'PhabricatorProfilePanelIconSet' => 'PhabricatorIconSet',
'PhabricatorProfilePanelPHIDType' => 'PhabricatorPHIDType',
'PhabricatorProject' => array(
'PhabricatorProjectDAO',
@ -7277,6 +7285,7 @@ phutil_register_library_map(array(
'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorProjectPanelController' => 'PhabricatorProjectController',
'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType',

View file

@ -635,4 +635,18 @@ abstract class PhabricatorApplication
return $base.'(?:query/(?P<queryKey>[^/]+)/)?';
}
protected function getPanelRouting($controller) {
$edit_route = $this->getEditRoutePattern();
return array(
'(?P<panelAction>view)/(?P<panelID>[^/]+)/' => $controller,
'(?P<panelAction>hide)/(?P<panelID>[^/]+)/' => $controller,
'(?P<panelAction>configure)/' => $controller,
'(?P<panelAction>edit)/'.$edit_route => $controller,
'(?P<panelAction>new)/(?<panelKey>[^/]+)/'.$edit_route => $controller,
'(?P<panelAction>builtin)/(?<panelID>[^/]+)/'.$edit_route
=> $controller,
);
}
}

View file

@ -61,6 +61,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
=> 'PhabricatorProjectEditPictureController',
$this->getEditRoutePattern('edit/')
=> 'PhabricatorProjectEditController',
'(?P<projectID>[1-9]\d*)/panel/'
=> $this->getPanelRouting('PhabricatorProjectPanelController'),
'subprojects/(?P<id>[1-9]\d*)/'
=> 'PhabricatorProjectSubprojectsController',
'milestones/(?P<id>[1-9]\d*)/'

View file

@ -18,7 +18,10 @@ abstract class PhabricatorProjectController extends PhabricatorController {
$viewer = $this->getViewer();
$request = $this->getRequest();
$id = $request->getURIData('id');
$id = nonempty(
$request->getURIData('projectID'),
$request->getURIData('id'));
$slug = $request->getURIData('slug');
if ($slug) {

View file

@ -0,0 +1,21 @@
<?php
final class PhabricatorProjectPanelController
extends PhabricatorProjectController {
public function handleRequest(AphrontRequest $request) {
$response = $this->loadProject();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$project = $this->getProject();
return id(new PhabricatorProfilePanelEngine())
->setProfileObject($project)
->setController($this)
->buildResponse();
}
}

View file

@ -5,6 +5,36 @@ final class PhabricatorProjectDetailsProfilePanel
const PANELKEY = 'project.details';
public function getPanelTypeName() {
return pht('Project Details');
}
private function getDefaultName() {
return pht('Project Details');
}
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) {

View file

@ -5,6 +5,36 @@ final class PhabricatorProjectMembersProfilePanel
const PANELKEY = 'project.members';
public function getPanelTypeName() {
return pht('Project Members');
}
private function getDefaultName() {
return pht('Members');
}
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) {
@ -12,7 +42,7 @@ final class PhabricatorProjectMembersProfilePanel
$id = $project->getID();
$name = pht('Members');
$name = $this->getDisplayName($config);
$icon = 'fa-group';
$href = "/project/members/{$id}/";

View file

@ -5,6 +5,36 @@ final class PhabricatorProjectWorkboardProfilePanel
const PANELKEY = 'project.workboard';
public function getPanelTypeName() {
return pht('Project Workboard');
}
private function getDefaultName() {
return pht('Workboard');
}
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) {
$viewer = $this->getViewer();
@ -29,7 +59,7 @@ final class PhabricatorProjectWorkboardProfilePanel
$id = $project->getID();
$href = "/project/board/{$id}/";
$name = pht('Workboard');
$name = $this->getDisplayName($config);
$item = id(new PHUIListItemView())
->setRenderNameAsTooltip(true)

View file

@ -658,36 +658,36 @@ final class PhabricatorProject extends PhabricatorProjectDAO
public function getBuiltinProfilePanels() {
$panels = array();
$panels[] = id(new PhabricatorProfilePanelConfiguration())
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey(self::PANEL_PROFILE)
->setPanelKey(PhabricatorProjectDetailsProfilePanel::PANELKEY);
$panels[] = id(new PhabricatorProfilePanelConfiguration())
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey(self::PANEL_WORKBOARD)
->setPanelKey(PhabricatorProjectWorkboardProfilePanel::PANELKEY);
// TODO: This is temporary.
$href = urisprintf(
$uri = urisprintf(
'/maniphest/?statuses=open()&projects=%s#R',
$this->getPHID());
$panels[] = id(new PhabricatorProfilePanelConfiguration())
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey('tasks')
->setPanelKey(PhabricatorLinkProfilePanel::PANELKEY)
->setPanelProperty('icon', 'fa-anchor')
->setPanelProperty('icon', 'maniphest')
->setPanelProperty('name', pht('Open Tasks'))
->setPanelProperty('href', $href);
->setPanelProperty('uri', $uri);
// TODO: This is temporary.
$id = $this->getID();
$panels[] = id(new PhabricatorProfilePanelConfiguration())
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey('feed')
->setPanelKey(PhabricatorLinkProfilePanel::PANELKEY)
->setPanelProperty('icon', 'fa-newspaper-o')
->setPanelProperty('icon', 'feed')
->setPanelProperty('name', pht('Feed'))
->setPanelProperty('href', "/project/feed/{$id}/");
->setPanelProperty('uri', "/project/feed/{$id}/");
$panels[] = id(new PhabricatorProfilePanelConfiguration())
$panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin()
->setBuiltinKey(self::PANEL_MEMBERS)
->setPanelKey(PhabricatorProjectMembersProfilePanel::PANELKEY);

View file

@ -0,0 +1,136 @@
<?php
final class PhabricatorProfilePanelEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'search.profilepanel';
private $panelEngine;
private $profileObject;
private $newPanelConfiguration;
private $isBuiltin;
public function isEngineConfigurable() {
return false;
}
public function setPanelEngine(PhabricatorProfilePanelEngine $engine) {
$this->panelEngine = $engine;
return $this;
}
public function getPanelEngine() {
return $this->panelEngine;
}
public function setProfileObject(
PhabricatorProfilePanelInterface $profile_object) {
$this->profileObject = $profile_object;
return $this;
}
public function getProfileObject() {
return $this->profileObject;
}
public function setNewPanelConfiguration(
PhabricatorProfilePanelConfiguration $configuration) {
$this->newPanelConfiguration = $configuration;
return $this;
}
public function getNewPanelConfiguration() {
return $this->newPanelConfiguration;
}
public function setIsBuiltin($is_builtin) {
$this->isBuiltin = $is_builtin;
return $this;
}
public function getIsBuiltin() {
return $this->isBuiltin;
}
public function getEngineName() {
return pht('Profile Panels');
}
public function getSummaryHeader() {
return pht('Edit Profile Panel Configurations');
}
public function getSummaryText() {
return pht('This engine is used to modify menu items on profiles.');
}
public function getEngineApplicationClass() {
return 'PhabricatorSearchApplication';
}
protected function newEditableObject() {
if (!$this->newPanelConfiguration) {
throw new Exception(
pht('Profile panels can not be generated without an object context.'));
}
return clone $this->newPanelConfiguration;
}
protected function newObjectQuery() {
return id(new PhabricatorProfilePanelConfigurationQuery());
}
protected function getObjectCreateTitleText($object) {
if ($this->getIsBuiltin()) {
return pht('Edit Builtin Item');
} else {
return pht('Create Menu Item');
}
}
protected function getObjectCreateButtonText($object) {
if ($this->getIsBuiltin()) {
return pht('Save Changes');
} else {
return pht('Create Menu Item');
}
}
protected function getObjectEditTitleText($object) {
return pht('Edit Menu Item: %s', $object->getDisplayName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Menu Item');
}
protected function getObjectCreateShortText() {
return pht('Edit Menu Item');
}
protected function getObjectCreateCancelURI($object) {
return $this->getPanelEngine()->getConfigureURI();
}
protected function getObjectViewURI($object) {
return $this->getPanelEngine()->getConfigureURI();
}
protected function buildCustomEditFields($object) {
$panel = $object->getPanel();
$fields = $panel->buildEditEngineFields($object);
$type_property =
PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY;
foreach ($fields as $field) {
$field
->setTransactionType($type_property)
->setMetadataValue('property.key', $field->getKey());
}
return $fields;
}
}

View file

@ -0,0 +1,70 @@
<?php
final class PhabricatorProfilePanelEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorSearchApplication';
}
public function getEditorObjectsDescription() {
return pht('Profile Panels');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
$key = $xaction->getMetadataValue('property.key');
return $object->getPanelProperty($key, null);
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
return $xaction->getNewValue();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
$key = $xaction->getMetadataValue('property.key');
$value = $xaction->getNewValue();
$object->setPanelProperty($key, $value);
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
}

View file

@ -5,6 +5,7 @@ final class PhabricatorProfilePanelEngine extends Phobject {
private $viewer;
private $profileObject;
private $panels;
private $controller;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@ -25,6 +26,103 @@ final class PhabricatorProfilePanelEngine extends Phobject {
return $this->profileObject;
}
public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
public function getController() {
return $this->controller;
}
public function buildResponse() {
$controller = $this->getController();
$viewer = $controller->getViewer();
$this->setViewer($viewer);
$request = $controller->getRequest();
$panel_action = $request->getURIData('panelAction');
$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) {
$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 '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 'new':
$panel_key = $request->getURIData('panelKey');
$content = $this->buildPanelNewContent($panel_key);
break;
case 'builtin':
$content = $this->buildPanelBuiltinContent($selected_panel);
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() {
$nav = id(new AphrontSideNavFilterView())
->setIconNav(true)
@ -60,6 +158,11 @@ final class PhabricatorProfilePanelEngine extends Phobject {
}
}
$configure_item = $this->newConfigureMenuItem();
if ($configure_item) {
$nav->addMenuItem($configure_item);
}
$nav->selectFilter(null);
return $nav;
@ -75,10 +178,25 @@ final class PhabricatorProfilePanelEngine extends Phobject {
private function loadPanels() {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$panels = $this->loadBuiltinProfilePanels();
// TODO: Load persisted panels.
$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) {
$panels[$builtin_key] = $stored_panel;
} else {
$panels[] = $stored_panel;
}
}
foreach ($panels as $panel) {
$impl = $panel->getPanel();
@ -86,6 +204,10 @@ final class PhabricatorProfilePanelEngine extends Phobject {
$impl->setViewer($viewer);
}
// Normalize keys since callers shouldn't rely on this array being
// partially keyed.
$panels = array_values($panels);
return $panels;
}
@ -128,6 +250,7 @@ final class PhabricatorProfilePanelEngine extends Phobject {
}
$builtin
->setProfilePHID($object->getPHID())
->attachPanel($panel)
->attachProfileObject($object)
->setPanelOrder($order);
@ -149,4 +272,208 @@ final class PhabricatorProfilePanelEngine extends Phobject {
}
}
private function newConfigureMenuItem() {
if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) {
return null;
}
$viewer = $this->getViewer();
$object = $this->getProfileObject();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
return id(new PHUIListItemView())
->setName('Configure Menu')
->setKey('panel.configure')
->setIcon('fa-gear')
->setHref($this->getPanelURI('configure/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setRenderNameAsTooltip(true);
}
public function getConfigureURI() {
return $this->getPanelURI('configure/');
}
private function getPanelURI($path) {
$project = $this->getProfileObject();
$id = $project->getID();
return "/project/{$id}/panel/{$path}";
}
private function buildPanelConfigureContent(array $panels) {
$viewer = $this->getViewer();
$object = $this->getProfileObject();
PhabricatorPolicyFilter::requireCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$list = new PHUIObjectItemListView();
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) {
if ($id) {
$item->setHref($this->getPanelURI("edit/{$id}/"));
} else {
$item->setHref($this->getPanelURI("builtin/{$builtin_key}/"));
}
}
$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')));
$action_view->addAction(
id(new PhabricatorActionView())
->setIcon('fa-book')
->setName(pht('TODO: Write Documentation')));
$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();
}
}

View file

@ -5,19 +5,89 @@ final class PhabricatorLinkProfilePanel
const PANELKEY = 'link';
public function getPanelTypeIcon() {
return 'fa-link';
}
public function getPanelTypeName() {
return pht('Link');
}
public function canAddToObject(
PhabricatorProfilePanelInterface $object) {
return true;
}
public function getDisplayName(
PhabricatorProfilePanelConfiguration $config) {
return $this->getLinkName($config);
}
public function buildEditEngineFields(
PhabricatorProfilePanelConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setIsRequired(true)
->setValue($this->getLinkName($config)),
id(new PhabricatorTextEditField())
->setKey('uri')
->setLabel(pht('URI'))
->setIsRequired(true)
->setValue($this->getLinkURI($config)),
id(new PhabricatorIconSetEditField())
->setKey('icon')
->setLabel(pht('Icon'))
->setIconSet(new PhabricatorProfilePanelIconSet())
->setValue($this->getLinkIcon($config)),
);
}
private function getLinkName(
PhabricatorProfilePanelConfiguration $config) {
return $config->getPanelProperty('name');
}
private function getLinkIcon(
PhabricatorProfilePanelConfiguration $config) {
return $config->getPanelProperty('icon', 'link');
}
private function getLinkURI(
PhabricatorProfilePanelConfiguration $config) {
return $config->getPanelProperty('uri');
}
private function isValidLinkURI($uri) {
return PhabricatorEnv::isValidURIForLink($uri);
}
protected function newNavigationMenuItems(
PhabricatorProfilePanelConfiguration $config) {
$icon = $config->getPanelProperty('icon');
$name = $config->getPanelProperty('name');
$href = $config->getPanelProperty('href');
$icon = $this->getLinkIcon($config);
$name = $this->getLinkName($config);
$href = $this->getLinkURI($config);
if (!$this->isValidLinkURI($href)) {
$href = '#';
}
$icon_object = id(new PhabricatorProfilePanelIconSet())
->getIcon($icon);
if ($icon_object) {
$icon_class = $icon_object->getIcon();
} else {
$icon_class = 'fa-link';
}
$item = id(new PHUIListItemView())
->setRenderNameAsTooltip(true)
->setType(PHUIListItemView::TYPE_ICON_NAV)
->setHref($href)
->setName($name)
->setIcon($icon);
->setIcon($icon_class);
return array(
$item,

View file

@ -12,6 +12,25 @@ abstract class PhabricatorProfilePanel extends Phobject {
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(
PhabricatorProfilePanelInterface $object) {
return false;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorProfilePanelIconSet
extends PhabricatorIconSet {
const ICONSETKEY = 'profilepanel';
public function getSelectIconTitleText() {
return pht('Choose Item Icon');
}
protected function newIcons() {
$list = array(
array(
'key' => 'link',
'icon' => 'fa-link',
'name' => pht('Link'),
),
array(
'key' => 'maniphest',
'icon' => 'fa-anchor',
'name' => pht('Maniphest'),
),
array(
'key' => 'feed',
'icon' => 'fa-newspaper-o',
'name' => pht('Feed'),
),
);
$icons = array();
foreach ($list as $spec) {
$icons[] = id(new PhabricatorIconSetIcon())
->setKey($spec['key'])
->setIcon($spec['icon'])
->setLabel($spec['name']);
}
return $icons;
}
}

View file

@ -5,7 +5,7 @@ final class PhabricatorProfilePanelConfigurationQuery
private $ids;
private $phids;
private $profileObjectPHIDs;
private $profilePHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -17,8 +17,8 @@ final class PhabricatorProfilePanelConfigurationQuery
return $this;
}
public function withProfileObjectPHIDs(array $phids) {
$this->profileObjectPHIDs = $phids;
public function withProfilePHIDs(array $phids) {
$this->profilePHIDs = $phids;
return $this;
}
@ -47,16 +47,54 @@ final class PhabricatorProfilePanelConfigurationQuery
$this->phids);
}
if ($this->profileObjectPHIDs !== null) {
if ($this->profilePHIDs !== null) {
$where[] = qsprintf(
$conn,
'profileObjectPHID IN (%Ls)',
$this->profileObjectPHIDs);
'profilePHID IN (%Ls)',
$this->profilePHIDs);
}
return $where;
}
protected function willFilterPage(array $page) {
$panels = PhabricatorProfilePanel::getAllPanels();
foreach ($page as $key => $panel) {
$panel_type = idx($panels, $panel->getPanelKey());
if (!$panel_type) {
$this->didRejectResult($panel);
unset($page[$key]);
continue;
}
$panel->attachPanel($panel_type);
}
if (!$page) {
return array();
}
$profile_phids = mpull($page, 'getProfilePHID');
$profiles = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($profile_phids)
->execute();
$profiles = mpull($profiles, null, 'getPHID');
foreach ($page as $key => $panel) {
$profile = idx($profiles, $panel->getProfilePHID());
if (!$profile) {
$this->didRejectResult($panel);
unset($page[$key]);
continue;
}
$panel->attachProfileObject($profile);
}
return $page;
}
public function getQueryApplicationClass() {
return 'PhabricatorSearchApplication';
}

View file

@ -4,26 +4,34 @@ final class PhabricatorProfilePanelConfiguration
extends PhabricatorSearchDAO
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface {
PhabricatorExtendedPolicyInterface,
PhabricatorApplicationTransactionInterface {
protected $profilePHID;
protected $panelKey;
protected $builtinKey;
protected $panelOrder;
protected $isDisabled;
protected $visibility;
protected $panelProperties = array();
private $profileObject = self::ATTACHABLE;
private $panel = self::ATTACHABLE;
const VISIBILITY_VISIBLE = 'visible';
const VISIBILITY_DISABLED = 'disabled';
public static function initializeNewBuiltin() {
return id(new self())
->setVisibility(self::VISIBILITY_VISIBLE);
}
public static function initializeNewPanelConfiguration(
PhabricatorProfilePanelInterface $profile_object,
PhabricatorProfilePanel $panel) {
return id(new self())
return self::initializeNewBuiltin()
->setProfilePHID($profile_object->getPHID())
->setPanelKey($panel->getPanelKey())
->setIsDisabled(0)
->attachPanel($panel)
->attachProfileObject($profile_object);
}
@ -36,9 +44,9 @@ final class PhabricatorProfilePanelConfiguration
),
self::CONFIG_COLUMN_SCHEMA => array(
'panelKey' => 'text64',
'builtinKey' => 'text64',
'panelOrder' => 'uint32',
'isDisabled' => 'bool',
'builtinKey' => 'text64?',
'panelOrder' => 'uint32?',
'visibility' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_profile' => array(
@ -48,6 +56,11 @@ final class PhabricatorProfilePanelConfiguration
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorProfilePanelPHIDType::TYPECONST);
}
public function attachPanel(PhabricatorProfilePanel $panel) {
$this->panel = $panel;
return $this;
@ -67,10 +80,6 @@ final class PhabricatorProfilePanelConfiguration
return $this->assertAttached($this->profileObject);
}
public function buildNavigationMenuItems() {
return $this->getPanel()->buildNavigationMenuItems($this);
}
public function setPanelProperty($key, $value) {
$this->panelProperties[$key] = $value;
return $this;
@ -80,6 +89,18 @@ final class PhabricatorProfilePanelConfiguration
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);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -121,4 +142,27 @@ final class PhabricatorProfilePanelConfiguration
);
}
/* -( 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;
}
}

View file

@ -3,6 +3,8 @@
final class PhabricatorProfilePanelConfigurationTransaction
extends PhabricatorApplicationTransaction {
const TYPE_PROPERTY = 'profilepanel.property';
public function getApplicationName() {
return 'search';
}

View file

@ -16,6 +16,10 @@ final class PhabricatorEditEngineConfigurationListController
$engine = PhabricatorEditEngine::getByKey($viewer, $engine_key)
->setViewer($viewer);
if (!$engine->isEngineConfigurable()) {
return new Aphront404Response();
}
$items = array();
$items[] = id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_LABEL)

View file

@ -72,6 +72,10 @@ abstract class PhabricatorEditEngineController
$engine = $config->getEngine();
}
if (!$engine->isEngineConfigurable()) {
return null;
}
return $config;
}

View file

@ -57,6 +57,10 @@ abstract class PhabricatorEditEngine
return $this;
}
public function isEngineConfigurable() {
return true;
}
/* -( Managing Fields )---------------------------------------------------- */
@ -1005,8 +1009,11 @@ abstract class PhabricatorEditEngine
}
$header = id(new PHUIHeaderView())
->setHeader($header_text)
->addActionLink($action_button);
->setHeader($header_text);
if ($action_button) {
$header->addActionLink($action_button);
}
$crumbs = $this->buildCrumbs($object, $final = true);
@ -1066,6 +1073,10 @@ abstract class PhabricatorEditEngine
}
private function buildEditFormActionButton($object) {
if (!$this->isEngineConfigurable()) {
return null;
}
$viewer = $this->getViewer();
$action_view = id(new PhabricatorActionListView())

View file

@ -3,8 +3,26 @@
final class PhabricatorTextEditField
extends PhabricatorEditField {
private $placeholder;
public function setPlaceholder($placeholder) {
$this->placeholder = $placeholder;
return $this;
}
public function getPlaceholder() {
return $this->placeholder;
}
protected function newControl() {
return new AphrontFormTextControl();
$control = new AphrontFormTextControl();
$placeholder = $this->getPlaceholder();
if (strlen($placeholder)) {
$control->setPlaceholder($placeholder);
}
return $control;
}
protected function newConduitParameterType() {

View file

@ -62,6 +62,10 @@ final class PhabricatorEditEngineSearchEngine
$list = id(new PHUIObjectItemListView())
->setUser($viewer);
foreach ($engines as $engine) {
if (!$engine->isEngineConfigurable()) {
continue;
}
$engine_key = $engine->getEngineKey();
$query_uri = "/transactions/editengine/{$engine_key}/";

View file

@ -39,7 +39,7 @@ Use {nav My Deprecated Calls} to find calls to deprecated methods you have
made, and {nav Deprecated Call Logs} to find deprecated calls by all users.
You can also search for calls by specific users. For example, it may be useful
to serach for any bot accounts you run to make sure they aren't calling
to search for any bot accounts you run to make sure they aren't calling
outdated APIs.
The most common cause of calls to deprecated methods is users running very