diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index ccd473931c..675b1cd630 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -1,339 +1,331 @@ getViewer(); $id = $request->getURIData('id'); $current_version = null; if ($id) { $is_new = false; $document = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needContent(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$document) { return new Aphront404Response(); } $current_version = $document->getContent()->getVersion(); $revert = $request->getInt('revert'); if ($revert) { $content = id(new PhrictionContentQuery()) ->setViewer($viewer) ->withDocumentPHIDs(array($document->getPHID())) ->withVersions(array($revert)) ->executeOne(); if (!$content) { return new Aphront404Response(); } } else { $content = $document->getContent(); } } else { $slug = $request->getStr('slug'); $slug = PhabricatorSlug::normalize($slug); if (!$slug) { return new Aphront404Response(); } $document = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withSlugs(array($slug)) ->needContent(true) ->executeOne(); if ($document) { $content = $document->getContent(); $current_version = $content->getVersion(); $is_new = false; } else { $document = PhrictionDocument::initializeNewDocument($viewer, $slug); $content = $document->getContent(); $is_new = true; } } if ($request->getBool('nodraft')) { $draft = null; $draft_key = null; } else { if ($document->getPHID()) { $draft_key = $document->getPHID().':'.$content->getVersion(); } else { $draft_key = 'phriction:'.$content->getSlug(); } $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $viewer->getPHID(), $draft_key); } if ($draft && strlen($draft->getDraft()) && ($draft->getDraft() != $content->getContent())) { $content_text = $draft->getDraft(); $discard = phutil_tag( 'a', array( 'href' => $request->getRequestURI()->alter('nodraft', true), ), pht('discard this draft')); $draft_note = new PHUIInfoView(); $draft_note->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $draft_note->setTitle(pht('Recovered Draft')); $draft_note->appendChild( pht('Showing a saved draft of your edits, you can %s.', $discard)); } else { $content_text = $content->getContent(); $draft_note = null; } require_celerity_resource('phriction-document-css'); $e_title = true; $e_content = true; $validation_exception = null; $notes = null; $title = $content->getTitle(); $overwrite = false; $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( $document->getPHID()); if ($is_new) { $v_projects = array(); } else { $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $document->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); } $v_space = $document->getSpacePHID(); if ($request->isFormPost()) { $title = $request->getStr('title'); $content_text = $request->getStr('content'); $notes = $request->getStr('description'); $current_version = $request->getInt('contentVersion'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); $v_cc = $request->getArr('cc'); $v_projects = $request->getArr('projects'); $v_space = $request->getStr('spacePHID'); $xactions = array(); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType( PhrictionDocumentContentTransaction::TRANSACTIONTYPE) ->setNewValue($content_text); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setNewValue($v_view); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) ->setNewValue($v_edit); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) ->setNewValue($v_space); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue(array('=' => $v_cc)); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhrictionTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setDescription($notes) ->setProcessContentVersionError(!$request->getBool('overwrite')) ->setContentVersion($current_version); try { $editor->applyTransactions($document, $xactions); if ($draft) { $draft->delete(); } $uri = PhrictionDocument::getSlugURI($document->getSlug()); return id(new AphrontRedirectResponse())->setURI($uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_title = nonempty( $ex->getShortMessage( PhrictionDocumentTitleTransaction::TRANSACTIONTYPE), true); $e_content = nonempty( $ex->getShortMessage( PhrictionDocumentContentTransaction::TRANSACTIONTYPE), true); // if we're not supposed to process the content version error, then // overwrite that content...! if (!$editor->getProcessContentVersionError()) { $overwrite = true; } $document->setViewPolicy($v_view); $document->setEditPolicy($v_edit); $document->setSpacePHID($v_space); } } if ($document->getID()) { $page_title = pht('Edit Document: %s', $content->getTitle()); if ($overwrite) { $submit_button = pht('Overwrite Changes'); } else { $submit_button = pht('Save Changes'); } } else { $submit_button = pht('Create Document'); $page_title = pht('Create Document'); } $uri = $document->getSlug(); $uri = PhrictionDocument::getSlugURI($uri); $uri = PhabricatorEnv::getProductionURI($uri); $cancel_uri = PhrictionDocument::getSlugURI($document->getSlug()); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($document) ->execute(); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; $edit_capability = PhabricatorPolicyCapability::CAN_EDIT; - $codex = id(PhabricatorPolicyCodex::newFromObject($document, $viewer)) - ->setCapability($view_capability); - $view_capability_description = $codex->getPolicySpecialRuleForCapability( - PhabricatorPolicyCapability::CAN_VIEW)->getDescription(); - $edit_capability_description = $codex->getPolicySpecialRuleForCapability( - PhabricatorPolicyCapability::CAN_EDIT)->getDescription(); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('slug', $document->getSlug()) ->addHiddenInput('nodraft', $request->getBool('nodraft')) ->addHiddenInput('contentVersion', $current_version) ->addHiddenInput('overwrite', $overwrite) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Title')) ->setValue($title) ->setError($e_title) ->setName('title')) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('URI')) ->setValue($uri)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setLabel(pht('Content')) ->setValue($content_text) ->setError($e_content) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setName('content') ->setID('document-textarea') ->setUser($viewer)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Tags')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('cc') ->setValue($v_cc) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) ->appendChild( id(new AphrontFormPolicyControl()) ->setViewer($viewer) ->setName('viewPolicy') ->setSpacePHID($v_space) ->setPolicyObject($document) ->setCapability($view_capability) - ->setPolicies($policies) - ->setCaption($view_capability_description)) + ->setPolicies($policies)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($document) ->setCapability($edit_capability) - ->setPolicies($policies) - ->setCaption($edit_capability_description)) + ->setPolicies($policies)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Edit Notes')) ->setValue($notes) ->setError(null) ->setName('description')) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_button)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setValidationException($validation_exception) ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $preview = id(new PHUIRemarkupPreviewPanel()) ->setHeader($content->getTitle()) ->setPreviewURI('/phriction/preview/'.$document->getSlug()) ->setControlID('document-textarea') ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); $crumbs = $this->buildApplicationCrumbs(); if ($document->getID()) { $crumbs->addTextCrumb( $content->getTitle(), PhrictionDocument::getSlugURI($document->getSlug())); $crumbs->addTextCrumb(pht('Edit')); } else { $crumbs->addTextCrumb(pht('Create')); } $crumbs->setBorder(true); $view = id(new PHUITwoColumnView()) - ->setFooter(array( - $draft_note, - $form_box, - $preview, - )); + ->setFooter( + array( + $draft_note, + $form_box, + $preview, + )); return $this->newPage() ->setTitle($page_title) ->setCrumbs($crumbs) ->appendChild($view); - } } diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php index 0b0e608048..53e6f940c9 100644 --- a/src/view/form/control/AphrontFormPolicyControl.php +++ b/src/view/form/control/AphrontFormPolicyControl.php @@ -1,401 +1,391 @@ object = $object; return $this; } public function setPolicies(array $policies) { assert_instances_of($policies, 'PhabricatorPolicy'); $this->policies = $policies; return $this; } public function setSpacePHID($space_phid) { $this->spacePHID = $space_phid; return $this; } public function getSpacePHID() { return $this->spacePHID; } public function setTemplatePHIDType($type) { $this->templatePHIDType = $type; return $this; } public function setTemplateObject($object) { $this->templateObject = $object; return $this; } public function getSerializedValue() { return json_encode(array( $this->getValue(), $this->getSpacePHID(), )); } public function readSerializedValue($value) { $decoded = phutil_json_decode($value); $policy_value = $decoded[0]; $space_phid = $decoded[1]; $this->setValue($policy_value); $this->setSpacePHID($space_phid); return $this; } public function readValueFromDictionary(array $dictionary) { // TODO: This is a little hacky but will only get us into trouble if we // have multiple view policy controls in multiple paged form views on the // same page, which seems unlikely. $this->setSpacePHID(idx($dictionary, 'spacePHID')); return parent::readValueFromDictionary($dictionary); } public function readValueFromRequest(AphrontRequest $request) { // See note in readValueFromDictionary(). $this->setSpacePHID($request->getStr('spacePHID')); return parent::readValueFromRequest($request); } public function setCapability($capability) { $this->capability = $capability; $labels = array( PhabricatorPolicyCapability::CAN_VIEW => pht('Visible To'), PhabricatorPolicyCapability::CAN_EDIT => pht('Editable By'), PhabricatorPolicyCapability::CAN_JOIN => pht('Joinable By'), ); if (isset($labels[$capability])) { $label = $labels[$capability]; } else { $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); if ($capobj) { $label = $capobj->getCapabilityName(); } else { $label = pht('Capability "%s"', $capability); } } $this->setLabel($label); return $this; } protected function getCustomControlClass() { return 'aphront-form-control-policy'; } 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; } } $options[$policy->getType()][$policy->getPHID()] = array( 'name' => $policy->getName(), '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; } protected function renderInput() { if (!$this->object) { throw new PhutilInvalidStateException('setPolicyObject'); } if (!$this->capability) { throw new PhutilInvalidStateException('setCapability'); } $policy = $this->object->getPolicy($this->capability); if (!$policy) { // TODO: Make this configurable. $policy = PhabricatorPolicies::POLICY_USER; } if (!$this->getValue()) { $this->setValue($policy); } $control_id = celerity_generate_unique_node_id(); $input_id = celerity_generate_unique_node_id(); $caret = phutil_tag( 'span', array( 'class' => 'caret', )); $input = phutil_tag( 'input', array( 'type' => 'hidden', 'id' => $input_id, 'name' => $this->getName(), 'value' => $this->getValue(), )); $options = $this->getOptions(); $order = array(); $labels = array(); foreach ($options as $key => $values) { $order[$key] = array_keys($values); $labels[$key] = PhabricatorPolicyType::getPolicyTypeName($key); } $flat_options = array_mergev($options); $icons = array(); foreach (igroup($flat_options, 'icon') as $icon => $ignored) { $icons[$icon] = id(new PHUIIconView()) ->setIcon($icon); } - if ($this->templatePHIDType) { $context_path = 'template/'.$this->templatePHIDType.'/'; } else { $object_phid = $this->object->getPHID(); if ($object_phid) { $context_path = 'object/'.$object_phid.'/'; } else { $object_type = phid_get_type($this->object->generatePHID()); $context_path = 'type/'.$object_type.'/'; } } Javelin::initBehavior( 'policy-control', array( 'controlID' => $control_id, 'inputID' => $input_id, 'options' => $flat_options, 'groups' => array_keys($options), 'order' => $order, 'labels' => $labels, 'value' => $this->getValue(), 'capability' => $this->capability, 'editURI' => '/policy/edit/'.$context_path, 'customKey' => $this->getSelectCustomKey(), 'projectKey' => $this->getSelectProjectKey(), 'disabled' => $this->getDisabled(), )); $selected = idx($flat_options, $this->getValue(), array()); $selected_icon = idx($selected, 'icon'); $selected_name = idx($selected, 'name'); $spaces_control = $this->buildSpacesControl(); return phutil_tag( 'div', array( ), array( $spaces_control, javelin_tag( 'a', array( 'class' => 'button button-grey dropdown has-icon has-text '. 'policy-control', 'href' => '#', 'mustcapture' => true, 'sigil' => 'policy-control', 'id' => $control_id, ), array( $caret, javelin_tag( 'span', array( 'sigil' => 'policy-label', 'class' => 'phui-button-text', ), array( idx($icons, $selected_icon), $selected_name, )), )), $input, )); - - return AphrontFormSelectControl::renderSelectTag( - $this->getValue(), - $this->getOptions(), - array( - 'name' => $this->getName(), - 'disabled' => $this->getDisabled() ? 'disabled' : null, - 'id' => $this->getID(), - )); } public static function getSelectCustomKey() { return 'select:custom'; } public static function getSelectProjectKey() { return 'select:project'; } private function buildSpacesControl() { if ($this->capability != PhabricatorPolicyCapability::CAN_VIEW) { return null; } if (!($this->object instanceof PhabricatorSpacesInterface)) { return null; } $viewer = $this->getUser(); if (!PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) { return null; } $space_phid = $this->getSpacePHID(); if ($space_phid === null) { $space_phid = $viewer->getDefaultSpacePHID(); } $select = AphrontFormSelectControl::renderSelectTag( $space_phid, PhabricatorSpacesNamespaceQuery::getSpaceOptionsForViewer( $viewer, $space_phid), array( 'disabled' => ($this->getDisabled() ? 'disabled' : null), 'name' => 'spacePHID', 'class' => 'aphront-space-select-control-knob', )); return $select; } }