diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 78ae8bd461..0ea66bec26 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '1a4e0c25', + 'core.pkg.css' => 'fdb27ef9', 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -176,7 +176,7 @@ return array( 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => 'b4719c50', - 'rsrc/css/phui/phui-timeline-view.css' => 'f21db7ca', + 'rsrc/css/phui/phui-timeline-view.css' => 'e2ef62b1', 'rsrc/css/phui/phui-two-column-view.css' => '44ec4951', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -871,7 +871,7 @@ return array( 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => 'b4719c50', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => 'f21db7ca', + 'phui-timeline-view-css' => 'e2ef62b1', 'phui-two-column-view-css' => '44ec4951', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 424d72dc72..11be8a50f0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -594,6 +594,7 @@ phutil_register_library_map(array( 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/DifferentialRevisionUpdateHistoryView.php', 'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php', 'DifferentialRevisionVoidTransaction' => 'applications/differential/xaction/DifferentialRevisionVoidTransaction.php', + 'DifferentialRevisionWrongStateTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php', 'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php', 'DifferentialStoredCustomField' => 'applications/differential/customfield/DifferentialStoredCustomField.php', @@ -831,6 +832,7 @@ phutil_register_library_map(array( 'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php', 'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php', 'DiffusionQueryPathsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php', + 'DiffusionQuickSearchEngineExtension' => 'applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php', 'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php', 'DiffusionRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRawDiffQueryConduitAPIMethod.php', 'DiffusionReadmeView' => 'applications/diffusion/view/DiffusionReadmeView.php', @@ -2535,6 +2537,8 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php', 'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php', 'PhabricatorCustomFieldFulltextEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php', + 'PhabricatorCustomFieldHeraldAction' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php', + 'PhabricatorCustomFieldHeraldActionGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php', 'PhabricatorCustomFieldHeraldField' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldField.php', 'PhabricatorCustomFieldHeraldFieldGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldFieldGroup.php', 'PhabricatorCustomFieldImplementationIncompleteException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php', @@ -3200,6 +3204,7 @@ phutil_register_library_map(array( 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php', 'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php', + 'PhabricatorMonogramQuickSearchEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php', 'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php', 'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php', 'PhabricatorMotivatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php', @@ -3523,6 +3528,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileTasksController' => 'applications/people/controller/PhabricatorPeopleProfileTasksController.php', 'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php', 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', + 'PhabricatorPeopleQuickSearchEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php', 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', @@ -3783,6 +3789,9 @@ phutil_register_library_map(array( 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', + 'PhabricatorQuickSearchApplicationEngineExtension' => 'applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php', + 'PhabricatorQuickSearchEngine' => 'applications/search/engine/PhabricatorQuickSearchEngine.php', + 'PhabricatorQuickSearchEngineExtension' => 'applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', @@ -4815,6 +4824,7 @@ phutil_register_library_map(array( 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', 'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', + 'ProjectQuickSearchEngineExtension' => 'applications/project/engineextension/ProjectQuickSearchEngineExtension.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php', @@ -5638,6 +5648,7 @@ phutil_register_library_map(array( 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialRevisionVoidTransaction' => 'DifferentialRevisionTransactionType', + 'DifferentialRevisionWrongStateTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialStoredCustomField' => 'DifferentialCustomField', @@ -5878,6 +5889,7 @@ phutil_register_library_map(array( 'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryPathsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'DiffusionRawDiffQuery' => 'DiffusionFileFutureQuery', 'DiffusionRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionReadmeView' => 'DiffusionView', @@ -7868,6 +7880,8 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldEditField' => 'PhabricatorEditField', 'PhabricatorCustomFieldEditType' => 'PhabricatorEditType', 'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', + 'PhabricatorCustomFieldHeraldAction' => 'HeraldAction', + 'PhabricatorCustomFieldHeraldActionGroup' => 'HeraldActionGroup', 'PhabricatorCustomFieldHeraldField' => 'HeraldField', 'PhabricatorCustomFieldHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception', @@ -8608,6 +8622,7 @@ phutil_register_library_map(array( 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorModularTransactionType' => 'Phobject', + 'PhabricatorMonogramQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting', 'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting', 'PhabricatorMotivatorProfileMenuItem' => 'PhabricatorProfileMenuItem', @@ -9000,6 +9015,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileTasksController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPeopleQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', @@ -9315,6 +9331,9 @@ phutil_register_library_map(array( 'Phobject', 'Iterator', ), + 'PhabricatorQuickSearchApplicationEngineExtension' => 'PhabricatorQuickSearchEngineExtension', + 'PhabricatorQuickSearchEngine' => 'Phobject', + 'PhabricatorQuickSearchEngineExtension' => 'Phobject', 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', @@ -10601,6 +10620,7 @@ phutil_register_library_map(array( 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', 'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', + 'ProjectQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php index f3e2562a1c..27e03485ca 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php @@ -9,9 +9,27 @@ final class PhabricatorAuthNeedsMultiFactorController return false; } + public function shouldRequireEnabledUser() { + // Users who haven't been approved yet are allowed to enroll in MFA. We'll + // kick disabled users out later. + return false; + } + + public function shouldRequireEmailVerification() { + // Users who haven't verified their email addresses yet can still enroll + // in MFA. + return false; + } + public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + if ($viewer->getIsDisabled()) { + // We allowed unapproved and disabled users to hit this controller, but + // want to kick out disabled users now. + return new Aphront400Response(); + } + $panel = id(new PhabricatorMultiFactorSettingsPanel()) ->setUser($viewer) ->setViewer($viewer) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index f923eb7b73..0fee880378 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -137,10 +137,6 @@ abstract class PhabricatorController extends AphrontController { } if ($this->shouldRequireEnabledUser()) { - if ($user->isLoggedIn() && !$user->getIsApproved()) { - $controller = new PhabricatorAuthNeedsApprovalController(); - return $this->delegateToController($controller); - } if ($user->getIsDisabled()) { $controller = new PhabricatorDisabledUserController(); return $this->delegateToController($controller); @@ -160,6 +156,15 @@ abstract class PhabricatorController extends AphrontController { } } + // Require users sign Legalpad documents before we check if they have + // MFA. If we don't do this, they can get stuck in a state where they + // can't add MFA until they sign, and can't sign until they add MFA. + // See T13024 and PHI223. + $result = $this->requireLegalpadSignatures(); + if ($result !== null) { + return $result; + } + // Check if the user needs to configure MFA. $need_mfa = $this->shouldRequireMultiFactorEnrollment(); $have_mfa = $user->getIsEnrolledInMultiFactor(); @@ -224,46 +229,15 @@ abstract class PhabricatorController extends AphrontController { ->withPHIDs(array($application->getPHID())) ->executeOne(); } - } - - if (!$this->shouldAllowLegallyNonCompliantUsers()) { - $legalpad_class = 'PhabricatorLegalpadApplication'; - $legalpad = id(new PhabricatorApplicationQuery()) - ->setViewer($user) - ->withClasses(array($legalpad_class)) - ->withInstalled(true) - ->execute(); - $legalpad = head($legalpad); - - $doc_query = id(new LegalpadDocumentQuery()) - ->setViewer($user) - ->withSignatureRequired(1) - ->needViewerSignatures(true); - - if ($user->hasSession() && - !$user->getSession()->getIsPartial() && - !$user->getSession()->getSignedLegalpadDocuments() && - $user->isLoggedIn() && - $legalpad) { - - $sign_docs = $doc_query->execute(); - $must_sign_docs = array(); - foreach ($sign_docs as $sign_doc) { - if (!$sign_doc->getUserSignature($user->getPHID())) { - $must_sign_docs[] = $sign_doc; - } - } - if ($must_sign_docs) { - $controller = new LegalpadDocumentSignController(); - $this->getRequest()->setURIMap(array( - 'id' => head($must_sign_docs)->getID(), - )); - $this->setCurrentApplication($legalpad); + // If users need approval, require they wait here. We do this near the + // end so they can take other actions (like verifying email, signing + // documents, and enrolling in MFA) while waiting for an admin to take a + // look at things. See T13024 for more discussion. + if ($this->shouldRequireEnabledUser()) { + if ($user->isLoggedIn() && !$user->getIsApproved()) { + $controller = new PhabricatorAuthNeedsApprovalController(); return $this->delegateToController($controller); - } else { - $engine = id(new PhabricatorAuthSessionEngine()) - ->signLegalpadDocuments($user, $sign_docs); } } } @@ -558,6 +532,81 @@ abstract class PhabricatorController extends AphrontController { return $this->buildApplicationCrumbs(); } + private function requireLegalpadSignatures() { + if (!$this->shouldRequireLogin()) { + return null; + } + + if ($this->shouldAllowLegallyNonCompliantUsers()) { + return null; + } + + $viewer = $this->getViewer(); + + if (!$viewer->hasSession()) { + return null; + } + + $session = $viewer->getSession(); + if ($session->getIsPartial()) { + // If the user hasn't made it through MFA yet, require they survive + // MFA first. + return null; + } + + if ($session->getSignedLegalpadDocuments()) { + return null; + } + + if (!$viewer->isLoggedIn()) { + return null; + } + + $legalpad_class = 'PhabricatorLegalpadApplication'; + $legalpad_installed = PhabricatorApplication::isClassInstalledForViewer( + $legalpad_class, + $viewer); + if (!$legalpad_installed) { + return null; + } + + $sign_docs = id(new LegalpadDocumentQuery()) + ->setViewer($viewer) + ->withSignatureRequired(1) + ->needViewerSignatures(true) + ->setOrder('oldest') + ->execute(); + + $must_sign_docs = array(); + foreach ($sign_docs as $sign_doc) { + if (!$sign_doc->getUserSignature($viewer->getPHID())) { + $must_sign_docs[] = $sign_doc; + } + } + + if (!$must_sign_docs) { + // If nothing needs to be signed (either because there are no documents + // which require a signature, or because the user has already signed + // all of them) mark the session as good and continue. + $engine = id(new PhabricatorAuthSessionEngine()) + ->signLegalpadDocuments($viewer, $sign_docs); + + return null; + } + + $request = $this->getRequest(); + $request->setURIMap( + array( + 'id' => head($must_sign_docs)->getID(), + )); + + $application = PhabricatorApplication::getByClass($legalpad_class); + $this->setCurrentApplication($application); + + $controller = new LegalpadDocumentSignController(); + return $this->delegateToController($controller); + } + /* -( Deprecated )--------------------------------------------------------- */ diff --git a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php index b6c268d88c..98fa948722 100644 --- a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php +++ b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php @@ -159,10 +159,10 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase { $u_unverified, $u_admin, $u_public, + $u_notapproved, ), array( $u_disabled, - $u_notapproved, )); @@ -224,7 +224,7 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase { )); $this->checkAccess( - pht('Application Controller'), + pht('Application Controller, No Login Required'), id(clone $app_controller)->setConfig('login', false), $request, array( @@ -232,10 +232,10 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase { $u_unverified, $u_admin, $u_public, + $u_notapproved, ), array( $u_disabled, - $u_notapproved, )); } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 7cea29359f..ae652ba0ec 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -10,6 +10,7 @@ final class DifferentialTransactionEditor private $hasReviewTransaction = false; private $affectedPaths; private $firstBroadcast = false; + private $wasDraft = false; public function getEditorApplicationClass() { return 'PhabricatorDifferentialApplication'; @@ -166,6 +167,8 @@ final class DifferentialTransactionEditor } } + $this->wasDraft = $object->isDraft(); + return parent::expandTransactions($object, $xactions); } @@ -1581,10 +1584,6 @@ final class DifferentialTransactionEditor $this->setActingAsPHID($author_phid); } - // Mark this as the first broadcast we're sending about the revision - // so mail can generate specially. - $this->firstBroadcast = true; - $xaction = $object->getApplicationTransactionTemplate() ->setAuthorPHID($author_phid) ->setTransactionType( @@ -1612,12 +1611,31 @@ final class DifferentialTransactionEditor $xactions[] = $xaction; } - } else { - // If this revision is being created into some state other than "Draft", - // this is the first broadcast and should include sections like "SUMMARY" - // and "TEST PLAN". - if ($this->getIsNewObject()) { + } + + // If the revision is new or was a draft, and is no longer a draft, we + // might be sending the first email about it. + + // This might mean it was created directly into a non-draft state, or + // it just automatically undrafted after builds finished, or a user + // explicitly promoted it out of the draft state with an action like + // "Request Review". + + // If we haven't sent any email about it yet, mark this email as the first + // email so the mail gets enriched with "SUMMARY" and "TEST PLAN". + + $is_new = $this->getIsNewObject(); + $was_draft = $this->wasDraft; + + if (!$object->isDraft() && ($was_draft || $is_new)) { + if (!$object->getHasBroadcast()) { + // Mark this as the first broadcast we're sending about the revision + // so mail can generate specially. $this->firstBroadcast = true; + + $object + ->setHasBroadcast(true) + ->save(); } } diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index 952c76c63f..c426c411e7 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -268,6 +268,16 @@ final class DifferentialDiffExtractionEngine extends Phobject { $xactions = array(); + // If the revision isn't closed or "Accepted", write a warning into the + // transaction log. This makes it more clear when users bend the rules. + if (!$revision->isClosed() && !$revision->isAccepted()) { + $wrong_type = DifferentialRevisionWrongStateTransaction::TRANSACTIONTYPE; + + $xactions[] = id(new DifferentialTransaction()) + ->setTransactionType($wrong_type) + ->setNewValue($revision->getModernRevisionStatus()); + } + $xactions[] = id(new DifferentialTransaction()) ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) ->setIgnoreOnNoEffect(true) diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index bf4bec0abc..73f22c91e4 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -60,6 +60,7 @@ final class DifferentialRevision extends DifferentialDAO const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; const PROPERTY_DRAFT_HOLD = 'draft.hold'; + const PROPERTY_HAS_BROADCAST = 'draft.broadcast'; public static function initializeNewRevision(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -719,6 +720,15 @@ final class DifferentialRevision extends DifferentialDAO return $this->setProperty(self::PROPERTY_DRAFT_HOLD, $hold); } + public function getHasBroadcast() { + return $this->getProperty(self::PROPERTY_HAS_BROADCAST, false); + } + + public function setHasBroadcast($has_broadcast) { + return $this->setProperty(self::PROPERTY_HAS_BROADCAST, $has_broadcast); + } + + public function loadActiveBuilds(PhabricatorUser $viewer) { $diff = $this->getActiveDiff(); diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index d52e2dbeb7..190a6a4920 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -162,6 +162,11 @@ final class DifferentialRevisionAcceptTransaction 'closed. Only open revisions can be accepted.')); } + if ($object->isDraft()) { + throw new Exception( + pht('You can not accept a draft revision.')); + } + $config_key = 'differential.allow-self-accept'; if (!PhabricatorEnv::getEnvConfig($config_key)) { if ($this->isViewerRevisionAuthor($object, $viewer)) { diff --git a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index 40a512202d..53b0b8da01 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -64,11 +64,6 @@ final class DifferentialRevisionResignTransaction 'been closed. You can only resign from open revisions.')); } - if ($object->isDraft()) { - throw new Exception( - pht('You can not resign from a draft revision.')); - } - $resigned = DifferentialReviewerStatus::STATUS_RESIGNED; if ($this->getViewerReviewerStatus($object, $viewer) == $resigned) { throw new Exception( diff --git a/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php b/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php new file mode 100644 index 0000000000..e19e54199c --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php @@ -0,0 +1,41 @@ +getNewValue(); + + $status = DifferentialRevisionStatus::newForStatus($new_value); + + return pht( + 'This revision was not accepted when it landed; it landed in state %s.', + $this->renderValue($status->getDisplayName())); + } + + public function getTitleForFeed() { + return null; + } +} diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index d7734856f0..cdce8702ca 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -438,7 +438,8 @@ final class DiffusionCommitController extends DiffusionController { $repository = $drequest->getRepository(); $view = id(new PHUIPropertyListView()) - ->setUser($this->getRequest()->getUser()); + ->setUser($this->getRequest()->getUser()) + ->setObject($commit); $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($commit_phid)) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index f64b72e1f8..35fce7f03d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -46,8 +46,20 @@ final class DiffusionRepositoryController extends DiffusionController { ->withRepositoryPHIDs(array($repository->getPHID())) ->withRefTypes(array(PhabricatorRepositoryRefCursor::TYPE_BRANCH)) ->withRefNames(array($drequest->getBranch())) + ->needPositions(true) ->execute(); - if ($ref_cursors) { + + // It's possible that this branch previously existed, but has been + // deleted. Make sure we have valid cursor positions, not just cursors. + $any_positions = false; + foreach ($ref_cursors as $ref_cursor) { + if ($ref_cursor->getPositions()) { + $any_positions = true; + break; + } + } + + if ($any_positions) { // This is a valid branch, so we necessarily have some content. $page_has_content = true; } else { diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 674efbc158..90ced865f0 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -347,11 +347,11 @@ final class DiffusionURIEditor continue; } - $io_type = $uri->getIoType(); + $io_type = $uri->getEffectiveIOType(); if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) { if ($no_readwrite) { - $readwite_conflict = $uri; + $readwrite_conflict = $uri; break; } } diff --git a/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php new file mode 100644 index 0000000000..80c705b4f8 --- /dev/null +++ b/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php @@ -0,0 +1,12 @@ +getViewer(); $this->phid = $request->getURIData('phid'); diff --git a/src/applications/legalpad/query/LegalpadDocumentQuery.php b/src/applications/legalpad/query/LegalpadDocumentQuery.php index 655da955e4..3d79e9f3a1 100644 --- a/src/applications/legalpad/query/LegalpadDocumentQuery.php +++ b/src/applications/legalpad/query/LegalpadDocumentQuery.php @@ -77,23 +77,12 @@ final class LegalpadDocumentQuery return $this; } + public function newResultObject() { + return new LegalpadDocument(); + } + protected function loadPage() { - $table = new LegalpadDocument(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT d.* FROM %T d %Q %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - $documents = $table->loadAllFromArray($data); - - return $documents; + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $documents) { @@ -134,12 +123,12 @@ final class LegalpadDocumentQuery return $documents; } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->contributorPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN edge contributor ON contributor.src = d.phid AND contributor.type = %d', PhabricatorObjectHasContributorEdgeType::EDGECONST); @@ -147,79 +136,81 @@ final class LegalpadDocumentQuery if ($this->signerPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T signer ON signer.documentPHID = d.phid AND signer.signerPHID IN (%Ls)', id(new LegalpadDocumentSignature())->getTableName(), $this->signerPHIDs); } - return implode(' ', $joins); + return $joins; } - protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { - if ($this->contributorPHIDs || $this->signerPHIDs) { - return 'GROUP BY d.id'; - } else { - return ''; + protected function shouldGroupQueryResultRows() { + if ($this->contributorPHIDs) { + return true; } + + if ($this->signerPHIDs) { + return true; + } + + return parent::shouldGroupQueryResultRows(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.phid IN (%Ls)', $this->phids); } if ($this->creatorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.creatorPHID IN (%Ls)', $this->creatorPHIDs); } if ($this->dateCreatedAfter !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.dateCreated <= %d', $this->dateCreatedBefore); } if ($this->contributorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'contributor.dst IN (%Ls)', $this->contributorPHIDs); } if ($this->signatureRequired !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.requireSignature = %d', $this->signatureRequired); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } private function loadDocumentBodies(array $documents) { @@ -275,4 +266,8 @@ final class LegalpadDocumentQuery return 'PhabricatorLegalpadApplication'; } + protected function getPrimaryTableAlias() { + return 'd'; + } + } diff --git a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php index 8b218cf821..1b608b02e3 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php @@ -11,105 +11,66 @@ final class LegalpadDocumentSearchEngine return 'PhabricatorLegalpadApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - $saved->setParameter( - 'creatorPHIDs', - $this->readUsersFromRequest($request, 'creators')); - - $saved->setParameter( - 'contributorPHIDs', - $this->readUsersFromRequest($request, 'contributors')); - - $saved->setParameter( - 'withViewerSignature', - $request->getBool('withViewerSignature')); - - $saved->setParameter('createdStart', $request->getStr('createdStart')); - $saved->setParameter('createdEnd', $request->getStr('createdEnd')); - - return $saved; + public function newQuery() { + return id(new LegalpadDocumentQuery()) + ->needViewerSignatures(true); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new LegalpadDocumentQuery()) - ->needViewerSignatures(true); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Signed By')) + ->setKey('signerPHIDs') + ->setAliases(array('signer', 'signers', 'signerPHID')) + ->setDescription( + pht('Search for documents signed by given users.')), + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Creators')) + ->setKey('creatorPHIDs') + ->setAliases(array('creator', 'creators', 'creatorPHID')) + ->setDescription( + pht('Search for documents with given creators.')), + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Contributors')) + ->setKey('contributorPHIDs') + ->setAliases(array('contributor', 'contributors', 'contributorPHID')) + ->setDescription( + pht('Search for documents with given contributors.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd'), + ); + } - $creator_phids = $saved->getParameter('creatorPHIDs', array()); - if ($creator_phids) { - $query->withCreatorPHIDs($creator_phids); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['signerPHIDs']) { + $query->withSignerPHIDs($map['signerPHIDs']); } - $contributor_phids = $saved->getParameter('contributorPHIDs', array()); - if ($contributor_phids) { - $query->withContributorPHIDs($contributor_phids); + if ($map['contributorPHIDs']) { + $query->withContributorPHIDs($map['creatorPHIDs']); } - if ($saved->getParameter('withViewerSignature')) { - $viewer_phid = $this->requireViewer()->getPHID(); - if ($viewer_phid) { - $query->withSignerPHIDs(array($viewer_phid)); - } + if ($map['creatorPHIDs']) { + $query->withCreatorPHIDs($map['creatorPHIDs']); } - $start = $this->parseDateTime($saved->getParameter('createdStart')); - $end = $this->parseDateTime($saved->getParameter('createdEnd')); - - if ($start) { - $query->withDateCreatedAfter($start); + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); } - if ($end) { - $query->withDateCreatedBefore($end); + if ($map['createdEnd']) { + $query->withDateCreatedAfter($map['createdStart']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $creator_phids = $saved_query->getParameter('creatorPHIDs', array()); - $contributor_phids = $saved_query->getParameter( - 'contributorPHIDs', array()); - - $viewer_signature = $saved_query->getParameter('withViewerSignature'); - if (!$this->requireViewer()->getPHID()) { - $viewer_signature = false; - } - - $form - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'withViewerSignature', - 1, - pht('Show only documents I have signed.'), - $viewer_signature) - ->setDisabled(!$this->requireViewer()->getPHID())) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('creators') - ->setLabel(pht('Creators')) - ->setValue($creator_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('contributors') - ->setLabel(pht('Contributors')) - ->setValue($contributor_phids)); - - $this->buildDateRange( - $form, - $saved_query, - 'createdStart', - pht('Created After'), - 'createdEnd', - pht('Created Before')); - } - protected function getURI($path) { return '/legalpad/'.$path; } @@ -130,10 +91,11 @@ final class LegalpadDocumentSearchEngine $query = $this->newSavedQuery(); $query->setQueryKey($query_key); + $viewer = $this->requireViewer(); + switch ($query_key) { case 'signed': - return $query - ->setParameter('withViewerSignature', true); + return $query->setParameter('signerPHIDs', array($viewer->getPHID())); case 'all': return $query; } @@ -141,12 +103,6 @@ final class LegalpadDocumentSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $documents, - PhabricatorSavedQuery $query) { - return array(); - } - protected function renderResultList( array $documents, PhabricatorSavedQuery $query, diff --git a/src/applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php b/src/applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php new file mode 100644 index 0000000000..8161c863ba --- /dev/null +++ b/src/applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php @@ -0,0 +1,11 @@ +isLoggedIn(); } + public function shouldAllowPartialSessions() { + return true; + } + public function getExtensionOrder() { return 1200; } @@ -65,42 +69,44 @@ final class PeopleMainMenuBarExtension $view = id(new PhabricatorActionListView()) ->setViewer($viewer); - $view->addAction( - id(new PhabricatorActionView()) - ->appendChild($user_view)); + if ($this->getIsFullSession()) { + $view->addAction( + id(new PhabricatorActionView()) + ->appendChild($user_view)); - $view->addAction( - id(new PhabricatorActionView()) - ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $view->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Profile')) - ->setHref('/p/'.$viewer->getUsername().'/')); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Profile')) + ->setHref('/p/'.$viewer->getUsername().'/')); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Settings')) - ->setHref('/settings/user/'.$viewer->getUsername().'/')); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Settings')) + ->setHref('/settings/user/'.$viewer->getUsername().'/')); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Manage')) - ->setHref('/people/manage/'.$viewer->getID().'/')); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manage')) + ->setHref('/people/manage/'.$viewer->getID().'/')); - if ($application) { - $help_links = $application->getHelpMenuItems($viewer); - if ($help_links) { - foreach ($help_links as $link) { - $view->addAction($link); + if ($application) { + $help_links = $application->getHelpMenuItems($viewer); + if ($help_links) { + foreach ($help_links as $link) { + $view->addAction($link); + } } } - } - $view->addAction( - id(new PhabricatorActionView()) - ->addSigil('logout-item') - ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $view->addAction( + id(new PhabricatorActionView()) + ->addSigil('logout-item') + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + } $view->addAction( id(new PhabricatorActionView()) diff --git a/src/applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php b/src/applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php new file mode 100644 index 0000000000..565838de72 --- /dev/null +++ b/src/applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php @@ -0,0 +1,11 @@ +getProjectPHIDs(); $project_handles = array_select_keys($this->handles, $project_phids); if ($project_handles) { - $card->setProjectHandles($project_handles); + $card + ->setHideArchivedProjects(true) + ->setProjectHandles($project_handles); } $cover_phid = $object->getCoverImageThumbnailPHID(); diff --git a/src/applications/project/engineextension/ProjectQuickSearchEngineExtension.php b/src/applications/project/engineextension/ProjectQuickSearchEngineExtension.php new file mode 100644 index 0000000000..6bb50e021e --- /dev/null +++ b/src/applications/project/engineextension/ProjectQuickSearchEngineExtension.php @@ -0,0 +1,11 @@ +getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index 1a6807ec45..3a7016ca74 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -8,6 +8,7 @@ final class ProjectBoardTaskCard extends Phobject { private $owner; private $canEdit; private $coverImageFile; + private $hideArchivedProjects; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -35,6 +36,15 @@ final class ProjectBoardTaskCard extends Phobject { return $this->coverImageFile; } + public function setHideArchivedProjects($hide_archived_projects) { + $this->hideArchivedProjects = $hide_archived_projects; + return $this; + } + + public function getHideArchivedProjects() { + return $this->hideArchivedProjects; + } + public function setTask(ManiphestTask $task) { $this->task = $task; return $this; @@ -126,10 +136,12 @@ final class ProjectBoardTaskCard extends Phobject { $project_handles = $this->getProjectHandles(); // Remove any archived projects from the list. - if ($project_handles) { - foreach ($project_handles as $key => $handle) { - if ($handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { - unset($project_handles[$key]); + if ($this->hideArchivedProjects) { + if ($project_handles) { + foreach ($project_handles as $key => $handle) { + if ($handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { + unset($project_handles[$key]); + } } } } diff --git a/src/applications/search/engine/PhabricatorQuickSearchEngine.php b/src/applications/search/engine/PhabricatorQuickSearchEngine.php new file mode 100644 index 0000000000..4b67dca659 --- /dev/null +++ b/src/applications/search/engine/PhabricatorQuickSearchEngine.php @@ -0,0 +1,8 @@ +setAncestorClass(__CLASS__) + ->execute(); + + $datasources = array(); + foreach ($extensions as $extension) { + $datasources[] = $extension->newQuickSearchDatasources(); + } + return array_mergev($datasources); + } +} diff --git a/src/applications/search/typeahead/PhabricatorSearchDatasource.php b/src/applications/search/typeahead/PhabricatorSearchDatasource.php index 2a17ad01eb..ed17a31185 100644 --- a/src/applications/search/typeahead/PhabricatorSearchDatasource.php +++ b/src/applications/search/typeahead/PhabricatorSearchDatasource.php @@ -16,14 +16,8 @@ final class PhabricatorSearchDatasource } public function getComponentDatasources() { - $sources = array( - new PhabricatorPeopleDatasource(), - new PhabricatorProjectDatasource(), - new PhabricatorApplicationDatasource(), - new PhabricatorTypeaheadMonogramDatasource(), - new DiffusionRepositoryDatasource(), - new DiffusionSymbolDatasource(), - ); + $sources = id(new PhabricatorQuickSearchEngine()) + ->getAllDatasources(); // These results are always rendered in the full browse display mode, so // set the browse flag on all component sources. diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php index 09b26819c0..5f371d69f3 100644 --- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php +++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php @@ -9,11 +9,19 @@ final class PhabricatorSubscriptionsUIEventListener } public function handleEvent(PhutilEvent $event) { + $object = $event->getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: $this->handleActionEvent($event); break; case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } diff --git a/src/applications/tokens/event/PhabricatorTokenUIEventListener.php b/src/applications/tokens/event/PhabricatorTokenUIEventListener.php index bbf3438b62..047c5504bf 100644 --- a/src/applications/tokens/event/PhabricatorTokenUIEventListener.php +++ b/src/applications/tokens/event/PhabricatorTokenUIEventListener.php @@ -9,11 +9,19 @@ final class PhabricatorTokenUIEventListener } public function handleEvent(PhutilEvent $event) { + $object = $event->getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: $this->handleActionEvent($event); break; case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } diff --git a/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php b/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php new file mode 100644 index 0000000000..078cb44685 --- /dev/null +++ b/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php @@ -0,0 +1,11 @@ +shouldAppearInTransactionMail(); case self::ROLE_HERALD: return $this->shouldAppearInHerald(); + case self::ROLE_HERALDACTION: + return $this->shouldAppearInHeraldActions(); case self::ROLE_EDITENGINE: return $this->shouldAppearInEditView() || $this->shouldAppearInEditEngine(); @@ -1476,4 +1479,56 @@ abstract class PhabricatorCustomField extends Phobject { } + public function shouldAppearInHeraldActions() { + if ($this->proxy) { + return $this->proxy->shouldAppearInHeraldActions(); + } + return false; + } + + + public function getHeraldActionName() { + if ($this->proxy) { + return $this->proxy->getHeraldActionName(); + } + + return null; + } + + + public function getHeraldActionStandardType() { + if ($this->proxy) { + return $this->proxy->getHeraldActionStandardType(); + } + + return null; + } + + + public function getHeraldActionDescription($value) { + if ($this->proxy) { + return $this->proxy->getHeraldActionDescription($value); + } + + return null; + } + + + public function getHeraldActionEffectDescription($value) { + if ($this->proxy) { + return $this->proxy->getHeraldActionEffectDescription($value); + } + + return null; + } + + + public function getHeraldActionDatasource() { + if ($this->proxy) { + return $this->proxy->getHeraldActionDatasource(); + } + + return null; + } + } diff --git a/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php new file mode 100644 index 0000000000..1d35ba59a2 --- /dev/null +++ b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php @@ -0,0 +1,105 @@ +customField = $custom_field; + return $this; + } + + public function getCustomField() { + return $this->customField; + } + + public function getActionGroupKey() { + return PhabricatorCustomFieldHeraldActionGroup::ACTIONGROUPKEY; + } + + public function supportsObject($object) { + return ($object instanceof PhabricatorCustomFieldInterface); + } + + public function supportsRuleType($rule_type) { + return true; + } + + public function getActionsForObject($object) { + $viewer = PhabricatorUser::getOmnipotentUser(); + $role = PhabricatorCustomField::ROLE_HERALDACTION; + + $field_list = PhabricatorCustomField::getObjectFields($object, $role) + ->setViewer($viewer) + ->readFieldsFromStorage($object); + + $map = array(); + foreach ($field_list->getFields() as $field) { + $key = $field->getFieldKey(); + $map[$key] = id(new self()) + ->setCustomField($field); + } + + return $map; + } + + public function applyEffect($object, HeraldEffect $effect) { + $field = $this->getCustomField(); + $value = $effect->getTarget(); + $adapter = $this->getAdapter(); + + $old_value = $field->getOldValueForApplicationTransactions(); + $new_value = id(clone $field) + ->setValueFromApplicationTransactions($value) + ->getValueForStorage(); + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) + ->setMetadataValue('customfield:key', $field->getFieldKey()) + ->setOldValue($old_value) + ->setNewValue($new_value); + + $adapter->queueTransaction($xaction); + + $this->logEffect(self::DO_SET_FIELD, $value); + } + + public function getHeraldActionName() { + return $this->getCustomField()->getHeraldActionName(); + } + + public function getHeraldActionStandardType() { + return $this->getCustomField()->getHeraldActionStandardType(); + } + + protected function getDatasource() { + return $this->getCustomField()->getHeraldActionDatasource(); + } + + public function renderActionDescription($value) { + return $this->getCustomField()->getHeraldActionDescription($value); + } + + protected function getActionEffectMap() { + return array( + self::DO_SET_FIELD => array( + 'icon' => 'fa-pencil', + 'color' => 'green', + 'name' => pht('Set Field Value'), + ), + ); + } + + protected function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_SET_FIELD: + return $this->getCustomField()->getHeraldActionEffectDescription($data); + } + } + + +} diff --git a/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php new file mode 100644 index 0000000000..b8f16ab202 --- /dev/null +++ b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php @@ -0,0 +1,16 @@ +getFieldName()); + } + + public function getHeraldActionDescription($value) { + $list = $this->renderHeraldHandleList($value); + return pht('Set "%s" to: %s.', $this->getFieldName(), $list); + } + + public function getHeraldActionEffectDescription($value) { + return $this->renderHeraldHandleList($value); + } + + public function getHeraldActionStandardType() { + return HeraldAction::STANDARD_PHID_LIST; + } + + public function getHeraldActionDatasource() { + return $this->getDatasource(); + } + + private function renderHeraldHandleList($value) { + if (!is_array($value)) { + return pht('(Invalid List)'); + } else { + return $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render(); + } + } + } diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php index fede2fdb85..d893cccac9 100644 --- a/src/view/page/menu/PhabricatorMainMenuBarExtension.php +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -5,6 +5,7 @@ abstract class PhabricatorMainMenuBarExtension extends Phobject { private $viewer; private $application; private $controller; + private $isFullSession; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -33,6 +34,15 @@ abstract class PhabricatorMainMenuBarExtension extends Phobject { return $this->controller; } + public function setIsFullSession($is_full_session) { + $this->isFullSession = $is_full_session; + return $this; + } + + public function getIsFullSession() { + return $this->isFullSession; + } + final public function getExtensionKey() { return $this->getPhobjectClassConstant('MAINMENUBARKEY'); } @@ -41,6 +51,10 @@ abstract class PhabricatorMainMenuBarExtension extends Phobject { return true; } + public function shouldAllowPartialSessions() { + return false; + } + public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { if (!$viewer->isLoggedIn()) { return false; diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index f9e4032d87..ba4bda41ea 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -46,7 +46,9 @@ final class PhabricatorMainMenuView extends AphrontView { $app_button = ''; $aural = null; - if ($viewer->isLoggedIn() && $viewer->isUserActivated()) { + $is_full = $this->isFullSession($viewer); + + if ($is_full) { list($menu, $dropdowns, $aural) = $this->renderNotificationMenu(); if (array_filter($menu)) { $alerts[] = $menu; @@ -54,14 +56,18 @@ final class PhabricatorMainMenuView extends AphrontView { $menu_bar = array_merge($menu_bar, $dropdowns); $app_button = $this->renderApplicationMenuButton(); $search_button = $this->renderSearchMenuButton($header_id); - } else { + } else if (!$viewer->isLoggedIn()) { $app_button = $this->renderApplicationMenuButton(); if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { $search_button = $this->renderSearchMenuButton($header_id); } } - $search_menu = $this->renderPhabricatorSearchMenu(); + if ($search_button) { + $search_menu = $this->renderPhabricatorSearchMenu(); + } else { + $search_menu = null; + } if ($alerts) { $alerts = javelin_tag( @@ -84,7 +90,9 @@ final class PhabricatorMainMenuView extends AphrontView { $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); foreach ($extensions as $extension) { - $extension->setViewer($viewer); + $extension + ->setViewer($viewer) + ->setIsFullSession($is_full); $controller = $this->getController(); if ($controller) { @@ -96,6 +104,14 @@ final class PhabricatorMainMenuView extends AphrontView { } } + if (!$is_full) { + foreach ($extensions as $key => $extension) { + if (!$extension->shouldAllowPartialSessions()) { + unset($extensions[$key]); + } + } + } + foreach ($extensions as $key => $extension) { if (!$extension->isExtensionEnabledForViewer($extension->getViewer())) { unset($extensions[$key]); @@ -677,4 +693,38 @@ final class PhabricatorMainMenuView extends AphrontView { ); } + private function isFullSession(PhabricatorUser $viewer) { + if (!$viewer->isLoggedIn()) { + return false; + } + + if (!$viewer->isUserActivated()) { + return false; + } + + if (!$viewer->hasSession()) { + return false; + } + + $session = $viewer->getSession(); + if ($session->getIsPartial()) { + return false; + } + + if (!$session->getSignedLegalpadDocuments()) { + return false; + } + + $mfa_key = 'security.require-multi-factor-auth'; + $need_mfa = PhabricatorEnv::getEnvConfig($mfa_key); + if ($need_mfa) { + $have_mfa = $viewer->getIsEnrolledInMultiFactor(); + if (!$have_mfa) { + return false; + } + } + + return true; + } + } diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index 33e0f8ed0c..b0ae9c0044 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -315,6 +315,10 @@ background-color: {$violet}; } +.phui-timeline-icon-fill-pink { + background-color: {$pink}; +} + .phui-timeline-icon-fill-grey { background-color: #888; }