@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

Add (Advanced) Custom Fields to Item List

Summary:
Allow PHP-coded Custom Fields to show things in Lists.

Also add Repository to Revision List and Flags to Maniphest lists.

Closes T15133. Ref T15512, T15750

Test Plan: Look at Repository List and Task lists that have flags.

Reviewers: O1 Blessed Committers, aklapper, valerio.bozzolan

Reviewed By: O1 Blessed Committers, aklapper, valerio.bozzolan

Subscribers: aklapper, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno

Maniphest Tasks: T15750, T15512, T15133

Differential Revision: https://we.phorge.it/D25548

+244 -6
+6
src/__phutil_library_map__.php
··· 1802 1802 'ManiphestEditConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestEditConduitAPIMethod.php', 1803 1803 'ManiphestEditEngine' => 'applications/maniphest/editor/ManiphestEditEngine.php', 1804 1804 'ManiphestEmailCommand' => 'applications/maniphest/command/ManiphestEmailCommand.php', 1805 + 'ManiphestFlagCustomField' => 'applications/maniphest/field/ManiphestFlagCustomField.php', 1805 1806 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php', 1806 1807 'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php', 1807 1808 'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php', ··· 5388 5389 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 5389 5390 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 5390 5391 'PhorgeCodeWarningSetupCheck' => 'applications/config/check/PhorgeCodeWarningSetupCheck.php', 5392 + 'PhorgeFlagFlaggedObjectCustomField' => 'applications/flag/customfield/PhorgeFlagFlaggedObjectCustomField.php', 5393 + 'PhorgeFlagFlaggedObjectFieldStorage' => 'applications/flag/customfield/PhorgeFlagFlaggedObjectFieldStorage.php', 5391 5394 'PhorgeSystemDeprecationWarningListener' => 'applications/system/events/PhorgeSystemDeprecationWarningListener.php', 5392 5395 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 5393 5396 'PhortuneAccountAddManagerController' => 'applications/phortune/controller/account/PhortuneAccountAddManagerController.php', ··· 8008 8011 'ManiphestEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 8009 8012 'ManiphestEditEngine' => 'PhabricatorEditEngine', 8010 8013 'ManiphestEmailCommand' => 'MetaMTAEmailTransactionCommand', 8014 + 'ManiphestFlagCustomField' => 'ManiphestCustomField', 8011 8015 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod', 8012 8016 'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 8013 8017 'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod', ··· 12212 12216 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 12213 12217 'PholioUploadedImageView' => 'AphrontView', 12214 12218 'PhorgeCodeWarningSetupCheck' => 'PhabricatorSetupCheck', 12219 + 'PhorgeFlagFlaggedObjectCustomField' => 'PhabricatorCustomField', 12220 + 'PhorgeFlagFlaggedObjectFieldStorage' => 'Phobject', 12215 12221 'PhorgeSystemDeprecationWarningListener' => 'PhabricatorEventListener', 12216 12222 'PhortuneAccount' => array( 12217 12223 'PhortuneDAO',
+12
src/applications/differential/customfield/DifferentialRepositoryField.php
··· 63 63 $repository->getMonogram().' '.$repository->getName()); 64 64 } 65 65 66 + public function shouldAppearInListView() { 67 + return true; 68 + } 69 + 70 + public function renderOnListItem(PHUIObjectItemView $view) { 71 + if ($this->getValue()) { 72 + $handle = $this->getViewer()->renderHandle($this->getValue()); 73 + $view->addByLine(pht('Repository: %s', $handle)); 74 + } 75 + } 76 + 77 + 66 78 }
+5
src/applications/differential/query/DifferentialRevisionSearchEngine.php
··· 199 199 200 200 $unlanded = $this->loadUnlandedDependencies($revisions); 201 201 202 + $custom_field_lists = $this->loadCustomFields( 203 + $revisions, 204 + PhabricatorCustomField::ROLE_LIST); 205 + 202 206 $views = array(); 203 207 if ($bucket) { 204 208 $bucket->setViewer($viewer); ··· 231 235 232 236 foreach ($views as $view) { 233 237 $view->setUnlandedDependencies($unlanded); 238 + $view->setCustomFieldLists($custom_field_lists); 234 239 } 235 240 236 241 if (count($views) == 1) {
+12
src/applications/differential/view/DifferentialRevisionListView.php
··· 11 11 private $noBox; 12 12 private $background = null; 13 13 private $unlandedDependencies = array(); 14 + private $customFieldLists = array(); 14 15 15 16 public function setUnlandedDependencies(array $unlanded_dependencies) { 16 17 $this->unlandedDependencies = $unlanded_dependencies; ··· 44 45 45 46 public function setBackground($background) { 46 47 $this->background = $background; 48 + return $this; 49 + } 50 + 51 + public function setCustomFieldLists(array $lists) { 52 + $this->customFieldLists = $lists; 47 53 return $this; 48 54 } 49 55 ··· 180 186 $item->setStatusIcon( 181 187 "{$icon} {$color}", 182 188 $revision->getStatusDisplayName()); 189 + 190 + $field_list = idx($this->customFieldLists, $revision->getPHID()); 191 + if ($field_list) { 192 + $field_list 193 + ->addFieldsToListViewItem($revision, $viewer, $item); 194 + } 183 195 184 196 $list->addItem($item); 185 197 }
+46
src/applications/flag/customfield/PhorgeFlagFlaggedObjectCustomField.php
··· 1 + <?php 2 + 3 + final class PhorgeFlagFlaggedObjectCustomField extends PhabricatorCustomField { 4 + 5 + private $flag; 6 + 7 + public function getFieldKey() { 8 + return 'flag:flag'; 9 + } 10 + public function shouldAppearInPropertyView() { 11 + return false; 12 + } 13 + 14 + public function shouldAppearInListView() { 15 + return true; 16 + } 17 + 18 + public function renderOnListItem(PHUIObjectItemView $view) { 19 + if (!$this->flag) { 20 + return; 21 + } 22 + // I'm very open to improvements in the way a Flag is displayed 23 + $icon = PhabricatorFlagColor::getIcon($this->flag->getColor()); 24 + $view->addIcon($icon); 25 + } 26 + 27 + 28 + public function shouldUseStorage() { 29 + return true; 30 + } 31 + 32 + public function setValueFromStorage($value) { 33 + $this->flag = $value; 34 + } 35 + 36 + // The parent function is defined to return a PhabricatorCustomFieldStorage, 37 + // but that assumes a DTO with a particular form; That doesn't apply here. 38 + // Maybe the function needs to be re-defined with a suitable interface. 39 + // For now, PhorgeFlagFlaggedObjectFieldStorage just duck-types into the 40 + // right shape. 41 + public function newStorageObject() { 42 + return id(new PhorgeFlagFlaggedObjectFieldStorage()) 43 + ->setViewer($this->getViewer()); 44 + } 45 + 46 + }
+36
src/applications/flag/customfield/PhorgeFlagFlaggedObjectFieldStorage.php
··· 1 + <?php 2 + 3 + final class PhorgeFlagFlaggedObjectFieldStorage extends Phobject { 4 + 5 + private $viewer; 6 + 7 + public function setViewer(PhabricatorUser $viewer) { 8 + $this->viewer = $viewer; 9 + return $this; 10 + } 11 + 12 + public function getStorageSourceKey() { 13 + return 'flags/flag'; 14 + } 15 + 16 + public function loadStorageSourceData(array $fields) { 17 + 18 + $objects = mpull($fields, 'getObject'); 19 + $object_phids = mpull($objects, 'getPHID'); 20 + $flags = (new PhabricatorFlagQuery()) 21 + ->setViewer($this->viewer) 22 + ->withOwnerPHIDs(array($this->viewer->getPHID())) 23 + ->withObjectPHIDs($object_phids) 24 + ->execute(); 25 + $flags = mpull($flags, null, 'getObjectPHID'); 26 + 27 + $result = array(); 28 + foreach ($fields as $key => $field) { 29 + $target_phid = $field->getObject()->getPHID(); 30 + $result[$key] = idx($flags, $target_phid); 31 + } 32 + 33 + return $result; 34 + } 35 + 36 + }
+18
src/applications/maniphest/field/ManiphestFlagCustomField.php
··· 1 + <?php 2 + 3 + // I'm not sure this is the right use for the Proxy capability. 4 + // Probably should just use Traits for this. 5 + final class ManiphestFlagCustomField extends ManiphestCustomField { 6 + 7 + public function __construct() { 8 + $this->setProxy(new PhorgeFlagFlaggedObjectCustomField()); 9 + } 10 + 11 + public function canSetProxy() { 12 + return true; 13 + } 14 + 15 + public function newStorageObject() { 16 + return $this->getProxy()->newStorageObject(); 17 + } 18 + }
+24
src/applications/maniphest/query/ManiphestTaskSearchEngine.php
··· 374 374 ManiphestBulkEditCapability::CAPABILITY); 375 375 } 376 376 377 + $custom_field_lists = $this->loadCustomFields( 378 + $tasks, 379 + PhabricatorCustomField::ROLE_LIST); 380 + 377 381 $list = id(new ManiphestTaskResultListView()) 378 382 ->setUser($viewer) 379 383 ->setTasks($tasks) 384 + ->setHandles($handles) 380 385 ->setSavedQuery($saved) 381 386 ->setCanBatchEdit($can_bulk_edit) 387 + ->setCustomFieldLists($custom_field_lists) 382 388 ->setShowBatchControls($this->showBatchControls); 383 389 384 390 $result = new PhabricatorApplicationSearchResultView(); 385 391 $result->setContent($list); 386 392 387 393 return $result; 394 + } 395 + 396 + protected function getRequiredHandlePHIDsForResultList( 397 + array $objects, 398 + PhabricatorSavedQuery $query) { 399 + 400 + $phids = array(); 401 + foreach ($objects as $task) { 402 + $assigned_phid = $task->getOwnerPHID(); 403 + if ($assigned_phid) { 404 + $phids[] = $assigned_phid; 405 + } 406 + foreach ($task->getProjectPHIDs() as $project_phid) { 407 + $phids[] = $project_phid; 408 + } 409 + } 410 + 411 + return $phids; 388 412 } 389 413 390 414 protected function willUseSavedQuery(PhabricatorSavedQuery $saved) {
+15
src/applications/maniphest/view/ManiphestTaskListView.php
··· 4 4 5 5 private $tasks; 6 6 private $handles; 7 + private $customFieldLists = array(); 7 8 private $showBatchControls; 8 9 private $noDataString; 9 10 ··· 16 17 public function setHandles(array $handles) { 17 18 assert_instances_of($handles, 'PhabricatorObjectHandle'); 18 19 $this->handles = $handles; 20 + return $this; 21 + } 22 + 23 + public function setCustomFieldLists(array $lists) { 24 + $this->customFieldLists = $lists; 19 25 return $this; 20 26 } 21 27 ··· 132 138 ->setHref($href)); 133 139 } 134 140 141 + 142 + $field_list = idx($this->customFieldLists, $task->getPHID()); 143 + if ($field_list) { 144 + $field_list 145 + ->addFieldsToListViewItem($task, $this->getViewer(), $item); 146 + } 147 + 135 148 $list->addItem($item); 136 149 } 137 150 138 151 return $list; 139 152 } 140 153 154 + // This method should be removed, and all call-sites switch 155 + // to use ManiphestSearchEngine 141 156 public static function loadTaskHandles( 142 157 PhabricatorUser $viewer, 143 158 array $tasks) {
+15 -3
src/applications/maniphest/view/ManiphestTaskResultListView.php
··· 3 3 final class ManiphestTaskResultListView extends ManiphestView { 4 4 5 5 private $tasks; 6 + private $handles; 7 + private $customFieldLists = array(); 6 8 private $savedQuery; 7 9 private $canBatchEdit; 8 10 private $showBatchControls; ··· 17 19 return $this; 18 20 } 19 21 22 + public function setHandles(array $handles) { 23 + $this->handles = $handles; 24 + return $this; 25 + } 26 + 27 + public function setCustomFieldLists(array $lists) { 28 + $this->customFieldLists = $lists; 29 + return $this; 30 + } 31 + 20 32 public function setCanBatchEdit($can_batch_edit) { 21 33 $this->canBatchEdit = $can_batch_edit; 22 34 return $this; ··· 42 54 $group_parameter = nonempty($query->getParameter('group'), 'priority'); 43 55 $order_parameter = nonempty($query->getParameter('order'), 'priority'); 44 56 45 - $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); 46 57 $groups = $this->groupTasks( 47 58 $tasks, 48 59 $group_parameter, 49 - $handles); 60 + $this->handles); 50 61 51 62 $result = array(); 52 63 ··· 56 67 $task_list->setShowBatchControls($this->showBatchControls); 57 68 $task_list->setUser($viewer); 58 69 $task_list->setTasks($list); 59 - $task_list->setHandles($handles); 70 + $task_list->setHandles($this->handles); 71 + $task_list->setCustomFieldLists($this->customFieldLists); 60 72 61 73 $header = id(new PHUIHeaderView()) 62 74 ->addSigil('task-group')
+31 -1
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 13 13 * @task read Reading Utilities 14 14 * @task exec Paging and Executing Queries 15 15 * @task render Rendering Results 16 + * @task custom Custom Fields 16 17 */ 17 18 abstract class PhabricatorApplicationSearchEngine extends Phobject { 18 19 ··· 1071 1072 if ($phids) { 1072 1073 $handles = id(new PhabricatorHandleQuery()) 1073 1074 ->setViewer($this->requireViewer()) 1074 - ->witHPHIDs($phids) 1075 + ->withPHIDs($phids) 1075 1076 ->execute(); 1076 1077 } else { 1077 1078 $handles = array(); ··· 1624 1625 } 1625 1626 1626 1627 return $supported; 1628 + } 1629 + 1630 + /** 1631 + * Load from object and from storage, and updates Custom Fields instances 1632 + * that are attached to each object. 1633 + * 1634 + * @return map<phid->PhabricatorCustomFieldList> of loaded fields. 1635 + * @task custom 1636 + */ 1637 + protected function loadCustomFields(array $objects, $role) { 1638 + assert_instances_of($objects, 'PhabricatorCustomFieldInterface'); 1639 + 1640 + $query = new PhabricatorCustomFieldStorageQuery(); 1641 + $lists = array(); 1642 + 1643 + foreach ($objects as $object) { 1644 + $field_list = PhabricatorCustomField::getObjectFields($object, $role); 1645 + $field_list->readFieldsFromObject($object); 1646 + foreach ($field_list->getFields() as $field) { 1647 + // TODO move $viewer into PhabricatorCustomFieldStorageQuery 1648 + $field->setViewer($this->viewer); 1649 + } 1650 + $lists[$object->getPHID()] = $field_list; 1651 + $query->addFields($field_list->getFields()); 1652 + } 1653 + // This updates the field_list objects. 1654 + $query->execute(); 1655 + 1656 + return $lists; 1627 1657 } 1628 1658 1629 1659 }
+11 -2
src/infrastructure/customfield/field/PhabricatorCustomField.php
··· 384 384 * 385 385 * @param PhabricatorCustomField Field implementation. 386 386 * @return this 387 + * @task proxy 387 388 */ 388 389 final public function setProxy(PhabricatorCustomField $proxy) { 389 390 if (!$this->canSetProxy()) { ··· 400 401 * @{method:canSetProxy}. 401 402 * 402 403 * @return PhabricatorCustomField|null Proxy field, if one is set. 404 + * @task proxy 403 405 */ 404 406 final public function getProxy() { 405 407 return $this->proxy; 406 408 } 407 409 408 - 410 + /** 411 + * @task proxy 412 + */ 413 + public function __clone() { 414 + if ($this->proxy) { 415 + $this->proxy = clone $this->proxy; 416 + } 417 + } 409 418 /* -( Contextual Data )---------------------------------------------------- */ 410 419 411 420 ··· 827 836 828 837 829 838 /** 830 - * Appearing in ApplicationTrasactions allows a field to be edited using 839 + * Appearing in ApplicationTransactions allows a field to be edited using 831 840 * standard workflows. 832 841 * 833 842 * @return bool True to appear in ApplicationTransactions.
+13
src/infrastructure/customfield/field/PhabricatorCustomFieldList.php
··· 200 200 } 201 201 } 202 202 203 + public function addFieldsToListViewItem( 204 + PhabricatorCustomFieldInterface $object, 205 + PhabricatorUser $viewer, 206 + PHUIObjectItemView $view) { 207 + 208 + foreach ($this->fields as $field) { 209 + if ($field->shouldAppearInListView()) { 210 + $field->setViewer($viewer); 211 + $field->renderOnListItem($view); 212 + } 213 + } 214 + } 215 + 203 216 public function buildFieldTransactionsFromRequest( 204 217 PhabricatorApplicationTransaction $template, 205 218 AphrontRequest $request) {