diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ 'names' => array( 'conpherence.pkg.css' => 'cea72e09', 'conpherence.pkg.js' => '6249a1cf', - 'core.pkg.css' => '4fc9469e', + 'core.pkg.css' => '66739a56', 'core.pkg.js' => '1a77dddf', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'a4ba74b5', @@ -21,7 +21,7 @@ 'maniphest.pkg.js' => '949a7498', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/dark-console.css' => 'f54bf286', - 'rsrc/css/aphront/dialog-view.css' => '84f1e6a6', + 'rsrc/css/aphront/dialog-view.css' => '1e6b8603', 'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', @@ -142,7 +142,7 @@ 'rsrc/css/phui/phui-form-view.css' => '9e22b190', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => '06385974', + 'rsrc/css/phui/phui-header-view.css' => '6ec8f155', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '417f80fb', @@ -552,7 +552,7 @@ 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', 'aphront-dark-console-css' => 'f54bf286', - 'aphront-dialog-view-css' => '84f1e6a6', + 'aphront-dialog-view-css' => '1e6b8603', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', 'aphront-panel-view-css' => '8427b78d', @@ -865,7 +865,7 @@ 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '9e22b190', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => '06385974', + 'phui-header-view-css' => '6ec8f155', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1668,6 +1668,7 @@ 'PHUIPagerView' => 'view/phui/PHUIPagerView.php', 'PHUIPinboardItemView' => 'view/phui/PHUIPinboardItemView.php', 'PHUIPinboardView' => 'view/phui/PHUIPinboardView.php', + 'PHUIPolicySectionView' => 'applications/policy/view/PHUIPolicySectionView.php', 'PHUIPropertyGroupView' => 'view/phui/PHUIPropertyGroupView.php', 'PHUIPropertyListExample' => 'applications/uiexample/examples/PHUIPropertyListExample.php', 'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php', @@ -2071,6 +2072,7 @@ 'PhabricatorCalendarEventNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php', 'PhabricatorCalendarEventNotificationView' => 'applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php', 'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php', + 'PhabricatorCalendarEventPolicyCodex' => 'applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php', 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', 'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php', 'PhabricatorCalendarEventRecurringTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventRecurringTransaction.php', @@ -3311,6 +3313,9 @@ 'PhabricatorPolicyCanViewCapability' => 'applications/policy/capability/PhabricatorPolicyCanViewCapability.php', 'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php', 'PhabricatorPolicyCapabilityTestCase' => 'applications/policy/capability/__tests__/PhabricatorPolicyCapabilityTestCase.php', + 'PhabricatorPolicyCodex' => 'applications/policy/codex/PhabricatorPolicyCodex.php', + 'PhabricatorPolicyCodexInterface' => 'applications/policy/codex/PhabricatorPolicyCodexInterface.php', + 'PhabricatorPolicyCodexRuleDescription' => 'applications/policy/codex/PhabricatorPolicyCodexRuleDescription.php', 'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php', 'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php', 'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php', @@ -6447,6 +6452,7 @@ 'PHUIPagerView' => 'AphrontView', 'PHUIPinboardItemView' => 'AphrontView', 'PHUIPinboardView' => 'AphrontView', + 'PHUIPolicySectionView' => 'AphrontTagView', 'PHUIPropertyGroupView' => 'AphrontTagView', 'PHUIPropertyListExample' => 'PhabricatorUIExample', 'PHUIPropertyListView' => 'AphrontView', @@ -6870,6 +6876,7 @@ 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', 'PhabricatorExtendedPolicyInterface', + 'PhabricatorPolicyCodexInterface', 'PhabricatorProjectInterface', 'PhabricatorMarkupInterface', 'PhabricatorApplicationTransactionInterface', @@ -6923,6 +6930,7 @@ 'PhabricatorCalendarEventNameTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventNotificationView' => 'Phobject', 'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorCalendarEventPolicyCodex' => 'PhabricatorPolicyCodex', 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand', 'PhabricatorCalendarEventRecurringTransaction' => 'PhabricatorCalendarEventTransactionType', @@ -8361,6 +8369,8 @@ 'PhabricatorPolicyCanViewCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPolicyCapability' => 'Phobject', 'PhabricatorPolicyCapabilityTestCase' => 'PhabricatorTestCase', + 'PhabricatorPolicyCodex' => 'Phobject', + 'PhabricatorPolicyCodexRuleDescription' => 'Phobject', 'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPolicyConstants' => 'Phobject', 'PhabricatorPolicyController' => 'PhabricatorController', diff --git a/src/applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php b/src/applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/codex/PhabricatorCalendarEventPolicyCodex.php @@ -0,0 +1,80 @@ +getObject(); + + if (!$object->isImportedEvent()) { + return null; + } + + return pht('Uses Import Policy'); + } + + public function getPolicyIcon() { + $object = $this->getObject(); + + if (!$object->isImportedEvent()) { + return null; + } + + return 'fa-download'; + } + + public function getPolicyTagClasses() { + $object = $this->getObject(); + + if (!$object->isImportedEvent()) { + return array(); + } + + return array( + 'policy-adjusted-special', + ); + } + + public function getPolicySpecialRuleDescriptions() { + $object = $this->getObject(); + + $rules = array(); + $rules[] = $this->newRule() + ->setDescription( + pht('The host of an event can always view and edit it.')); + + $rules[] = $this->newRule() + ->setCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->setDescription( + pht('Users who are invited to an event can always view it.')); + + + $rules[] = $this->newRule() + ->setCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->setIsActive($object->isImportedEvent()) + ->setDescription( + pht( + 'Imported events can only be viewed by users who can view '. + 'the import source.')); + + $rules[] = $this->newRule() + ->setCapabilities( + array( + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->setIsActive($object->isImportedEvent()) + ->setDescription( + pht( + 'Imported events can not be edited in Phabricator.')); + + return $rules; + } + + +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -4,6 +4,7 @@ implements PhabricatorPolicyInterface, PhabricatorExtendedPolicyInterface, + PhabricatorPolicyCodexInterface, PhabricatorProjectInterface, PhabricatorMarkupInterface, PhabricatorApplicationTransactionInterface, @@ -1217,18 +1218,6 @@ return false; } - public function describeAutomaticCapability($capability) { - if ($this->isImportedEvent()) { - return pht( - 'Events imported from external sources can not be edited in '. - 'Phabricator.'); - } - - return pht( - 'The host of an event can always view and edit it. Users who are '. - 'invited to an event can always view it.'); - } - /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ @@ -1251,6 +1240,12 @@ return $extended; } +/* -( PhabricatorPolicyCodexInterface )------------------------------------ */ + + public function newPolicyCodex() { + return new PhabricatorCalendarEventPolicyCodex(); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -370,14 +370,6 @@ switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: - // NOTE: In Differential, an automatic capability on a revision (being - // an author) is sufficient to view it, even if you can not see the - // repository the revision belongs to. We can bail out early in this - // case. - if ($this->hasAutomaticCapability($capability, $viewer)) { - break; - } - $repository_phid = $this->getRepositoryPHID(); $repository = $this->getRepository(); diff --git a/src/applications/policy/codex/PhabricatorPolicyCodex.php b/src/applications/policy/codex/PhabricatorPolicyCodex.php new file mode 100644 --- /dev/null +++ b/src/applications/policy/codex/PhabricatorPolicyCodex.php @@ -0,0 +1,106 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function setObject(PhabricatorPolicyCodexInterface $object) { + $this->object = $object; + return $this; + } + + final public function getObject() { + return $this->object; + } + + final public function setCapability($capability) { + $this->capability = $capability; + return $this; + } + + final public function getCapability() { + return $this->capability; + } + + final public function setPolicy(PhabricatorPolicy $policy) { + $this->policy = $policy; + return $this; + } + + final public function getPolicy() { + return $this->policy; + } + + final public static function newFromObject( + PhabricatorPolicyCodexInterface $object, + PhabricatorUser $viewer) { + + if (!($object instanceof PhabricatorPolicyInterface)) { + throw new Exception( + pht( + 'Object (of class "%s") implements interface "%s", but must also '. + 'implement interface "%s".', + get_class($object), + 'PhabricatorPolicyCodexInterface', + 'PhabricatorPolicyInterface')); + } + + $codex = $object->newPolicyCodex(); + if (!($codex instanceof PhabricatorPolicyCodex)) { + throw new Exception( + pht( + 'Object (of class "%s") implements interface "%s", but defines '. + 'method "%s" incorrectly: this method must return an object of '. + 'class "%s".', + get_class($object), + 'PhabricatorPolicyCodexInterface', + 'newPolicyCodex()', + __CLASS__)); + } + + $codex + ->setObject($object) + ->setViewer($viewer); + + return $codex; + } + +} diff --git a/src/applications/policy/codex/PhabricatorPolicyCodexInterface.php b/src/applications/policy/codex/PhabricatorPolicyCodexInterface.php new file mode 100644 --- /dev/null +++ b/src/applications/policy/codex/PhabricatorPolicyCodexInterface.php @@ -0,0 +1,18 @@ +>PolicyCodex(); + } + +*/ diff --git a/src/applications/policy/codex/PhabricatorPolicyCodexRuleDescription.php b/src/applications/policy/codex/PhabricatorPolicyCodexRuleDescription.php new file mode 100644 --- /dev/null +++ b/src/applications/policy/codex/PhabricatorPolicyCodexRuleDescription.php @@ -0,0 +1,37 @@ +description = $description; + return $this; + } + + public function getDescription() { + return $this->description; + } + + public function setCapabilities(array $capabilities) { + $this->capabilities = $capabilities; + return $this; + } + + public function getCapabilities() { + return $this->capabilities; + } + + public function setIsActive($is_active) { + $this->isActive = $is_active; + return $this; + } + + public function getIsActive() { + return $this->isActive; + } + +} diff --git a/src/applications/policy/controller/PhabricatorPolicyExplainController.php b/src/applications/policy/controller/PhabricatorPolicyExplainController.php --- a/src/applications/policy/controller/PhabricatorPolicyExplainController.php +++ b/src/applications/policy/controller/PhabricatorPolicyExplainController.php @@ -34,50 +34,136 @@ ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); - $object_uri = nonempty($handle->getURI(), '/'); - $explanation = PhabricatorPolicy::getPolicyExplanation( - $viewer, - $policy->getPHID()); + $object_name = $handle->getName(); + $object_uri = nonempty($handle->getURI(), '/'); - $auto_info = (array)$object->describeAutomaticCapability($capability); + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setClass('aphront-access-dialog') + ->setTitle(pht('Policy Details: %s', $object_name)) + ->addCancelButton($object_uri, pht('Done')); - $auto_info = array_merge( - array($explanation), - $auto_info); - $auto_info = array_filter($auto_info); + $this->appendSpaceInformation($dialog, $object, $policy, $capability); - $capability_name = $capability; - $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); - if ($capobj) { - $capability_name = $capobj->getCapabilityName(); + if ($object instanceof PhabricatorExtendedPolicyInterface) { + $extended_rules = $object->getExtendedPolicy($capability, $viewer); + if ($extended_rules) { + $extended_section = id(new PHUIPolicySectionView()) + ->setViewer($viewer) + ->setIcon('fa-link') + ->setHeader(pht('Required Capabilities on Other Objects')) + ->appendParagraph( + pht( + 'To access this object, users must have first have access '. + 'capabilties on these other objects:')); + + $items = array(); + foreach ($extended_rules as $extended_rule) { + $extended_target = $extended_rule[0]; + $extended_capabilities = (array)$extended_rule[1]; + if (is_object($extended_target)) { + $extended_target = $extended_target->getPHID(); + } + + foreach ($extended_capabilities as $extended_capability) { + $ex_name = $this->getCapabilityName($extended_capability); + $items[] = array( + phutil_tag('strong', array(), pht('%s:', $ex_name)), + ' ', + $viewer->renderHandle($extended_target)->setAsTag(true), + ); + } + } + + $extended_section->appendList($items); + + $dialog->appendChild($extended_section); + } } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setClass('aphront-access-dialog'); + if ($object instanceof PhabricatorPolicyCodexInterface) { + $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); + $rules = $codex->getPolicySpecialRuleDescriptions(); + + $exceptions = array(); + foreach ($rules as $rule) { + $is_active = $rule->getIsActive(); + if ($is_active) { + $rule_capabilities = $rule->getCapabilities(); + if ($rule_capabilities) { + if (!in_array($capability, $rule_capabilities)) { + $is_active = false; + } + } + } + + $description = $rule->getDescription(); + + if (!$is_active) { + $description = phutil_tag( + 'span', + array( + 'class' => 'phui-policy-section-view-inactive-rule', + ), + $description); + } - $this->appendSpaceInformation($dialog, $object, $policy, $capability); + $exceptions[] = $description; + } + } else if (method_exists($object, 'describeAutomaticCapability')) { + $exceptions = (array)$object->describeAutomaticCapability($capability); + $exceptions = array_filter($exceptions); + } else { + $exceptions = array(); + } - $intro = pht( - 'Users with the "%s" capability for this object:', - $capability_name); + if ($exceptions) { + $exceptions_section = id(new PHUIPolicySectionView()) + ->setViewer($viewer) + ->setIcon('fa-unlock-alt red') + ->setHeader(pht('Special Rules')) + ->appendParagraph( + pht( + 'This object has special rules which override normal object '. + 'policy rules:')) + ->appendList($exceptions); - $object_name = pht( - '%s %s', - $handle->getTypeName(), - $handle->getObjectName()); + $dialog->appendChild($exceptions_section); + } - $dialog - ->setTitle(pht('Policy Details: %s', $object_name)) - ->appendParagraph($intro) - ->addCancelButton($object_uri, pht('Done')); + $capability_name = $this->getCapabilityName($capability); - if ($auto_info) { - $dialog->appendList($auto_info); + $object_section = id(new PHUIPolicySectionView()) + ->setViewer($viewer) + ->setIcon($handle->getIcon().' bluegrey') + ->setHeader(pht('Object Policy')) + ->appendList( + array( + array( + phutil_tag('strong', array(), pht('%s:', $capability_name)), + ' ', + $policy->getShortName(), + ), + )) + ->appendParagraph( + pht( + 'In detail, this means that these users can take this action, '. + 'provided they pass all of the checks described above first:')) + ->appendList( + array( + PhabricatorPolicy::getPolicyExplanation( + $viewer, + $policy->getPHID()), + )); + + $strength = $this->getStrengthInformation($object, $policy, $capability); + if ($strength) { + $object_section->appendHint($strength); } - $this->appendStrengthInformation($dialog, $object, $policy, $capability); + $dialog->appendChild($object_section); + return $dialog; } @@ -97,35 +183,9 @@ return; } - // NOTE: We're intentionally letting users through here, even if they only - // have access to one space. The intent is to help users in "space jail" - // understand who objects they create are visible to: - $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( $object); - $handles = $viewer->loadHandles(array($space_phid)); - $doc_href = PhabricatorEnv::getDoclink('Spaces User Guide'); - - $dialog->appendParagraph( - array( - pht( - 'This object is in %s, and can only be seen or edited by users with '. - 'access to view objects in the space.', - $handles[$space_phid]->renderLink()), - ' ', - phutil_tag( - 'strong', - array(), - phutil_tag( - 'a', - array( - 'href' => $doc_href, - 'target' => '_blank', - ), - pht('Learn More'))), - )); - $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); $space = idx($spaces, $space_phid); if (!$space) { @@ -138,20 +198,49 @@ return; } + $handles = $viewer->loadHandles(array($space_phid)); + $doc_href = PhabricatorEnv::getDoclink('Spaces User Guide'); + $capability_name = $this->getCapabilityName($capability); + + $space_section = id(new PHUIPolicySectionView()) + ->setViewer($viewer) + ->setIcon('fa-th-large bluegrey') + ->setHeader(pht('Space')) + ->setDocumentationLink(pht('Spaces Documentation'), $doc_href) + ->appendList( + array( + array( + phutil_tag('strong', array(), pht('Space:')), + ' ', + $viewer->renderHandle($space_phid)->setAsTag(true), + ), + array( + phutil_tag('strong', array(), pht('%s:', $capability_name)), + ' ', + $space_policy->getShortName(), + ), + )) + ->appendParagraph( + pht( + 'This object is in %s and can only be seen or edited by users '. + 'with access to view objects in the space.', + $viewer->renderHandle($space_phid))); + $space_explanation = PhabricatorPolicy::getPolicyExplanation( $viewer, $space_policy->getPHID()); $items = array(); $items[] = $space_explanation; - $dialog->appendParagraph(pht('Users who can see objects in this space:')); - $dialog->appendList($items); + $space_section + ->appendParagraph(pht('Users who can see objects in this space:')) + ->appendList($items); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; if ($capability == $view_capability) { $stronger = $space_policy->isStrongerThan($policy); if ($stronger) { - $dialog->appendParagraph( + $space_section->appendHint( pht( 'The space this object is in has a more restrictive view '. 'policy ("%s") than the object does ("%s"), so the space\'s '. @@ -161,14 +250,16 @@ } } - $dialog->appendParagraph( + $space_section->appendHint( pht( 'After a user passes space policy checks, they must still pass '. 'object policy checks.')); + + + $dialog->appendChild($space_section); } - private function appendStrengthInformation( - AphrontDialogView $dialog, + private function getStrengthInformation( PhabricatorPolicyInterface $object, PhabricatorPolicy $policy, $capability) { @@ -206,7 +297,18 @@ $default_policy->getShortName()); } - $dialog->appendParagraph($info); + return $info; } + private function getCapabilityName($capability) { + $capability_name = $capability; + $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); + if ($capobj) { + $capability_name = $capobj->getCapabilityName(); + } + + return $capability_name; + } + + } diff --git a/src/applications/policy/interface/PhabricatorPolicyInterface.php b/src/applications/policy/interface/PhabricatorPolicyInterface.php --- a/src/applications/policy/interface/PhabricatorPolicyInterface.php +++ b/src/applications/policy/interface/PhabricatorPolicyInterface.php @@ -6,34 +6,6 @@ public function getPolicy($capability); public function hasAutomaticCapability($capability, PhabricatorUser $viewer); - /** - * Describe exceptions to an object's policy setting. - * - * The intent of this method is to explain and inform users about special - * cases which override configured policy settings. If this object has any - * such exceptions, explain them by returning one or more human-readable - * strings which describe the exception in a broad, categorical way. For - * example: - * - * - "The owner of an X can always view and edit it." - * - "Members of a Y can always view it." - * - * You can return `null`, a single string, or a list of strings. - * - * The relevant capability to explain (like "view") is passed as a parameter. - * You should tailor any messages to be relevant to that capability, although - * they do not need to exclusively describe the capability, and in some cases - * being more general ("The author can view and edit...") will be more clear. - * - * Messages should describe general rules, not specific objects, because the - * main goal is to teach the user the rules. For example, write "the author", - * not the specific author's name. - * - * @param const @{class:PhabricatorPolicyCapability} constant. - * @return wild Description of policy exceptions. See above. - */ - public function describeAutomaticCapability($capability); - } // TEMPLATE IMPLEMENTATION ///////////////////////////////////////////////////// @@ -58,8 +30,4 @@ return false; } - public function describeAutomaticCapability($capability) { - return null; - } - */ diff --git a/src/applications/policy/storage/PhabricatorPolicy.php b/src/applications/policy/storage/PhabricatorPolicy.php --- a/src/applications/policy/storage/PhabricatorPolicy.php +++ b/src/applications/policy/storage/PhabricatorPolicy.php @@ -225,7 +225,9 @@ switch ($policy) { case PhabricatorPolicies::POLICY_PUBLIC: - return pht('This object is public.'); + return pht( + 'This object is public and can be viewed by anyone, even if they '. + 'do not have a Phabricator account.'); case PhabricatorPolicies::POLICY_USER: return pht('Logged in users can take this action.'); case PhabricatorPolicies::POLICY_ADMIN: diff --git a/src/applications/policy/view/PHUIPolicySectionView.php b/src/applications/policy/view/PHUIPolicySectionView.php new file mode 100644 --- /dev/null +++ b/src/applications/policy/view/PHUIPolicySectionView.php @@ -0,0 +1,142 @@ +header = $header; + return $this; + } + + public function getHeader() { + return $this->header; + } + + public function setIcon($icon) { + $this->icon = $icon; + return $this; + } + + public function getIcon() { + return $this->icon; + } + + public function setDocumentationLink($name, $href) { + $link = phutil_tag( + 'a', + array( + 'href' => $href, + 'target' => '_blank', + ), + $name); + + $this->documentationLink = phutil_tag( + 'div', + array( + 'class' => 'phui-policy-section-view-link', + ), + array( + id(new PHUIIconView())->setIcon('fa-book'), + $link, + )); + + return $this; + } + + public function getDocumentationLink() { + return $this->documentationLink; + } + + public function appendList(array $items) { + foreach ($items as $key => $item) { + $items[$key] = phutil_tag( + 'li', + array( + 'class' => 'remarkup-list-item', + ), + $item); + } + + $list = phutil_tag( + 'ul', + array( + 'class' => 'remarkup-list', + ), + $items); + + return $this->appendChild($list); + } + + public function appendHint($content) { + $hint = phutil_tag( + 'p', + array( + 'class' => 'phui-policy-section-view-hint', + ), + array( + id(new PHUIIconView()) + ->setIcon('fa-sticky-note bluegrey'), + ' ', + pht('Note:'), + ' ', + $content, + )); + + return $this->appendChild($hint); + } + + public function appendParagraph($content) { + return $this->appendChild(phutil_tag('p', array(), $content)); + } + + protected function getTagAttributes() { + return array( + 'class' => 'phui-policy-section-view', + ); + } + + protected function getTagContent() { + require_celerity_resource('phui-header-view-css'); + + $icon_view = null; + $icon = $this->getIcon(); + if ($icon !== null) { + $icon_view = id(new PHUIIconView()) + ->setIcon($icon); + } + + $header_view = phutil_tag( + 'span', + array( + 'class' => 'phui-policy-section-view-header-text', + ), + $this->getHeader()); + + $header = phutil_tag( + 'div', + array( + 'class' => 'phui-policy-section-view-header', + ), + array( + $icon_view, + $header_view, + $this->getDocumentationLink(), + )); + + return array( + $header, + phutil_tag( + 'div', + array( + 'class' => 'phui-policy-section-view-body', + ), + $this->renderChildren()), + ); + } + + +} diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -452,6 +452,7 @@ // show them information about the existence of spaces if they click // through. $use_space_policy = false; + $containing_space = null; if ($object instanceof PhabricatorSpacesInterface) { $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( $object); @@ -459,6 +460,7 @@ $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); $space = idx($spaces, $space_phid); if ($space) { + $containing_space = $space; $space_policies = PhabricatorPolicyQuery::loadPolicies( $viewer, $space); @@ -506,8 +508,90 @@ } } + $policy_name = array($policy->getShortName()); + $policy_icon = $policy->getIcon().' bluegrey'; + + if ($object instanceof PhabricatorExtendedPolicyInterface) { + $extended_objects = $object->getExtendedPolicy( + $view_capability, + $viewer); + if ($extended_objects) { + $extended_phids = array(); + foreach ($extended_objects as $extended_object) { + if (is_object($extended_object[0])) { + $extended_phids[] = $extended_object[0]->getPHID(); + } else { + $extended_phids[] = $extended_object[0]; + } + } + + $extended_handles = $viewer->loadHandles($extended_phids); + + $extended_names = array(); + foreach ($extended_phids as $extended_phid) { + // TODO: It would be nice to just show a monogram here, but we don't + // currently have that on handles. + $extended_names[] = $extended_handles[$extended_phid]->getName(); + } + + $extended_names = phutil_implode_html( + " \xC2\xB7 ", + $extended_names); + + array_unshift($policy_name, $extended_names); + } + } + + if ($object instanceof PhabricatorPolicyCodexInterface) { + $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); + + $codex_name = $codex->getPolicyShortName($policy, $view_capability); + if ($codex_name !== null) { + $policy_name = $codex_name; + } + + $codex_icon = $codex->getPolicyIcon($policy, $view_capability); + if ($codex_icon !== null) { + $policy_icon = $codex_icon; + } + + $codex_classes = $codex->getPolicyTagClasses($policy, $view_capability); + foreach ($codex_classes as $codex_class) { + $container_classes[] = $codex_class; + } + } + + if (!is_array($policy_name)) { + $policy_name = (array)$policy_name; + } + + if ($containing_space) { + $default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); + + $is_default_space = + ($default_space) && + ($default_space->getPHID() == $containing_space->getPHID()); + + if (!$is_default_space) { + $space_tag = phutil_tag( + 'span', + array( + 'class' => 'policy-space-container', + ), + $containing_space->getMonogram()); + + array_unshift($policy_name, $space_tag); + } + } + + $arrow = id(new PHUIIconView()) + ->setIcon('fa-angle-right') + ->addClass('policy-tier-separator'); + + $policy_name = phutil_implode_html($arrow, $policy_name); + $icon = id(new PHUIIconView()) - ->setIcon($policy->getIcon().' bluegrey'); + ->setIcon($policy_icon); $link = javelin_tag( 'a', @@ -516,7 +600,7 @@ 'href' => '/policy/explain/'.$phid.'/'.$view_capability.'/', 'sigil' => 'workflow', ), - $policy->getShortName()); + $policy_name); return phutil_tag( 'span', diff --git a/webroot/rsrc/css/aphront/dialog-view.css b/webroot/rsrc/css/aphront/dialog-view.css --- a/webroot/rsrc/css/aphront/dialog-view.css +++ b/webroot/rsrc/css/aphront/dialog-view.css @@ -128,6 +128,10 @@ width: 50%; } +.aphront-access-dialog .aphront-dialog-body { + padding: 0 12px; +} + .aphront-policy-rejection { font-weight: bold; } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -240,6 +240,26 @@ color: {$sh-orangetext}; } +.policy-header-callout.policy-adjusted-special { + background: {$sh-indigobackground}; +} + +.policy-header-callout.policy-adjusted-special .policy-link, +.policy-header-callout.policy-adjusted-special .phui-icon-view { + color: {$sh-indigotext}; +} + +.policy-header-callout .policy-space-container { + font-weight: bold; + color: {$sh-redtext}; +} + +.policy-header-callout .policy-tier-separator { + padding: 0 0 0 4px; + color: {$lightgreytext}; +} + + .phui-header-action-links .phui-mobile-menu { display: none; } @@ -333,3 +353,45 @@ .phui-header-view .phui-tag-shade-indigo a { color: {$sh-indigotext}; } + +.phui-policy-section-view { + margin-bottom: 24px; +} + +.phui-policy-section-view-header { + background: {$bluebackground}; + border-bottom: 1px solid {$lightblueborder}; + padding: 4px 8px; + color: {$darkbluetext}; + margin-bottom: 8px; +} + +.phui-policy-section-view-header-text { + font-weight: bold; +} + +.phui-policy-section-view-header .phui-icon-view { + margin-right: 8px; +} + +.phui-policy-section-view-link { + float: right; +} + +.phui-policy-section-view-link .phui-icon-view { + color: {$bluetext}; +} + +.phui-policy-section-view-hint { + color: {$greytext}; + background: {$lightbluebackground}; + padding: 8px; +} + +.phui-policy-section-view-body { + padding: 0 12px; +} + +.phui-policy-section-view-inactive-rule { + color: {$greytext}; +}