protected function getOptions()
 {
     $capability = $this->capability;
     $options = array();
     foreach ($this->policies as $policy) {
         if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
             // Never expose "Public" for capabilities which don't support it.
             $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
             if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
                 continue;
             }
         }
         $policy_short_name = id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(28)->truncateString($policy->getName());
         $options[$policy->getType()][$policy->getPHID()] = array('name' => $policy_short_name, 'full' => $policy->getName(), 'icon' => $policy->getIcon());
     }
     // If we were passed several custom policy options, throw away the ones
     // which aren't the value for this capability. For example, an object might
     // have a custom view pollicy and a custom edit policy. When we render
     // the selector for "Can View", we don't want to show the "Can Edit"
     // custom policy -- if we did, the menu would look like this:
     //
     //   Custom
     //     Custom Policy
     //     Custom Policy
     //
     // ...where one is the "view" custom policy, and one is the "edit" custom
     // policy.
     $type_custom = PhabricatorPolicyType::TYPE_CUSTOM;
     if (!empty($options[$type_custom])) {
         $options[$type_custom] = array_select_keys($options[$type_custom], array($this->getValue()));
     }
     // If there aren't any custom policies, add a placeholder policy so we
     // render a menu item. This allows the user to switch to a custom policy.
     if (empty($options[$type_custom])) {
         $placeholder = new PhabricatorPolicy();
         $placeholder->setName(pht('Custom Policy...'));
         $options[$type_custom][$this->getCustomPolicyPlaceholder()] = array('name' => $placeholder->getName(), 'full' => $placeholder->getName(), 'icon' => $placeholder->getIcon());
     }
     $options = array_select_keys($options, array(PhabricatorPolicyType::TYPE_GLOBAL, PhabricatorPolicyType::TYPE_USER, PhabricatorPolicyType::TYPE_CUSTOM, PhabricatorPolicyType::TYPE_PROJECT));
     return $options;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $phid = $request->getURIData('phid');
     $capability = $request->getURIData('capability');
     $object = id(new PhabricatorObjectQuery())->setViewer($viewer)->withPHIDs(array($phid))->executeOne();
     if (!$object) {
         return new Aphront404Response();
     }
     $policies = PhabricatorPolicyQuery::loadPolicies($viewer, $object);
     $policy = idx($policies, $capability);
     if (!$policy) {
         return new Aphront404Response();
     }
     $handle = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs(array($phid))->executeOne();
     $object_uri = nonempty($handle->getURI(), '/');
     $explanation = PhabricatorPolicy::getPolicyExplanation($viewer, $policy->getPHID());
     $auto_info = (array) $object->describeAutomaticCapability($capability);
     $auto_info = array_merge(array($explanation), $auto_info);
     $auto_info = array_filter($auto_info);
     foreach ($auto_info as $key => $info) {
         $auto_info[$key] = phutil_tag('li', array(), $info);
     }
     if ($auto_info) {
         $auto_info = phutil_tag('ul', array(), $auto_info);
     }
     $capability_name = $capability;
     $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
     if ($capobj) {
         $capability_name = $capobj->getCapabilityName();
     }
     $dialog = id(new AphrontDialogView())->setUser($viewer)->setClass('aphront-access-dialog');
     $this->appendSpaceInformation($dialog, $object, $policy, $capability);
     $intro = pht('Users with the "%s" capability for this object:', $capability_name);
     $object_name = pht('%s %s', $handle->getTypeName(), $handle->getObjectName());
     $dialog->setTitle(pht('Policy Details: %s', $object_name))->appendParagraph($intro)->appendChild($auto_info)->addCancelButton($object_uri, pht('Done'));
     $this->appendStrengthInformation($dialog, $object, $policy, $capability);
     return $dialog;
 }
 public function rejectObject(PhabricatorPolicyInterface $object, $policy, $capability)
 {
     if (!$this->raisePolicyExceptions) {
         return;
     }
     if ($this->viewer->isOmnipotent()) {
         // Never raise policy exceptions for the omnipotent viewer. Although we
         // will never normally issue a policy rejection for the omnipotent
         // viewer, we can end up here when queries blanket reject objects that
         // have failed to load, without distinguishing between nonexistent and
         // nonvisible objects.
         return;
     }
     $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
     $rejection = null;
     if ($capobj) {
         $rejection = $capobj->describeCapabilityRejection();
         $capability_name = $capobj->getCapabilityName();
     } else {
         $capability_name = $capability;
     }
     if (!$rejection) {
         // We couldn't find the capability object, or it doesn't provide a
         // tailored rejection string.
         $rejection = pht('You do not have the required capability ("%s") to do whatever you ' . 'are trying to do.', $capability);
     }
     $more = PhabricatorPolicy::getPolicyExplanation($this->viewer, $policy);
     $exceptions = $object->describeAutomaticCapability($capability);
     $details = array_filter(array_merge(array($more), (array) $exceptions));
     $access_denied = $this->renderAccessDenied($object);
     $full_message = pht('[%s] (%s) %s // %s', $access_denied, $capability_name, $rejection, implode(' ', $details));
     $exception = id(new PhabricatorPolicyException($full_message))->setTitle($access_denied)->setObjectPHID($object->getPHID())->setRejection($rejection)->setCapability($capability)->setCapabilityName($capability_name)->setMoreInfo($details);
     throw $exception;
 }
 public final function getCapabilityLabel($capability)
 {
     switch ($capability) {
         case PhabricatorPolicyCapability::CAN_VIEW:
             return pht('Can Use Application');
         case PhabricatorPolicyCapability::CAN_EDIT:
             return pht('Can Configure Application');
     }
     $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
     if ($capobj) {
         return $capobj->getCapabilityName();
     }
     return null;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $user = $request->getUser();
     $application = $request->getURIData('application');
     $application = id(new PhabricatorApplicationQuery())->setViewer($user)->withClasses(array($application))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
     if (!$application) {
         return new Aphront404Response();
     }
     $title = $application->getName();
     $view_uri = $this->getApplicationURI('view/' . get_class($application) . '/');
     $policies = id(new PhabricatorPolicyQuery())->setViewer($user)->setObject($application)->execute();
     if ($request->isFormPost()) {
         $result = array();
         foreach ($application->getCapabilities() as $capability) {
             $old = $application->getPolicy($capability);
             $new = $request->getStr('policy:' . $capability);
             if ($old == $new) {
                 // No change to the setting.
                 continue;
             }
             if (empty($policies[$new])) {
                 // Not a standard policy, check for a custom policy.
                 $policy = id(new PhabricatorPolicyQuery())->setViewer($user)->withPHIDs(array($new))->executeOne();
                 if (!$policy) {
                     // Not a custom policy either. Can't set the policy to something
                     // invalid, so skip this.
                     continue;
                 }
             }
             if ($new == PhabricatorPolicies::POLICY_PUBLIC) {
                 $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
                 if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
                     // Can't set non-public policies to public.
                     continue;
                 }
             }
             $result[$capability] = $new;
         }
         if ($result) {
             $key = 'phabricator.application-settings';
             $config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
             $value = $config_entry->getValue();
             $phid = $application->getPHID();
             if (empty($value[$phid])) {
                 $value[$application->getPHID()] = array();
             }
             if (empty($value[$phid]['policy'])) {
                 $value[$phid]['policy'] = array();
             }
             $value[$phid]['policy'] = $result + $value[$phid]['policy'];
             // Don't allow users to make policy edits which would lock them out of
             // applications, since they would be unable to undo those actions.
             PhabricatorEnv::overrideConfig($key, $value);
             PhabricatorPolicyFilter::mustRetainCapability($user, $application, PhabricatorPolicyCapability::CAN_VIEW);
             PhabricatorPolicyFilter::mustRetainCapability($user, $application, PhabricatorPolicyCapability::CAN_EDIT);
             PhabricatorConfigEditor::storeNewValue($user, $config_entry, $value, PhabricatorContentSource::newFromRequest($request));
         }
         return id(new AphrontRedirectResponse())->setURI($view_uri);
     }
     $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions($user, $application);
     $form = id(new AphrontFormView())->setUser($user);
     $locked_policies = PhabricatorEnv::getEnvConfig('policy.locked');
     foreach ($application->getCapabilities() as $capability) {
         $label = $application->getCapabilityLabel($capability);
         $can_edit = $application->isCapabilityEditable($capability);
         $locked = idx($locked_policies, $capability);
         $caption = $application->getCapabilityCaption($capability);
         if (!$can_edit || $locked) {
             $form->appendChild(id(new AphrontFormStaticControl())->setLabel($label)->setValue(idx($descriptions, $capability))->setCaption($caption));
         } else {
             $control = id(new AphrontFormPolicyControl())->setUser($user)->setDisabled($locked)->setCapability($capability)->setPolicyObject($application)->setPolicies($policies)->setLabel($label)->setName('policy:' . $capability)->setCaption($caption);
             $template = $application->getCapabilityTemplatePHIDType($capability);
             if ($template) {
                 $phid_types = PhabricatorPHIDType::getAllTypes();
                 $phid_type = idx($phid_types, $template);
                 if ($phid_type) {
                     $template_object = $phid_type->newObject();
                     if ($template_object) {
                         $template_policies = id(new PhabricatorPolicyQuery())->setViewer($user)->setObject($template_object)->execute();
                         // NOTE: We want to expose both any object template policies
                         // (like "Subscribers") and any custom policy.
                         $all_policies = $template_policies + $policies;
                         $control->setPolicies($all_policies);
                         $control->setTemplateObject($template_object);
                     }
                 }
                 $control->setTemplatePHIDType($template);
             }
             $form->appendControl($control);
         }
     }
     $form->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Save Policies'))->addCancelButton($view_uri));
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($application->getName(), $view_uri);
     $crumbs->addTextCrumb(pht('Edit Policies'));
     $header = id(new PHUIHeaderView())->setHeader(pht('Edit Policies: %s', $application->getName()));
     $object_box = id(new PHUIObjectBoxView())->setHeader($header)->setForm($form);
     return $this->buildApplicationPage(array($crumbs, $object_box), array('title' => $title));
 }
 protected function getOptions()
 {
     $capability = $this->capability;
     $policies = $this->policies;
     // Exclude object policies which don't make sense here. This primarily
     // filters object policies associated from template capabilities (like
     // "Default Task View Policy" being set to "Task Author") so they aren't
     // made available on non-template capabilities (like "Can Bulk Edit").
     foreach ($policies as $key => $policy) {
         if ($policy->getType() != PhabricatorPolicyType::TYPE_OBJECT) {
             continue;
         }
         $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy->getPHID());
         if (!$rule) {
             continue;
         }
         $target = nonempty($this->templateObject, $this->object);
         if (!$rule->canApplyToObject($target)) {
             unset($policies[$key]);
             continue;
         }
     }
     $options = array();
     foreach ($policies as $policy) {
         if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
             // Never expose "Public" for capabilities which don't support it.
             $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
             if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
                 continue;
             }
         }
         $policy_short_name = id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(28)->truncateString($policy->getName());
         $options[$policy->getType()][$policy->getPHID()] = array('name' => $policy_short_name, 'full' => $policy->getName(), 'icon' => $policy->getIcon());
     }
     // If we were passed several custom policy options, throw away the ones
     // which aren't the value for this capability. For example, an object might
     // have a custom view pollicy and a custom edit policy. When we render
     // the selector for "Can View", we don't want to show the "Can Edit"
     // custom policy -- if we did, the menu would look like this:
     //
     //   Custom
     //     Custom Policy
     //     Custom Policy
     //
     // ...where one is the "view" custom policy, and one is the "edit" custom
     // policy.
     $type_custom = PhabricatorPolicyType::TYPE_CUSTOM;
     if (!empty($options[$type_custom])) {
         $options[$type_custom] = array_select_keys($options[$type_custom], array($this->getValue()));
     }
     // If there aren't any custom policies, add a placeholder policy so we
     // render a menu item. This allows the user to switch to a custom policy.
     if (empty($options[$type_custom])) {
         $placeholder = new PhabricatorPolicy();
         $placeholder->setName(pht('Custom Policy...'));
         $options[$type_custom][$this->getCustomPolicyPlaceholder()] = array('name' => $placeholder->getName(), 'full' => $placeholder->getName(), 'icon' => $placeholder->getIcon());
     }
     $options = array_select_keys($options, array(PhabricatorPolicyType::TYPE_GLOBAL, PhabricatorPolicyType::TYPE_OBJECT, PhabricatorPolicyType::TYPE_USER, PhabricatorPolicyType::TYPE_CUSTOM, PhabricatorPolicyType::TYPE_PROJECT));
     return $options;
 }
 public function rejectObject(PhabricatorPolicyInterface $object, $policy, $capability)
 {
     if (!$this->raisePolicyExceptions) {
         return;
     }
     if ($this->viewer->isOmnipotent()) {
         // Never raise policy exceptions for the omnipotent viewer. Although we
         // will never normally issue a policy rejection for the omnipotent
         // viewer, we can end up here when queries blanket reject objects that
         // have failed to load, without distinguishing between nonexistent and
         // nonvisible objects.
         return;
     }
     $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
     $rejection = null;
     if ($capobj) {
         $rejection = $capobj->describeCapabilityRejection();
         $capability_name = $capobj->getCapabilityName();
     } else {
         $capability_name = $capability;
     }
     if (!$rejection) {
         // We couldn't find the capability object, or it doesn't provide a
         // tailored rejection string.
         $rejection = pht('You do not have the required capability ("%s") to do whatever you ' . 'are trying to do.', $capability);
     }
     $more = PhabricatorPolicy::getPolicyExplanation($this->viewer, $policy);
     $exceptions = $object->describeAutomaticCapability($capability);
     $details = array_filter(array_merge(array($more), (array) $exceptions));
     // NOTE: Not every type of policy object has a real PHID; just load an
     // empty handle if a real PHID isn't available.
     $phid = nonempty($object->getPHID(), PhabricatorPHIDConstants::PHID_VOID);
     $handle = id(new PhabricatorHandleQuery())->setViewer($this->viewer)->withPHIDs(array($phid))->executeOne();
     $object_name = pht('%s %s', $handle->getTypeName(), $handle->getObjectName());
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
     if ($is_serious) {
         $title = pht('Access Denied: %s', $object_name);
     } else {
         $title = pht('You Shall Not Pass: %s', $object_name);
     }
     $full_message = pht('[%s] (%s) %s // %s', $title, $capability_name, $rejection, implode(' ', $details));
     $exception = id(new PhabricatorPolicyException($full_message))->setTitle($title)->setRejection($rejection)->setCapabilityName($capability_name)->setMoreInfo($details);
     throw $exception;
 }
 private function getCapabilityName($capability)
 {
     $capability_name = $capability;
     $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
     if ($capobj) {
         $capability_name = $capobj->getCapabilityName();
     }
     return $capability_name;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $object_phid = $request->getURIData('objectPHID');
     if ($object_phid) {
         $object = id(new PhabricatorObjectQuery())->setViewer($viewer)->withPHIDs(array($object_phid))->executeOne();
         if (!$object) {
             return new Aphront404Response();
         }
     } else {
         $object_type = $request->getURIData('objectType');
         if (!$object_type) {
             $object_type = $request->getURIData('templateType');
         }
         $phid_types = PhabricatorPHIDType::getAllInstalledTypes($viewer);
         if (empty($phid_types[$object_type])) {
             return new Aphront404Response();
         }
         $object = $phid_types[$object_type]->newObject();
         if (!$object) {
             return new Aphront404Response();
         }
     }
     $action_options = array(PhabricatorPolicy::ACTION_ALLOW => pht('Allow'), PhabricatorPolicy::ACTION_DENY => pht('Deny'));
     $rules = id(new PhutilClassMapQuery())->setAncestorClass('PhabricatorPolicyRule')->execute();
     foreach ($rules as $key => $rule) {
         if (!$rule->canApplyToObject($object)) {
             unset($rules[$key]);
         }
     }
     $rules = msort($rules, 'getRuleOrder');
     $default_rule = array('action' => head_key($action_options), 'rule' => head_key($rules), 'value' => null);
     $phid = $request->getURIData('phid');
     if ($phid) {
         $policies = id(new PhabricatorPolicyQuery())->setViewer($viewer)->withPHIDs(array($phid))->execute();
         if (!$policies) {
             return new Aphront404Response();
         }
         $policy = head($policies);
     } else {
         $policy = id(new PhabricatorPolicy())->setRules(array($default_rule))->setDefaultAction(PhabricatorPolicy::ACTION_DENY);
     }
     $root_id = celerity_generate_unique_node_id();
     $default_action = $policy->getDefaultAction();
     $rule_data = $policy->getRules();
     $errors = array();
     if ($request->isFormPost()) {
         $data = $request->getStr('rules');
         try {
             $data = phutil_json_decode($data);
         } catch (PhutilJSONParserException $ex) {
             throw new PhutilProxyException(pht('Failed to JSON decode rule data!'), $ex);
         }
         $rule_data = array();
         foreach ($data as $rule) {
             $action = idx($rule, 'action');
             switch ($action) {
                 case 'allow':
                 case 'deny':
                     break;
                 default:
                     throw new Exception(pht("Invalid action '%s'!", $action));
             }
             $rule_class = idx($rule, 'rule');
             if (empty($rules[$rule_class])) {
                 throw new Exception(pht("Invalid rule class '%s'!", $rule_class));
             }
             $rule_obj = $rules[$rule_class];
             $value = $rule_obj->getValueForStorage(idx($rule, 'value'));
             $rule_data[] = array('action' => $action, 'rule' => $rule_class, 'value' => $value);
         }
         // Filter out nonsense rules, like a "users" rule without any users
         // actually specified.
         $valid_rules = array();
         foreach ($rule_data as $rule) {
             $rule_class = $rule['rule'];
             if ($rules[$rule_class]->ruleHasEffect($rule['value'])) {
                 $valid_rules[] = $rule;
             }
         }
         if (!$valid_rules) {
             $errors[] = pht('None of these policy rules have any effect.');
         }
         // NOTE: Policies are immutable once created, and we always create a new
         // policy here. If we didn't, we would need to lock this endpoint down,
         // as users could otherwise just go edit the policies of objects with
         // custom policies.
         if (!$errors) {
             $new_policy = new PhabricatorPolicy();
             $new_policy->setRules($valid_rules);
             $new_policy->setDefaultAction($request->getStr('default'));
             $new_policy->save();
             $data = array('phid' => $new_policy->getPHID(), 'info' => array('name' => $new_policy->getName(), 'full' => $new_policy->getName(), 'icon' => $new_policy->getIcon()));
             return id(new AphrontAjaxResponse())->setContent($data);
         }
     }
     // Convert rule values to display format (for example, expanding PHIDs
     // into tokens).
     foreach ($rule_data as $key => $rule) {
         $rule_data[$key]['value'] = $rules[$rule['rule']]->getValueForDisplay($viewer, $rule['value']);
     }
     $default_select = AphrontFormSelectControl::renderSelectTag($default_action, $action_options, array('name' => 'default'));
     if ($errors) {
         $errors = id(new PHUIInfoView())->setErrors($errors);
     }
     $form = id(new PHUIFormLayoutView())->appendChild($errors)->appendChild(javelin_tag('input', array('type' => 'hidden', 'name' => 'rules', 'sigil' => 'rules')))->appendChild(id(new PHUIFormInsetView())->setTitle(pht('Rules'))->setRightButton(javelin_tag('a', array('href' => '#', 'class' => 'button green', 'sigil' => 'create-rule', 'mustcapture' => true), pht('New Rule')))->setDescription(pht('These rules are processed in order.'))->setContent(javelin_tag('table', array('sigil' => 'rules', 'class' => 'policy-rules-table'), '')))->appendChild(id(new AphrontFormMarkupControl())->setLabel(pht('If No Rules Match'))->setValue(pht('%s all other users.', $default_select)));
     $form = phutil_tag('div', array('id' => $root_id), $form);
     $rule_options = mpull($rules, 'getRuleDescription');
     $type_map = mpull($rules, 'getValueControlType');
     $templates = mpull($rules, 'getValueControlTemplate');
     require_celerity_resource('policy-edit-css');
     Javelin::initBehavior('policy-rule-editor', array('rootID' => $root_id, 'actions' => $action_options, 'rules' => $rule_options, 'types' => $type_map, 'templates' => $templates, 'data' => $rule_data, 'defaultRule' => $default_rule));
     $title = pht('Custom Policy');
     $key = $request->getStr('capability');
     if ($key) {
         $capability = PhabricatorPolicyCapability::getCapabilityByKey($key);
         $title = pht('Custom "%s" Policy', $capability->getCapabilityName());
     }
     $dialog = id(new AphrontDialogView())->setWidth(AphrontDialogView::WIDTH_FULL)->setUser($viewer)->setTitle($title)->appendChild($form)->addSubmitButton(pht('Save Policy'))->addCancelButton('#');
     return id(new AphrontDialogResponse())->setDialog($dialog);
 }
 public function testGetCapabilityMap()
 {
     PhabricatorPolicyCapability::getCapabilityMap();
     $this->assertTrue(true);
 }
 protected function getOptions()
 {
     $capability = $this->capability;
     $policies = $this->policies;
     $viewer = $this->getUser();
     // Check if we're missing the policy for the current control value. This
     // is unusual, but can occur if the user is submitting a form and selected
     // an unusual project as a policy but the change has not been saved yet.
     $policy_map = mpull($policies, null, 'getPHID');
     $value = $this->getValue();
     if ($value && empty($policy_map[$value])) {
         $handle = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs(array($value))->executeOne();
         if ($handle->isComplete()) {
             $policies[] = PhabricatorPolicy::newFromPolicyAndHandle($value, $handle);
         }
     }
     // Exclude object policies which don't make sense here. This primarily
     // filters object policies associated from template capabilities (like
     // "Default Task View Policy" being set to "Task Author") so they aren't
     // made available on non-template capabilities (like "Can Bulk Edit").
     foreach ($policies as $key => $policy) {
         if ($policy->getType() != PhabricatorPolicyType::TYPE_OBJECT) {
             continue;
         }
         $rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy->getPHID());
         if (!$rule) {
             continue;
         }
         $target = nonempty($this->templateObject, $this->object);
         if (!$rule->canApplyToObject($target)) {
             unset($policies[$key]);
             continue;
         }
     }
     $options = array();
     foreach ($policies as $policy) {
         if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
             // Never expose "Public" for capabilities which don't support it.
             $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
             if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
                 continue;
             }
         }
         $policy_short_name = id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(28)->truncateString($policy->getName());
         $options[$policy->getType()][$policy->getPHID()] = array('name' => $policy_short_name, 'full' => $policy->getName(), 'icon' => $policy->getIcon(), 'sort' => phutil_utf8_strtolower($policy->getName()));
     }
     $type_project = PhabricatorPolicyType::TYPE_PROJECT;
     // Make sure we have a "Projects" group before we adjust it.
     if (empty($options[$type_project])) {
         $options[$type_project] = array();
     }
     $options[$type_project] = isort($options[$type_project], 'sort');
     $placeholder = id(new PhabricatorPolicy())->setName(pht('Other Project...'))->setIcon('fa-search');
     $options[$type_project][$this->getSelectProjectKey()] = array('name' => $placeholder->getName(), 'full' => $placeholder->getName(), 'icon' => $placeholder->getIcon());
     // If we were passed several custom policy options, throw away the ones
     // which aren't the value for this capability. For example, an object might
     // have a custom view policy and a custom edit policy. When we render
     // the selector for "Can View", we don't want to show the "Can Edit"
     // custom policy -- if we did, the menu would look like this:
     //
     //   Custom
     //     Custom Policy
     //     Custom Policy
     //
     // ...where one is the "view" custom policy, and one is the "edit" custom
     // policy.
     $type_custom = PhabricatorPolicyType::TYPE_CUSTOM;
     if (!empty($options[$type_custom])) {
         $options[$type_custom] = array_select_keys($options[$type_custom], array($this->getValue()));
     }
     // If there aren't any custom policies, add a placeholder policy so we
     // render a menu item. This allows the user to switch to a custom policy.
     if (empty($options[$type_custom])) {
         $placeholder = new PhabricatorPolicy();
         $placeholder->setName(pht('Custom Policy...'));
         $options[$type_custom][$this->getSelectCustomKey()] = array('name' => $placeholder->getName(), 'full' => $placeholder->getName(), 'icon' => $placeholder->getIcon());
     }
     $options = array_select_keys($options, array(PhabricatorPolicyType::TYPE_GLOBAL, PhabricatorPolicyType::TYPE_OBJECT, PhabricatorPolicyType::TYPE_USER, PhabricatorPolicyType::TYPE_CUSTOM, PhabricatorPolicyType::TYPE_PROJECT));
     return $options;
 }