getReleaseTemplateKey(); if (!$this->isAllowedForTemplateKey($template_key)) { $errors[] = 'This Release type does not support this action'; return $errors; } if ($this->requiresBranches()) { $branch_name = $release->getBranchNameForAllRepos(); if (!strlen($branch_name)) { $errors[] = 'This revision doesn\'t have a consistent branches, so we can\'t '. 'update it.'; } } if ($this->requiresNoBranches()) { $refs = $release->getCurrentRefs(); foreach ($refs as $ref) { if ($release->isBranch($ref)) { $errors[] = 'This release has branches, so this action does not apply.'; break; } } } return $errors; } protected function requiresBranches() { return false; } protected function requiresNoBranches() { return false; } public function needsEditPermission() { return true; } public function assertPolicy($release) { // maybe `get required capabilities()? if ($this->needsEditPermission()) { // it would be nicer to fold this into $errors[] instead of exception. PhabricatorPolicyFilter::requireCapability( $this->getViewer(), $release, PhabricatorPolicyCapability::CAN_EDIT); } } /** * Get initial values to put in the form. Returns array of fields. */ abstract public function getDefaultFieldValues( PhabricatorReleaseRelease $release); /** * Build the form. $fields is the output of getDefaultFieldValues() or * handleForm(). */ abstract public function buildForm( PhabricatorReleaseRelease $release, array $fields); /** * Receives the dialog Post; Updates $fields, and any validation errors. * Doesn't actually take any action. * return array($fields, $errors) */ abstract public function handleFormPost( PhabricatorReleaseRelease $release, AphrontRequest $request, array $fields); /** * The transaction will actually do the action. * This method is only called if both handleFormPost() and * initialValidationsForRelease() return no errors. * * return TransactionType (string) that will run the action. */ abstract public function getTransactionType( PhabricatorReleaseRelease $release, array $fields); /** * Produce the transaction's initial value; This will be updated again * by the transaction itself. */ abstract public function generateTransactionValue( PhabricatorReleaseRelease $release, array $fields); /** * Where to go after applying the transaction. */ public function getResultURI( PhabricatorReleaseRelease $release, array $fields, PhabricatorReleaseReleaseTransaction $xaction) { return $release->getURI(); } final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; } final public function getViewer() { return $this->viewer; } } final class MagicReleaseCustomActionController extends PhabricatorController { public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $release_id = $request->getURIData('release_id'); $release = id(new PhabricatorReleaseReleaseQuery()) ->setViewer($viewer) ->withIDs(array($release_id)) ->executeOne(); if (!$release) { return new Aphront404Response(); } $action_class = $request->getURIData('action'); $custom_action = is_subclass_of( $action_class, 'MagicReleaseCustomAction'); if ($custom_action) { $action = newv($action_class, array()); $action->setViewer($viewer); } else { throw new Exception( pht( "Action type must be a valid class name and must subclass ". "%s. '%s' is not a subclass of %s", 'MagicReleaseCustomAction', $this->strategyClass, 'MagicReleaseCustomAction')); } $action->assertPolicy($release); $fields = $action->getDefaultFieldValues($release); $errors = $action->initialValidationsForRelease($release); if ($request->isDialogFormPost()) { list($fields, $errors2) = $action->handleFormPost($release, $request, $fields); $errors = array_merge($errors, $errors2); if (!$errors) { $xaction_type = $action->getTransactionType($release, $fields); $value = $action->generateTransactionValue($release, $fields); $xaction = id(new PhabricatorReleaseReleaseTransaction()) ->setTransactionType($xaction_type) ->setNewValue($value); $editor = id(new PhabricatorReleaseReleaseEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($release, array($xaction)); $uri = $action->getResultURI($release, $fields, $xaction); return id(new AphrontRedirectResponse())->setURI($uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $errors[] = 'Failed to initiate action! '.$ex->getMessage(); } } } $preamble = $action->getFormPremble($release); $preamble = new PHUIRemarkupView($viewer, $preamble); $form = $action->buildForm($release, $fields); $form->setViewer($viewer); return $this->newDialog() ->setSubmitURI($request->getRequestURI()) ->setTitle($action->getFormTitle($release)) ->appendChild($preamble) ->setErrors($errors) ->appendForm($form) ->addSubmitButton($action->getSubmitText()) ->addCancelButton('#'); } } final class MagicRenderEventListener extends PhabricatorEventListener { public function handleEvent(PhutilEvent $event) { switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: if ($object instanceof PhabricatorReleaseRelease) { $this->addReleaseActions($event); } break; } } private function addReleaseActions(PhutilEvent $event) { $release = $event->getValue('object'); $release_id = $release->getID(); $template_key = $release->getReleaseTemplateKey(); $can_edit = PhabricatorPolicyFilter::hasCapability( $event->getUser(), $release, PhabricatorPolicyCapability::CAN_EDIT); $actions = array(); $custom_actions = id(new PhutilClassMapQuery()) ->setAncestorClass('MagicReleaseCustomAction') ->execute(); foreach ($custom_actions as $custom_action) { if ($custom_action->isAllowedForTemplateKey($template_key)) { $action_class = get_class($custom_action); $action = id(new PhabricatorActionView()) ->setName($custom_action->getActionItemText()) ->setWorkflow(true) ->setIcon($custom_action->getActionItemIcon()) ->setHref("/magic/release/{$release_id}/action/{$action_class}/"); if ($custom_action->needsEditPermission()) { $action->setDisabled(!$can_edit); } $actions[] = $action; } } $actions[] = id(new PhabricatorActionView()) ->setName('Compare to Another Release') ->setWorkflow(true) ->setIcon('fa-search') ->setHref("/magic/release/{$release_id}/compare/"); $this->addActionMenuItems($event, $actions); } }