Path: blob/master/src/view/form/control/AphrontFormPolicyControl.php
12256 views
<?php12final class AphrontFormPolicyControl extends AphrontFormControl {34private $object;5private $capability;6private $policies;7private $spacePHID;8private $templatePHIDType;9private $templateObject;1011public function setPolicyObject(PhabricatorPolicyInterface $object) {12$this->object = $object;13return $this;14}1516public function setPolicies(array $policies) {17assert_instances_of($policies, 'PhabricatorPolicy');18$this->policies = $policies;19return $this;20}2122public function setSpacePHID($space_phid) {23$this->spacePHID = $space_phid;24return $this;25}2627public function getSpacePHID() {28return $this->spacePHID;29}3031public function setTemplatePHIDType($type) {32$this->templatePHIDType = $type;33return $this;34}3536public function setTemplateObject($object) {37$this->templateObject = $object;38return $this;39}4041public function getSerializedValue() {42return json_encode(array(43$this->getValue(),44$this->getSpacePHID(),45));46}4748public function readSerializedValue($value) {49$decoded = phutil_json_decode($value);50$policy_value = $decoded[0];51$space_phid = $decoded[1];52$this->setValue($policy_value);53$this->setSpacePHID($space_phid);54return $this;55}5657public function readValueFromDictionary(array $dictionary) {58// TODO: This is a little hacky but will only get us into trouble if we59// have multiple view policy controls in multiple paged form views on the60// same page, which seems unlikely.61$this->setSpacePHID(idx($dictionary, 'spacePHID'));6263return parent::readValueFromDictionary($dictionary);64}6566public function readValueFromRequest(AphrontRequest $request) {67// See note in readValueFromDictionary().68$this->setSpacePHID($request->getStr('spacePHID'));6970return parent::readValueFromRequest($request);71}7273public function setCapability($capability) {74$this->capability = $capability;7576$labels = array(77PhabricatorPolicyCapability::CAN_VIEW => pht('Visible To'),78PhabricatorPolicyCapability::CAN_EDIT => pht('Editable By'),79PhabricatorPolicyCapability::CAN_JOIN => pht('Joinable By'),80);8182if (isset($labels[$capability])) {83$label = $labels[$capability];84} else {85$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);86if ($capobj) {87$label = $capobj->getCapabilityName();88} else {89$label = pht('Capability "%s"', $capability);90}91}9293$this->setLabel($label);9495return $this;96}9798protected function getCustomControlClass() {99return 'aphront-form-control-policy';100}101102protected function getOptions() {103$capability = $this->capability;104$policies = $this->policies;105$viewer = $this->getUser();106107// Check if we're missing the policy for the current control value. This108// is unusual, but can occur if the user is submitting a form and selected109// an unusual project as a policy but the change has not been saved yet.110$policy_map = mpull($policies, null, 'getPHID');111$value = $this->getValue();112if ($value && empty($policy_map[$value])) {113$handle = id(new PhabricatorHandleQuery())114->setViewer($viewer)115->withPHIDs(array($value))116->executeOne();117if ($handle->isComplete()) {118$policies[] = PhabricatorPolicy::newFromPolicyAndHandle(119$value,120$handle);121}122}123124// Exclude object policies which don't make sense here. This primarily125// filters object policies associated from template capabilities (like126// "Default Task View Policy" being set to "Task Author") so they aren't127// made available on non-template capabilities (like "Can Bulk Edit").128foreach ($policies as $key => $policy) {129if ($policy->getType() != PhabricatorPolicyType::TYPE_OBJECT) {130continue;131}132133$rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy->getPHID());134if (!$rule) {135continue;136}137138$target = nonempty($this->templateObject, $this->object);139if (!$rule->canApplyToObject($target)) {140unset($policies[$key]);141continue;142}143}144145$options = array();146foreach ($policies as $policy) {147if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {148// Never expose "Public" for capabilities which don't support it.149$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);150if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {151continue;152}153}154155$options[$policy->getType()][$policy->getPHID()] = array(156'name' => $policy->getName(),157'full' => $policy->getName(),158'icon' => $policy->getIcon(),159'sort' => phutil_utf8_strtolower($policy->getName()),160);161}162163$type_project = PhabricatorPolicyType::TYPE_PROJECT;164165// Make sure we have a "Projects" group before we adjust it.166if (empty($options[$type_project])) {167$options[$type_project] = array();168}169170$options[$type_project] = isort($options[$type_project], 'sort');171172$placeholder = id(new PhabricatorPolicy())173->setName(pht('Other Project...'))174->setIcon('fa-search');175176$options[$type_project][$this->getSelectProjectKey()] = array(177'name' => $placeholder->getName(),178'full' => $placeholder->getName(),179'icon' => $placeholder->getIcon(),180);181182// If we were passed several custom policy options, throw away the ones183// which aren't the value for this capability. For example, an object might184// have a custom view policy and a custom edit policy. When we render185// the selector for "Can View", we don't want to show the "Can Edit"186// custom policy -- if we did, the menu would look like this:187//188// Custom189// Custom Policy190// Custom Policy191//192// ...where one is the "view" custom policy, and one is the "edit" custom193// policy.194195$type_custom = PhabricatorPolicyType::TYPE_CUSTOM;196if (!empty($options[$type_custom])) {197$options[$type_custom] = array_select_keys(198$options[$type_custom],199array($this->getValue()));200}201202// If there aren't any custom policies, add a placeholder policy so we203// render a menu item. This allows the user to switch to a custom policy.204205if (empty($options[$type_custom])) {206$placeholder = new PhabricatorPolicy();207$placeholder->setName(pht('Custom Policy...'));208$options[$type_custom][$this->getSelectCustomKey()] = array(209'name' => $placeholder->getName(),210'full' => $placeholder->getName(),211'icon' => $placeholder->getIcon(),212);213}214215$options = array_select_keys(216$options,217array(218PhabricatorPolicyType::TYPE_GLOBAL,219PhabricatorPolicyType::TYPE_OBJECT,220PhabricatorPolicyType::TYPE_USER,221PhabricatorPolicyType::TYPE_CUSTOM,222PhabricatorPolicyType::TYPE_PROJECT,223));224225return $options;226}227228protected function renderInput() {229if (!$this->object) {230throw new PhutilInvalidStateException('setPolicyObject');231}232if (!$this->capability) {233throw new PhutilInvalidStateException('setCapability');234}235236$policy = $this->object->getPolicy($this->capability);237if (!$policy) {238// TODO: Make this configurable.239$policy = PhabricatorPolicies::POLICY_USER;240}241242if (!$this->getValue()) {243$this->setValue($policy);244}245246$control_id = celerity_generate_unique_node_id();247$input_id = celerity_generate_unique_node_id();248249$caret = phutil_tag(250'span',251array(252'class' => 'caret',253));254255$input = phutil_tag(256'input',257array(258'type' => 'hidden',259'id' => $input_id,260'name' => $this->getName(),261'value' => $this->getValue(),262));263264$options = $this->getOptions();265266$order = array();267$labels = array();268foreach ($options as $key => $values) {269$order[$key] = array_keys($values);270$labels[$key] = PhabricatorPolicyType::getPolicyTypeName($key);271}272273$flat_options = array_mergev($options);274275$icons = array();276foreach (igroup($flat_options, 'icon') as $icon => $ignored) {277$icons[$icon] = id(new PHUIIconView())278->setIcon($icon);279}280281if ($this->templatePHIDType) {282$context_path = 'template/'.$this->templatePHIDType.'/';283} else {284$object_phid = $this->object->getPHID();285if ($object_phid) {286$context_path = 'object/'.$object_phid.'/';287} else {288$object_type = phid_get_type($this->object->generatePHID());289$context_path = 'type/'.$object_type.'/';290}291}292293Javelin::initBehavior(294'policy-control',295array(296'controlID' => $control_id,297'inputID' => $input_id,298'options' => $flat_options,299'groups' => array_keys($options),300'order' => $order,301'labels' => $labels,302'value' => $this->getValue(),303'capability' => $this->capability,304'editURI' => '/policy/edit/'.$context_path,305'customKey' => $this->getSelectCustomKey(),306'projectKey' => $this->getSelectProjectKey(),307'disabled' => $this->getDisabled(),308));309310$selected = idx($flat_options, $this->getValue(), array());311$selected_icon = idx($selected, 'icon');312$selected_name = idx($selected, 'name');313314$spaces_control = $this->buildSpacesControl();315316return phutil_tag(317'div',318array(319),320array(321$spaces_control,322javelin_tag(323'a',324array(325'class' => 'button button-grey dropdown has-icon has-text '.326'policy-control',327'href' => '#',328'mustcapture' => true,329'sigil' => 'policy-control',330'id' => $control_id,331),332array(333$caret,334javelin_tag(335'span',336array(337'sigil' => 'policy-label',338'class' => 'phui-button-text',339),340array(341idx($icons, $selected_icon),342$selected_name,343)),344)),345$input,346));347}348349public static function getSelectCustomKey() {350return 'select:custom';351}352353public static function getSelectProjectKey() {354return 'select:project';355}356357private function buildSpacesControl() {358if ($this->capability != PhabricatorPolicyCapability::CAN_VIEW) {359return null;360}361362if (!($this->object instanceof PhabricatorSpacesInterface)) {363return null;364}365366$viewer = $this->getUser();367if (!PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) {368return null;369}370371$space_phid = $this->getSpacePHID();372if ($space_phid === null) {373$space_phid = $viewer->getDefaultSpacePHID();374}375376$select = AphrontFormSelectControl::renderSelectTag(377$space_phid,378PhabricatorSpacesNamespaceQuery::getSpaceOptionsForViewer(379$viewer,380$space_phid),381array(382'disabled' => ($this->getDisabled() ? 'disabled' : null),383'name' => 'spacePHID',384'class' => 'aphront-space-select-control-knob',385));386387return $select;388}389390}391392393