diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bc8b6a17d0..d1a4e587af 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -688,6 +688,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php', 'DiffusionRepositoryEditActionsController' => 'applications/diffusion/controller/DiffusionRepositoryEditActionsController.php', 'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php', + 'DiffusionRepositoryEditAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php', 'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php', 'DiffusionRepositoryEditBranchesController' => 'applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php', 'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php', @@ -875,6 +876,7 @@ phutil_register_library_map(array( 'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php', 'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', + 'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php', @@ -4424,6 +4426,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryDefaultController' => 'DiffusionController', 'DiffusionRepositoryEditActionsController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryEditAutomationController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditBranchesController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditController' => 'DiffusionController', @@ -4645,6 +4648,7 @@ phutil_register_library_map(array( 'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'DrydockObjectAuthorizationView' => 'AphrontView', 'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DrydockResource' => array( 'DrydockDAO', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 0314318643..09a5b35f19 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -102,6 +102,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { 'update/' => 'DiffusionRepositoryEditUpdateController', 'symbol/' => 'DiffusionRepositorySymbolsController', 'staging/' => 'DiffusionRepositoryEditStagingController', + 'automation/' => 'DiffusionRepositoryEditAutomationController', ), 'pathtree/(?P.*)' => 'DiffusionPathTreeController', 'mirror/' => array( diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php new file mode 100644 index 0000000000..1c94e8bbc5 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php @@ -0,0 +1,94 @@ +getUser(); + $drequest = $this->diffusionRequest; + $repository = $drequest->getRepository(); + + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->withIDs(array($repository->getID())) + ->executeOne(); + if (!$repository) { + return new Aphront404Response(); + } + + if (!$repository->supportsAutomation()) { + return new Aphront404Response(); + } + + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + + $v_blueprints = $repository->getHumanReadableDetail( + 'automation.blueprintPHIDs'); + + if ($request->isFormPost()) { + $v_blueprints = $request->getArr('blueprintPHIDs'); + + $xactions = array(); + $template = id(new PhabricatorRepositoryTransaction()); + + $type_blueprints = + PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS; + + $xactions[] = id(clone $template) + ->setTransactionType($type_blueprints) + ->setNewValue($v_blueprints); + + id(new PhabricatorRepositoryEditor()) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->applyTransactions($repository, $xactions); + + return id(new AphrontRedirectResponse())->setURI($edit_uri); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Edit Automation')); + + $title = pht('Edit %s', $repository->getName()); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendRemarkupInstructions( + pht( + "Configure **Repository Automation** to allow Phabricator to ". + "write to this repository.". + "\n\n". + "IMPORTANT: This feature is new, experimental, and not supported. ". + "Use it at your own risk.")) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setLabel(pht('Use Blueprints')) + ->setName('blueprintPHIDs') + ->setValue($v_blueprints) + ->setDatasource(new DrydockBlueprintDatasource())) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save')) + ->addCancelButton($edit_uri)); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setForm($form); + + return $this->buildApplicationPage( + array( + $crumbs, + $object_box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 6519a4380e..5def1c754d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -31,6 +31,7 @@ final class DiffusionRepositoryEditMainController $has_branches = ($is_git || $is_hg); $has_local = $repository->usesLocalWorkingCopy(); $supports_staging = $repository->supportsStaging(); + $supports_automation = $repository->supportsAutomation(); $crumbs = $this->buildApplicationCrumbs($is_main = true); @@ -100,6 +101,13 @@ final class DiffusionRepositoryEditMainController $this->buildStagingActions($repository)); } + $automation_properties = null; + if ($supports_automation) { + $automation_properties = $this->buildAutomationProperties( + $repository, + $this->buildAutomationActions($repository)); + } + $actions_properties = $this->buildActionsProperties( $repository, $this->buildActionsActions($repository)); @@ -171,6 +179,12 @@ final class DiffusionRepositoryEditMainController ->addPropertyList($staging_properties); } + if ($automation_properties) { + $boxes[] = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Automation')) + ->addPropertyList($automation_properties); + } + $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Text Encoding')) ->addPropertyList($encoding_properties); @@ -622,7 +636,6 @@ final class DiffusionRepositoryEditMainController return $view; } - private function buildStagingActions(PhabricatorRepository $repository) { $viewer = $this->getViewer(); @@ -661,6 +674,47 @@ final class DiffusionRepositoryEditMainController return $view; } + private function buildAutomationActions(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + + $view = id(new PhabricatorActionListView()) + ->setObjectURI($this->getRequest()->getRequestURI()) + ->setUser($viewer); + + $edit = id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Automation')) + ->setHref( + $this->getRepositoryControllerURI($repository, 'edit/automation/')); + $view->addAction($edit); + + return $view; + } + + private function buildAutomationProperties( + PhabricatorRepository $repository, + PhabricatorActionListView $actions) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setActionList($actions); + + $blueprint_phids = $repository->getAutomationBlueprintPHIDs(); + if (!$blueprint_phids) { + $blueprint_view = phutil_tag('em', array(), pht('Not Configured')); + } else { + $blueprint_view = id(new DrydockObjectAuthorizationView()) + ->setUser($viewer) + ->setObjectPHID($repository->getPHID()) + ->setBlueprintPHIDs($blueprint_phids); + } + + $view->addProperty(pht('Automation'), $blueprint_view); + + return $view; + } + private function buildHostingActions(PhabricatorRepository $repository) { $user = $this->getRequest()->getUser(); diff --git a/src/applications/drydock/storage/DrydockAuthorization.php b/src/applications/drydock/storage/DrydockAuthorization.php index 2bb5decb14..8872ef7f6f 100644 --- a/src/applications/drydock/storage/DrydockAuthorization.php +++ b/src/applications/drydock/storage/DrydockAuthorization.php @@ -93,6 +93,86 @@ final class DrydockAuthorization extends DrydockDAO return idx($map, $state, pht('', $state)); } + /** + * Apply external authorization effects after a user chagnes the value of a + * blueprint selector control an object. + * + * @param PhabricatorUser User applying the change. + * @param phid Object PHID change is being applied to. + * @param list Old blueprint PHIDs. + * @param list New blueprint PHIDs. + * @return void + */ + public static function applyAuthorizationChanges( + PhabricatorUser $viewer, + $object_phid, + array $old, + array $new) { + + $old_phids = array_fuse($old); + $new_phids = array_fuse($new); + + $rem_phids = array_diff_key($old_phids, $new_phids); + $add_phids = array_diff_key($new_phids, $old_phids); + + $altered_phids = $rem_phids + $add_phids; + + if (!$altered_phids) { + return; + } + + $authorizations = id(new DrydockAuthorizationQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withObjectPHIDs(array($object_phid)) + ->withBlueprintPHIDs($altered_phids) + ->execute(); + $authorizations = mpull($authorizations, null, 'getBlueprintPHID'); + + $state_active = self::OBJECTAUTH_ACTIVE; + $state_inactive = self::OBJECTAUTH_INACTIVE; + + $state_requested = self::BLUEPRINTAUTH_REQUESTED; + + // Disable the object side of the authorization for any existing + // authorizations. + foreach ($rem_phids as $rem_phid) { + $authorization = idx($authorizations, $rem_phid); + if (!$authorization) { + continue; + } + + $authorization + ->setObjectAuthorizationState($state_inactive) + ->save(); + } + + // For new authorizations, either add them or reactivate them depending + // on the current state. + foreach ($add_phids as $add_phid) { + $needs_update = false; + + $authorization = idx($authorizations, $add_phid); + if (!$authorization) { + $authorization = id(new DrydockAuthorization()) + ->setObjectPHID($object_phid) + ->setObjectAuthorizationState($state_active) + ->setBlueprintPHID($add_phid) + ->setBlueprintAuthorizationState($state_requested); + + $needs_update = true; + } else { + $current_state = $authorization->getObjectAuthorizationState(); + if ($current_state != $state_active) { + $authorization->setObjectAuthorizationState($state_active); + $needs_update = true; + } + } + + if ($needs_update) { + $authorization->save(); + } + } + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/view/DrydockObjectAuthorizationView.php b/src/applications/drydock/view/DrydockObjectAuthorizationView.php new file mode 100644 index 0000000000..261829bfe5 --- /dev/null +++ b/src/applications/drydock/view/DrydockObjectAuthorizationView.php @@ -0,0 +1,79 @@ +objectPHID = $object_phid; + return $this; + } + + public function getObjectPHID() { + return $this->objectPHID; + } + + public function setBlueprintPHIDs(array $blueprint_phids) { + $this->blueprintPHIDs = $blueprint_phids; + return $this; + } + + public function getBlueprintPHIDs() { + return $this->blueprintPHIDs; + } + + public function render() { + $viewer = $this->getUser(); + $blueprint_phids = $this->getBlueprintPHIDs(); + $object_phid = $this->getObjectPHID(); + + // NOTE: We're intentionally letting you see the authorization state on + // blueprints you can't see because this has a tremendous potential to + // be extremely confusing otherwise. You still can't see the blueprints + // themselves, but you can know if the object is authorized on something. + + if ($blueprint_phids) { + $handles = $viewer->loadHandles($blueprint_phids); + + $authorizations = id(new DrydockAuthorizationQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withObjectPHIDs(array($object_phid)) + ->withBlueprintPHIDs($blueprint_phids) + ->execute(); + $authorizations = mpull($authorizations, null, 'getBlueprintPHID'); + } else { + $handles = array(); + $authorizations = array(); + } + + $items = array(); + foreach ($blueprint_phids as $phid) { + $authorization = idx($authorizations, $phid); + if (!$authorization) { + continue; + } + + $handle = $handles[$phid]; + + $item = id(new PHUIStatusItemView()) + ->setTarget($handle->renderLink()); + + $state = $authorization->getBlueprintAuthorizationState(); + $item->setIcon( + DrydockAuthorization::getBlueprintStateIcon($state), + null, + DrydockAuthorization::getBlueprintStateName($state)); + + $items[] = $item; + } + + $status = new PHUIStatusListView(); + foreach ($items as $item) { + $status->addItem($item); + } + + return $status; + } + +} diff --git a/src/applications/phid/query/PhabricatorObjectQuery.php b/src/applications/phid/query/PhabricatorObjectQuery.php index d4bcc9edf3..65e9938311 100644 --- a/src/applications/phid/query/PhabricatorObjectQuery.php +++ b/src/applications/phid/query/PhabricatorObjectQuery.php @@ -178,4 +178,40 @@ final class PhabricatorObjectQuery return null; } + + /** + * Select invalid or restricted PHIDs from a list. + * + * PHIDs are invalid if their objects do not exist or can not be seen by the + * viewer. This method is generally used to validate that PHIDs affected by + * a transaction are valid. + * + * @param PhabricatorUser Viewer. + * @param list List of ostensibly valid PHIDs. + * @return list List of invalid or restricted PHIDs. + */ + public static function loadInvalidPHIDsForViewer( + PhabricatorUser $viewer, + array $phids) { + + if (!$phids) { + return array(); + } + + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs($phids) + ->execute(); + $objects = mpull($objects, null, 'getPHID'); + + $invalid = array(); + foreach ($phids as $phid) { + if (empty($objects[$phid])) { + $invalid[] = $phid; + } + } + + return $invalid; + } + } diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 2e769975ab..af4226a4f6 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -44,6 +44,7 @@ final class PhabricatorRepositoryEditor $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE; $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES; $types[] = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; + $types[] = PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS; $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -107,6 +108,8 @@ final class PhabricatorRepositoryEditor return $object->getSymbolSources(); case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: return $object->getDetail('staging-uri'); + case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: + return $object->getDetail('automation.blueprintPHIDs', array()); } } @@ -143,6 +146,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: + case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: return $xaction->getNewValue(); case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: @@ -226,6 +230,11 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: $object->setDetail('staging-uri', $xaction->getNewValue()); return; + case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: + $object->setDetail( + 'automation.blueprintPHIDs', + $xaction->getNewValue()); + return; case PhabricatorRepositoryTransaction::TYPE_ENCODING: // Make sure the encoding is valid by converting to UTF-8. This tests // that the user has mbstring installed, and also that they didn't type @@ -276,33 +285,17 @@ final class PhabricatorRepositoryEditor $editor->save(); break; + case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: + DrydockAuthorization::applyAuthorizationChanges( + $this->getActor(), + $object->getPHID(), + $xaction->getOldValue(), + $xaction->getNewValue()); + break; } } - protected function mergeTransactions( - PhabricatorApplicationTransaction $u, - PhabricatorApplicationTransaction $v) { - - $type = $u->getTransactionType(); - switch ($type) {} - - return parent::mergeTransactions($u, $v); - } - - protected function transactionHasEffect( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $type = $xaction->getTransactionType(); - switch ($type) {} - - return parent::transactionHasEffect($object, $xaction); - } - protected function requireCapabilities( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -338,6 +331,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: + case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, @@ -431,6 +425,29 @@ final class PhabricatorRepositoryEditor } } break; + + case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: + foreach ($xactions as $xaction) { + $old = nonempty($xaction->getOldValue(), array()); + $new = nonempty($xaction->getNewValue(), array()); + + $add = array_diff($new, $old); + + $invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer( + $this->getActor(), + $add); + if ($invalid) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'Some of the selected automation blueprints are invalid '. + 'or restricted: %s.', + implode(', ', $invalid)), + $xaction); + } + } + break; } return $errors; diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 084f531a10..c15b9d9dc2 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1799,7 +1799,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } -/* -( Staging )-------------------------------------------------------------*/ +/* -( Staging )------------------------------------------------------------ */ public function supportsStaging() { @@ -1815,6 +1815,22 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } +/* -( Automation )--------------------------------------------------------- */ + + + public function supportsAutomation() { + return $this->isGit(); + } + + + public function getAutomationBlueprintPHIDs() { + if (!$this->supportsAutomation()) { + return array(); + } + return $this->getDetail('automation.blueprintPHIDs', array()); + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index de1310f320..c9077e0236 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -28,6 +28,7 @@ final class PhabricatorRepositoryTransaction const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source'; const TYPE_SYMBOLS_LANGUAGE = 'repo:symbol-language'; const TYPE_STAGING_URI = 'repo:staging-uri'; + const TYPE_AUTOMATION_BLUEPRINTS = 'repo:automation-blueprints'; // TODO: Clean up these legacy transaction types. const TYPE_SSH_LOGIN = 'repo:ssh-login'; @@ -65,6 +66,7 @@ final class PhabricatorRepositoryTransaction } break; case self::TYPE_SYMBOLS_SOURCES: + case self::TYPE_AUTOMATION_BLUEPRINTS: if ($old) { $phids = array_merge($phids, $old); } @@ -436,6 +438,34 @@ final class PhabricatorRepositoryTransaction $old, $new); } + + case self::TYPE_AUTOMATION_BLUEPRINTS: + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ($add && $rem) { + return pht( + '%s changed %s automation blueprint(s), '. + 'added %s: %s; removed %s: %s.', + $this->renderHandleLink($author_phid), + new PhutilNumber(count($add) + count($rem)), + new PhutilNumber(count($add)), + $this->renderHandleList($add), + new PhutilNumber(count($rem)), + $this->renderHandleList($rem)); + } else if ($add) { + return pht( + '%s added %s automation blueprint(s): %s.', + $this->renderHandleLink($author_phid), + new PhutilNumber(count($add)), + $this->renderHandleList($add)); + } else { + return pht( + '%s removed %s automation blueprint(s): %s.', + $this->renderHandleLink($author_phid), + new PhutilNumber(count($rem)), + $this->renderHandleList($rem)); + } } return parent::getTitle(); diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php index 29edd6d472..ad2bb62d81 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php @@ -14,75 +14,14 @@ final class PhabricatorStandardCustomFieldBlueprints public function applyApplicationTransactionExternalEffects( PhabricatorApplicationTransaction $xaction) { - $object_phid = $xaction->getObjectPHID(); - $old = $this->decodeValue($xaction->getOldValue()); $new = $this->decodeValue($xaction->getNewValue()); - $old_phids = array_fuse($old); - $new_phids = array_fuse($new); - - $rem_phids = array_diff_key($old_phids, $new_phids); - $add_phids = array_diff_key($new_phids, $old_phids); - - $altered_phids = $rem_phids + $add_phids; - - if (!$altered_phids) { - return; - } - - $authorizations = id(new DrydockAuthorizationQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($object_phid)) - ->withBlueprintPHIDs($altered_phids) - ->execute(); - $authorizations = mpull($authorizations, null, 'getBlueprintPHID'); - - $state_active = DrydockAuthorization::OBJECTAUTH_ACTIVE; - $state_inactive = DrydockAuthorization::OBJECTAUTH_INACTIVE; - - $state_requested = DrydockAuthorization::BLUEPRINTAUTH_REQUESTED; - - // Disable the object side of the authorization for any existing - // authorizations. - foreach ($rem_phids as $rem_phid) { - $authorization = idx($authorizations, $rem_phid); - if (!$authorization) { - continue; - } - - $authorization - ->setObjectAuthorizationState($state_inactive) - ->save(); - } - - // For new authorizations, either add them or reactivate them depending - // on the current state. - foreach ($add_phids as $add_phid) { - $needs_update = false; - - $authorization = idx($authorizations, $add_phid); - if (!$authorization) { - $authorization = id(new DrydockAuthorization()) - ->setObjectPHID($object_phid) - ->setObjectAuthorizationState($state_active) - ->setBlueprintPHID($add_phid) - ->setBlueprintAuthorizationState($state_requested); - - $needs_update = true; - } else { - $current_state = $authorization->getObjectAuthorizationState(); - if ($current_state != $state_active) { - $authorization->setObjectAuthorizationState($state_active); - $needs_update = true; - } - } - - if ($needs_update) { - $authorization->save(); - } - } - + DrydockAuthorization::applyAuthorizationChanges( + $this->getViewer(), + $xaction->getObjectPHID(), + $old, + $new); } public function renderPropertyViewValue(array $handles) { @@ -91,55 +30,10 @@ final class PhabricatorStandardCustomFieldBlueprints return phutil_tag('em', array(), pht('No authorized blueprints.')); } - $object = $this->getObject(); - $object_phid = $object->getPHID(); - - // NOTE: We're intentionally letting you see the authorization state on - // blueprints you can't see because this has a tremendous potential to - // be extremely confusing otherwise. You still can't see the blueprints - // themselves, but you can know if the object is authorized on something. - - if ($value) { - $handles = $this->getViewer()->loadHandles($value); - - $authorizations = id(new DrydockAuthorizationQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($object_phid)) - ->withBlueprintPHIDs($value) - ->execute(); - $authorizations = mpull($authorizations, null, 'getBlueprintPHID'); - } else { - $handles = array(); - $authorizations = array(); - } - - $items = array(); - foreach ($value as $phid) { - $authorization = idx($authorizations, $phid); - if (!$authorization) { - continue; - } - - $handle = $handles[$phid]; - - $item = id(new PHUIStatusItemView()) - ->setTarget($handle->renderLink()); - - $state = $authorization->getBlueprintAuthorizationState(); - $item->setIcon( - DrydockAuthorization::getBlueprintStateIcon($state), - null, - DrydockAuthorization::getBlueprintStateName($state)); - - $items[] = $item; - } - - $status = new PHUIStatusListView(); - foreach ($items as $item) { - $status->addItem($item); - } - - return $status; + return id(new DrydockObjectAuthorizationView()) + ->setUser($this->getViewer()) + ->setObjectPHID($this->getObject()->getPHID()) + ->setBlueprintPHIDs($value); } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php index c7be0f7acb..81b94aff31 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php @@ -158,22 +158,9 @@ abstract class PhabricatorStandardCustomFieldPHIDs $add = array_diff($new, $old); - if (!$add) { - continue; - } - - $objects = id(new PhabricatorObjectQuery()) - ->setViewer($editor->getActor()) - ->withPHIDs($add) - ->execute(); - $objects = mpull($objects, null, 'getPHID'); - - $invalid = array(); - foreach ($add as $phid) { - if (empty($objects[$phid])) { - $invalid[] = $phid; - } - } + $invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer( + $editor->getActor(), + $add); if ($invalid) { $error = new PhabricatorApplicationTransactionValidationError( diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index beda39c21c..a3c1833468 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1403,6 +1403,23 @@ final class PhabricatorUSEnglishTranslation 'Waiting %s seconds for lease to activate.', ), + '%s changed %s automation blueprint(s), added %s: %s; removed %s: %s.' => + '%s changed automation blueprints, added: %4$s; removed: %6$s.', + + '%s added %s automation blueprint(s): %s.' => array( + array( + '%s added an automation blueprint: %3$s.', + '%s added automation blueprints: %3$s.', + ), + ), + + '%s removed %s automation blueprint(s): %s.' => array( + array( + '%s removed an automation blueprint: %3$s.', + '%s removed automation blueprints: %3$s.', + ), + ), + ); }