diff --git a/src/applications/project/controller/PhabricatorProjectMembersAddController.php b/src/applications/project/controller/PhabricatorProjectMembersAddController.php index 79c66dc5b1..572e71f51a 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersAddController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersAddController.php @@ -1,77 +1,82 @@ <?php final class PhabricatorProjectMembersAddController extends PhabricatorProjectController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $project = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$project) { return new Aphront404Response(); } $this->setProject($project); $done_uri = "/project/members/{$id}/"; if (!$project->supportsEditMembers()) { $copy = pht('Parent projects and milestones do not support adding '. 'members. You can add members directly to any non-parent subproject.'); + $subprojects_uri = "/project/subprojects/{$id}/"; + return $this->newDialog() ->setTitle(pht('Unsupported Project')) ->appendParagraph($copy) + ->setSubmitURI($subprojects_uri) + ->addSubmitButton(pht('See Subprojects')) + ->setDisableWorkflowOnSubmit(true) ->addCancelButton($done_uri); } if ($request->isFormPost()) { $member_phids = $request->getArr('memberPHIDs'); $type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $type_member) ->setNewValue( array( '+' => array_fuse($member_phids), )); $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($project, $xactions); return id(new AphrontRedirectResponse()) ->setURI($done_uri); } $form = id(new AphrontFormView()) ->setUser($viewer) ->appendControl( id(new AphrontFormTokenizerControl()) ->setName('memberPHIDs') ->setLabel(pht('Members')) ->setDatasource(new PhabricatorPeopleDatasource())); return $this->newDialog() ->setTitle(pht('Add Members')) ->appendForm($form) ->addCancelButton($done_uri) ->addSubmitButton(pht('Add Members')); } } diff --git a/src/applications/project/controller/PhabricatorProjectUpdateController.php b/src/applications/project/controller/PhabricatorProjectUpdateController.php index 7cbd77b4cb..5707693654 100644 --- a/src/applications/project/controller/PhabricatorProjectUpdateController.php +++ b/src/applications/project/controller/PhabricatorProjectUpdateController.php @@ -1,120 +1,125 @@ <?php final class PhabricatorProjectUpdateController extends PhabricatorProjectController { public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $action = $request->getURIData('action'); $capabilities = array( PhabricatorPolicyCapability::CAN_VIEW, ); switch ($action) { case 'join': $capabilities[] = PhabricatorPolicyCapability::CAN_JOIN; break; case 'leave': break; default: return new Aphront404Response(); } $project = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needMembers(true) ->requireCapabilities($capabilities) ->executeOne(); if (!$project) { return new Aphront404Response(); } $done_uri = "/project/members/{$id}/"; if (!$project->supportsEditMembers()) { $copy = pht('Parent projects and milestones do not support adding '. 'members. You can add members directly to any non-parent subproject.'); + $subprojects_uri = "/project/subprojects/{$id}/"; + return $this->newDialog() ->setTitle(pht('Unsupported Project')) ->appendParagraph($copy) + ->setSubmitURI($subprojects_uri) + ->addSubmitButton(pht('See Subprojects')) + ->setDisableWorkflowOnSubmit(true) ->addCancelButton($done_uri); } if ($request->isFormPost()) { $edge_action = null; switch ($action) { case 'join': $edge_action = '+'; break; case 'leave': $edge_action = '-'; break; } $type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; $member_spec = array( $edge_action => array($viewer->getPHID() => $viewer->getPHID()), ); $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $type_member) ->setNewValue($member_spec); $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($project, $xactions); return id(new AphrontRedirectResponse())->setURI($done_uri); } $is_locked = $project->getIsMembershipLocked(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $can_leave = ($can_edit || !$is_locked); $button = null; if ($action == 'leave') { if ($can_leave) { $title = pht('Leave Project'); $body = pht( 'Your tremendous contributions to this project will be sorely '. 'missed. Are you sure you want to leave?'); $button = pht('Leave Project'); } else { $title = pht('Membership Locked'); $body = pht( 'Membership for this project is locked. You can not leave.'); } } else { $title = pht('Join Project'); $body = pht( 'Join this project? You will become a member and enjoy whatever '. 'benefits membership may confer.'); $button = pht('Join Project'); } $dialog = $this->newDialog() ->setTitle($title) ->appendParagraph($body) ->addCancelButton($done_uri); if ($button) { $dialog->addSubmitButton($button); } return $dialog; } } diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php index b8b00a6b3e..14aa4a19b6 100644 --- a/src/view/AphrontDialogView.php +++ b/src/view/AphrontDialogView.php @@ -1,470 +1,496 @@ <?php final class AphrontDialogView extends AphrontView implements AphrontResponseProducerInterface { private $title; private $shortTitle; private $submitButton; private $cancelURI; private $cancelText = 'Cancel'; private $submitURI; private $hidden = array(); private $class; private $renderAsForm = true; private $formID; private $footers = array(); private $isStandalone; private $method = 'POST'; private $disableWorkflowOnSubmit; private $disableWorkflowOnCancel; private $width = 'default'; private $errors = array(); private $flush; private $validationException; private $objectList; private $resizeX; private $resizeY; const WIDTH_DEFAULT = 'default'; const WIDTH_FORM = 'form'; const WIDTH_FULL = 'full'; public function setMethod($method) { $this->method = $method; return $this; } public function setIsStandalone($is_standalone) { $this->isStandalone = $is_standalone; return $this; } public function setErrors(array $errors) { $this->errors = $errors; return $this; } public function getIsStandalone() { return $this->isStandalone; } + /** + * Set the URI associated to the Submit Button + * + * If you want a normal link and not any form submission, + * see also: setDisableWorkflowOnSubmit(false). + * + * @param string $uri + * @return self + */ public function setSubmitURI($uri) { $this->submitURI = $uri; return $this; } public function setTitle($title) { $this->title = $title; return $this; } public function getTitle() { return $this->title; } public function setShortTitle($short_title) { $this->shortTitle = $short_title; return $this; } public function getShortTitle() { return $this->shortTitle; } public function setResizeY($resize_y) { $this->resizeY = $resize_y; return $this; } public function getResizeY() { return $this->resizeY; } public function setResizeX($resize_x) { $this->resizeX = $resize_x; return $this; } public function getResizeX() { return $this->resizeX; } + /** + * Add a Submit Button and specify its text + * + * If you want to associate an URI for this Button, + * see also: setSubmitURI(). + * + * @param string $text Text shown for that button + * @return self + */ public function addSubmitButton($text = null) { if (!$text) { $text = pht('Okay'); } $this->submitButton = $text; return $this; } public function addCancelButton($uri, $text = null) { if (!$text) { $text = pht('Cancel'); } $this->cancelURI = $uri; $this->cancelText = $text; return $this; } public function addFooter($footer) { $this->footers[] = $footer; return $this; } public function addHiddenInput($key, $value) { if (is_array($value)) { foreach ($value as $hidden_key => $hidden_value) { $this->hidden[] = array($key.'['.$hidden_key.']', $hidden_value); } } else { $this->hidden[] = array($key, $value); } return $this; } public function setClass($class) { $this->class = $class; return $this; } public function setFlush($flush) { $this->flush = $flush; return $this; } public function setRenderDialogAsDiv() { // TODO: This API is awkward. $this->renderAsForm = false; return $this; } public function setFormID($id) { $this->formID = $id; return $this; } public function setWidth($width) { $this->width = $width; return $this; } public function setObjectList(PHUIObjectItemListView $list) { $this->objectList = true; $box = id(new PHUIObjectBoxView()) ->setObjectList($list); return $this->appendChild($box); } public function appendRemarkup($remarkup) { $viewer = $this->getViewer(); $view = new PHUIRemarkupView($viewer, $remarkup); $view_tag = phutil_tag( 'div', array( 'class' => 'aphront-dialog-view-paragraph', ), $view); return $this->appendChild($view_tag); } public function appendParagraph($paragraph) { return $this->appendParagraphTag($paragraph); } public function appendCommand($command) { $command_tag = phutil_tag('tt', array(), $command); return $this->appendParagraphTag( $command_tag, 'aphront-dialog-view-command'); } private function appendParagraphTag($content, $classes = null) { if ($classes) { $classes = (array)$classes; } else { $classes = array(); } array_unshift($classes, 'aphront-dialog-view-paragraph'); $paragraph_tag = phutil_tag( 'p', array( 'class' => implode(' ', $classes), ), $content); return $this->appendChild($paragraph_tag); } public function appendList(array $items) { $listitems = array(); foreach ($items as $item) { $listitems[] = phutil_tag( 'li', array( 'class' => 'remarkup-list-item', ), $item); } return $this->appendChild( phutil_tag( 'ul', array( 'class' => 'remarkup-list', ), $listitems)); } public function appendForm(AphrontFormView $form) { return $this->appendChild($form->buildLayoutView()); } + /** + * Enable or Disable a Workflow on Submit + * + * For example, if your Submit Button should be a normal link, + * without activating any Workflow, you can set false. + * @param bool $disable_workflow_on_submit + * @return self + */ public function setDisableWorkflowOnSubmit($disable_workflow_on_submit) { $this->disableWorkflowOnSubmit = $disable_workflow_on_submit; return $this; } public function getDisableWorkflowOnSubmit() { return $this->disableWorkflowOnSubmit; } public function setDisableWorkflowOnCancel($disable_workflow_on_cancel) { $this->disableWorkflowOnCancel = $disable_workflow_on_cancel; return $this; } public function getDisableWorkflowOnCancel() { return $this->disableWorkflowOnCancel; } public function setValidationException( PhabricatorApplicationTransactionValidationException $ex = null) { $this->validationException = $ex; return $this; } public function render() { require_celerity_resource('aphront-dialog-view-css'); $buttons = array(); if ($this->submitButton) { $meta = array(); if ($this->disableWorkflowOnSubmit) { $meta['disableWorkflow'] = true; } $buttons[] = javelin_tag( 'button', array( 'name' => '__submit__', 'sigil' => '__default__', 'type' => 'submit', 'meta' => $meta, ), $this->submitButton); } if ($this->cancelURI) { $meta = array(); if ($this->disableWorkflowOnCancel) { $meta['disableWorkflow'] = true; } $buttons[] = javelin_tag( 'a', array( 'href' => $this->cancelURI, 'class' => 'button button-grey', 'name' => '__cancel__', 'sigil' => 'jx-workflow-button', 'meta' => $meta, ), $this->cancelText); } if (!$this->hasViewer()) { throw new Exception( pht( 'You must call %s when rendering an %s.', 'setViewer()', __CLASS__)); } $classes = array(); $classes[] = 'aphront-dialog-view'; $classes[] = $this->class; if ($this->flush) { $classes[] = 'aphront-dialog-flush'; } switch ($this->width) { case self::WIDTH_FORM: case self::WIDTH_FULL: $classes[] = 'aphront-dialog-view-width-'.$this->width; break; case self::WIDTH_DEFAULT: break; default: throw new Exception( pht( "Unknown dialog width '%s'!", $this->width)); } if ($this->isStandalone) { $classes[] = 'aphront-dialog-view-standalone'; } if ($this->objectList) { $classes[] = 'aphront-dialog-object-list'; } $attributes = array( 'class' => implode(' ', $classes), 'sigil' => 'jx-dialog', 'role' => 'dialog', ); $form_attributes = array( 'action' => $this->submitURI, 'method' => $this->method, 'id' => $this->formID, ); $hidden_inputs = array(); $hidden_inputs[] = phutil_tag( 'input', array( 'type' => 'hidden', 'name' => '__dialog__', 'value' => '1', )); foreach ($this->hidden as $desc) { list($key, $value) = $desc; $hidden_inputs[] = javelin_tag( 'input', array( 'type' => 'hidden', 'name' => $key, 'value' => $value, 'sigil' => 'aphront-dialog-application-input', )); } if (!$this->renderAsForm) { $buttons = array( phabricator_form( $this->getViewer(), $form_attributes, array_merge($hidden_inputs, $buttons)), ); } $children = $this->renderChildren(); $errors = $this->errors; $ex = $this->validationException; $exception_errors = null; if ($ex) { foreach ($ex->getErrors() as $error) { $errors[] = $error->getMessage(); } } if ($errors) { $children = array( id(new PHUIInfoView())->setErrors($errors), $children, ); } $header = new PHUIHeaderView(); $header->setHeader($this->title); $footer = null; if ($this->footers) { $footer = phutil_tag( 'div', array( 'class' => 'aphront-dialog-foot', ), $this->footers); } $resize = null; if ($this->resizeX || $this->resizeY) { $resize = javelin_tag( 'div', array( 'class' => 'aphront-dialog-resize', 'sigil' => 'jx-dialog-resize', 'meta' => array( 'resizeX' => $this->resizeX, 'resizeY' => $this->resizeY, ), )); } $tail = null; if ($buttons || $footer) { $tail = phutil_tag( 'div', array( 'class' => 'aphront-dialog-tail grouped', ), array( $buttons, $footer, $resize, )); } $content = array( phutil_tag( 'div', array( 'class' => 'aphront-dialog-head', ), $header), phutil_tag('div', array( 'class' => 'aphront-dialog-body grouped', ), $children), $tail, ); if ($this->renderAsForm) { return phabricator_form( $this->getViewer(), $form_attributes + $attributes, array($hidden_inputs, $content)); } else { return javelin_tag( 'div', $attributes, $content); } } /* -( AphrontResponseProducerInterface )----------------------------------- */ public function produceAphrontResponse() { return id(new AphrontDialogResponse()) ->setDialog($this); } }