diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4b138f4c4f..0df73916a6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1917,6 +1917,8 @@ phutil_register_library_map(array( 'PhabricatorAuthProviderConfigQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigQuery.php', 'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php', 'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php', + 'PhabricatorAuthProvidersGuidanceContext' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php', + 'PhabricatorAuthProvidersGuidanceEngineExtension' => 'applications/auth/guidance/PhabricatorAuthProvidersGuidanceEngineExtension.php', 'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php', 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', 'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php', @@ -2728,6 +2730,10 @@ phutil_register_library_map(array( 'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php', 'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php', 'PhabricatorGoogleAuthProvider' => 'applications/auth/provider/PhabricatorGoogleAuthProvider.php', + 'PhabricatorGuidanceContext' => 'applications/guides/guidance/PhabricatorGuidanceContext.php', + 'PhabricatorGuidanceEngine' => 'applications/guides/guidance/PhabricatorGuidanceEngine.php', + 'PhabricatorGuidanceEngineExtension' => 'applications/guides/guidance/PhabricatorGuidanceEngineExtension.php', + 'PhabricatorGuidanceMessage' => 'applications/guides/guidance/PhabricatorGuidanceMessage.php', 'PhabricatorGuideApplication' => 'applications/guides/application/PhabricatorGuideApplication.php', 'PhabricatorGuideController' => 'applications/guides/controller/PhabricatorGuideController.php', 'PhabricatorGuideInstallModule' => 'applications/guides/module/PhabricatorGuideInstallModule.php', @@ -3218,6 +3224,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php', 'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php', 'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php', + 'PhabricatorPeopleCreateGuidanceContext' => 'applications/people/guidance/PhabricatorPeopleCreateGuidanceContext.php', 'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php', 'PhabricatorPeopleDeleteController' => 'applications/people/controller/PhabricatorPeopleDeleteController.php', 'PhabricatorPeopleDetailsProfilePanel' => 'applications/people/profilepanel/PhabricatorPeopleDetailsProfilePanel.php', @@ -6730,6 +6737,8 @@ phutil_register_library_map(array( 'PhabricatorAuthProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorAuthProvidersGuidanceContext' => 'PhabricatorGuidanceContext', + 'PhabricatorAuthProvidersGuidanceEngineExtension' => 'PhabricatorGuidanceEngineExtension', 'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod', 'PhabricatorAuthRegisterController' => 'PhabricatorAuthController', 'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController', @@ -7687,6 +7696,10 @@ phutil_register_library_map(array( 'PhabricatorGlobalLock' => 'PhutilLock', 'PhabricatorGlobalUploadTargetView' => 'AphrontView', 'PhabricatorGoogleAuthProvider' => 'PhabricatorOAuth2AuthProvider', + 'PhabricatorGuidanceContext' => 'Phobject', + 'PhabricatorGuidanceEngine' => 'Phobject', + 'PhabricatorGuidanceEngineExtension' => 'Phobject', + 'PhabricatorGuidanceMessage' => 'Phobject', 'PhabricatorGuideApplication' => 'PhabricatorApplication', 'PhabricatorGuideController' => 'PhabricatorController', 'PhabricatorGuideInstallModule' => 'PhabricatorGuideModule', @@ -8256,6 +8269,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController', + 'PhabricatorPeopleCreateGuidanceContext' => 'PhabricatorGuidanceContext', 'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPeopleDeleteController' => 'PhabricatorPeopleController', 'PhabricatorPeopleDetailsProfilePanel' => 'PhabricatorProfilePanel', diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php index df168dfe14..ae5808ed78 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthListController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php @@ -94,58 +94,12 @@ final class PhabricatorAuthListController $crumbs->addTextCrumb(pht('Auth Providers')); $crumbs->setBorder(true); - $domains_key = 'auth.email-domains'; - $domains_link = $this->renderConfigLink($domains_key); - $domains_value = PhabricatorEnv::getEnvConfig($domains_key); + $guidance_context = new PhabricatorAuthProvidersGuidanceContext(); - $approval_key = 'auth.require-approval'; - $approval_link = $this->renderConfigLink($approval_key); - $approval_value = PhabricatorEnv::getEnvConfig($approval_key); - - $issues = array(); - if ($domains_value) { - $issues[] = pht( - 'Phabricator is configured with an email domain whitelist (in %s), so '. - 'only users with a verified email address at one of these %s '. - 'allowed domain(s) will be able to register an account: %s', - $domains_link, - phutil_count($domains_value), - phutil_tag('strong', array(), implode(', ', $domains_value))); - } else { - $issues[] = pht( - 'Anyone who can browse to this Phabricator install will be able to '. - 'register an account. To add email domain restrictions, configure '. - '%s.', - $domains_link); - } - - if ($approval_value) { - $issues[] = pht( - 'Administrative approvals are enabled (in %s), so all new users must '. - 'have their accounts approved by an administrator.', - $approval_link); - } else { - $issues[] = pht( - 'Administrative approvals are disabled, so users who register will '. - 'be able to use their accounts immediately. To enable approvals, '. - 'configure %s.', - $approval_link); - } - - if (!$domains_value && !$approval_value) { - $severity = PHUIInfoView::SEVERITY_WARNING; - $issues[] = pht( - 'You can safely ignore this warning if the install itself has '. - 'access controls (for example, it is deployed on a VPN) or if all of '. - 'the configured providers have access controls (for example, they are '. - 'all private LDAP or OAuth servers).'); - } else { - $severity = PHUIInfoView::SEVERITY_NOTICE; - } - - $warning = id(new PHUIInfoView()) - ->setSeverity($severity) - ->setErrors($issues); + $guidance = id(new PhabricatorGuidanceEngine()) + ->setViewer($viewer) + ->setGuidanceContext($guidance_context) + ->newInfoView(); $button = id(new PHUIButtonView()) ->setTag('a') @@ -170,7 +124,7 @@ final class PhabricatorAuthListController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setFooter(array( - $warning, + $guidance, $list, )); @@ -180,14 +134,4 @@ final class PhabricatorAuthListController ->appendChild($view); } - private function renderConfigLink($key) { - return phutil_tag( - 'a', - array( - 'href' => '/config/edit/'.$key.'/', - 'target' => '_blank', - ), - $key); - } - } diff --git a/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php new file mode 100644 index 0000000000..1302846ec3 --- /dev/null +++ b/src/applications/auth/guidance/PhabricatorAuthProvidersGuidanceContext.php @@ -0,0 +1,4 @@ +renderConfigLink($domains_key); + $domains_value = PhabricatorEnv::getEnvConfig($domains_key); + + $approval_key = 'auth.require-approval'; + $approval_link = $this->renderConfigLink($approval_key); + $approval_value = PhabricatorEnv::getEnvConfig($approval_key); + + $results = array(); + + if ($domains_value) { + $message = pht( + 'Phabricator is configured with an email domain whitelist (in %s), so '. + 'only users with a verified email address at one of these %s '. + 'allowed domain(s) will be able to register an account: %s', + $domains_link, + phutil_count($domains_value), + phutil_tag('strong', array(), implode(', ', $domains_value))); + + $results[] = $this->newGuidance('core.auth.email-domains.on') + ->setMessage($message); + } else { + $message = pht( + 'Anyone who can browse to this Phabricator install will be able to '. + 'register an account. To add email domain restrictions, configure '. + '%s.', + $domains_link); + + $results[] = $this->newGuidance('core.auth.email-domains.off') + ->setMessage($message); + } + + if ($approval_value) { + $message = pht( + 'Administrative approvals are enabled (in %s), so all new users must '. + 'have their accounts approved by an administrator.', + $approval_link); + + $results[] = $this->newGuidance('core.auth.require-approval.on') + ->setMessage($message); + } else { + $message = pht( + 'Administrative approvals are disabled, so users who register will '. + 'be able to use their accounts immediately. To enable approvals, '. + 'configure %s.', + $approval_link); + + $results[] = $this->newGuidance('core.auth.require-approval.off') + ->setMessage($message); + } + + if (!$domains_value && !$approval_value) { + $message = pht( + 'You can safely ignore these warnings if the install itself has '. + 'access controls (for example, it is deployed on a VPN) or if all of '. + 'the configured providers have access controls (for example, they are '. + 'all private LDAP or OAuth servers).'); + + $results[] = $this->newWarning('core.auth.warning') + ->setMessage($message); + } + + return $results; + } + + private function renderConfigLink($key) { + return phutil_tag( + 'a', + array( + 'href' => '/config/edit/'.$key.'/', + 'target' => '_blank', + ), + $key); + } + +} diff --git a/src/applications/guides/guidance/PhabricatorGuidanceContext.php b/src/applications/guides/guidance/PhabricatorGuidanceContext.php new file mode 100644 index 0000000000..75559fd14f --- /dev/null +++ b/src/applications/guides/guidance/PhabricatorGuidanceContext.php @@ -0,0 +1,4 @@ +guidanceContext = $guidance_context; + return $this; + } + + public function getGuidanceContext() { + return $this->guidanceContext; + } + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function newInfoView() { + $extensions = PhabricatorGuidanceEngineExtension::getAllExtensions(); + $context = $this->getGuidanceContext(); + + $keep = array(); + foreach ($extensions as $key => $extension) { + if (!$extension->canGenerateGuidance($context)) { + continue; + } + $keep[$key] = id(clone $extension); + } + + $guidance_map = array(); + foreach ($keep as $extension) { + $guidance_list = $extension->generateGuidance($context); + foreach ($guidance_list as $guidance) { + $key = $guidance->getKey(); + + if (isset($guidance_map[$key])) { + throw new Exception( + pht( + 'Two guidance extensions generated guidance with the same '. + 'key ("%s"). Each piece of guidance must have a unique key.', + $key)); + } + + $guidance_map[$key] = $guidance; + } + } + + foreach ($keep as $extension) { + $guidance_map = $extension->didGenerateGuidance($context, $guidance_map); + } + + if (!$guidance_map) { + return null; + } + + $guidance_map = msortv($guidance_map, 'getSortVector'); + + $severity = PhabricatorGuidanceMessage::SEVERITY_NOTICE; + $strength = null; + foreach ($guidance_map as $guidance) { + if ($strength !== null) { + if ($guidance->getSeverityStrength() <= $strength) { + continue; + } + } + + $strength = $guidance->getSeverityStrength(); + $severity = $guidance->getSeverity(); + } + + $severity_map = array( + PhabricatorGuidanceMessage::SEVERITY_NOTICE + => PHUIInfoView::SEVERITY_NOTICE, + PhabricatorGuidanceMessage::SEVERITY_WARNING + => PHUIInfoView::SEVERITY_WARNING, + ); + + $messages = mpull($guidance_map, 'getMessage', 'getKey'); + + return id(new PHUIInfoView()) + ->setViewer($this->getViewer()) + ->setSeverity(idx($severity_map, $severity, $severity)) + ->setErrors($messages); + } + +} diff --git a/src/applications/guides/guidance/PhabricatorGuidanceEngineExtension.php b/src/applications/guides/guidance/PhabricatorGuidanceEngineExtension.php new file mode 100644 index 0000000000..b552b10b05 --- /dev/null +++ b/src/applications/guides/guidance/PhabricatorGuidanceEngineExtension.php @@ -0,0 +1,39 @@ +getPhobjectClassConstant('GUIDANCEKEY', 64); + } + + abstract public function canGenerateGuidance( + PhabricatorGuidanceContext $context); + + abstract public function generateGuidance( + PhabricatorGuidanceContext $context); + + public function didGenerateGuidance( + PhabricatorGuidanceContext $context, + array $guidance) { + return $guidance; + } + + final protected function newGuidance($key) { + return id(new PhabricatorGuidanceMessage()) + ->setKey($key); + } + + final protected function newWarning($key) { + return $this->newGuidance($key) + ->setSeverity(PhabricatorGuidanceMessage::SEVERITY_WARNING); + } + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + +} diff --git a/src/applications/guides/guidance/PhabricatorGuidanceMessage.php b/src/applications/guides/guidance/PhabricatorGuidanceMessage.php new file mode 100644 index 0000000000..d2720c1602 --- /dev/null +++ b/src/applications/guides/guidance/PhabricatorGuidanceMessage.php @@ -0,0 +1,65 @@ +severity = $severity; + return $this; + } + + public function getSeverity() { + return $this->severity; + } + + public function setKey($key) { + $this->key = $key; + return $this; + } + + public function getKey() { + return $this->key; + } + + public function setMessage($message) { + $this->message = $message; + return $this; + } + + public function getMessage() { + return $this->message; + } + + public function getSortVector() { + return id(new PhutilSortVector()) + ->addInt($this->getPriority()); + } + + public function setPriority($priority) { + $this->priority = $priority; + return $this; + } + + public function getPriority() { + return $this->priority; + } + + public function getSeverityStrength() { + $map = array( + self::SEVERITY_NOTICE => 1, + self::SEVERITY_WARNING => 2, + ); + + return idx($map, $this->getSeverity(), 0); + } + + +} diff --git a/src/applications/people/controller/PhabricatorPeopleCreateController.php b/src/applications/people/controller/PhabricatorPeopleCreateController.php index 82a27e2f9e..04b2f4a8a4 100644 --- a/src/applications/people/controller/PhabricatorPeopleCreateController.php +++ b/src/applications/people/controller/PhabricatorPeopleCreateController.php @@ -101,9 +101,20 @@ final class PhabricatorPeopleCreateController ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $guidance_context = new PhabricatorPeopleCreateGuidanceContext(); + + $guidance = id(new PhabricatorGuidanceEngine()) + ->setViewer($admin) + ->setGuidanceContext($guidance_context) + ->newInfoView(); + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter($box); + ->setFooter( + array( + $guidance, + $box, + )); return $this->newPage() ->setTitle($title) diff --git a/src/applications/people/guidance/PhabricatorPeopleCreateGuidanceContext.php b/src/applications/people/guidance/PhabricatorPeopleCreateGuidanceContext.php new file mode 100644 index 0000000000..9740f5b4df --- /dev/null +++ b/src/applications/people/guidance/PhabricatorPeopleCreateGuidanceContext.php @@ -0,0 +1,4 @@ +errors[0]; + $list = head($this->errors); } else { $list = null; }