Changeset View
Changeset View
Standalone View
Standalone View
src/applications/herald/controller/HeraldNewController.php
| <?php | <?php | ||||
| final class HeraldNewController extends HeraldController { | final class HeraldNewController extends HeraldController { | ||||
| public function handleRequest(AphrontRequest $request) { | public function handleRequest(AphrontRequest $request) { | ||||
| $viewer = $request->getViewer(); | $viewer = $this->getViewer(); | ||||
| $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); | $adapter_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); | ||||
| $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); | $adapter_type = $request->getStr('adapter'); | ||||
| $errors = array(); | |||||
| $e_type = null; | if (!isset($adapter_type_map[$adapter_type])) { | ||||
| $e_rule = null; | $title = pht('Create Herald Rule'); | ||||
| $e_object = null; | $content = $this->newAdapterMenu($title); | ||||
| } else { | |||||
| $adapter = HeraldAdapter::getAdapterForContentType($adapter_type); | |||||
| $step = $request->getInt('step'); | $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); | ||||
| if ($request->isFormPost()) { | $rule_type = $request->getStr('type'); | ||||
| $content_type = $request->getStr('content_type'); | |||||
| if (empty($content_type_map[$content_type])) { | |||||
| $errors[] = pht('You must choose a content type for this rule.'); | |||||
| $e_type = pht('Required'); | |||||
| $step = 0; | |||||
| } | |||||
| if (!$errors && $step > 1) { | if (!isset($rule_type_map[$rule_type])) { | ||||
| $rule_type = $request->getStr('rule_type'); | $title = pht( | ||||
| if (empty($rule_type_map[$rule_type])) { | 'Create Herald Rule: %s', | ||||
| $errors[] = pht('You must choose a rule type for this rule.'); | $adapter->getAdapterContentName()); | ||||
| $e_rule = pht('Required'); | |||||
| $step = 1; | |||||
| } | |||||
| } | |||||
| if (!$errors && $step >= 2) { | $content = $this->newTypeMenu($adapter, $title); | ||||
| } else { | |||||
| if ($rule_type !== HeraldRuleTypeConfig::RULE_TYPE_OBJECT) { | |||||
| $target_phid = null; | $target_phid = null; | ||||
| $target_okay = true; | |||||
| } else { | |||||
| $object_name = $request->getStr('objectName'); | $object_name = $request->getStr('objectName'); | ||||
| $done = false; | $target_okay = false; | ||||
| if ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_OBJECT) { | |||||
| $done = true; | $errors = array(); | ||||
| } else if (strlen($object_name)) { | $e_object = null; | ||||
| if ($request->isFormPost()) { | |||||
| if (strlen($object_name)) { | |||||
| $target_object = id(new PhabricatorObjectQuery()) | $target_object = id(new PhabricatorObjectQuery()) | ||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->withNames(array($object_name)) | ->withNames(array($object_name)) | ||||
| ->executeOne(); | ->executeOne(); | ||||
| if ($target_object) { | if ($target_object) { | ||||
| $can_edit = PhabricatorPolicyFilter::hasCapability( | $can_edit = PhabricatorPolicyFilter::hasCapability( | ||||
| $viewer, | $viewer, | ||||
| $target_object, | $target_object, | ||||
| PhabricatorPolicyCapability::CAN_EDIT); | PhabricatorPolicyCapability::CAN_EDIT); | ||||
| if (!$can_edit) { | if (!$can_edit) { | ||||
| $errors[] = pht( | $errors[] = pht( | ||||
| 'You can not create a rule for that object, because you do '. | 'You can not create a rule for that object, because you '. | ||||
| 'not have permission to edit it. You can only create rules '. | 'do not have permission to edit it. You can only create '. | ||||
| 'for objects you can edit.'); | 'rules for objects you can edit.'); | ||||
| $e_object = pht('Not Editable'); | $e_object = pht('Not Editable'); | ||||
| $step = 2; | |||||
| } else { | } else { | ||||
| $adapter = HeraldAdapter::getAdapterForContentType($content_type); | |||||
| if (!$adapter->canTriggerOnObject($target_object)) { | if (!$adapter->canTriggerOnObject($target_object)) { | ||||
| $errors[] = pht( | $errors[] = pht( | ||||
| 'This object is not of an allowed type for the rule. '. | 'This object is not of an allowed type for the rule. '. | ||||
| 'Rules can only trigger on certain objects.'); | 'Rules can only trigger on certain objects.'); | ||||
| $e_object = pht('Invalid'); | $e_object = pht('Invalid'); | ||||
| $step = 2; | |||||
| } else { | } else { | ||||
| $target_phid = $target_object->getPHID(); | $target_phid = $target_object->getPHID(); | ||||
| $done = true; | |||||
| } | } | ||||
| } | } | ||||
| } else { | } else { | ||||
| $errors[] = pht('No object exists by that name.'); | $errors[] = pht('No object exists by that name.'); | ||||
| $e_object = pht('Invalid'); | $e_object = pht('Invalid'); | ||||
| $step = 2; | |||||
| } | } | ||||
| } else if ($step > 2) { | } else { | ||||
| $errors[] = pht( | $errors[] = pht( | ||||
| 'You must choose an object to associate this rule with.'); | 'You must choose an object to associate this rule with.'); | ||||
| $e_object = pht('Required'); | $e_object = pht('Required'); | ||||
| $step = 2; | |||||
| } | } | ||||
| if (!$errors && $done) { | $target_okay = !$errors; | ||||
| } | |||||
| } | |||||
| if (!$target_okay) { | |||||
| $title = pht('Choose Object'); | |||||
| $content = $this->newTargetForm( | |||||
| $adapter, | |||||
| $rule_type, | |||||
| $object_name, | |||||
| $errors, | |||||
| $e_object, | |||||
| $title); | |||||
| } else { | |||||
| $params = array( | $params = array( | ||||
| 'content_type' => $content_type, | 'content_type' => $adapter_type, | ||||
| 'rule_type' => $rule_type, | 'rule_type' => $rule_type, | ||||
| 'targetPHID' => $target_phid, | 'targetPHID' => $target_phid, | ||||
| ); | ); | ||||
| $uri = new PhutilURI('edit/', $params); | $edit_uri = $this->getApplicationURI('edit/'); | ||||
| $uri = $this->getApplicationURI($uri); | $edit_uri = new PhutilURI($edit_uri, $params); | ||||
| return id(new AphrontRedirectResponse())->setURI($uri); | |||||
| return id(new AphrontRedirectResponse()) | |||||
| ->setURI($edit_uri); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| $content_type = $request->getStr('content_type'); | $crumbs = $this | ||||
| $rule_type = $request->getStr('rule_type'); | ->buildApplicationCrumbs() | ||||
| ->addTextCrumb(pht('Create Rule')) | |||||
| ->setBorder(true); | |||||
| $form = id(new AphrontFormView()) | $view = id(new PHUITwoColumnView()) | ||||
| ->setFooter($content); | |||||
| return $this->newPage() | |||||
| ->setTitle($title) | |||||
| ->setCrumbs($crumbs) | |||||
| ->appendChild($view); | |||||
| } | |||||
| private function newAdapterMenu($title) { | |||||
| $viewer = $this->getViewer(); | |||||
| $types = HeraldAdapter::getEnabledAdapterMap($viewer); | |||||
| foreach ($types as $key => $type) { | |||||
| $types[$key] = HeraldAdapter::getAdapterForContentType($key); | |||||
| } | |||||
| $types = msort($types, 'getAdapterContentName'); | |||||
| $base_uri = $this->getApplicationURI('create/'); | |||||
| $menu = id(new PHUIObjectItemListView()) | |||||
| ->setViewer($viewer) | |||||
| ->setBig(true); | |||||
| foreach ($types as $key => $adapter) { | |||||
| $adapter_uri = id(new PhutilURI($base_uri)) | |||||
| ->replaceQueryParam('adapter', $key); | |||||
| $description = $adapter->getAdapterContentDescription(); | |||||
| $description = phutil_escape_html_newlines($description); | |||||
| $item = id(new PHUIObjectItemView()) | |||||
| ->setHeader($adapter->getAdapterContentName()) | |||||
| ->setImageIcon($adapter->getAdapterContentIcon()) | |||||
| ->addAttribute($description) | |||||
| ->setHref($adapter_uri) | |||||
| ->setClickable(true); | |||||
| $menu->addItem($item); | |||||
| } | |||||
| $box = id(new PHUIObjectBoxView()) | |||||
| ->setHeaderText($title) | |||||
| ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) | |||||
| ->setObjectList($menu); | |||||
| return id(new PHUILauncherView()) | |||||
| ->appendChild($box); | |||||
| } | |||||
| private function newTypeMenu(HeraldAdapter $adapter, $title) { | |||||
| $viewer = $this->getViewer(); | |||||
| $global_capability = HeraldManageGlobalRulesCapability::CAPABILITY; | |||||
| $can_global = $this->hasApplicationCapability($global_capability); | |||||
| if ($can_global) { | |||||
| $global_note = pht( | |||||
| 'You have permission to create and manage global rules.'); | |||||
| } else { | |||||
| $global_note = pht( | |||||
| 'You do not have permission to create or manage global rules.'); | |||||
| } | |||||
| $global_note = phutil_tag('em', array(), $global_note); | |||||
| $specs = array( | |||||
| HeraldRuleTypeConfig::RULE_TYPE_PERSONAL => array( | |||||
| 'name' => pht('Personal Rule'), | |||||
| 'icon' => 'fa-user', | |||||
| 'help' => pht( | |||||
| 'Personal rules notify you about events. You own them, but they can '. | |||||
| 'only affect you. Personal rules only trigger for objects you have '. | |||||
| 'permission to see.'), | |||||
| 'enabled' => true, | |||||
| ), | |||||
| HeraldRuleTypeConfig::RULE_TYPE_OBJECT => array( | |||||
| 'name' => pht('Object Rule'), | |||||
| 'icon' => 'fa-cube', | |||||
| 'help' => pht( | |||||
| 'Object rules notify anyone about events. They are bound to an '. | |||||
| 'object (like a repository) and can only act on that object. You '. | |||||
| 'must be able to edit an object to create object rules for it. '. | |||||
| 'Other users who can edit the object can edit its rules.'), | |||||
| 'enabled' => true, | |||||
| ), | |||||
| HeraldRuleTypeConfig::RULE_TYPE_GLOBAL => array( | |||||
| 'name' => pht('Global Rule'), | |||||
| 'icon' => 'fa-globe', | |||||
| 'help' => array( | |||||
| pht( | |||||
| 'Global rules notify anyone about events. Global rules can '. | |||||
| 'bypass access control policies and act on any object.'), | |||||
| $global_note, | |||||
| ), | |||||
| 'enabled' => $can_global, | |||||
| ), | |||||
| ); | |||||
| $adapter_type = $adapter->getAdapterContentType(); | |||||
| $base_uri = new PhutilURI($this->getApplicationURI('create/')); | |||||
| $adapter_uri = id(clone $base_uri) | |||||
| ->replaceQueryParam('adapter', $adapter_type); | |||||
| $menu = id(new PHUIObjectItemListView()) | |||||
| ->setUser($viewer) | ->setUser($viewer) | ||||
| ->setAction($this->getApplicationURI('new/')); | ->setBig(true); | ||||
| foreach ($specs as $rule_type => $spec) { | |||||
| $type_uri = id(clone $adapter_uri) | |||||
| ->replaceQueryParam('type', $rule_type); | |||||
| $name = $spec['name']; | |||||
| $icon = $spec['icon']; | |||||
| $description = $spec['help']; | |||||
| $description = (array)$description; | |||||
| $enabled = $spec['enabled']; | |||||
| if ($enabled) { | |||||
| $enabled = $adapter->supportsRuleType($rule_type); | |||||
| if (!$enabled) { | |||||
| $description[] = phutil_tag( | |||||
| 'em', | |||||
| array(), | |||||
| pht( | |||||
| 'This rule type is not supported by the selected '. | |||||
| 'content type.')); | |||||
| } | |||||
| } | |||||
| $description = phutil_implode_html( | |||||
| array( | |||||
| phutil_tag('br'), | |||||
| phutil_tag('br'), | |||||
| ), | |||||
| $description); | |||||
| switch ($step) { | $item = id(new PHUIObjectItemView()) | ||||
| case 0: | ->setHeader($name) | ||||
| default: | ->setImageIcon($icon) | ||||
| $content_types = $this->renderContentTypeControl( | ->addAttribute($description); | ||||
| $content_type_map, | |||||
| $e_type); | if ($enabled) { | ||||
| $item | |||||
| $form | ->setHref($type_uri) | ||||
| ->addHiddenInput('step', 1) | ->setClickable(true); | ||||
| ->appendChild($content_types); | } else { | ||||
| $item->setDisabled(true); | |||||
| } | |||||
| $cancel_text = null; | $menu->addItem($item); | ||||
| $cancel_uri = $this->getApplicationURI(); | } | ||||
| $title = pht('Create Herald Rule'); | |||||
| break; | $box = id(new PHUIObjectBoxView()) | ||||
| case 1: | ->setHeaderText($title) | ||||
| $rule_types = $this->renderRuleTypeControl( | ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) | ||||
| $rule_type_map, | ->setObjectList($menu); | ||||
| $e_rule); | |||||
| $box->newTailButton() | |||||
| $form | ->setText(pht('Back to Content Types')) | ||||
| ->addHiddenInput('content_type', $content_type) | ->setIcon('fa-chevron-left') | ||||
| ->addHiddenInput('step', 2) | ->setHref($base_uri); | ||||
| ->appendChild($rule_types); | |||||
| return id(new PHUILauncherView()) | |||||
| ->appendChild($box); | |||||
| } | |||||
| private function newTargetForm( | |||||
| HeraldAdapter $adapter, | |||||
| $rule_type, | |||||
| $object_name, | |||||
| $errors, | |||||
| $e_object, | |||||
| $title) { | |||||
| $viewer = $this->getViewer(); | |||||
| $content_type = $adapter->getAdapterContentType(); | |||||
| $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); | |||||
| $params = array( | $params = array( | ||||
| 'content_type' => $content_type, | 'adapter' => $content_type, | ||||
| 'step' => '0', | 'type' => $rule_type, | ||||
| ); | ); | ||||
| $cancel_text = pht('Back'); | $form = id(new AphrontFormView()) | ||||
| $cancel_uri = new PhutilURI('new/', $params); | ->setViewer($viewer) | ||||
| $cancel_uri = $this->getApplicationURI($cancel_uri); | |||||
| $title = pht('Create Herald Rule: %s', | |||||
| idx($content_type_map, $content_type)); | |||||
| break; | |||||
| case 2: | |||||
| $adapter = HeraldAdapter::getAdapterForContentType($content_type); | |||||
| $form | |||||
| ->addHiddenInput('content_type', $content_type) | |||||
| ->addHiddenInput('rule_type', $rule_type) | |||||
| ->addHiddenInput('step', 3) | |||||
| ->appendChild( | ->appendChild( | ||||
| id(new AphrontFormStaticControl()) | id(new AphrontFormStaticControl()) | ||||
| ->setLabel(pht('Rule for')) | ->setLabel(pht('Rule for')) | ||||
| ->setValue( | ->setValue( | ||||
| phutil_tag( | phutil_tag( | ||||
| 'strong', | 'strong', | ||||
| array(), | array(), | ||||
| idx($content_type_map, $content_type)))) | $adapter->getAdapterContentName()))) | ||||
| ->appendChild( | ->appendChild( | ||||
| id(new AphrontFormStaticControl()) | id(new AphrontFormStaticControl()) | ||||
| ->setLabel(pht('Rule Type')) | ->setLabel(pht('Rule Type')) | ||||
| ->setValue( | ->setValue( | ||||
| phutil_tag( | phutil_tag( | ||||
| 'strong', | 'strong', | ||||
| array(), | array(), | ||||
| idx($rule_type_map, $rule_type)))) | idx($rule_type_map, $rule_type)))) | ||||
| ->appendRemarkupInstructions( | ->appendRemarkupInstructions( | ||||
| pht( | pht( | ||||
| 'Choose the object this rule will act on (for example, enter '. | 'Choose the object this rule will act on (for example, enter '. | ||||
| '`rX` to act on the `rX` repository, or `#project` to act on '. | '`rX` to act on the `rX` repository, or `#project` to act on '. | ||||
| 'a project).')) | 'a project).')) | ||||
| ->appendRemarkupInstructions( | ->appendRemarkupInstructions( | ||||
| $adapter->explainValidTriggerObjects()) | $adapter->explainValidTriggerObjects()) | ||||
| ->appendChild( | ->appendChild( | ||||
| id(new AphrontFormTextControl()) | id(new AphrontFormTextControl()) | ||||
| ->setName('objectName') | ->setName('objectName') | ||||
| ->setError($e_object) | ->setError($e_object) | ||||
| ->setValue($request->getStr('objectName')) | ->setValue($object_name) | ||||
| ->setLabel(pht('Object'))); | ->setLabel(pht('Object'))); | ||||
| $params = array( | foreach ($params as $key => $value) { | ||||
| 'content_type' => $content_type, | $form->addHiddenInput($key, $value); | ||||
| 'rule_type' => $rule_type, | |||||
| 'step' => 1, | |||||
| ); | |||||
| $cancel_text = pht('Back'); | |||||
| $cancel_uri = new PhutilURI('new/', $params); | |||||
| $cancel_uri = $this->getApplicationURI($cancel_uri); | |||||
| $title = pht('Create Herald Rule: %s', | |||||
| idx($content_type_map, $content_type)); | |||||
| break; | |||||
| } | } | ||||
| $form | $cancel_params = $params; | ||||
| ->appendChild( | unset($cancel_params['type']); | ||||
| $cancel_uri = $this->getApplicationURI('new/'); | |||||
| $cancel_uri = new PhutilURI($cancel_uri, $params); | |||||
| $form->appendChild( | |||||
| id(new AphrontFormSubmitControl()) | id(new AphrontFormSubmitControl()) | ||||
| ->setValue(pht('Continue')) | ->setValue(pht('Continue')) | ||||
| ->addCancelButton($cancel_uri, $cancel_text)); | ->addCancelButton($cancel_uri, pht('Back'))); | ||||
| $form_box = id(new PHUIObjectBoxView()) | $form_box = id(new PHUIObjectBoxView()) | ||||
| ->setHeaderText($title) | ->setHeaderText($title) | ||||
| ->setFormErrors($errors) | ->setFormErrors($errors) | ||||
| ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) | ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) | ||||
| ->setForm($form); | ->setForm($form); | ||||
| $crumbs = $this | return $form_box; | ||||
| ->buildApplicationCrumbs() | |||||
| ->addTextCrumb(pht('Create Rule')) | |||||
| ->setBorder(true); | |||||
| $view = id(new PHUITwoColumnView()) | |||||
| ->setFooter($form_box); | |||||
| return $this->newPage() | |||||
| ->setTitle($title) | |||||
| ->setCrumbs($crumbs) | |||||
| ->appendChild( | |||||
| array( | |||||
| $view, | |||||
| )); | |||||
| } | |||||
| private function renderContentTypeControl(array $content_type_map, $e_type) { | |||||
| $request = $this->getRequest(); | |||||
| $radio = id(new AphrontFormRadioButtonControl()) | |||||
| ->setLabel(pht('New Rule for')) | |||||
| ->setName('content_type') | |||||
| ->setValue($request->getStr('content_type')) | |||||
| ->setError($e_type); | |||||
| foreach ($content_type_map as $value => $name) { | |||||
| $adapter = HeraldAdapter::getAdapterForContentType($value); | |||||
| $radio->addButton( | |||||
| $value, | |||||
| $name, | |||||
| phutil_escape_html_newlines($adapter->getAdapterContentDescription())); | |||||
| } | |||||
| return $radio; | |||||
| } | |||||
| private function renderRuleTypeControl(array $rule_type_map, $e_rule) { | |||||
| $request = $this->getRequest(); | |||||
| // Reorder array to put less powerful rules first. | |||||
| $rule_type_map = array_select_keys( | |||||
| $rule_type_map, | |||||
| array( | |||||
| HeraldRuleTypeConfig::RULE_TYPE_PERSONAL, | |||||
| HeraldRuleTypeConfig::RULE_TYPE_OBJECT, | |||||
| HeraldRuleTypeConfig::RULE_TYPE_GLOBAL, | |||||
| )) + $rule_type_map; | |||||
| list($can_global, $global_link) = $this->explainApplicationCapability( | |||||
| HeraldManageGlobalRulesCapability::CAPABILITY, | |||||
| pht('You have permission to create and manage global rules.'), | |||||
| pht('You do not have permission to create or manage global rules.')); | |||||
| $captions = array( | |||||
| HeraldRuleTypeConfig::RULE_TYPE_PERSONAL => | |||||
| pht( | |||||
| 'Personal rules notify you about events. You own them, but they can '. | |||||
| 'only affect you. Personal rules only trigger for objects you have '. | |||||
| 'permission to see.'), | |||||
| HeraldRuleTypeConfig::RULE_TYPE_OBJECT => | |||||
| pht( | |||||
| 'Object rules notify anyone about events. They are bound to an '. | |||||
| 'object (like a repository) and can only act on that object. You '. | |||||
| 'must be able to edit an object to create object rules for it. '. | |||||
| 'Other users who can edit the object can edit its rules.'), | |||||
| HeraldRuleTypeConfig::RULE_TYPE_GLOBAL => | |||||
| array( | |||||
| pht( | |||||
| 'Global rules notify anyone about events. Global rules can '. | |||||
| 'bypass access control policies and act on any object.'), | |||||
| $global_link, | |||||
| ), | |||||
| ); | |||||
| $radio = id(new AphrontFormRadioButtonControl()) | |||||
| ->setLabel(pht('Rule Type')) | |||||
| ->setName('rule_type') | |||||
| ->setValue($request->getStr('rule_type')) | |||||
| ->setError($e_rule); | |||||
| $adapter = HeraldAdapter::getAdapterForContentType( | |||||
| $request->getStr('content_type')); | |||||
| foreach ($rule_type_map as $value => $name) { | |||||
| $caption = idx($captions, $value); | |||||
| $disabled = ($value == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) && | |||||
| (!$can_global); | |||||
| if (!$adapter->supportsRuleType($value)) { | |||||
| $disabled = true; | |||||
| $caption = array( | |||||
| $caption, | |||||
| "\n\n", | |||||
| phutil_tag( | |||||
| 'em', | |||||
| array(), | |||||
| pht( | |||||
| 'This rule type is not supported by the selected content type.')), | |||||
| ); | |||||
| } | |||||
| $radio->addButton( | |||||
| $value, | |||||
| $name, | |||||
| phutil_escape_html_newlines($caption), | |||||
| $disabled ? 'disabled' : null, | |||||
| $disabled); | |||||
| } | |||||
| return $radio; | |||||
| } | } | ||||
| } | } | ||||