@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+999 -39
+13
resources/sql/autopatches/20160113.propanel.1.storage.sql
··· 1 + CREATE TABLE {$NAMESPACE}_search.search_profilepanelconfiguration ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + profilePHID VARBINARY(64) NOT NULL, 5 + panelKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, 6 + builtinKey VARCHAR(64) COLLATE {$COLLATE_TEXT}, 7 + panelOrder INT UNSIGNED, 8 + visibility VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, 9 + panelProperties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 10 + dateCreated INT UNSIGNED NOT NULL, 11 + dateModified INT UNSIGNED NOT NULL, 12 + KEY `key_profile` (profilePHID, panelOrder) 13 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+19
resources/sql/autopatches/20160113.propanel.2.xaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_search.search_profilepanelconfigurationtransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + objectPHID VARBINARY(64) NOT NULL, 6 + viewPolicy VARBINARY(64) NOT NULL, 7 + editPolicy VARBINARY(64) NOT NULL, 8 + commentPHID VARBINARY(64) DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 11 + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 12 + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 13 + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 14 + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+9
src/__phutil_library_map__.php
··· 2831 2831 'PhabricatorProfilePanelConfiguration' => 'applications/search/storage/PhabricatorProfilePanelConfiguration.php', 2832 2832 'PhabricatorProfilePanelConfigurationQuery' => 'applications/search/query/PhabricatorProfilePanelConfigurationQuery.php', 2833 2833 'PhabricatorProfilePanelConfigurationTransaction' => 'applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php', 2834 + 'PhabricatorProfilePanelEditEngine' => 'applications/search/editor/PhabricatorProfilePanelEditEngine.php', 2835 + 'PhabricatorProfilePanelEditor' => 'applications/search/editor/PhabricatorProfilePanelEditor.php', 2834 2836 'PhabricatorProfilePanelEngine' => 'applications/search/engine/PhabricatorProfilePanelEngine.php', 2837 + 'PhabricatorProfilePanelIconSet' => 'applications/search/profilepanel/PhabricatorProfilePanelIconSet.php', 2835 2838 'PhabricatorProfilePanelInterface' => 'applications/search/interface/PhabricatorProfilePanelInterface.php', 2836 2839 'PhabricatorProfilePanelPHIDType' => 'applications/search/phidtype/PhabricatorProfilePanelPHIDType.php', 2837 2840 'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php', ··· 2896 2899 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', 2897 2900 'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php', 2898 2901 'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php', 2902 + 'PhabricatorProjectPanelController' => 'applications/project/controller/PhabricatorProjectPanelController.php', 2899 2903 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', 2900 2904 'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php', 2901 2905 'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php', ··· 7188 7192 'PhabricatorSearchDAO', 7189 7193 'PhabricatorPolicyInterface', 7190 7194 'PhabricatorExtendedPolicyInterface', 7195 + 'PhabricatorApplicationTransactionInterface', 7191 7196 ), 7192 7197 'PhabricatorProfilePanelConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 7193 7198 'PhabricatorProfilePanelConfigurationTransaction' => 'PhabricatorApplicationTransaction', 7199 + 'PhabricatorProfilePanelEditEngine' => 'PhabricatorEditEngine', 7200 + 'PhabricatorProfilePanelEditor' => 'PhabricatorApplicationTransactionEditor', 7194 7201 'PhabricatorProfilePanelEngine' => 'Phobject', 7202 + 'PhabricatorProfilePanelIconSet' => 'PhabricatorIconSet', 7195 7203 'PhabricatorProfilePanelPHIDType' => 'PhabricatorPHIDType', 7196 7204 'PhabricatorProject' => array( 7197 7205 'PhabricatorProjectDAO', ··· 7277 7285 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 7278 7286 'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 7279 7287 'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver', 7288 + 'PhabricatorProjectPanelController' => 'PhabricatorProjectController', 7280 7289 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 7281 7290 'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType', 7282 7291 'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType',
+14
src/applications/base/PhabricatorApplication.php
··· 635 635 return $base.'(?:query/(?P<queryKey>[^/]+)/)?'; 636 636 } 637 637 638 + protected function getPanelRouting($controller) { 639 + $edit_route = $this->getEditRoutePattern(); 640 + 641 + return array( 642 + '(?P<panelAction>view)/(?P<panelID>[^/]+)/' => $controller, 643 + '(?P<panelAction>hide)/(?P<panelID>[^/]+)/' => $controller, 644 + '(?P<panelAction>configure)/' => $controller, 645 + '(?P<panelAction>edit)/'.$edit_route => $controller, 646 + '(?P<panelAction>new)/(?<panelKey>[^/]+)/'.$edit_route => $controller, 647 + '(?P<panelAction>builtin)/(?<panelID>[^/]+)/'.$edit_route 648 + => $controller, 649 + ); 650 + } 651 + 638 652 }
+2
src/applications/project/application/PhabricatorProjectApplication.php
··· 61 61 => 'PhabricatorProjectEditPictureController', 62 62 $this->getEditRoutePattern('edit/') 63 63 => 'PhabricatorProjectEditController', 64 + '(?P<projectID>[1-9]\d*)/panel/' 65 + => $this->getPanelRouting('PhabricatorProjectPanelController'), 64 66 'subprojects/(?P<id>[1-9]\d*)/' 65 67 => 'PhabricatorProjectSubprojectsController', 66 68 'milestones/(?P<id>[1-9]\d*)/'
+4 -1
src/applications/project/controller/PhabricatorProjectController.php
··· 18 18 $viewer = $this->getViewer(); 19 19 $request = $this->getRequest(); 20 20 21 - $id = $request->getURIData('id'); 21 + $id = nonempty( 22 + $request->getURIData('projectID'), 23 + $request->getURIData('id')); 24 + 22 25 $slug = $request->getURIData('slug'); 23 26 24 27 if ($slug) {
+21
src/applications/project/controller/PhabricatorProjectPanelController.php
··· 1 + <?php 2 + 3 + final class PhabricatorProjectPanelController 4 + extends PhabricatorProjectController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $response = $this->loadProject(); 8 + if ($response) { 9 + return $response; 10 + } 11 + 12 + $viewer = $this->getViewer(); 13 + $project = $this->getProject(); 14 + 15 + return id(new PhabricatorProfilePanelEngine()) 16 + ->setProfileObject($project) 17 + ->setController($this) 18 + ->buildResponse(); 19 + } 20 + 21 + }
+30
src/applications/project/profilepanel/PhabricatorProjectDetailsProfilePanel.php
··· 5 5 6 6 const PANELKEY = 'project.details'; 7 7 8 + public function getPanelTypeName() { 9 + return pht('Project Details'); 10 + } 11 + 12 + private function getDefaultName() { 13 + return pht('Project Details'); 14 + } 15 + 16 + public function getDisplayName( 17 + PhabricatorProfilePanelConfiguration $config) { 18 + $name = $config->getPanelProperty('name'); 19 + 20 + if (strlen($name)) { 21 + return $name; 22 + } 23 + 24 + return $this->getDefaultName(); 25 + } 26 + 27 + public function buildEditEngineFields( 28 + PhabricatorProfilePanelConfiguration $config) { 29 + return array( 30 + id(new PhabricatorTextEditField()) 31 + ->setKey('name') 32 + ->setLabel(pht('Name')) 33 + ->setPlaceholder($this->getDefaultName()) 34 + ->setValue($config->getPanelProperty('name')), 35 + ); 36 + } 37 + 8 38 protected function newNavigationMenuItems( 9 39 PhabricatorProfilePanelConfiguration $config) { 10 40
+31 -1
src/applications/project/profilepanel/PhabricatorProjectMembersProfilePanel.php
··· 5 5 6 6 const PANELKEY = 'project.members'; 7 7 8 + public function getPanelTypeName() { 9 + return pht('Project Members'); 10 + } 11 + 12 + private function getDefaultName() { 13 + return pht('Members'); 14 + } 15 + 16 + public function getDisplayName( 17 + PhabricatorProfilePanelConfiguration $config) { 18 + $name = $config->getPanelProperty('name'); 19 + 20 + if (strlen($name)) { 21 + return $name; 22 + } 23 + 24 + return $this->getDefaultName(); 25 + } 26 + 27 + public function buildEditEngineFields( 28 + PhabricatorProfilePanelConfiguration $config) { 29 + return array( 30 + id(new PhabricatorTextEditField()) 31 + ->setKey('name') 32 + ->setLabel(pht('Name')) 33 + ->setPlaceholder($this->getDefaultName()) 34 + ->setValue($config->getPanelProperty('name')), 35 + ); 36 + } 37 + 8 38 protected function newNavigationMenuItems( 9 39 PhabricatorProfilePanelConfiguration $config) { 10 40 ··· 12 42 13 43 $id = $project->getID(); 14 44 15 - $name = pht('Members'); 45 + $name = $this->getDisplayName($config); 16 46 $icon = 'fa-group'; 17 47 $href = "/project/members/{$id}/"; 18 48
+31 -1
src/applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php
··· 5 5 6 6 const PANELKEY = 'project.workboard'; 7 7 8 + public function getPanelTypeName() { 9 + return pht('Project Workboard'); 10 + } 11 + 12 + private function getDefaultName() { 13 + return pht('Workboard'); 14 + } 15 + 16 + public function getDisplayName( 17 + PhabricatorProfilePanelConfiguration $config) { 18 + $name = $config->getPanelProperty('name'); 19 + 20 + if (strlen($name)) { 21 + return $name; 22 + } 23 + 24 + return $this->getDefaultName(); 25 + } 26 + 27 + public function buildEditEngineFields( 28 + PhabricatorProfilePanelConfiguration $config) { 29 + return array( 30 + id(new PhabricatorTextEditField()) 31 + ->setKey('name') 32 + ->setLabel(pht('Name')) 33 + ->setPlaceholder($this->getDefaultName()) 34 + ->setValue($config->getPanelProperty('name')), 35 + ); 36 + } 37 + 8 38 protected function newNavigationMenuItems( 9 39 PhabricatorProfilePanelConfiguration $config) { 10 40 $viewer = $this->getViewer(); ··· 29 59 30 60 $id = $project->getID(); 31 61 $href = "/project/board/{$id}/"; 32 - $name = pht('Workboard'); 62 + $name = $this->getDisplayName($config); 33 63 34 64 $item = id(new PHUIListItemView()) 35 65 ->setRenderNameAsTooltip(true)
+10 -10
src/applications/project/storage/PhabricatorProject.php
··· 658 658 public function getBuiltinProfilePanels() { 659 659 $panels = array(); 660 660 661 - $panels[] = id(new PhabricatorProfilePanelConfiguration()) 661 + $panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin() 662 662 ->setBuiltinKey(self::PANEL_PROFILE) 663 663 ->setPanelKey(PhabricatorProjectDetailsProfilePanel::PANELKEY); 664 664 665 - $panels[] = id(new PhabricatorProfilePanelConfiguration()) 665 + $panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin() 666 666 ->setBuiltinKey(self::PANEL_WORKBOARD) 667 667 ->setPanelKey(PhabricatorProjectWorkboardProfilePanel::PANELKEY); 668 668 669 669 // TODO: This is temporary. 670 - $href = urisprintf( 670 + $uri = urisprintf( 671 671 '/maniphest/?statuses=open()&projects=%s#R', 672 672 $this->getPHID()); 673 673 674 - $panels[] = id(new PhabricatorProfilePanelConfiguration()) 674 + $panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin() 675 675 ->setBuiltinKey('tasks') 676 676 ->setPanelKey(PhabricatorLinkProfilePanel::PANELKEY) 677 - ->setPanelProperty('icon', 'fa-anchor') 677 + ->setPanelProperty('icon', 'maniphest') 678 678 ->setPanelProperty('name', pht('Open Tasks')) 679 - ->setPanelProperty('href', $href); 679 + ->setPanelProperty('uri', $uri); 680 680 681 681 // TODO: This is temporary. 682 682 $id = $this->getID(); 683 - $panels[] = id(new PhabricatorProfilePanelConfiguration()) 683 + $panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin() 684 684 ->setBuiltinKey('feed') 685 685 ->setPanelKey(PhabricatorLinkProfilePanel::PANELKEY) 686 - ->setPanelProperty('icon', 'fa-newspaper-o') 686 + ->setPanelProperty('icon', 'feed') 687 687 ->setPanelProperty('name', pht('Feed')) 688 - ->setPanelProperty('href', "/project/feed/{$id}/"); 688 + ->setPanelProperty('uri', "/project/feed/{$id}/"); 689 689 690 - $panels[] = id(new PhabricatorProfilePanelConfiguration()) 690 + $panels[] = PhabricatorProfilePanelConfiguration::initializeNewBuiltin() 691 691 ->setBuiltinKey(self::PANEL_MEMBERS) 692 692 ->setPanelKey(PhabricatorProjectMembersProfilePanel::PANELKEY); 693 693
+136
src/applications/search/editor/PhabricatorProfilePanelEditEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorProfilePanelEditEngine 4 + extends PhabricatorEditEngine { 5 + 6 + const ENGINECONST = 'search.profilepanel'; 7 + 8 + private $panelEngine; 9 + private $profileObject; 10 + private $newPanelConfiguration; 11 + private $isBuiltin; 12 + 13 + public function isEngineConfigurable() { 14 + return false; 15 + } 16 + 17 + public function setPanelEngine(PhabricatorProfilePanelEngine $engine) { 18 + $this->panelEngine = $engine; 19 + return $this; 20 + } 21 + 22 + public function getPanelEngine() { 23 + return $this->panelEngine; 24 + } 25 + 26 + public function setProfileObject( 27 + PhabricatorProfilePanelInterface $profile_object) { 28 + $this->profileObject = $profile_object; 29 + return $this; 30 + } 31 + 32 + public function getProfileObject() { 33 + return $this->profileObject; 34 + } 35 + 36 + public function setNewPanelConfiguration( 37 + PhabricatorProfilePanelConfiguration $configuration) { 38 + $this->newPanelConfiguration = $configuration; 39 + return $this; 40 + } 41 + 42 + public function getNewPanelConfiguration() { 43 + return $this->newPanelConfiguration; 44 + } 45 + 46 + public function setIsBuiltin($is_builtin) { 47 + $this->isBuiltin = $is_builtin; 48 + return $this; 49 + } 50 + 51 + public function getIsBuiltin() { 52 + return $this->isBuiltin; 53 + } 54 + 55 + public function getEngineName() { 56 + return pht('Profile Panels'); 57 + } 58 + 59 + public function getSummaryHeader() { 60 + return pht('Edit Profile Panel Configurations'); 61 + } 62 + 63 + public function getSummaryText() { 64 + return pht('This engine is used to modify menu items on profiles.'); 65 + } 66 + 67 + public function getEngineApplicationClass() { 68 + return 'PhabricatorSearchApplication'; 69 + } 70 + 71 + protected function newEditableObject() { 72 + if (!$this->newPanelConfiguration) { 73 + throw new Exception( 74 + pht('Profile panels can not be generated without an object context.')); 75 + } 76 + 77 + return clone $this->newPanelConfiguration; 78 + } 79 + 80 + protected function newObjectQuery() { 81 + return id(new PhabricatorProfilePanelConfigurationQuery()); 82 + } 83 + 84 + protected function getObjectCreateTitleText($object) { 85 + if ($this->getIsBuiltin()) { 86 + return pht('Edit Builtin Item'); 87 + } else { 88 + return pht('Create Menu Item'); 89 + } 90 + } 91 + 92 + protected function getObjectCreateButtonText($object) { 93 + if ($this->getIsBuiltin()) { 94 + return pht('Save Changes'); 95 + } else { 96 + return pht('Create Menu Item'); 97 + } 98 + } 99 + 100 + protected function getObjectEditTitleText($object) { 101 + return pht('Edit Menu Item: %s', $object->getDisplayName()); 102 + } 103 + 104 + protected function getObjectEditShortText($object) { 105 + return pht('Edit Menu Item'); 106 + } 107 + 108 + protected function getObjectCreateShortText() { 109 + return pht('Edit Menu Item'); 110 + } 111 + 112 + protected function getObjectCreateCancelURI($object) { 113 + return $this->getPanelEngine()->getConfigureURI(); 114 + } 115 + 116 + protected function getObjectViewURI($object) { 117 + return $this->getPanelEngine()->getConfigureURI(); 118 + } 119 + 120 + protected function buildCustomEditFields($object) { 121 + $panel = $object->getPanel(); 122 + $fields = $panel->buildEditEngineFields($object); 123 + 124 + $type_property = 125 + PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY; 126 + 127 + foreach ($fields as $field) { 128 + $field 129 + ->setTransactionType($type_property) 130 + ->setMetadataValue('property.key', $field->getKey()); 131 + } 132 + 133 + return $fields; 134 + } 135 + 136 + }
+70
src/applications/search/editor/PhabricatorProfilePanelEditor.php
··· 1 + <?php 2 + 3 + final class PhabricatorProfilePanelEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorSearchApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Profile Panels'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY; 18 + 19 + return $types; 20 + } 21 + 22 + protected function getCustomTransactionOldValue( 23 + PhabricatorLiskDAO $object, 24 + PhabricatorApplicationTransaction $xaction) { 25 + 26 + switch ($xaction->getTransactionType()) { 27 + case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY: 28 + $key = $xaction->getMetadataValue('property.key'); 29 + return $object->getPanelProperty($key, null); 30 + } 31 + } 32 + 33 + protected function getCustomTransactionNewValue( 34 + PhabricatorLiskDAO $object, 35 + PhabricatorApplicationTransaction $xaction) { 36 + 37 + switch ($xaction->getTransactionType()) { 38 + case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY: 39 + return $xaction->getNewValue(); 40 + } 41 + } 42 + 43 + protected function applyCustomInternalTransaction( 44 + PhabricatorLiskDAO $object, 45 + PhabricatorApplicationTransaction $xaction) { 46 + 47 + switch ($xaction->getTransactionType()) { 48 + case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY: 49 + $key = $xaction->getMetadataValue('property.key'); 50 + $value = $xaction->getNewValue(); 51 + $object->setPanelProperty($key, $value); 52 + return; 53 + } 54 + 55 + return parent::applyCustomInternalTransaction($object, $xaction); 56 + } 57 + 58 + protected function applyCustomExternalTransaction( 59 + PhabricatorLiskDAO $object, 60 + PhabricatorApplicationTransaction $xaction) { 61 + 62 + switch ($xaction->getTransactionType()) { 63 + case PhabricatorProfilePanelConfigurationTransaction::TYPE_PROPERTY: 64 + return; 65 + } 66 + 67 + return parent::applyCustomExternalTransaction($object, $xaction); 68 + } 69 + 70 + }
+328 -1
src/applications/search/engine/PhabricatorProfilePanelEngine.php
··· 5 5 private $viewer; 6 6 private $profileObject; 7 7 private $panels; 8 + private $controller; 8 9 9 10 public function setViewer(PhabricatorUser $viewer) { 10 11 $this->viewer = $viewer; ··· 25 26 return $this->profileObject; 26 27 } 27 28 29 + public function setController(PhabricatorController $controller) { 30 + $this->controller = $controller; 31 + return $this; 32 + } 33 + 34 + public function getController() { 35 + return $this->controller; 36 + } 37 + 38 + public function buildResponse() { 39 + $controller = $this->getController(); 40 + 41 + $viewer = $controller->getViewer(); 42 + $this->setViewer($viewer); 43 + 44 + $request = $controller->getRequest(); 45 + 46 + $panel_action = $request->getURIData('panelAction'); 47 + $panel_id = $request->getURIData('panelID'); 48 + 49 + $panel_list = $this->loadPanels(); 50 + 51 + $selected_panel = null; 52 + if (strlen($panel_id)) { 53 + $panel_id_int = (int)$panel_id; 54 + foreach ($panel_list as $panel) { 55 + if ($panel_id_int) { 56 + if ((int)$panel->getID() === $panel_id) { 57 + $selected_panel = $panel; 58 + break; 59 + } 60 + } 61 + 62 + $builtin_key = $panel->getBuiltinKey(); 63 + if ($builtin_key === (string)$panel_id) { 64 + $selected_panel = $panel; 65 + break; 66 + } 67 + } 68 + } 69 + 70 + switch ($panel_action) { 71 + case 'view': 72 + case 'info': 73 + case 'hide': 74 + case 'builtin': 75 + if (!$selected_panel) { 76 + return new Aphront404Response(); 77 + } 78 + break; 79 + } 80 + 81 + $navigation = $this->buildNavigation(); 82 + $navigation->selectFilter('panel.configure'); 83 + 84 + $crumbs = $controller->buildApplicationCrumbsForEditEngine(); 85 + 86 + switch ($panel_action) { 87 + case 'view': 88 + $content = $this->buildPanelViewContent($selected_panel); 89 + break; 90 + case 'configure': 91 + $content = $this->buildPanelConfigureContent($panel_list); 92 + $crumbs->addTextCrumb(pht('Configure Menu')); 93 + break; 94 + case 'new': 95 + $panel_key = $request->getURIData('panelKey'); 96 + $content = $this->buildPanelNewContent($panel_key); 97 + break; 98 + case 'builtin': 99 + $content = $this->buildPanelBuiltinContent($selected_panel); 100 + break; 101 + case 'edit': 102 + $content = $this->buildPanelEditContent(); 103 + break; 104 + default: 105 + throw new Exception( 106 + pht( 107 + 'Unsupported panel action "%s".', 108 + $panel_action)); 109 + } 110 + 111 + if ($content instanceof AphrontResponse) { 112 + return $content; 113 + } 114 + 115 + if ($content instanceof AphrontResponseProducerInterface) { 116 + return $content; 117 + } 118 + 119 + return $controller->newPage() 120 + ->setTitle(pht('Profile Stuff')) 121 + ->setNavigation($navigation) 122 + ->setCrumbs($crumbs) 123 + ->appendChild($content); 124 + } 125 + 28 126 public function buildNavigation() { 29 127 $nav = id(new AphrontSideNavFilterView()) 30 128 ->setIconNav(true) ··· 60 158 } 61 159 } 62 160 161 + $configure_item = $this->newConfigureMenuItem(); 162 + if ($configure_item) { 163 + $nav->addMenuItem($configure_item); 164 + } 165 + 63 166 $nav->selectFilter(null); 64 167 65 168 return $nav; ··· 75 178 76 179 private function loadPanels() { 77 180 $viewer = $this->getViewer(); 181 + $object = $this->getProfileObject(); 78 182 79 183 $panels = $this->loadBuiltinProfilePanels(); 80 184 81 - // TODO: Load persisted panels. 185 + $stored_panels = id(new PhabricatorProfilePanelConfigurationQuery()) 186 + ->setViewer($viewer) 187 + ->withProfilePHIDs(array($object->getPHID())) 188 + ->execute(); 189 + 190 + // Merge the stored panels into the builtin panels. If a builtin panel has 191 + // a stored version, replace the defaults with the stored changes. 192 + foreach ($stored_panels as $stored_panel) { 193 + $builtin_key = $stored_panel->getBuiltinKey(); 194 + if ($builtin_key !== null) { 195 + $panels[$builtin_key] = $stored_panel; 196 + } else { 197 + $panels[] = $stored_panel; 198 + } 199 + } 82 200 83 201 foreach ($panels as $panel) { 84 202 $impl = $panel->getPanel(); 85 203 86 204 $impl->setViewer($viewer); 87 205 } 206 + 207 + // Normalize keys since callers shouldn't rely on this array being 208 + // partially keyed. 209 + $panels = array_values($panels); 88 210 89 211 return $panels; 90 212 } ··· 128 250 } 129 251 130 252 $builtin 253 + ->setProfilePHID($object->getPHID()) 131 254 ->attachPanel($panel) 132 255 ->attachProfileObject($object) 133 256 ->setPanelOrder($order); ··· 147 270 'Expected buildNavigationMenuItems() to return a list of '. 148 271 'PHUIListItemView objects, but got a surprise.')); 149 272 } 273 + } 274 + 275 + private function newConfigureMenuItem() { 276 + if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { 277 + return null; 278 + } 279 + 280 + $viewer = $this->getViewer(); 281 + $object = $this->getProfileObject(); 282 + 283 + $can_edit = PhabricatorPolicyFilter::hasCapability( 284 + $viewer, 285 + $object, 286 + PhabricatorPolicyCapability::CAN_EDIT); 287 + 288 + return id(new PHUIListItemView()) 289 + ->setName('Configure Menu') 290 + ->setKey('panel.configure') 291 + ->setIcon('fa-gear') 292 + ->setHref($this->getPanelURI('configure/')) 293 + ->setDisabled(!$can_edit) 294 + ->setWorkflow(!$can_edit) 295 + ->setRenderNameAsTooltip(true); 296 + } 297 + 298 + public function getConfigureURI() { 299 + return $this->getPanelURI('configure/'); 300 + } 301 + 302 + private function getPanelURI($path) { 303 + $project = $this->getProfileObject(); 304 + $id = $project->getID(); 305 + return "/project/{$id}/panel/{$path}"; 306 + } 307 + 308 + private function buildPanelConfigureContent(array $panels) { 309 + $viewer = $this->getViewer(); 310 + $object = $this->getProfileObject(); 311 + 312 + PhabricatorPolicyFilter::requireCapability( 313 + $viewer, 314 + $object, 315 + PhabricatorPolicyCapability::CAN_EDIT); 316 + 317 + $list = new PHUIObjectItemListView(); 318 + foreach ($panels as $panel) { 319 + $id = $panel->getID(); 320 + $builtin_key = $panel->getBuiltinKey(); 321 + 322 + $can_edit = PhabricatorPolicyFilter::hasCapability( 323 + $viewer, 324 + $panel, 325 + PhabricatorPolicyCapability::CAN_EDIT); 326 + 327 + $item = id(new PHUIObjectItemView()); 328 + 329 + $name = $panel->getDisplayName(); 330 + $type = $panel->getPanelTypeName(); 331 + if (!strlen(trim($name))) { 332 + $name = pht('Untitled "%s" Item', $type); 333 + } 334 + 335 + $item->setHeader($name); 336 + $item->addAttribute($type); 337 + 338 + if ($can_edit) { 339 + if ($id) { 340 + $item->setHref($this->getPanelURI("edit/{$id}/")); 341 + } else { 342 + $item->setHref($this->getPanelURI("builtin/{$builtin_key}/")); 343 + } 344 + } 345 + 346 + $list->addItem($item); 347 + } 348 + 349 + $action_view = id(new PhabricatorActionListView()) 350 + ->setUser($viewer); 351 + 352 + $panel_types = PhabricatorProfilePanel::getAllPanels(); 353 + 354 + $action_view->addAction( 355 + id(new PhabricatorActionView()) 356 + ->setLabel(true) 357 + ->setName(pht('Add New Menu Item...'))); 358 + 359 + foreach ($panel_types as $panel_type) { 360 + if (!$panel_type->canAddToObject($object)) { 361 + continue; 362 + } 363 + 364 + $panel_key = $panel_type->getPanelKey(); 365 + 366 + $action_view->addAction( 367 + id(new PhabricatorActionView()) 368 + ->setIcon($panel_type->getPanelTypeIcon()) 369 + ->setName($panel_type->getPanelTypeName()) 370 + ->setHref($this->getPanelURI("new/{$panel_key}/"))); 371 + } 372 + 373 + $action_view->addAction( 374 + id(new PhabricatorActionView()) 375 + ->setLabel(true) 376 + ->setName(pht('Documentation'))); 377 + 378 + $action_view->addAction( 379 + id(new PhabricatorActionView()) 380 + ->setIcon('fa-book') 381 + ->setName(pht('TODO: Write Documentation'))); 382 + 383 + $action_button = id(new PHUIButtonView()) 384 + ->setTag('a') 385 + ->setText(pht('Configure Menu')) 386 + ->setHref('#') 387 + ->setIconFont('fa-gear') 388 + ->setDropdownMenu($action_view); 389 + 390 + $header = id(new PHUIHeaderView()) 391 + ->setHeader(pht('Profile Menu Items')) 392 + ->addActionLink($action_button); 393 + 394 + $box = id(new PHUIObjectBoxView()) 395 + ->setHeader($header) 396 + ->setObjectList($list); 397 + 398 + return $box; 399 + } 400 + 401 + private function buildPanelNewContent($panel_key) { 402 + $panel_types = PhabricatorProfilePanel::getAllPanels(); 403 + $panel_type = idx($panel_types, $panel_key); 404 + if (!$panel_type) { 405 + return new Aphront404Response(); 406 + } 407 + 408 + $object = $this->getProfileObject(); 409 + if (!$panel_type->canAddToObject($object)) { 410 + return new Aphront404Response(); 411 + } 412 + 413 + $configuration = 414 + PhabricatorProfilePanelConfiguration::initializeNewPanelConfiguration( 415 + $object, 416 + $panel_type); 417 + 418 + $viewer = $this->getViewer(); 419 + 420 + PhabricatorPolicyFilter::requireCapability( 421 + $viewer, 422 + $configuration, 423 + PhabricatorPolicyCapability::CAN_EDIT); 424 + 425 + $controller = $this->getController(); 426 + 427 + return id(new PhabricatorProfilePanelEditEngine()) 428 + ->setPanelEngine($this) 429 + ->setProfileObject($object) 430 + ->setNewPanelConfiguration($configuration) 431 + ->setController($controller) 432 + ->buildResponse(); 433 + } 434 + 435 + private function buildPanelEditContent() { 436 + $viewer = $this->getViewer(); 437 + $object = $this->getProfileObject(); 438 + $controller = $this->getController(); 439 + 440 + return id(new PhabricatorProfilePanelEditEngine()) 441 + ->setPanelEngine($this) 442 + ->setProfileObject($object) 443 + ->setController($controller) 444 + ->buildResponse(); 445 + } 446 + 447 + private function buildPanelBuiltinContent( 448 + PhabricatorProfilePanelConfiguration $configuration) { 449 + 450 + // If this builtin panel has already been persisted, redirect to the 451 + // edit page. 452 + $id = $configuration->getID(); 453 + if ($id) { 454 + return id(new AphrontRedirectResponse()) 455 + ->setURI($this->getPanelURI("edit/{$id}/")); 456 + } 457 + 458 + // Otherwise, act like we're creating a new panel, we're just starting 459 + // with the builtin template. 460 + $viewer = $this->getViewer(); 461 + 462 + PhabricatorPolicyFilter::requireCapability( 463 + $viewer, 464 + $configuration, 465 + PhabricatorPolicyCapability::CAN_EDIT); 466 + 467 + $object = $this->getProfileObject(); 468 + $controller = $this->getController(); 469 + 470 + return id(new PhabricatorProfilePanelEditEngine()) 471 + ->setIsBuiltin(true) 472 + ->setPanelEngine($this) 473 + ->setProfileObject($object) 474 + ->setNewPanelConfiguration($configuration) 475 + ->setController($controller) 476 + ->buildResponse(); 150 477 } 151 478 152 479 }
+74 -4
src/applications/search/profilepanel/PhabricatorLinkProfilePanel.php
··· 5 5 6 6 const PANELKEY = 'link'; 7 7 8 + public function getPanelTypeIcon() { 9 + return 'fa-link'; 10 + } 11 + 12 + public function getPanelTypeName() { 13 + return pht('Link'); 14 + } 15 + 16 + public function canAddToObject( 17 + PhabricatorProfilePanelInterface $object) { 18 + return true; 19 + } 20 + 21 + public function getDisplayName( 22 + PhabricatorProfilePanelConfiguration $config) { 23 + return $this->getLinkName($config); 24 + } 25 + 26 + public function buildEditEngineFields( 27 + PhabricatorProfilePanelConfiguration $config) { 28 + return array( 29 + id(new PhabricatorTextEditField()) 30 + ->setKey('name') 31 + ->setLabel(pht('Name')) 32 + ->setIsRequired(true) 33 + ->setValue($this->getLinkName($config)), 34 + id(new PhabricatorTextEditField()) 35 + ->setKey('uri') 36 + ->setLabel(pht('URI')) 37 + ->setIsRequired(true) 38 + ->setValue($this->getLinkURI($config)), 39 + id(new PhabricatorIconSetEditField()) 40 + ->setKey('icon') 41 + ->setLabel(pht('Icon')) 42 + ->setIconSet(new PhabricatorProfilePanelIconSet()) 43 + ->setValue($this->getLinkIcon($config)), 44 + ); 45 + } 46 + 47 + private function getLinkName( 48 + PhabricatorProfilePanelConfiguration $config) { 49 + return $config->getPanelProperty('name'); 50 + } 51 + 52 + private function getLinkIcon( 53 + PhabricatorProfilePanelConfiguration $config) { 54 + return $config->getPanelProperty('icon', 'link'); 55 + } 56 + 57 + private function getLinkURI( 58 + PhabricatorProfilePanelConfiguration $config) { 59 + return $config->getPanelProperty('uri'); 60 + } 61 + 62 + private function isValidLinkURI($uri) { 63 + return PhabricatorEnv::isValidURIForLink($uri); 64 + } 65 + 8 66 protected function newNavigationMenuItems( 9 67 PhabricatorProfilePanelConfiguration $config) { 10 68 11 - $icon = $config->getPanelProperty('icon'); 12 - $name = $config->getPanelProperty('name'); 13 - $href = $config->getPanelProperty('href'); 69 + $icon = $this->getLinkIcon($config); 70 + $name = $this->getLinkName($config); 71 + $href = $this->getLinkURI($config); 72 + 73 + if (!$this->isValidLinkURI($href)) { 74 + $href = '#'; 75 + } 76 + 77 + $icon_object = id(new PhabricatorProfilePanelIconSet()) 78 + ->getIcon($icon); 79 + if ($icon_object) { 80 + $icon_class = $icon_object->getIcon(); 81 + } else { 82 + $icon_class = 'fa-link'; 83 + } 14 84 15 85 $item = id(new PHUIListItemView()) 16 86 ->setRenderNameAsTooltip(true) 17 87 ->setType(PHUIListItemView::TYPE_ICON_NAV) 18 88 ->setHref($href) 19 89 ->setName($name) 20 - ->setIcon($icon); 90 + ->setIcon($icon_class); 21 91 22 92 return array( 23 93 $item,
+19
src/applications/search/profilepanel/PhabricatorProfilePanel.php
··· 12 12 abstract protected function newNavigationMenuItems( 13 13 PhabricatorProfilePanelConfiguration $config); 14 14 15 + public function getPanelTypeIcon() { 16 + return null; 17 + } 18 + 19 + abstract public function getPanelTypeName(); 20 + 21 + abstract public function getDisplayName( 22 + PhabricatorProfilePanelConfiguration $config); 23 + 24 + public function buildEditEngineFields( 25 + PhabricatorProfilePanelConfiguration $config) { 26 + return array(); 27 + } 28 + 29 + public function canAddToObject( 30 + PhabricatorProfilePanelInterface $object) { 31 + return false; 32 + } 33 + 15 34 public function setViewer(PhabricatorUser $viewer) { 16 35 $this->viewer = $viewer; 17 36 return $this;
+42
src/applications/search/profilepanel/PhabricatorProfilePanelIconSet.php
··· 1 + <?php 2 + 3 + final class PhabricatorProfilePanelIconSet 4 + extends PhabricatorIconSet { 5 + 6 + const ICONSETKEY = 'profilepanel'; 7 + 8 + public function getSelectIconTitleText() { 9 + return pht('Choose Item Icon'); 10 + } 11 + 12 + protected function newIcons() { 13 + $list = array( 14 + array( 15 + 'key' => 'link', 16 + 'icon' => 'fa-link', 17 + 'name' => pht('Link'), 18 + ), 19 + array( 20 + 'key' => 'maniphest', 21 + 'icon' => 'fa-anchor', 22 + 'name' => pht('Maniphest'), 23 + ), 24 + array( 25 + 'key' => 'feed', 26 + 'icon' => 'fa-newspaper-o', 27 + 'name' => pht('Feed'), 28 + ), 29 + ); 30 + 31 + $icons = array(); 32 + foreach ($list as $spec) { 33 + $icons[] = id(new PhabricatorIconSetIcon()) 34 + ->setKey($spec['key']) 35 + ->setIcon($spec['icon']) 36 + ->setLabel($spec['name']); 37 + } 38 + 39 + return $icons; 40 + } 41 + 42 + }
+44 -6
src/applications/search/query/PhabricatorProfilePanelConfigurationQuery.php
··· 5 5 6 6 private $ids; 7 7 private $phids; 8 - private $profileObjectPHIDs; 8 + private $profilePHIDs; 9 9 10 10 public function withIDs(array $ids) { 11 11 $this->ids = $ids; ··· 17 17 return $this; 18 18 } 19 19 20 - public function withProfileObjectPHIDs(array $phids) { 21 - $this->profileObjectPHIDs = $phids; 20 + public function withProfilePHIDs(array $phids) { 21 + $this->profilePHIDs = $phids; 22 22 return $this; 23 23 } 24 24 ··· 47 47 $this->phids); 48 48 } 49 49 50 - if ($this->profileObjectPHIDs !== null) { 50 + if ($this->profilePHIDs !== null) { 51 51 $where[] = qsprintf( 52 52 $conn, 53 - 'profileObjectPHID IN (%Ls)', 54 - $this->profileObjectPHIDs); 53 + 'profilePHID IN (%Ls)', 54 + $this->profilePHIDs); 55 55 } 56 56 57 57 return $where; 58 + } 59 + 60 + protected function willFilterPage(array $page) { 61 + $panels = PhabricatorProfilePanel::getAllPanels(); 62 + foreach ($page as $key => $panel) { 63 + $panel_type = idx($panels, $panel->getPanelKey()); 64 + if (!$panel_type) { 65 + $this->didRejectResult($panel); 66 + unset($page[$key]); 67 + continue; 68 + } 69 + $panel->attachPanel($panel_type); 70 + } 71 + 72 + if (!$page) { 73 + return array(); 74 + } 75 + 76 + $profile_phids = mpull($page, 'getProfilePHID'); 77 + 78 + $profiles = id(new PhabricatorObjectQuery()) 79 + ->setViewer($this->getViewer()) 80 + ->setParentQuery($this) 81 + ->withPHIDs($profile_phids) 82 + ->execute(); 83 + $profiles = mpull($profiles, null, 'getPHID'); 84 + 85 + foreach ($page as $key => $panel) { 86 + $profile = idx($profiles, $panel->getProfilePHID()); 87 + if (!$profile) { 88 + $this->didRejectResult($panel); 89 + unset($page[$key]); 90 + continue; 91 + } 92 + $panel->attachProfileObject($profile); 93 + } 94 + 95 + return $page; 58 96 } 59 97 60 98 public function getQueryApplicationClass() {
+55 -11
src/applications/search/storage/PhabricatorProfilePanelConfiguration.php
··· 4 4 extends PhabricatorSearchDAO 5 5 implements 6 6 PhabricatorPolicyInterface, 7 - PhabricatorExtendedPolicyInterface { 7 + PhabricatorExtendedPolicyInterface, 8 + PhabricatorApplicationTransactionInterface { 8 9 9 10 protected $profilePHID; 10 11 protected $panelKey; 11 12 protected $builtinKey; 12 13 protected $panelOrder; 13 - protected $isDisabled; 14 + protected $visibility; 14 15 protected $panelProperties = array(); 15 16 16 17 private $profileObject = self::ATTACHABLE; 17 18 private $panel = self::ATTACHABLE; 18 19 20 + const VISIBILITY_VISIBLE = 'visible'; 21 + const VISIBILITY_DISABLED = 'disabled'; 22 + 23 + public static function initializeNewBuiltin() { 24 + return id(new self()) 25 + ->setVisibility(self::VISIBILITY_VISIBLE); 26 + } 27 + 19 28 public static function initializeNewPanelConfiguration( 20 29 PhabricatorProfilePanelInterface $profile_object, 21 30 PhabricatorProfilePanel $panel) { 22 31 23 - return id(new self()) 32 + return self::initializeNewBuiltin() 24 33 ->setProfilePHID($profile_object->getPHID()) 25 34 ->setPanelKey($panel->getPanelKey()) 26 - ->setIsDisabled(0) 27 35 ->attachPanel($panel) 28 36 ->attachProfileObject($profile_object); 29 37 } ··· 36 44 ), 37 45 self::CONFIG_COLUMN_SCHEMA => array( 38 46 'panelKey' => 'text64', 39 - 'builtinKey' => 'text64', 40 - 'panelOrder' => 'uint32', 41 - 'isDisabled' => 'bool', 47 + 'builtinKey' => 'text64?', 48 + 'panelOrder' => 'uint32?', 49 + 'visibility' => 'text32', 42 50 ), 43 51 self::CONFIG_KEY_SCHEMA => array( 44 52 'key_profile' => array( ··· 48 56 ) + parent::getConfiguration(); 49 57 } 50 58 59 + public function generatePHID() { 60 + return PhabricatorPHID::generateNewPHID( 61 + PhabricatorProfilePanelPHIDType::TYPECONST); 62 + } 63 + 51 64 public function attachPanel(PhabricatorProfilePanel $panel) { 52 65 $this->panel = $panel; 53 66 return $this; ··· 67 80 return $this->assertAttached($this->profileObject); 68 81 } 69 82 70 - public function buildNavigationMenuItems() { 71 - return $this->getPanel()->buildNavigationMenuItems($this); 72 - } 73 - 74 83 public function setPanelProperty($key, $value) { 75 84 $this->panelProperties[$key] = $value; 76 85 return $this; ··· 80 89 return idx($this->panelProperties, $key, $default); 81 90 } 82 91 92 + public function buildNavigationMenuItems() { 93 + return $this->getPanel()->buildNavigationMenuItems($this); 94 + } 95 + 96 + public function getPanelTypeName() { 97 + return $this->getPanel()->getPanelTypeName(); 98 + } 99 + 100 + public function getDisplayName() { 101 + return $this->getPanel()->getDisplayName($this); 102 + } 103 + 83 104 84 105 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 85 106 ··· 119 140 $capability, 120 141 ), 121 142 ); 143 + } 144 + 145 + 146 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 147 + 148 + 149 + public function getApplicationTransactionEditor() { 150 + return new PhabricatorProfilePanelEditor(); 151 + } 152 + 153 + public function getApplicationTransactionObject() { 154 + return $this; 155 + } 156 + 157 + public function getApplicationTransactionTemplate() { 158 + return new PhabricatorProfilePanelConfigurationTransaction(); 159 + } 160 + 161 + public function willRenderTimeline( 162 + PhabricatorApplicationTransactionView $timeline, 163 + AphrontRequest $request) { 164 + 165 + return $timeline; 122 166 } 123 167 124 168 }
+2
src/applications/search/storage/PhabricatorProfilePanelConfigurationTransaction.php
··· 3 3 final class PhabricatorProfilePanelConfigurationTransaction 4 4 extends PhabricatorApplicationTransaction { 5 5 6 + const TYPE_PROPERTY = 'profilepanel.property'; 7 + 6 8 public function getApplicationName() { 7 9 return 'search'; 8 10 }
+4
src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php
··· 16 16 $engine = PhabricatorEditEngine::getByKey($viewer, $engine_key) 17 17 ->setViewer($viewer); 18 18 19 + if (!$engine->isEngineConfigurable()) { 20 + return new Aphront404Response(); 21 + } 22 + 19 23 $items = array(); 20 24 $items[] = id(new PHUIListItemView()) 21 25 ->setType(PHUIListItemView::TYPE_LABEL)
+4
src/applications/transactions/controller/PhabricatorEditEngineController.php
··· 72 72 $engine = $config->getEngine(); 73 73 } 74 74 75 + if (!$engine->isEngineConfigurable()) { 76 + return null; 77 + } 78 + 75 79 return $config; 76 80 } 77 81
+13 -2
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 57 57 return $this; 58 58 } 59 59 60 + public function isEngineConfigurable() { 61 + return true; 62 + } 63 + 60 64 61 65 /* -( Managing Fields )---------------------------------------------------- */ 62 66 ··· 1005 1009 } 1006 1010 1007 1011 $header = id(new PHUIHeaderView()) 1008 - ->setHeader($header_text) 1009 - ->addActionLink($action_button); 1012 + ->setHeader($header_text); 1013 + 1014 + if ($action_button) { 1015 + $header->addActionLink($action_button); 1016 + } 1010 1017 1011 1018 $crumbs = $this->buildCrumbs($object, $final = true); 1012 1019 ··· 1066 1073 } 1067 1074 1068 1075 private function buildEditFormActionButton($object) { 1076 + if (!$this->isEngineConfigurable()) { 1077 + return null; 1078 + } 1079 + 1069 1080 $viewer = $this->getViewer(); 1070 1081 1071 1082 $action_view = id(new PhabricatorActionListView())
+19 -1
src/applications/transactions/editfield/PhabricatorTextEditField.php
··· 3 3 final class PhabricatorTextEditField 4 4 extends PhabricatorEditField { 5 5 6 + private $placeholder; 7 + 8 + public function setPlaceholder($placeholder) { 9 + $this->placeholder = $placeholder; 10 + return $this; 11 + } 12 + 13 + public function getPlaceholder() { 14 + return $this->placeholder; 15 + } 16 + 6 17 protected function newControl() { 7 - return new AphrontFormTextControl(); 18 + $control = new AphrontFormTextControl(); 19 + 20 + $placeholder = $this->getPlaceholder(); 21 + if (strlen($placeholder)) { 22 + $control->setPlaceholder($placeholder); 23 + } 24 + 25 + return $control; 8 26 } 9 27 10 28 protected function newConduitParameterType() {
+4
src/applications/transactions/query/PhabricatorEditEngineSearchEngine.php
··· 62 62 $list = id(new PHUIObjectItemListView()) 63 63 ->setUser($viewer); 64 64 foreach ($engines as $engine) { 65 + if (!$engine->isEngineConfigurable()) { 66 + continue; 67 + } 68 + 65 69 $engine_key = $engine->getEngineKey(); 66 70 $query_uri = "/transactions/editengine/{$engine_key}/"; 67 71
+1 -1
src/docs/user/field/conduit_changes.diviner
··· 39 39 made, and {nav Deprecated Call Logs} to find deprecated calls by all users. 40 40 41 41 You can also search for calls by specific users. For example, it may be useful 42 - to serach for any bot accounts you run to make sure they aren't calling 42 + to search for any bot accounts you run to make sure they aren't calling 43 43 outdated APIs. 44 44 45 45 The most common cause of calls to deprecated methods is users running very