diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fdc6357f60..48a8367edb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,12 +7,12 @@ */ return array( 'names' => array( - 'core.pkg.css' => '9c8e888d', + 'core.pkg.css' => 'a93de192', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => '7d0a63a7', + 'differential.pkg.css' => '7ba78475', 'differential.pkg.js' => 'd0cd0df6', - 'diffusion.pkg.css' => 'f45955ed', + 'diffusion.pkg.css' => 'dc8e0cc2', 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', @@ -57,16 +57,16 @@ return array( 'rsrc/css/application/dashboard/dashboard.css' => 'eb458607', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'b6b0d1bb', - 'rsrc/css/application/differential/core.css' => '7ac3cabc', + 'rsrc/css/application/differential/changeset-view.css' => '3e3b0b76', + 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-icons.css' => '2941baf1', - 'rsrc/css/application/diffusion/diffusion-readme.css' => '356a4f3c', - 'rsrc/css/application/diffusion/diffusion-source.css' => '075ba788', + 'rsrc/css/application/diffusion/diffusion-icons.css' => '3311444d', + 'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb', + 'rsrc/css/application/diffusion/diffusion-source.css' => '68b30fd3', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', 'rsrc/css/application/flag/flag.css' => '5337623f', @@ -123,7 +123,7 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => '3830ab21', + 'rsrc/css/phui/phui-box.css' => 'b2d49bae', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', @@ -135,28 +135,28 @@ return array( 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', - 'rsrc/css/phui/phui-head-thing.css' => '31638812', - 'rsrc/css/phui/phui-header-view.css' => '26cffd3d', + 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', + 'rsrc/css/phui/phui-header-view.css' => '230254d3', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '3f33ab57', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', - 'rsrc/css/phui/phui-info-view.css' => '6d7c3509', + 'rsrc/css/phui/phui-info-view.css' => '28efab79', 'rsrc/css/phui/phui-list.css' => '9da2aa00', - 'rsrc/css/phui/phui-object-box.css' => '91628842', + 'rsrc/css/phui/phui-object-box.css' => '6b487c57', 'rsrc/css/phui/phui-object-item-list-view.css' => '18b2ce8e', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-profile-menu.css' => '7e92a89a', - 'rsrc/css/phui/phui-property-list-view.css' => 'b12e801c', + 'rsrc/css/phui/phui-property-list-view.css' => '1d42ee7c', 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-segment-bar-view.css' => '46342871', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => 'a0173eba', - 'rsrc/css/phui/phui-two-column-view.css' => 'e6bf86b6', + 'rsrc/css/phui/phui-two-column-view.css' => '37d704f3', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -545,17 +545,17 @@ return array( 'conpherence-update-css' => 'faf6be09', 'conpherence-widget-pane-css' => '775eaaba', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'b6b0d1bb', - 'differential-core-view-css' => '7ac3cabc', + 'differential-changeset-view-css' => '3e3b0b76', + 'differential-core-view-css' => '5b7b8ff4', 'differential-inline-comment-editor' => '64a5550f', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-icons-css' => '2941baf1', - 'diffusion-readme-css' => '356a4f3c', - 'diffusion-source-css' => '075ba788', + 'diffusion-icons-css' => '3311444d', + 'diffusion-readme-css' => '297373eb', + 'diffusion-source-css' => '68b30fd3', 'diviner-shared-css' => 'aa3656aa', 'font-aleo' => '8bdb2835', 'font-fontawesome' => 'c43323c5', @@ -805,7 +805,7 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => '3830ab21', + 'phui-box-css' => 'b2d49bae', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', @@ -822,23 +822,23 @@ return array( 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '4a1a0f5e', - 'phui-head-thing-view-css' => '31638812', - 'phui-header-view-css' => '26cffd3d', + 'phui-head-thing-view-css' => 'fd311e5f', + 'phui-header-view-css' => '230254d3', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', 'phui-icon-view-css' => '3f33ab57', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', - 'phui-info-view-css' => '6d7c3509', + 'phui-info-view-css' => '28efab79', 'phui-inline-comment-view-css' => '5953c28e', 'phui-list-view-css' => '9da2aa00', - 'phui-object-box-css' => '91628842', + 'phui-object-box-css' => '6b487c57', 'phui-object-item-list-view-css' => '18b2ce8e', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', 'phui-profile-menu-css' => '7e92a89a', - 'phui-property-list-view-css' => 'b12e801c', + 'phui-property-list-view-css' => '1d42ee7c', 'phui-remarkup-preview-css' => '1a8f2591', 'phui-segment-bar-view-css' => '46342871', 'phui-spacing-css' => '042804d6', @@ -846,7 +846,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => 'a0173eba', - 'phui-two-column-view-css' => 'e6bf86b6', + 'phui-two-column-view-css' => '37d704f3', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', @@ -1124,6 +1124,9 @@ return array( 'javelin-util', 'javelin-uri', ), + '3e3b0b76' => array( + 'phui-inline-comment-view-css', + ), '3f5d6dbf' => array( 'javelin-behavior', 'javelin-dom', @@ -1791,9 +1794,6 @@ return array( 'javelin-json', 'phabricator-draggable-list', ), - 'b6b0d1bb' => array( - 'phui-inline-comment-view-css', - ), 'bae58312' => array( 'javelin-install', 'javelin-workboard-card', diff --git a/resources/sql/autopatches/20160316.lfs.01.token.resource.sql b/resources/sql/autopatches/20160316.lfs.01.token.resource.sql new file mode 100644 index 0000000000..7be5bbda54 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.01.token.resource.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + CHANGE objectPHID tokenResource VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20160316.lfs.02.token.user.sql b/resources/sql/autopatches/20160316.lfs.02.token.user.sql new file mode 100644 index 0000000000..72174d6fe8 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.02.token.user.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + ADD userPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20160316.lfs.03.token.properties.sql b/resources/sql/autopatches/20160316.lfs.03.token.properties.sql new file mode 100644 index 0000000000..2cb4449d73 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.03.token.properties.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160316.lfs.04.token.default.sql b/resources/sql/autopatches/20160316.lfs.04.token.default.sql new file mode 100644 index 0000000000..0f0ce4abc4 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.04.token.default.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_auth.auth_temporarytoken + SET properties = '{}' WHERE properties = ''; diff --git a/resources/sql/autopatches/20160317.lfs.01.ref.sql b/resources/sql/autopatches/20160317.lfs.01.ref.sql new file mode 100644 index 0000000000..18c45c525a --- /dev/null +++ b/resources/sql/autopatches/20160317.lfs.01.ref.sql @@ -0,0 +1,11 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_gitlfsref ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + repositoryPHID VARBINARY(64) NOT NULL, + objectHash BINARY(64) NOT NULL, + byteSize BIGINT UNSIGNED NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + filePHID VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_hash` (repositoryPHID, objectHash) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bfad24494a..02a011ec72 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -473,7 +473,6 @@ phutil_register_library_map(array( 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', 'DifferentialPathField' => 'applications/differential/customfield/DifferentialPathField.php', - 'DifferentialPrimaryPaneView' => 'applications/differential/view/DifferentialPrimaryPaneView.php', 'DifferentialProjectReviewersField' => 'applications/differential/customfield/DifferentialProjectReviewersField.php', 'DifferentialProjectsField' => 'applications/differential/customfield/DifferentialProjectsField.php', 'DifferentialQueryConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryConduitAPIMethod.php', @@ -508,7 +507,6 @@ phutil_register_library_map(array( 'DifferentialRevisionControlSystem' => 'applications/differential/constants/DifferentialRevisionControlSystem.php', 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php', 'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php', - 'DifferentialRevisionDetailView' => 'applications/differential/view/DifferentialRevisionDetailView.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php', 'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php', @@ -635,6 +633,9 @@ phutil_register_library_map(array( 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', + 'DiffusionGitLFSAuthenticateWorkflow' => 'applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php', + 'DiffusionGitLFSResponse' => 'applications/diffusion/response/DiffusionGitLFSResponse.php', + 'DiffusionGitLFSTemporaryTokenType' => 'applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php', 'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php', 'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php', 'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php', @@ -1818,6 +1819,8 @@ phutil_register_library_map(array( 'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php', 'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php', 'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php', + 'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php', + 'PhabricatorAuthPasswordResetTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php', 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', 'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php', 'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php', @@ -1843,9 +1846,12 @@ phutil_register_library_map(array( 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', + 'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php', 'PhabricatorAuthTemporaryToken' => 'applications/auth/storage/PhabricatorAuthTemporaryToken.php', 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php', 'PhabricatorAuthTemporaryTokenQuery' => 'applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php', + 'PhabricatorAuthTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php', + 'PhabricatorAuthTemporaryTokenTypeModule' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php', 'PhabricatorAuthTerminateSessionController' => 'applications/auth/controller/PhabricatorAuthTerminateSessionController.php', 'PhabricatorAuthTryFactorAction' => 'applications/auth/action/PhabricatorAuthTryFactorAction.php', 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', @@ -2349,6 +2355,7 @@ phutil_register_library_map(array( 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php', 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', + 'PhabricatorFileAccessTemporaryTokenType' => 'applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php', 'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php', 'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php', 'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php', @@ -2447,6 +2454,7 @@ phutil_register_library_map(array( 'PhabricatorHandlePool' => 'applications/phid/handle/pool/PhabricatorHandlePool.php', 'PhabricatorHandlePoolTestCase' => 'applications/phid/handle/pool/__tests__/PhabricatorHandlePoolTestCase.php', 'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php', + 'PhabricatorHandleRemarkupRule' => 'applications/phid/remarkup/PhabricatorHandleRemarkupRule.php', 'PhabricatorHandlesEditField' => 'applications/transactions/editfield/PhabricatorHandlesEditField.php', 'PhabricatorHarbormasterApplication' => 'applications/harbormaster/application/PhabricatorHarbormasterApplication.php', 'PhabricatorHarbormasterConfigOptions' => 'applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php', @@ -2493,6 +2501,7 @@ phutil_register_library_map(array( 'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php', 'PhabricatorIteratedMD5PasswordHasher' => 'infrastructure/util/password/PhabricatorIteratedMD5PasswordHasher.php', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php', + 'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php', 'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', @@ -2669,6 +2678,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php', 'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php', 'PhabricatorOAuth1AuthProvider' => 'applications/auth/provider/PhabricatorOAuth1AuthProvider.php', + 'PhabricatorOAuth1SecretTemporaryTokenType' => 'applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php', 'PhabricatorOAuth2AuthProvider' => 'applications/auth/provider/PhabricatorOAuth2AuthProvider.php', 'PhabricatorOAuthAuthProvider' => 'applications/auth/provider/PhabricatorOAuthAuthProvider.php', 'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/PhabricatorOAuthClientAuthorization.php', @@ -3091,6 +3101,8 @@ phutil_register_library_map(array( 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php', + 'PhabricatorRepositoryGitLFSRef' => 'applications/repository/storage/PhabricatorRepositoryGitLFSRef.php', + 'PhabricatorRepositoryGitLFSRefQuery' => 'applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php', 'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php', 'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php', 'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php', @@ -4580,7 +4592,6 @@ phutil_register_library_map(array( 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', 'DifferentialPathField' => 'DifferentialCustomField', - 'DifferentialPrimaryPaneView' => 'AphrontView', 'DifferentialProjectReviewersField' => 'DifferentialCustomField', 'DifferentialProjectsField' => 'DifferentialCoreCustomField', 'DifferentialQueryConduitAPIMethod' => 'DifferentialConduitAPIMethod', @@ -4630,7 +4641,6 @@ phutil_register_library_map(array( 'DifferentialRevisionControlSystem' => 'Phobject', 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType', - 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine', 'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType', @@ -4757,6 +4767,9 @@ phutil_register_library_map(array( 'DiffusionGitBranch' => 'Phobject', 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', + 'DiffusionGitLFSAuthenticateWorkflow' => 'DiffusionGitSSHWorkflow', + 'DiffusionGitLFSResponse' => 'AphrontResponse', + 'DiffusionGitLFSTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionGitRequest' => 'DiffusionRequest', @@ -6127,6 +6140,8 @@ phutil_register_library_map(array( 'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController', 'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController', + 'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', + 'PhabricatorAuthPasswordResetTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorAuthProvider' => 'Phobject', 'PhabricatorAuthProviderConfig' => array( 'PhabricatorAuthDAO', @@ -6163,12 +6178,15 @@ phutil_register_library_map(array( 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', + 'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorAuthTemporaryToken' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthTemporaryTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorAuthTemporaryTokenType' => 'Phobject', + 'PhabricatorAuthTemporaryTokenTypeModule' => 'PhabricatorConfigModule', 'PhabricatorAuthTerminateSessionController' => 'PhabricatorAuthController', 'PhabricatorAuthTryFactorAction' => 'PhabricatorSystemAction', 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', @@ -6763,6 +6781,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorFileAccessTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorFileBundleLoader' => 'Phobject', 'PhabricatorFileChunk' => array( 'PhabricatorFileDAO', @@ -6882,6 +6901,7 @@ phutil_register_library_map(array( 'PhabricatorHandlePool' => 'Phobject', 'PhabricatorHandlePoolTestCase' => 'PhabricatorTestCase', 'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorHandleRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorHandlesEditField' => 'PhabricatorPHIDListEditField', 'PhabricatorHarbormasterApplication' => 'PhabricatorApplication', 'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -6928,6 +6948,7 @@ phutil_register_library_map(array( 'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase', + 'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource', 'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', @@ -7116,6 +7137,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationsApplication' => 'PhabricatorApplication', 'PhabricatorNuanceApplication' => 'PhabricatorApplication', 'PhabricatorOAuth1AuthProvider' => 'PhabricatorOAuthAuthProvider', + 'PhabricatorOAuth1SecretTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorOAuth2AuthProvider' => 'PhabricatorOAuthAuthProvider', 'PhabricatorOAuthAuthProvider' => 'PhabricatorAuthProvider', 'PhabricatorOAuthClientAuthorization' => array( @@ -7647,6 +7669,12 @@ phutil_register_library_map(array( 'PhabricatorRepositoryEngine' => 'Phobject', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', + 'PhabricatorRepositoryGitLFSRef' => array( + 'PhabricatorRepositoryDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorRepositoryGitLFSRefQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryGraphCache' => 'Phobject', 'PhabricatorRepositoryGraphStream' => 'Phobject', 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index cd1347411a..be23895bec 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -25,11 +25,18 @@ class AphrontDefaultApplicationConfiguration $content_type = idx($_SERVER, 'CONTENT_TYPE'); $is_form_data = preg_match('@^multipart/form-data@i', $content_type); - $raw_input = PhabricatorStartup::getRawInput(); - if (strlen($raw_input) && !$is_form_data) { - $data += $parser->parseQueryString($raw_input); - } else if ($_POST) { - $data += $_POST; + $request_method = idx($_SERVER, 'REQUEST_METHOD'); + if ($request_method === 'PUT') { + // For PUT requests, do nothing: in particular, do NOT read input. This + // allows us to stream input later and process very large PUT requests, + // like those coming from Git LFS. + } else { + $raw_input = PhabricatorStartup::getRawInput(); + if (strlen($raw_input) && !$is_form_data) { + $data += $parser->parseQueryString($raw_input); + } else if ($_POST) { + $data += $_POST; + } } $data += $parser->parseQueryString(idx($_SERVER, 'QUERY_STRING', '')); diff --git a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php index c96f22e338..ca959d4aef 100644 --- a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php +++ b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php @@ -37,9 +37,11 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject { $color = 'red'; break; case self::NEEDS_AUDIT: - case self::PARTIALLY_AUDITED: $color = 'orange'; break; + case self::PARTIALLY_AUDITED: + $color = 'yellow'; + break; case self::FULLY_AUDITED: $color = 'green'; break; @@ -53,11 +55,11 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject { public static function getStatusIcon($code) { switch ($code) { case self::CONCERN_RAISED: - $icon = 'fa-exclamation-triangle'; + $icon = 'fa-exclamation-circle'; break; case self::NEEDS_AUDIT: case self::PARTIALLY_AUDITED: - $icon = 'fa-exclamation-triangle'; + $icon = 'fa-exclamation-circle'; break; case self::FULLY_AUDITED: $icon = 'fa-check'; diff --git a/src/applications/audit/storage/PhabricatorAuditTransaction.php b/src/applications/audit/storage/PhabricatorAuditTransaction.php index 20203a6f68..e1f7a9d08a 100644 --- a/src/applications/audit/storage/PhabricatorAuditTransaction.php +++ b/src/applications/audit/storage/PhabricatorAuditTransaction.php @@ -117,12 +117,37 @@ final class PhabricatorAuditTransaction return 'red'; case PhabricatorAuditActionConstants::ACCEPT: return 'green'; + case PhabricatorAuditActionConstants::RESIGN: + return 'black'; + case PhabricatorAuditActionConstants::CLOSE: + return 'indigo'; } } return parent::getColor(); } + public function getIcon() { + + $type = $this->getTransactionType(); + + switch ($type) { + case PhabricatorAuditActionConstants::ACTION: + switch ($this->getNewValue()) { + case PhabricatorAuditActionConstants::CONCERN: + return 'fa-exclamation-circle'; + case PhabricatorAuditActionConstants::ACCEPT: + return 'fa-check'; + case PhabricatorAuditActionConstants::RESIGN: + return 'fa-plane'; + case PhabricatorAuditActionConstants::CLOSE: + return 'fa-check'; + } + } + + return parent::getIcon(); + } + public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index ccc66ffeb7..d98879d0ed 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -105,23 +105,17 @@ final class PhabricatorAuthOneTimeLoginController // the link in the "Welcome" email is good enough, without requiring users // to go through a second round of email verification. + $editor = id(new PhabricatorUserEditor()) + ->setActor($target_user); + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); // Nuke the token and all other outstanding password reset tokens. // There is no particular security benefit to destroying them all, but // it should reduce HackerOne reports of nebulous harm. - - PhabricatorAuthTemporaryToken::revokeTokens( - $target_user, - array($target_user->getPHID()), - array( - PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE, - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE, - )); + $editor->revokePasswordResetLinks($target_user); if ($target_email) { - id(new PhabricatorUserEditor()) - ->setActor($target_user) - ->verifyEmail($target_user, $target_email); + $editor->verifyEmail($target_user, $target_email); } unset($unguarded); @@ -133,12 +127,13 @@ final class PhabricatorAuthOneTimeLoginController // We're going to let the user reset their password without knowing // the old one. Generate a one-time token for that. $key = Filesystem::readRandomCharacters(16); + $password_type = + PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE; $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($target_user->getPHID()) - ->setTokenType( - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE) + ->setTokenResource($target_user->getPHID()) + ->setTokenType($password_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); diff --git a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php index c1f0c21cb1..6d516916eb 100644 --- a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php +++ b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php @@ -11,7 +11,7 @@ final class PhabricatorAuthRevokeTokenController $query = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs(array($viewer->getPHID())); + ->withTokenResources(array($viewer->getPHID())); if (!$is_all) { $query->withIDs(array($id)); } diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 77ba80bccb..98b6a63b5a 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -39,17 +39,6 @@ final class PhabricatorAuthSessionEngine extends Phobject { const KIND_UNKNOWN = '?'; - /** - * Temporary tokens for one time logins. - */ - const ONETIME_TEMPORARY_TOKEN_TYPE = 'login:onetime'; - - - /** - * Temporary tokens for password recovery after one time login. - */ - const PASSWORD_TEMPORARY_TOKEN_TYPE = 'login:password'; - const ONETIME_RECOVER = 'recover'; const ONETIME_RESET = 'reset'; const ONETIME_WELCOME = 'welcome'; @@ -642,11 +631,12 @@ final class PhabricatorAuthSessionEngine extends Phobject { $key = Filesystem::readRandomCharacters(32); $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key); + $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE; $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($user->getPHID()) - ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) + ->setTokenResource($user->getPHID()) + ->setTokenType($onetime_type) ->setTokenExpires(time() + phutil_units('1 day in seconds')) ->setTokenCode($key_hash) ->save(); @@ -685,11 +675,12 @@ final class PhabricatorAuthSessionEngine extends Phobject { $key = null) { $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key); + $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE; return id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) - ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) + ->withTokenResources(array($user->getPHID())) + ->withTokenTypes(array($onetime_type)) ->withTokenCodes(array($key_hash)) ->withExpired(false) ->executeOne(); diff --git a/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php b/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php new file mode 100644 index 0000000000..02f62e76be --- /dev/null +++ b/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php @@ -0,0 +1,17 @@ +getStr('totpkey'); if (strlen($key)) { // If the user is providing a key, make sure it's a key we generated. @@ -36,8 +36,8 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { $temporary_token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) - ->withTokenTypes(array(self::TEMPORARY_TOKEN_TYPE)) + ->withTokenResources(array($user->getPHID())) + ->withTokenTypes(array($totp_token_type)) ->withExpired(false) ->withTokenCodes(array(PhabricatorHash::digest($key))) ->executeOne(); @@ -55,8 +55,8 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($user->getPHID()) - ->setTokenType(self::TEMPORARY_TOKEN_TYPE) + ->setTokenResource($user->getPHID()) + ->setTokenType($totp_token_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); diff --git a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php index a22707b6cc..530bf30583 100644 --- a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php +++ b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php @@ -9,8 +9,6 @@ abstract class PhabricatorOAuth1AuthProvider const PROPERTY_CONSUMER_SECRET = 'oauth1:consumer:secret'; const PROPERTY_PRIVATE_KEY = 'oauth1:private:key'; - const TEMPORARY_TOKEN_TYPE = 'oauth1:request:secret'; - protected function getIDKey() { return self::PROPERTY_CONSUMER_KEY; } @@ -215,13 +213,14 @@ abstract class PhabricatorOAuth1AuthProvider private function saveHandshakeTokenSecret($client_code, $secret) { + $secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE; $key = $this->getHandshakeTokenKeyFromClientCode($client_code); - $type = $this->getTemporaryTokenType(self::TEMPORARY_TOKEN_TYPE); + $type = $this->getTemporaryTokenType($secret_type); // Wipe out an existing token, if one exists. $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($key)) + ->withTokenResources(array($key)) ->withTokenTypes(array($type)) ->executeOne(); if ($token) { @@ -230,7 +229,7 @@ abstract class PhabricatorOAuth1AuthProvider // Save the new secret. id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($key) + ->setTokenResource($key) ->setTokenType($type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode($secret) @@ -238,12 +237,13 @@ abstract class PhabricatorOAuth1AuthProvider } private function loadHandshakeTokenSecret($client_code) { + $secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE; $key = $this->getHandshakeTokenKeyFromClientCode($client_code); - $type = $this->getTemporaryTokenType(self::TEMPORARY_TOKEN_TYPE); + $type = $this->getTemporaryTokenType($secret_type); $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($key)) + ->withTokenResources(array($key)) ->withTokenTypes(array($type)) ->withExpired(false) ->executeOne(); @@ -263,6 +263,9 @@ abstract class PhabricatorOAuth1AuthProvider // others' toes if a user starts Mediawiki and Bitbucket auth at the // same time. + // TODO: This isn't really a proper use of the table and should get + // cleaned up some day: the type should be constant. + return $core_type.':'.$this->getProviderConfig()->getID(); } diff --git a/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php b/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php new file mode 100644 index 0000000000..b104427bfa --- /dev/null +++ b/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php @@ -0,0 +1,17 @@ +objectPHIDs = $object_phids; + public function withTokenResources(array $resources) { + $this->tokenResources = $resources; return $this; } @@ -34,41 +35,39 @@ final class PhabricatorAuthTemporaryTokenQuery return $this; } - protected function loadPage() { - $table = new PhabricatorAuthTemporaryToken(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + public function withUserPHIDs(array $phids) { + $this->userPHIDs = $phids; + return $this; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + public function newResultObject() { + return new PhabricatorAuthTemporaryToken(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->objectPHIDs !== null) { + if ($this->tokenResources !== null) { $where[] = qsprintf( - $conn_r, - 'objectPHID IN (%Ls)', - $this->objectPHIDs); + $conn, + 'tokenResource IN (%Ls)', + $this->tokenResources); } if ($this->tokenTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenType IN (%Ls)', $this->tokenTypes); } @@ -76,12 +75,12 @@ final class PhabricatorAuthTemporaryTokenQuery if ($this->expired !== null) { if ($this->expired) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenExpires <= %d', time()); } else { $where[] = qsprintf( - $conn_r, + $conn, 'tokenExpires > %d', time()); } @@ -89,14 +88,19 @@ final class PhabricatorAuthTemporaryTokenQuery if ($this->tokenCodes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenCode IN (%Ls)', $this->tokenCodes); } - $where[] = $this->buildPagingClause($conn_r); + if ($this->userPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'userPHID IN (%Ls)', + $this->userPHIDs); + } - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php index e379a6aaeb..be08f5db2d 100644 --- a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php +++ b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php @@ -3,42 +3,58 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO implements PhabricatorPolicyInterface { - // TODO: OAuth1 stores a client identifier here, which is not a real PHID. - // At some point, we should rename this column to be a little more generic. - protected $objectPHID; - + // NOTE: This is usually a PHID, but may be some other kind of resource + // identifier for some token types. + protected $tokenResource; protected $tokenType; protected $tokenExpires; protected $tokenCode; + protected $userPHID; + protected $properties; protected function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( + 'tokenResource' => 'phid', 'tokenType' => 'text64', 'tokenExpires' => 'epoch', 'tokenCode' => 'text64', + 'userPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_token' => array( - 'columns' => array('objectPHID', 'tokenType', 'tokenCode'), + 'columns' => array('tokenResource', 'tokenType', 'tokenCode'), 'unique' => true, ), 'key_expires' => array( 'columns' => array('tokenExpires'), ), + 'key_user' => array( + 'columns' => array('userPHID'), + ), ), ) + parent::getConfiguration(); } + private function newTokenTypeImplementation() { + $types = PhabricatorAuthTemporaryTokenType::getAllTypes(); + + $type = idx($types, $this->tokenType); + if ($type) { + return clone $type; + } + + return null; + } + public function getTokenReadableTypeName() { - // Eventually, it would be nice to let applications implement token types - // so we can put this in modular subclasses. - switch ($this->tokenType) { - case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: - return pht('One-Time Login Token'); - case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: - return pht('Password Reset Token'); + $type = $this->newTokenTypeImplementation(); + if ($type) { + return $type->getTokenReadableTypeName($this); } return $this->tokenType; @@ -49,10 +65,9 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO return false; } - switch ($this->tokenType) { - case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: - case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: - return true; + $type = $this->newTokenTypeImplementation(); + if ($type) { + return $type->isTokenRevocable($this); } return false; @@ -67,12 +82,12 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO public static function revokeTokens( PhabricatorUser $viewer, - array $object_phids, + array $token_resources, array $token_types) { $tokens = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs($object_phids) + ->withTokenResources($token_resources) ->withTokenTypes($token_types) ->withExpired(false) ->execute(); @@ -82,6 +97,15 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO } } + public function getTemporaryTokenProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setTemporaryTokenProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php b/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php new file mode 100644 index 0000000000..b5b0b35271 --- /dev/null +++ b/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php @@ -0,0 +1,21 @@ +getPhobjectClassConstant('TOKENTYPE', 64); + } + + final public static function getAllTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getTokenTypeConstant') + ->execute(); + } + +} diff --git a/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php b/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php new file mode 100644 index 0000000000..8f4ad9ea9b --- /dev/null +++ b/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php @@ -0,0 +1,47 @@ +getViewer(); + + $types = PhabricatorAuthTemporaryTokenType::getAllTypes(); + + $rows = array(); + foreach ($types as $type) { + $rows[] = array( + get_class($type), + $type->getTokenTypeConstant(), + $type->getTokenTypeDisplayName(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Class'), + pht('Key'), + pht('Name'), + )) + ->setColumnClasses( + array( + null, + null, + 'wide pri', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Temporary Token Types')) + ->setTable($table); + } + +} diff --git a/src/applications/differential/constants/DifferentialChangeType.php b/src/applications/differential/constants/DifferentialChangeType.php index 2bf4b217a7..7ee3c89b65 100644 --- a/src/applications/differential/constants/DifferentialChangeType.php +++ b/src/applications/differential/constants/DifferentialChangeType.php @@ -81,6 +81,21 @@ final class DifferentialChangeType extends Phobject { return idx($icons, $type, 'fa-file'); } + public static function getIconColorForFileType($type) { + static $icons = array( + self::FILE_TEXT => 'black', + self::FILE_IMAGE => 'black', + self::FILE_BINARY => 'green', + self::FILE_DIRECTORY => 'blue', + self::FILE_SYMLINK => 'blue', + self::FILE_DELETED => 'red', + self::FILE_NORMAL => 'black', + self::FILE_SUBMODULE => 'blue', + ); + + return idx($icons, $type, 'black'); + } + public static function isOldLocationChangeType($type) { static $types = array( self::TYPE_MOVE_AWAY => true, diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 1aba876c68..43252c07f6 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -28,7 +28,8 @@ abstract class DifferentialController extends PhabricatorController { $viewer = $this->getViewer(); $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $have_owners = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorOwnersApplication', diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index 716a183b5b..b2c497f6ac 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -126,27 +126,40 @@ final class DifferentialDiffViewController extends DifferentialController { ->setRenderingReferences($refs) ->setStandaloneURI('/differential/changeset/') ->setDiff($diff) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTitle(pht('Diff %d', $diff->getID())) ->setUser($request->getUser()); + $title = pht('Diff %d', $diff->getID()); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Diff %d', $diff->getID())); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title); $prop_box = id(new PHUIObjectBoxView()) ->setHeader($property_head) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($property_view) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + + )) + ->setFooter(array( $prop_box, $table_of_contents, $details, - ), - array( - 'title' => pht('Diff View'), )); + + $page = $this->newPage() + ->setTitle(pht('Diff View')) + ->setCrumbs($crumbs) + ->appendChild($view); + return $page; } private function loadSelectableRevisions( diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index cad2932a0c..75602af0a6 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -40,7 +40,6 @@ final class DifferentialRevisionViewController extends DifferentialController { $revision->attachActiveDiff(last($diffs)); $diff_vs = $request->getInt('vs'); - $target_id = $request->getInt('id'); $target = idx($diffs, $target_id, end($diffs)); @@ -210,14 +209,10 @@ final class DifferentialRevisionViewController extends DifferentialController { $commits_for_links = array(); } - $revision_detail = id(new DifferentialRevisionDetailView()) - ->setUser($viewer) - ->setRevision($revision) - ->setDiff(end($diffs)) - ->setCustomFields($field_list) - ->setURI($request->getRequestURI()); - - $actions = $this->getRevisionActions($revision); + $header = $this->buildHeader($revision); + $subheader = $this->buildSubheaderView($revision); + $details = $this->buildDetails($revision, $field_list); + $curtain = $this->buildCurtain($revision); $whitespace = $request->getStr( 'whitespace', @@ -232,21 +227,16 @@ final class DifferentialRevisionViewController extends DifferentialController { $symbol_indexes = array(); } - $revision_detail->setActions($actions); - $revision_detail->setUser($viewer); - - $revision_detail_box = $revision_detail->render(); - $revision_warnings = $this->buildRevisionWarnings( $revision, $field_list, $warning_handle_map, $handles); + $info_view = null; if ($revision_warnings) { - $revision_warnings = id(new PHUIInfoView()) + $info_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors($revision_warnings); - $revision_detail_box->setInfoView($revision_warnings); } $detail_diffs = array_select_keys( @@ -277,39 +267,31 @@ final class DifferentialRevisionViewController extends DifferentialController { $comment_view->setQuoteTargetID('comment-content'); } - $wrap_id = celerity_generate_unique_node_id(); - $comment_view = phutil_tag( - 'div', - array( - 'id' => $wrap_id, - ), - $comment_view); + $changeset_view = id(new DifferentialChangesetListView()) + ->setChangesets($changesets) + ->setVisibleChangesets($visible_changesets) + ->setStandaloneURI('/differential/changeset/') + ->setRawFileURIs( + '/differential/changeset/?view=old', + '/differential/changeset/?view=new') + ->setUser($viewer) + ->setDiff($target) + ->setRenderingReferences($rendering_references) + ->setVsMap($vs_map) + ->setWhitespace($whitespace) + ->setSymbolIndexes($symbol_indexes) + ->setTitle(pht('Diff %s', $target->getID())) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - $changeset_view = new DifferentialChangesetListView(); - $changeset_view->setChangesets($changesets); - $changeset_view->setVisibleChangesets($visible_changesets); + if ($repository) { + $changeset_view->setRepository($repository); + } if (!$viewer_is_anonymous) { $changeset_view->setInlineCommentControllerURI( '/differential/comment/inline/edit/'.$revision->getID().'/'); } - $changeset_view->setStandaloneURI('/differential/changeset/'); - $changeset_view->setRawFileURIs( - '/differential/changeset/?view=old', - '/differential/changeset/?view=new'); - - $changeset_view->setUser($viewer); - $changeset_view->setDiff($target); - $changeset_view->setRenderingReferences($rendering_references); - $changeset_view->setVsMap($vs_map); - $changeset_view->setWhitespace($whitespace); - if ($repository) { - $changeset_view->setRepository($repository); - } - $changeset_view->setSymbolIndexes($symbol_indexes); - $changeset_view->setTitle(pht('Diff %s', $target->getID())); - $diff_history = id(new DifferentialRevisionUpdateHistoryView()) ->setUser($viewer) ->setDiffs($diffs) @@ -344,71 +326,9 @@ final class DifferentialRevisionViewController extends DifferentialController { $comment_form = null; if (!$viewer_is_anonymous) { - $draft = id(new PhabricatorDraft())->loadOneWhere( - 'authorPHID = %s AND draftKey = %s', - $viewer->getPHID(), - 'differential-comment-'.$revision->getID()); - - $reviewers = array(); - $ccs = array(); - if ($draft) { - $reviewers = idx($draft->getMetadata(), 'reviewers', array()); - $ccs = idx($draft->getMetadata(), 'ccs', array()); - if ($reviewers || $ccs) { - $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs)); - $reviewers = array_select_keys($handles, $reviewers); - $ccs = array_select_keys($handles, $ccs); - } - } - - $comment_form = new DifferentialAddCommentView(); - $comment_form->setRevision($revision); - - $review_warnings = array(); - foreach ($field_list->getFields() as $field) { - $review_warnings[] = $field->getWarningsForDetailView(); - } - $review_warnings = array_mergev($review_warnings); - - if ($review_warnings) { - $review_warnings_panel = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->setErrors($review_warnings); - $comment_form->setInfoView($review_warnings_panel); - } - - $comment_form->setActions($this->getRevisionCommentActions($revision)); - $action_uri = $this->getApplicationURI( - 'comment/save/'.$revision->getID().'/'); - - $comment_form->setActionURI($action_uri); - $comment_form->setUser($viewer); - $comment_form->setDraft($draft); - $comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID')); - $comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID')); - - // TODO: This just makes the "Z" key work. Generalize this and remove - // it at some point. - $comment_form = phutil_tag( - 'div', - array( - 'class' => 'differential-add-comment-panel', - ), - $comment_form); + $comment_form = $this->buildCommentForm($revision, $field_list); } - $pane_id = celerity_generate_unique_node_id(); - Javelin::initBehavior( - 'differential-keyboard-navigation', - array( - 'haunt' => $pane_id, - )); - Javelin::initBehavior('differential-user-select'); - - $page_pane = id(new DifferentialPrimaryPaneView()) - ->setID($pane_id) - ->appendChild($comment_view); - $signatures = DifferentialRequiredSignaturesField::loadForRevision( $revision); $missing_signatures = false; @@ -418,21 +338,17 @@ final class DifferentialRevisionViewController extends DifferentialController { } } + $footer = array(); + $signature_message = null; if ($missing_signatures) { $signature_message = id(new PHUIInfoView()) - ->setErrors( - array( - array( - phutil_tag('strong', array(), pht('Content Hidden:')), - ' ', - pht( - 'The content of this revision is hidden until the author has '. - 'signed all of the required legal agreements.'), - ), - )); - $page_pane->appendChild($signature_message); + ->setTitle(pht('Content Hidden')) + ->appendChild( + pht( + 'The content of this revision is hidden until the author has '. + 'signed all of the required legal agreements.')); } else { - $page_pane->appendChild( + $footer[] = array( $diff_history, $warning, @@ -440,37 +356,28 @@ final class DifferentialRevisionViewController extends DifferentialController { $toc_view, $other_view, $changeset_view, - )); + ); } if ($comment_form) { - $page_pane->appendChild($comment_form); + $footer[] = $comment_form; } else { // TODO: For now, just use this to get "Login to Comment". - $page_pane->appendChild( - id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setRequestURI($request->getRequestURI())); + $footer[] = id(new PhabricatorApplicationTransactionCommentView()) + ->setUser($viewer) + ->setRequestURI($request->getRequestURI()); } $object_id = 'D'.$revision->getID(); - $operations_box = $this->buildOperationsBox($revision); - $content = array( - $operations_box, - $revision_detail_box, - $diff_detail_box, - $unit_box, - $page_pane, - ); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($object_id, '/'.$object_id); + $crumbs->setBorder(true); $prefs = $viewer->loadPreferences(); - $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; + $nav = null; if ($prefs->getPreference($pref_filetree)) { $collapsed = $prefs->getPreference( PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED, @@ -481,15 +388,38 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setBaseURI(new PhutilURI('/D'.$revision->getID())) ->setCollapsed((bool)$collapsed) ->build($changesets); - } else { - $nav = null; } - $page = $this->newPage() + // Haunt Mode + $pane_id = celerity_generate_unique_node_id(); + Javelin::initBehavior( + 'differential-keyboard-navigation', + array( + 'haunt' => $pane_id, + )); + Javelin::initBehavior('differential-user-select'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setCurtain($curtain) + ->setID($pane_id) + ->setMainColumn(array( + $operations_box, + $info_view, + $details, + $diff_detail_box, + $unit_box, + $comment_view, + $signature_message, + )) + ->setFooter($footer); + + $page = $this->newPage() ->setTitle($object_id.' '.$revision->getTitle()) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($revision->getPHID())) - ->appendChild($content); + ->appendChild($view); if ($nav) { $page->setNavigation($nav); @@ -498,59 +428,183 @@ final class DifferentialRevisionViewController extends DifferentialController { return $page; } - private function getRevisionActions(DifferentialRevision $revision) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader(DifferentialRevision $revision) { + $view = id(new PHUIHeaderView()) + ->setHeader($revision->getTitle($revision)) + ->setUser($this->getViewer()) + ->setPolicyObject($revision) + ->setHeaderIcon('fa-cog'); + + $status = $revision->getStatus(); + $status_name = + DifferentialRevisionStatus::renderFullDescription($status); + + $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); + + return $view; + } + + private function buildSubheaderView(DifferentialRevision $revision) { + $viewer = $this->getViewer(); + + $author_phid = $revision->getAuthorPHID(); + + $author = $viewer->renderHandle($author_phid)->render(); + $date = phabricator_datetime($revision->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $handles = $viewer->loadHandles(array($author_phid)); + $image_uri = $handles[$author_phid]->getImageURI(); + $image_href = $handles[$author_phid]->getURI(); + + $content = pht('Authored by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + + private function buildDetails( + DifferentialRevision $revision, + $custom_fields) { + $viewer = $this->getViewer(); + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + if ($custom_fields) { + $custom_fields->appendFieldsToPropertyList( + $revision, + $viewer, + $properties); + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('DETAILS')); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + } + + private function buildCurtain(DifferentialRevision $revision) { + $viewer = $this->getViewer(); $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); + $curtain = $this->newCurtainView($revision); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $revision, PhabricatorPolicyCapability::CAN_EDIT); - $actions = array(); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setHref("/differential/revision/edit/{$revision_id}/") + ->setName(pht('Edit Revision')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setHref("/differential/revision/edit/{$revision_id}/") - ->setName(pht('Edit Revision')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-upload') - ->setHref("/differential/revision/update/{$revision_id}/") - ->setName(pht('Update Diff')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-upload') + ->setHref("/differential/revision/update/{$revision_id}/") + ->setName(pht('Update Diff')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); $this->requireResource('phabricator-object-selector-css'); $this->requireResource('javelin-behavior-phabricator-object-selector'); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-link') - ->setName(pht('Edit Dependencies')) - ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/") - ->setWorkflow(true) - ->setDisabled(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-link') + ->setName(pht('Edit Dependencies')) + ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/") + ->setWorkflow(true) + ->setDisabled(!$can_edit)); $maniphest = 'PhabricatorManiphestApplication'; if (PhabricatorApplication::isClassInstalled($maniphest)) { - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-anchor') - ->setName(pht('Edit Maniphest Tasks')) - ->setHref("/search/attach/{$revision_phid}/TASK/") - ->setWorkflow(true) - ->setDisabled(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-anchor') + ->setName(pht('Edit Maniphest Tasks')) + ->setHref("/search/attach/{$revision_phid}/TASK/") + ->setWorkflow(true) + ->setDisabled(!$can_edit)); } $request_uri = $this->getRequest()->getRequestURI(); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-download') - ->setName(pht('Download Raw Diff')) - ->setHref($request_uri->alter('download', 'true')); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-download') + ->setName(pht('Download Raw Diff')) + ->setHref($request_uri->alter('download', 'true'))); - return $actions; + return $curtain; + } + + private function buildCommentForm( + DifferentialRevision $revision, + $field_list) { + + $viewer = $this->getViewer(); + + $draft = id(new PhabricatorDraft())->loadOneWhere( + 'authorPHID = %s AND draftKey = %s', + $viewer->getPHID(), + 'differential-comment-'.$revision->getID()); + + $reviewers = array(); + $ccs = array(); + if ($draft) { + $reviewers = idx($draft->getMetadata(), 'reviewers', array()); + $ccs = idx($draft->getMetadata(), 'ccs', array()); + if ($reviewers || $ccs) { + $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs)); + $reviewers = array_select_keys($handles, $reviewers); + $ccs = array_select_keys($handles, $ccs); + } + } + + $comment_form = id(new DifferentialAddCommentView()) + ->setRevision($revision); + + $review_warnings = array(); + foreach ($field_list->getFields() as $field) { + $review_warnings[] = $field->getWarningsForDetailView(); + } + $review_warnings = array_mergev($review_warnings); + + if ($review_warnings) { + $review_warnings_panel = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors($review_warnings); + $comment_form->setInfoView($review_warnings_panel); + } + + $action_uri = $this->getApplicationURI( + 'comment/save/'.$revision->getID().'/'); + + $comment_form->setActions($this->getRevisionCommentActions($revision)) + ->setActionURI($action_uri) + ->setUser($viewer) + ->setDraft($draft) + ->setReviewers(mpull($reviewers, 'getFullName', 'getPHID')) + ->setCCs(mpull($ccs, 'getFullName', 'getPHID')); + + // TODO: This just makes the "Z" key work. Generalize this and remove + // it at some point. + $comment_form = phutil_tag( + 'div', + array( + 'class' => 'differential-add-comment-panel', + ), + $comment_form); + return $comment_form; } private function getRevisionCommentActions(DifferentialRevision $revision) { @@ -558,7 +612,7 @@ final class DifferentialRevisionViewController extends DifferentialController { DifferentialAction::ACTION_COMMENT => true, ); - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $viewer_phid = $viewer->getPHID(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); @@ -814,11 +868,12 @@ final class DifferentialRevisionViewController extends DifferentialController { $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Similar Open Revisions')); + ->setHeader(pht('Recent Similar Revisions')); $view = id(new DifferentialRevisionListView()) ->setHeader($header) ->setRevisions($revisions) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUser($viewer); $phids = $view->getRequiredHandlePHIDs(); @@ -845,7 +900,7 @@ final class DifferentialRevisionViewController extends DifferentialController { assert_instances_of($changesets, 'DifferentialChangeset'); assert_instances_of($vs_changesets, 'DifferentialChangeset'); - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); id(new DifferentialHunkQuery()) ->setViewer($viewer) @@ -978,7 +1033,8 @@ final class DifferentialRevisionViewController extends DifferentialController { } $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Diff Detail')) + ->setHeaderText(pht('DIFF DETAIL')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUser($viewer); $last_tab = null; @@ -1061,7 +1117,8 @@ final class DifferentialRevisionViewController extends DifferentialController { } $box_view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Active Operations')); + ->setHeaderText(pht('ACTIVE OPERATIONS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return id(new DrydockRepositoryOperationStatusView()) ->setUser($viewer) @@ -1074,11 +1131,11 @@ final class DifferentialRevisionViewController extends DifferentialController { DifferentialRevision $revision) { $viewer = $this->getViewer(); - if (!$diff->getUnitMessages()) { + if (!$diff->getBuildable()) { return null; } - if (!$diff->getBuildable()) { + if (!$diff->getUnitMessages()) { return null; } @@ -1109,6 +1166,7 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setBuildable($diff->getBuildable()) ->setUnitMessages($diff->getUnitMessages()) ->setLimit(5) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setShowViewAll(true); } diff --git a/src/applications/differential/customfield/DifferentialAuthorField.php b/src/applications/differential/customfield/DifferentialAuthorField.php index bac57755ab..fbd74dd2ab 100644 --- a/src/applications/differential/customfield/DifferentialAuthorField.php +++ b/src/applications/differential/customfield/DifferentialAuthorField.php @@ -20,7 +20,7 @@ final class DifferentialAuthorField } public function shouldAppearInPropertyView() { - return true; + return false; } public function renderPropertyViewLabel() { diff --git a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php index d0bd5917dc..6c178f44e0 100644 --- a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php +++ b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php @@ -70,8 +70,6 @@ final class DifferentialHovercardEngineExtension $hovercard->addField(pht('Summary'), $summary); } - $tag = DifferentialRevisionDetailView::renderTagForRevision($revision); - $hovercard->addTag($tag); } } diff --git a/src/applications/differential/view/DifferentialAddCommentView.php b/src/applications/differential/view/DifferentialAddCommentView.php index 2d586e7be4..f32c935e12 100644 --- a/src/applications/differential/view/DifferentialAddCommentView.php +++ b/src/applications/differential/view/DifferentialAddCommentView.php @@ -163,7 +163,7 @@ final class DifferentialAddCommentView extends AphrontView { $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $header_text = $is_serious ? pht('Add Comment') - : pht('Leap Into Action'); + : pht('Leap Into Action!'); $header = id(new PHUIHeaderView()) ->setHeader($header_text); diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 0cd2923018..f0c611c609 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -8,6 +8,8 @@ final class DifferentialChangesetListView extends AphrontView { private $inlineURI; private $renderURI = '/differential/changeset/'; private $whitespace; + private $background; + private $header; private $standaloneURI; private $leftRawFileURI; @@ -112,6 +114,16 @@ final class DifferentialChangesetListView extends AphrontView { return $this; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + + public function setHeader($header) { + $this->header = $header; + return $this; + } + public function render() { $viewer = $this->getViewer(); @@ -240,8 +252,12 @@ final class DifferentialChangesetListView extends AphrontView { )); } - $header = id(new PHUIHeaderView()) - ->setHeader($this->getTitle()); + if ($this->header) { + $header = $this->header; + } else { + $header = id(new PHUIHeaderView()) + ->setHeader($this->getTitle()); + } $content = phutil_tag( 'div', @@ -253,6 +269,7 @@ final class DifferentialChangesetListView extends AphrontView { $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground($this->background) ->setCollapsed(true) ->appendChild($content); diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php index ebacabc3f3..639b62fc4b 100644 --- a/src/applications/differential/view/DifferentialLocalCommitsView.php +++ b/src/applications/differential/view/DifferentialLocalCommitsView.php @@ -127,6 +127,7 @@ final class DifferentialLocalCommitsView extends AphrontView { return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Local Commits')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } diff --git a/src/applications/differential/view/DifferentialPrimaryPaneView.php b/src/applications/differential/view/DifferentialPrimaryPaneView.php deleted file mode 100644 index cfa4215fd5..0000000000 --- a/src/applications/differential/view/DifferentialPrimaryPaneView.php +++ /dev/null @@ -1,23 +0,0 @@ -id = $id; - return $this; - } - - public function render() { - - return phutil_tag( - 'div', - array( - 'class' => 'differential-primary-pane', - 'id' => $this->id, - ), - $this->renderChildren()); - } - -} diff --git a/src/applications/differential/view/DifferentialRevisionDetailView.php b/src/applications/differential/view/DifferentialRevisionDetailView.php deleted file mode 100644 index a8436811c2..0000000000 --- a/src/applications/differential/view/DifferentialRevisionDetailView.php +++ /dev/null @@ -1,121 +0,0 @@ -uri = $uri; - return $this; - } - public function getURI() { - return $this->uri; - } - - public function setDiff(DifferentialDiff $diff) { - $this->diff = $diff; - return $this; - } - private function getDiff() { - return $this->diff; - } - - public function setRevision(DifferentialRevision $revision) { - $this->revision = $revision; - return $this; - } - - public function setActions(array $actions) { - $this->actions = $actions; - return $this; - } - private function getActions() { - return $this->actions; - } - - public function setActionList(PhabricatorActionListView $list) { - $this->actionList = $list; - return $this; - } - - public function getActionList() { - return $this->actionList; - } - - public function setCustomFields(PhabricatorCustomFieldList $list) { - $this->customFields = $list; - return $this; - } - - public function render() { - - $this->requireResource('differential-core-view-css'); - - $revision = $this->revision; - $user = $this->getUser(); - - $header = $this->renderHeader($revision); - - $actions = id(new PhabricatorActionListView()) - ->setUser($user) - ->setObject($revision); - foreach ($this->getActions() as $action) { - $actions->addAction($action); - } - - $properties = id(new PHUIPropertyListView()) - ->setUser($user) - ->setObject($revision); - - $properties->setHasKeyboardShortcuts(true); - $properties->setActionList($actions); - $this->setActionList($actions); - - $field_list = $this->customFields; - if ($field_list) { - $field_list->appendFieldsToPropertyList( - $revision, - $user, - $properties); - } - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - - return $object_box; - } - - private function renderHeader(DifferentialRevision $revision) { - $view = id(new PHUIHeaderView()) - ->setHeader($revision->getTitle($revision)) - ->setUser($this->getUser()) - ->setPolicyObject($revision); - - $status = $revision->getStatus(); - $status_name = - DifferentialRevisionStatus::renderFullDescription($status); - - $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); - - return $view; - } - - public static function renderTagForRevision( - DifferentialRevision $revision) { - - $status = $revision->getStatus(); - $status_name = - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); - - return id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setName($status_name); - } - -} diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 92394fcb3e..fbafb5a6cc 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -11,6 +11,7 @@ final class DifferentialRevisionListView extends AphrontView { private $header; private $noDataString; private $noBox; + private $background = null; public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; @@ -38,6 +39,11 @@ final class DifferentialRevisionListView extends AphrontView { return $this; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + public function getRequiredHandlePHIDs() { $phids = array(); foreach ($this->revisions as $revision) { @@ -192,6 +198,7 @@ final class DifferentialRevisionListView extends AphrontView { if ($this->header && !$this->noBox) { $list->setFlush(true); $list = id(new PHUIObjectBoxView()) + ->setBackground($this->background) ->setObjectList($list); if ($this->header instanceof PHUIHeaderView) { diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php index 082344a492..5ce766afb9 100644 --- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php +++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php @@ -305,7 +305,7 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView { return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Revision Update History')) - ->setFlush(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($content); } diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index 3a025e90c5..5c76469b5e 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -48,26 +48,37 @@ final class DiffusionBranchTableController extends DiffusionController { ->withRepository($repository) ->execute(); - $view = id(new DiffusionBranchTableView()) + $table = id(new DiffusionBranchTableView()) ->setUser($viewer) ->setBranches($branches) ->setCommits($commits) ->setDiffusionRequest($drequest); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Branches')) - ->setTable($view); - - $content = $panel; + $content = id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); } $crumbs = $this->buildCrumbs( array( 'branches' => true, )); + $crumbs->setBorder(true); $pager_box = $this->renderTablePagerBox($pager); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Branches')) + ->setHeaderIcon('fa-code-fork'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $content, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -77,8 +88,7 @@ final class DiffusionBranchTableController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index a2e9a3a4b3..56d00d53ce 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -55,20 +55,17 @@ final class DiffusionBrowseController extends DiffusionController { } private function browseSearch() { + $drequest = $this->getDiffusionRequest(); + $header = $this->buildHeaderView($drequest); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); + $search_form = $this->renderSearchForm(); + $search_results = $this->renderSearchResults(); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content = array(); - - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = false); - $content[] = $this->renderSearchResults(); + $search_form = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Search')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($search_form); $crumbs = $this->buildCrumbs( array( @@ -76,6 +73,14 @@ final class DiffusionBrowseController extends DiffusionController { 'path' => true, 'view' => 'browse', )); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $search_form, + $search_results, + )); return $this->newPage() ->setTitle( @@ -84,7 +89,7 @@ final class DiffusionBrowseController extends DiffusionController { $drequest->getRepository()->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild($view); } private function browseFile() { @@ -187,7 +192,25 @@ final class DiffusionBrowseController extends DiffusionController { } $data = $file->loadFileData(); - if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { + + $ref = $this->getGitLFSRef($repository, $data); + if ($ref) { + if ($view == 'git-lfs') { + $file = $this->loadGitLFSFile($ref); + + // Rename the file locally so we generate a better vanity URI for + // it. In storage, it just has a name like "lfs-13f9a94c0923...", + // since we don't get any hints about possible human-readable names + // at upload time. + $basename = basename($drequest->getPath()); + $file->makeEphemeral(); + $file->setName($basename); + + return $file->getRedirectResponse(); + } else { + $corpus = $this->buildGitLFSCorpus($ref); + } + } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { $file_uri = $file->getBestURI(); if ($file->isViewableImage()) { @@ -218,20 +241,18 @@ final class DiffusionBrowseController extends DiffusionController { require_celerity_resource('diffusion-source-css'); // Render the page. - $view = $this->buildActionView($drequest); - $action_list = $this->enrichActionView( + $view = $this->buildCurtain($drequest); + $curtain = $this->enrichCurtain( $view, $drequest, $show_blame, $show_color); - $properties = $this->buildPropertyView($drequest, $action_list); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); + $properties = $this->buildPropertyView($drequest); + $header = $this->buildHeaderView($drequest); + $header->setHeaderIcon('fa-file-code-o'); $content = array(); - $content[] = $object_box; $follow = $request->getStr('follow'); if ($follow) { @@ -277,17 +298,31 @@ final class DiffusionBrowseController extends DiffusionController { 'path' => true, 'view' => 'browse', )); + $crumbs->setBorder(true); $basename = basename($this->getDiffusionRequest()->getPath()); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $content, + )); + + if ($properties) { + $view->addPropertySection(pht('DETAILS'), $properties); + } + + $title = array($basename, $repository->getDisplayName()); + return $this->newPage() - ->setTitle( - array( - $basename, - $repository->getDisplayName(), - )) + ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild( + array( + $view, + )); + } public function browseDirectory( @@ -300,23 +335,21 @@ final class DiffusionBrowseController extends DiffusionController { $reason = $results->getReasonForEmptyResultSet(); - $content = array(); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); + $curtain = $this->buildCurtain($drequest); + $details = $this->buildPropertyView($drequest); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); + $header = $this->buildHeaderView($drequest); + $header->setHeaderIcon('fa-folder-open'); - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = true); + $search_form = $this->renderSearchForm(); + $empty_result = null; + $browse_panel = null; if (!$results->isValidResults()) { $empty_result = new DiffusionEmptyResultView(); $empty_result->setDiffusionRequest($drequest); $empty_result->setDiffusionBrowseResultSet($results); $empty_result->setView($request->getStr('view')); - $content[] = $empty_result; } else { $phids = array(); foreach ($results->getPaths() as $result) { @@ -331,21 +364,30 @@ final class DiffusionBrowseController extends DiffusionController { $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); - $browse_table = new DiffusionBrowseTableView(); - $browse_table->setDiffusionRequest($drequest); - $browse_table->setHandles($handles); - $browse_table->setPaths($results->getPaths()); - $browse_table->setUser($request->getUser()); + $browse_table = id(new DiffusionBrowseTableView()) + ->setDiffusionRequest($drequest) + ->setHandles($handles) + ->setPaths($results->getPaths()) + ->setUser($request->getUser()); - $browse_panel = new PHUIObjectBoxView(); - $browse_panel->setHeaderText($drequest->getPath(), '/'); - $browse_panel->setTable($browse_table); + $browse_header = id(new PHUIHeaderView()) + ->setHeader(nonempty(basename($drequest->getPath()), '/')) + ->setHeaderIcon('fa-folder-open'); - $content[] = $browse_panel; + $browse_panel = id(new PHUIObjectBoxView()) + ->setHeader($browse_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($browse_table); + + $browse_panel->setShowHide( + array(pht('Show Search')), + pht('Hide Search'), + $search_form, + '#'); } - $content[] = $this->buildOpenRevisions(); - $content[] = $this->renderDirectoryReadme($results); + $open_revisions = $this->buildOpenRevisions(); + $readme = $this->renderDirectoryReadme($results); $crumbs = $this->buildCrumbs( array( @@ -355,18 +397,34 @@ final class DiffusionBrowseController extends DiffusionController { )); $pager_box = $this->renderTablePagerBox($pager); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $empty_result, + $browse_panel, + )) + ->setFooter(array( + $open_revisions, + $readme, + $pager_box, + )); + + if ($details) { + $view->addPropertySection(pht('DETAILS'), $details); + } return $this->newPage() - ->setTitle( - array( + ->setTitle(array( nonempty(basename($drequest->getPath()), '/'), $repository->getDisplayName(), )) ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } @@ -431,6 +489,7 @@ final class DiffusionBrowseController extends DiffusionController { $box = id(new PHUIObjectBoxView()) ->setHeaderText($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $pager_box = $this->renderTablePagerBox($pager); @@ -697,12 +756,14 @@ final class DiffusionBrowseController extends DiffusionController { $edit = $this->renderEditButton(); $file = $this->renderFileButton(); $header = id(new PHUIHeaderView()) - ->setHeader(pht('File Contents')) + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->setHeaderIcon('fa-file-code-o') ->addActionLink($edit) ->addActionLink($file); $corpus = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($corpus) ->setCollapsed(true); @@ -737,16 +798,16 @@ final class DiffusionBrowseController extends DiffusionController { return $corpus; } - private function enrichActionView( - PhabricatorActionListView $view, + private function enrichCurtain( + PHUICurtainView $curtain, DiffusionRequest $drequest, $show_blame, $show_color) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $base_uri = $this->getRequest()->getRequestURI(); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Last Change')) ->setHref( @@ -766,7 +827,7 @@ final class DiffusionBrowseController extends DiffusionController { $blame_value = 1; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($blame_text) ->setHref($base_uri->alter('blame', $blame_value)) @@ -784,7 +845,7 @@ final class DiffusionBrowseController extends DiffusionController { $highlight_value = 1; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($highlight_text) ->setHref($base_uri->alter('color', $highlight_value)) @@ -809,14 +870,57 @@ final class DiffusionBrowseController extends DiffusionController { ))->alter('lint', ''); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($lint_text) ->setHref($href) ->setIcon('fa-exclamation-triangle') ->setDisabled(!$href)); - return $view; + + $repository = $drequest->getRepository(); + + $owners = 'PhabricatorOwnersApplication'; + if (PhabricatorApplication::isClassInstalled($owners)) { + $package_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl( + $repository->getPHID(), + array( + $drequest->getPath(), + )); + + $package_query->execute(); + + $packages = $package_query->getControllingPackagesForPath( + $repository->getPHID(), + $drequest->getPath()); + + if ($packages) { + $ownership = id(new PHUIStatusListView()) + ->setUser($viewer); + + foreach ($packages as $package) { + $icon = 'fa-list-alt'; + $color = 'grey'; + + $item = id(new PHUIStatusItemView()) + ->setIcon($icon, $color) + ->setTarget($viewer->renderHandle($package->getPHID())); + + $ownership->addItem($item); + } + } else { + $ownership = phutil_tag('em', array(), pht('None')); + } + + $curtain->newPanel() + ->setHeaderText(pht('Owners')) + ->appendChild($ownership); + } + + return $curtain; } private function renderEditButton() { @@ -844,7 +948,7 @@ final class DiffusionBrowseController extends DiffusionController { return $button; } - private function renderFileButton($file_uri = null) { + private function renderFileButton($file_uri = null, $label = null) { $base_uri = $this->getRequest()->getRequestURI(); @@ -858,6 +962,10 @@ final class DiffusionBrowseController extends DiffusionController { $icon = 'fa-file-text'; } + if ($label !== null) { + $text = $label; + } + $button = id(new PHUIButtonView()) ->setTag('a') ->setText($text) @@ -867,6 +975,21 @@ final class DiffusionBrowseController extends DiffusionController { return $button; } + private function renderGitLFSButton() { + $viewer = $this->getViewer(); + + $uri = $this->getRequest()->getRequestURI(); + $href = $uri->alter('view', 'git-lfs'); + + $text = pht('Download from Git LFS'); + $icon = 'fa-download'; + + return id(new PHUIButtonView()) + ->setTag('a') + ->setText($text) + ->setHref($href) + ->setIcon($icon); + } private function buildDisplayRows( array $lines, @@ -1265,11 +1388,13 @@ final class DiffusionBrowseController extends DiffusionController { $file = $this->renderFileButton($file_uri); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Image')) - ->addActionLink($file); + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->addActionLink($file) + ->setHeaderIcon('fa-file-image-o'); return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); } @@ -1282,11 +1407,12 @@ final class DiffusionBrowseController extends DiffusionController { $file = $this->renderFileButton($file_uri); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')) + ->setHeader(pht('DETAILS')) ->addActionLink($file); $box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($text); return $box; @@ -1298,7 +1424,7 @@ final class DiffusionBrowseController extends DiffusionController { ->appendChild($message); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')); + ->setHeader(pht('DETAILS')); $box = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -1461,12 +1587,12 @@ final class DiffusionBrowseController extends DiffusionController { return "{$summary}\n{$date} \xC2\xB7 {$author}"; } - protected function renderSearchForm($collapsed) { + protected function renderSearchForm() { $drequest = $this->getDiffusionRequest(); $forms = array(); $form = id(new AphrontFormView()) - ->setUser($this->getRequest()->getUser()) + ->setUser($this->getViewer()) ->setMethod('GET'); switch ($drequest->getRepository()->getVersionControlSystem()) { @@ -1492,22 +1618,10 @@ final class DiffusionBrowseController extends DiffusionController { break; } - $filter = new AphrontListFilterView(); - $filter->appendChild($forms); + require_celerity_resource('diffusion-icons-css'); + $form_box = phutil_tag_div('diffusion-search-boxen', $forms); - if ($collapsed) { - $filter->setCollapsed( - pht('Show Search'), - pht('Hide Search'), - pht('Search for file names or content in this directory.'), - '#'); - } - - $filter = id(new PHUIBoxView()) - ->addClass('mlt mlb') - ->appendChild($filter); - - return $filter; + return $form_box; } protected function markupText($text) { @@ -1526,28 +1640,29 @@ final class DiffusionBrowseController extends DiffusionController { } protected function buildHeaderView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); + + $tag = $this->renderCommitHashTag($drequest); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($this->renderPathLinks($drequest, $mode = 'browse')) - ->setPolicyObject($drequest->getRepository()); + ->addTag($tag); return $header; } - protected function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + protected function buildCurtain(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView($drequest); $history_uri = $drequest->generateURI( array( 'action' => 'history', )); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View History')) ->setHref($history_uri) @@ -1559,40 +1674,22 @@ final class DiffusionBrowseController extends DiffusionController { 'commit' => '', 'action' => 'browse', )); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Jump to HEAD')) ->setHref($head_uri) ->setIcon('fa-home') ->setDisabled(!$behind_head)); - return $view; + return $curtain; } protected function buildPropertyView( - DiffusionRequest $drequest, - PhabricatorActionListView $actions) { + DiffusionRequest $drequest) { $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $stable_commit = $drequest->getStableCommit(); - - $view->addProperty( - pht('Commit'), - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $stable_commit, - )), - ), - $drequest->getRepository()->formatCommitName($stable_commit))); + ->setUser($viewer); if ($drequest->getSymbolicType() == 'tag') { $symbolic = $drequest->getSymbolicCommit(); @@ -1616,47 +1713,11 @@ final class DiffusionBrowseController extends DiffusionController { } } - $repository = $drequest->getRepository(); - - $owners = 'PhabricatorOwnersApplication'; - if (PhabricatorApplication::isClassInstalled($owners)) { - $package_query = id(new PhabricatorOwnersPackageQuery()) - ->setViewer($viewer) - ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) - ->withControl( - $repository->getPHID(), - array( - $drequest->getPath(), - )); - - $package_query->execute(); - - $packages = $package_query->getControllingPackagesForPath( - $repository->getPHID(), - $drequest->getPath()); - - if ($packages) { - $ownership = id(new PHUIStatusListView()) - ->setUser($viewer); - - foreach ($packages as $package) { - $icon = 'fa-list-alt'; - $color = 'grey'; - - $item = id(new PHUIStatusItemView()) - ->setIcon($icon, $color) - ->setTarget($viewer->renderHandle($package->getPHID())); - - $ownership->addItem($item); - } - } else { - $ownership = phutil_tag('em', array(), pht('None')); - } - - $view->addProperty(pht('Packages'), $ownership); + if ($view->hasAnyProperties()) { + return $view; } - return $view; + return null; } private function buildOpenRevisions() { @@ -1865,4 +1926,90 @@ final class DiffusionBrowseController extends DiffusionController { $corpus); } + private function getGitLFSRef(PhabricatorRepository $repository, $data) { + if (!$repository->canUseGitLFS()) { + return null; + } + + $lfs_pattern = '(^version https://git-lfs\\.github\\.com/spec/v1[\r\n])'; + if (!preg_match($lfs_pattern, $data)) { + return null; + } + + $matches = null; + if (!preg_match('(^oid sha256:(.*)$)m', $data, $matches)) { + return null; + } + + $hash = $matches[1]; + $hash = trim($hash); + + return id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($this->getViewer()) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes(array($hash)) + ->executeOne(); + } + + private function buildGitLFSCorpus(PhabricatorRepositoryGitLFSRef $ref) { + // TODO: We should probably test if we can load the file PHID here and + // show the user an error if we can't, rather than making them click + // through to hit an error. + + $header = id(new PHUIHeaderView()) + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->setHeaderIcon('fa-archive'); + + $severity = PHUIInfoView::SEVERITY_NOTICE; + + $messages = array(); + $messages[] = pht( + 'This %s file is stored in Git Large File Storage.', + phutil_format_bytes($ref->getByteSize())); + + try { + $file = $this->loadGitLFSFile($ref); + $data = $this->renderGitLFSButton(); + $header->addActionLink($data); + } catch (Exception $ex) { + $severity = PHUIInfoView::SEVERITY_ERROR; + $messages[] = pht('The data for this file could not be loaded.'); + } + + $raw = $this->renderFileButton(null, pht('View Raw LFS Pointer')); + $header->addActionLink($raw); + + $corpus = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setCollapsed(true); + + if ($messages) { + $corpus->setInfoView( + id(new PHUIInfoView()) + ->setSeverity($severity) + ->setErrors($messages)); + } + + return $corpus; + } + + private function loadGitLFSFile(PhabricatorRepositoryGitLFSRef $ref) { + $viewer = $this->getViewer(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($ref->getFilePHID())) + ->executeOne(); + if (!$file) { + throw new Exception( + pht( + 'Failed to load file object for Git LFS ref "%s"!', + $ref->getObjectHash())); + } + + return $file; + } + + } diff --git a/src/applications/diffusion/controller/DiffusionChangeController.php b/src/applications/diffusion/controller/DiffusionChangeController.php index 86b371c38b..90258134c4 100644 --- a/src/applications/diffusion/controller/DiffusionChangeController.php +++ b/src/applications/diffusion/controller/DiffusionChangeController.php @@ -15,8 +15,6 @@ final class DiffusionChangeController extends DiffusionController { $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); - $content = array(); - $data = $this->callConduitWithDiffusionRequest( 'diffusion.diffquery', array( @@ -42,9 +40,11 @@ final class DiffusionChangeController extends DiffusionController { 0 => $changeset, ); + $changeset_header = $this->buildChangesetHeader($drequest); + $changeset_view = new DifferentialChangesetListView(); - $changeset_view->setTitle(pht('Change')); $changeset_view->setChangesets($changesets); + $changeset_view->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $changeset_view->setVisibleChangesets($changesets); $changeset_view->setRenderingReferences( array( @@ -68,11 +68,11 @@ final class DiffusionChangeController extends DiffusionController { $changeset_view->setWhitespace( DifferentialChangesetParser::WHITESPACE_SHOW_ALL); $changeset_view->setUser($viewer); + $changeset_view->setHeader($changeset_header); // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); - $content[] = $changeset_view->render(); $crumbs = $this->buildCrumbs( array( @@ -80,19 +80,18 @@ final class DiffusionChangeController extends DiffusionController { 'path' => true, 'view' => 'change', )); + $crumbs->setBorder(true); $links = $this->renderPathLinks($drequest, $mode = 'browse'); + $header = $this->buildHeader($drequest, $links); - $header = id(new PHUIHeaderView()) - ->setHeader($links) - ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); + ->setMainColumn(array( + )) + ->setFooter(array( + $changeset_view, + )); return $this->newPage() ->setTitle( @@ -103,25 +102,41 @@ final class DiffusionChangeController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, + $view, )); } - private function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader( + DiffusionRequest $drequest, + $links) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $tag = $this->renderCommitHashTag($drequest); + + $header = id(new PHUIHeaderView()) + ->setHeader($links) + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()) + ->addTag($tag); + + return $header; + } + + private function buildChangesetHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Changes')); $history_uri = $drequest->generateURI( array( 'action' => 'history', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View History')) + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View History')) ->setHref($history_uri) ->setIcon('fa-clock-o')); @@ -130,13 +145,14 @@ final class DiffusionChangeController extends DiffusionController { 'action' => 'browse', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Browse Content')) + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Browse Content')) ->setHref($browse_uri) ->setIcon('fa-files-o')); - return $view; + return $header; } protected function buildPropertyView( diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 6736b99430..c6ac32bf7f 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -24,8 +24,7 @@ final class DiffusionCommitController extends DiffusionController { } $drequest = $this->getDiffusionRequest(); - - $user = $request->getUser(); + $viewer = $request->getUser(); if ($request->getStr('diff')) { return $this->buildRawDiffResponse($drequest); @@ -33,9 +32,8 @@ final class DiffusionCommitController extends DiffusionController { $repository = $drequest->getRepository(); - $content = array(); $commit = id(new DiffusionCommitQuery()) - ->setViewer($request->getUser()) + ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers(array($drequest->getCommit())) ->needCommitData(true) @@ -45,6 +43,7 @@ final class DiffusionCommitController extends DiffusionController { $crumbs = $this->buildCrumbs(array( 'commit' => true, )); + $crumbs->setBorder(true); if (!$commit) { if (!$this->getCommitExists()) { @@ -70,10 +69,11 @@ final class DiffusionCommitController extends DiffusionController { $audit_requests = $commit->getAudits(); $this->auditAuthorityPHIDs = - PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); + PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer); $commit_data = $commit->getCommitData(); $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); + $error_panel = null; if ($is_foreign) { $subpath = $commit_data->getCommitDetail('svn-subpath'); @@ -87,43 +87,41 @@ final class DiffusionCommitController extends DiffusionController { "didn't affect the tracked subdirectory ('%s'), so no ". "information is available.", $subpath)); - $content[] = $error_panel; } else { $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); - $engine->setConfig('viewer', $user); + $engine->setConfig('viewer', $viewer); - $headsup_view = id(new PHUIHeaderView()) + $commit_tag = $this->renderCommitHashTag($drequest); + $header = id(new PHUIHeaderView()) ->setHeader(nonempty($commit->getSummary(), pht('Commit Detail'))) - ->setSubheader(pht('Commit: %s', $commit->getCommitIdentifier())); + ->setHeaderIcon('fa-code-fork') + ->addTag($commit_tag); - $headsup_actions = $this->renderHeadsupActionList($commit, $repository); + if ($commit->getAuditStatus()) { + $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon( + $commit->getAuditStatus()); + $color = PhabricatorAuditCommitStatusConstants::getStatusColor( + $commit->getAuditStatus()); + $status = PhabricatorAuditCommitStatusConstants::getStatusName( + $commit->getAuditStatus()); - $commit_properties = $this->loadCommitProperties( + $header->setStatus($icon, $color, $status); + } + + $curtain = $this->buildCurtain($commit, $repository); + $subheader = $this->buildSubheaderView($commit, $commit_data); + $details = $this->buildPropertyListView( $commit, $commit_data, $audit_requests); - $property_list = id(new PHUIPropertyListView()) - ->setHasKeyboardShortcuts(true) - ->setUser($user) - ->setObject($commit); - foreach ($commit_properties as $key => $value) { - $property_list->addProperty($key, $value); - } $message = $commit_data->getCommitMessage(); $revision = $commit->getCommitIdentifier(); $message = $this->linkBugtraq($message); - $message = $engine->markupText($message); - $property_list->invokeWillRenderEvent(); - $property_list->setActionList($headsup_actions); - $detail_list = new PHUIPropertyListView(); - $detail_list->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); $detail_list->addTextContent( phutil_tag( 'div', @@ -132,19 +130,14 @@ final class DiffusionCommitController extends DiffusionController { ), $message)); - $headsup_view->setTall(true); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($headsup_view) - ->setFormErrors($this->getCommitErrors()) - ->addPropertyList($property_list) - ->addPropertyList($detail_list); - - $content[] = $object_box; + if ($this->getCommitErrors()) { + $error_panel = id(new PHUIInfoView()) + ->appendChild($this->getCommitErrors()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + } } - $content[] = $this->buildComments($commit); - + $timeline = $this->buildComments($commit); $hard_limit = 1000; if ($commit->isImported()) { @@ -161,10 +154,10 @@ final class DiffusionCommitController extends DiffusionController { $changes = array_slice($changes, 0, $hard_limit); } - $content[] = $this->buildMergesTable($commit); + $merge_table = $this->buildMergesTable($commit); $highlighted_audits = $commit->getAuthorityAudits( - $user, + $viewer, $this->auditAuthorityPHIDs); $count = count($changes); @@ -179,32 +172,35 @@ final class DiffusionCommitController extends DiffusionController { } $show_changesets = false; + $info_panel = null; + $change_list = null; + $change_table = null; if ($bad_commit) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Bad Commit'), $bad_commit['description']); } else if ($is_foreign) { // Don't render anything else. } else if (!$commit->isImported()) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Still Importing...'), pht( 'This commit is still importing. Changes will be visible once '. 'the import finishes.')); } else if (!count($changes)) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Empty Commit'), pht( 'This commit is empty and does not affect any paths.')); } else if ($was_limited) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Enormous Commit'), pht( 'This commit is enormous, and affects more than %d files. '. 'Changes are not shown.', $hard_limit)); } else if (!$this->getCommitExists()) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Commit No Longer Exists'), pht('This commit no longer exists in the repository.')); } else { @@ -214,13 +210,11 @@ final class DiffusionCommitController extends DiffusionController { // changes inline even if there are more than the soft limit. $show_all_details = $request->getBool('show_all'); - $change_panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Changes (%s)', new PhutilNumber($count))); - $change_panel->setID('toc'); + $change_header = id(new PHUIHeaderView()) + ->setHeader(pht('Changes (%s)', new PhutilNumber($count))); + $warning_view = null; if ($count > self::CHANGES_LIMIT && !$show_all_details) { - $button = id(new PHUIButtonView()) ->setText(pht('Show All Changes')) ->setHref('?show_all=true') @@ -233,23 +227,19 @@ final class DiffusionCommitController extends DiffusionController { ->appendChild( pht('This commit is very large. Load each file individually.')); - $change_panel->setInfoView($warning_view); - $header->addActionLink($button); + $change_header->addActionLink($button); } $changesets = DiffusionPathChange::convertToDifferentialChangesets( - $user, + $viewer, $changes); // TODO: This table and panel shouldn't really be separate, but we need // to clean up the "Load All Files" interaction first. $change_table = $this->buildTableOfContents( - $changesets); - - $change_panel->setTable($change_table); - $change_panel->setHeader($header); - - $content[] = $change_panel; + $changesets, + $change_header, + $warning_view); $vcs = $repository->getVersionControlSystem(); switch ($vcs) { @@ -296,7 +286,7 @@ final class DiffusionCommitController extends DiffusionController { } else { $visible_changesets = array(); $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments( - $user, + $viewer, $commit->getPHID()); $path_ids = mpull($inlines, null, 'getPathID'); foreach ($changesets as $key => $changeset) { @@ -313,10 +303,10 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setChangesets($changesets); $change_list->setVisibleChangesets($visible_changesets); $change_list->setRenderingReferences($references); - $change_list->setRenderURI( - $repository->getPathURI('diff/')); + $change_list->setRenderURI($repository->getPathURI('diff/')); $change_list->setRepository($repository); - $change_list->setUser($user); + $change_list->setUser($viewer); + $change_list->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); // TODO: Try to setBranch() to something reasonable here? @@ -332,48 +322,74 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/'); - $content[] = $change_list->render(); } - $content[] = $this->renderAddCommentPanel($commit, $audit_requests); + $add_comment = $this->renderAddCommentPanel($commit, $audit_requests); - $prefs = $user->loadPreferences(); + $prefs = $viewer->loadPreferences(); $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED; $show_filetree = $prefs->getPreference($pref_filetree); $collapsed = $prefs->getPreference($pref_collapse); + $nav = null; if ($show_changesets && $show_filetree) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setTitle($commit->getDisplayName()) ->setBaseURI(new PhutilURI($commit->getURI())) ->build($changesets) ->setCrumbs($crumbs) - ->setCollapsed((bool)$collapsed) - ->appendChild($content); - $content = $nav; - } else { - $content = array($crumbs, $content); + ->setCollapsed((bool)$collapsed); } - return $this->buildApplicationPage( - $content, - array( - 'title' => $commit->getDisplayName(), - 'pageObjects' => array($commit->getPHID()), + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setMainColumn(array( + $error_panel, + $timeline, + $merge_table, + $info_panel, + )) + ->setFooter(array( + $change_table, + $change_list, + $add_comment, + )) + ->addPropertySection(pht('DESCRIPTION'), $detail_list) + ->addPropertySection(pht('DETAILS'), $details) + ->setCurtain($curtain); + + $page = $this->newPage() + ->setTitle($commit->getDisplayName()) + ->setCrumbs($crumbs) + ->setPageObjectPHIDS(array($commit->getPHID())) + ->appendChild( + array( + $view, )); + + if ($nav) { + $page->setNavigation($nav); + } + + return $page; + } - private function loadCommitProperties( + private function buildPropertyListView( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, array $audit_requests) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $commit_phid = $commit->getPHID(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + $view = id(new PHUIPropertyListView()) + ->setUser($this->getRequest()->getUser()); + $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($commit_phid)) ->withEdgeTypes(array( @@ -437,31 +453,6 @@ final class DiffusionCommitController extends DiffusionController { $props = array(); - if ($commit->getAuditStatus()) { - $status = PhabricatorAuditCommitStatusConstants::getStatusName( - $commit->getAuditStatus()); - $tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setName($status); - - switch ($commit->getAuditStatus()) { - case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT: - $tag->setBackgroundColor(PHUITagView::COLOR_ORANGE); - break; - case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED: - $tag->setBackgroundColor(PHUITagView::COLOR_RED); - break; - case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED: - $tag->setBackgroundColor(PHUITagView::COLOR_BLUE); - break; - case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED: - $tag->setBackgroundColor(PHUITagView::COLOR_GREEN); - break; - } - - $props['Status'] = $tag; - } - if ($audit_requests) { $user_requests = array(); $other_requests = array(); @@ -474,37 +465,21 @@ final class DiffusionCommitController extends DiffusionController { } if ($user_requests) { - $props['Auditors'] = $this->renderAuditStatusView( - $user_requests); + $view->addProperty( + pht('Auditors'), + $this->renderAuditStatusView($user_requests)); } if ($other_requests) { - $props['Project/Package Auditors'] = $this->renderAuditStatusView( - $other_requests); + $view->addProperty( + pht('Project/Package Auditors'), + $this->renderAuditStatusView($other_requests)); } } $author_phid = $data->getCommitDetail('authorPHID'); $author_name = $data->getAuthorName(); - - if (!$repository->isSVN()) { - $authored_info = id(new PHUIStatusItemView()); - - $author_epoch = $data->getCommitDetail('authorEpoch'); - if ($author_epoch !== null) { - $authored_info->setNote( - phabricator_datetime($author_epoch, $viewer)); - } - - if ($author_phid) { - $authored_info->setTarget($handles[$author_phid]->renderLink()); - } else if (strlen($author_name)) { - $authored_info->setTarget($author_name); - } - - $props['Authored'] = id(new PHUIStatusListView()) - ->addItem($authored_info); - } + $author_epoch = $data->getCommitDetail('authorEpoch'); $committed_info = id(new PHUIStatusItemView()) ->setNote(phabricator_datetime($commit->getEpoch(), $viewer)); @@ -521,8 +496,9 @@ final class DiffusionCommitController extends DiffusionController { $committed_info->setTarget($author_name); } - $props['Committed'] = id(new PHUIStatusListView()) - ->addItem($committed_info); + $view->addProperty( + pht('Committed'), + $committed_info); if ($push_logs) { $pushed_list = new PHUIStatusListView(); @@ -534,36 +510,49 @@ final class DiffusionCommitController extends DiffusionController { $pushed_list->addItem($pushed_item); } - $props['Pushed'] = $pushed_list; + $view->addProperty( + pht('Pushed'), + $pushed_list); } $reviewer_phid = $data->getCommitDetail('reviewerPHID'); if ($reviewer_phid) { - $props['Reviewer'] = $handles[$reviewer_phid]->renderLink(); + $view->addProperty( + pht('Reviewer'), + $handles[$reviewer_phid]->renderLink()); } if ($revision_phid) { - $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); + $view->addProperty( + pht('Differential Revision'), + $handles[$revision_phid]->renderLink()); } $parents = $this->getCommitParents(); if ($parents) { - $props['Parents'] = $viewer->renderHandleList(mpull($parents, 'getPHID')); + $view->addProperty( + pht('Parents'), + $viewer->renderHandleList(mpull($parents, 'getPHID'))); } if ($this->getCommitExists()) { - $props['Branches'] = phutil_tag( + $view->addProperty( + pht('Branches'), + phutil_tag( 'span', array( 'id' => 'commit-branches', ), - pht('Unknown')); - $props['Tags'] = phutil_tag( + pht('Unknown'))); + + $view->addProperty( + pht('Tags'), + phutil_tag( 'span', array( 'id' => 'commit-tags', ), - pht('Unknown')); + pht('Unknown'))); $identifier = $commit->getCommitIdentifier(); $root = $repository->getPathURI("commit/{$identifier}"); @@ -586,16 +575,21 @@ final class DiffusionCommitController extends DiffusionController { ), $ref_data['ref']); } - $props['References'] = phutil_implode_html(', ', $ref_links); + $view->addProperty( + pht('References'), + phutil_implode_html(', ', $ref_links)); } if ($reverts_phids) { - $props[pht('Reverts')] = $viewer->renderHandleList($reverts_phids); + $view->addProperty( + pht('Reverts'), + $viewer->renderHandleList($reverts_phids)); } if ($reverted_by_phids) { - $props[pht('Reverted By')] = $viewer->renderHandleList( - $reverted_by_phids); + $view->addProperty( + pht('Reverted By'), + $viewer->renderHandleList($reverted_by_phids)); } if ($task_phids) { @@ -604,12 +598,62 @@ final class DiffusionCommitController extends DiffusionController { $task_list[] = $handles[$phid]->renderLink(); } $task_list = phutil_implode_html(phutil_tag('br'), $task_list); - $props['Tasks'] = $task_list; + $view->addProperty( + pht('Tasks'), + $task_list); } - return $props; + return $view; } + private function buildSubheaderView( + PhabricatorRepositoryCommit $commit, + PhabricatorRepositoryCommitData $data) { + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + if ($repository->isSVN()) { + return null; + } + + $author_phid = $data->getCommitDetail('authorPHID'); + $author_name = $data->getAuthorName(); + $author_epoch = $data->getCommitDetail('authorEpoch'); + $date = null; + if ($author_epoch !== null) { + $date = phabricator_datetime($author_epoch, $viewer); + } + + if ($author_phid) { + $handles = $viewer->loadHandles(array($author_phid)); + $image_uri = $handles[$author_phid]->getImageURI(); + $image_href = $handles[$author_phid]->getURI(); + $author = $handles[$author_phid]->renderLink(); + } else if (strlen($author_name)) { + $author = $author_name; + $image_uri = null; + $image_href = null; + } else { + return null; + } + + $author = phutil_tag('strong', array(), $author); + if ($date) { + $content = pht('Authored by %s on %s.', $author, $date); + } else { + $content = pht('Authored by %s.', $author); + } + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + + } + + private function buildComments(PhabricatorRepositoryCommit $commit) { $timeline = $this->buildTransactionTimeline( $commit, @@ -624,11 +668,11 @@ final class DiffusionCommitController extends DiffusionController { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); - if (!$user->isLoggedIn()) { + if (!$viewer->isLoggedIn()) { return id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setRequestURI($request->getRequestURI()); } @@ -643,7 +687,7 @@ final class DiffusionCommitController extends DiffusionController { $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', - $user->getPHID(), + $viewer->getPHID(), 'diffusion-audit-'.$commit->getID()); if ($draft) { $draft = $draft->getDraft(); @@ -657,7 +701,7 @@ final class DiffusionCommitController extends DiffusionController { $auditor_source = new DiffusionAuditorDatasource(); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setAction('/audit/addcomment/') ->addHiddenInput('commit', $commit->getPHID()) ->appendChild( @@ -690,7 +734,7 @@ final class DiffusionCommitController extends DiffusionController { ->setName('content') ->setValue($draft) ->setID('audit-content') - ->setUser($user)) + ->setUser($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Submit'))); @@ -776,13 +820,13 @@ final class DiffusionCommitController extends DiffusionController { PhabricatorRepositoryCommit $commit, array $audit_requests) { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); - $user_is_author = ($commit->getAuthorPHID() == $user->getPHID()); + $user_is_author = ($commit->getAuthorPHID() == $viewer->getPHID()); $user_request = null; foreach ($audit_requests as $audit_request) { - if ($audit_request->getAuditorPHID() == $user->getPHID()) { + if ($audit_request->getAuditorPHID() == $viewer->getPHID()) { $user_request = $audit_request; break; } @@ -876,9 +920,10 @@ final class DiffusionCommitController extends DiffusionController { $history_table->loadRevisions(); - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Merged Changes')); - $panel->setTable($history_table); + $panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Merged Changes')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($history_table); if ($caption) { $panel->setInfoView($caption); } @@ -886,19 +931,16 @@ final class DiffusionCommitController extends DiffusionController { return $panel; } - private function renderHeadsupActionList( + private function buildCurtain( PhabricatorRepositoryCommit $commit, PhabricatorRepository $repository) { $request = $this->getRequest(); - $user = $request->getUser(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($user) - ->setObject($commit); + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($commit); $can_edit = PhabricatorPolicyFilter::hasCapability( - $user, + $viewer, $commit, PhabricatorPolicyCapability::CAN_EDIT); @@ -911,7 +953,7 @@ final class DiffusionCommitController extends DiffusionController { ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); - $actions->addAction($action); + $curtain->addAction($action); require_celerity_resource('phabricator-object-selector-css'); require_celerity_resource('javelin-behavior-phabricator-object-selector'); @@ -924,16 +966,16 @@ final class DiffusionCommitController extends DiffusionController { ->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/') ->setWorkflow(true) ->setDisabled(!$can_edit); - $actions->addAction($action); + $curtain->addAction($action); } $action = id(new PhabricatorActionView()) ->setName(pht('Download Raw Diff')) ->setHref($request->getRequestURI()->alter('diff', true)) ->setIcon('fa-download'); - $actions->addAction($action); + $curtain->addAction($action); - return $actions; + return $curtain; } private function buildRawDiffResponse(DiffusionRequest $drequest) { @@ -1017,12 +1059,22 @@ final class DiffusionCommitController extends DiffusionController { return $parser->processCorpus($corpus); } - private function buildTableOfContents(array $changesets) { + private function buildTableOfContents( + array $changesets, + $header, + $info_view) { + $drequest = $this->getDiffusionRequest(); $viewer = $this->getViewer(); $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + + if ($info_view) { + $toc_view->setInfoView($info_view); + } // TODO: This is hacky, we just want access to the linkX() methods on // DiffusionView. diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index c83990cc15..a77b000db9 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -291,6 +291,7 @@ abstract class DiffusionController extends PhabricatorController { return id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle($title) + ->setFlush(true) ->appendChild($body); } @@ -300,6 +301,27 @@ abstract class DiffusionController extends PhabricatorController { ->appendChild($pager); } + protected function renderCommitHashTag(DiffusionRequest $drequest) { + $stable_commit = $drequest->getStableCommit(); + $commit = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'commit', + 'commit' => $stable_commit, + )), + ), + $drequest->getRepository()->formatCommitName($stable_commit, true)); + + $tag = id(new PHUITagView()) + ->setName($commit) + ->setShade('indigo') + ->setType(PHUITagView::TYPE_SHADE); + + return $tag; + } + protected function renderDirectoryReadme(DiffusionBrowseResultSet $browse) { $readme_path = $browse->getReadmePath(); if ($readme_path === null) { diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 59248b7acf..eca8fe2a2f 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -40,8 +40,6 @@ final class DiffusionHistoryController extends DiffusionController { $history = $pager->sliceResults($history); $show_graph = !strlen($drequest->getPath()); - $content = array(); - $history_table = id(new DiffusionHistoryTableView()) ->setUser($request->getUser()) ->setDiffusionRequest($drequest) @@ -55,23 +53,13 @@ final class DiffusionHistoryController extends DiffusionController { $history_table->setIsTail(!$pager->getHasMorePages()); } - $history_panel = new PHUIObjectBoxView(); - $history_panel->setHeaderText(pht('History')); - $history_panel->setTable($history_table); + $history_header = $this->buildHistoryHeader($drequest); + $history_panel = id(new PHUIObjectBoxView()) + ->setHeader($history_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($history_table); - $content[] = $history_panel; - - $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setPolicyObject($repository) - ->setHeader($this->renderPathLinks($drequest, $mode = 'history')); - - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $header = $this->buildHeader($drequest, $repository); $crumbs = $this->buildCrumbs( array( @@ -79,9 +67,17 @@ final class DiffusionHistoryController extends DiffusionController { 'path' => true, 'view' => 'history', )); + $crumbs->setBorder(true); $pager_box = $this->renderTablePagerBox($pager); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $history_panel, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -91,28 +87,39 @@ final class DiffusionHistoryController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, - $pager_box, + $view, )); } - private function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $tag = $this->renderCommitHashTag($drequest); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()) + ->addTag($tag) + ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) + ->setHeaderIcon('fa-clock-o'); + + return $header; + + } + + private function buildHistoryHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); $browse_uri = $drequest->generateURI( array( 'action' => 'browse', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Browse Content')) - ->setHref($browse_uri) - ->setIcon('fa-files-o')); + $browse_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Browse')) + ->setHref($browse_uri) + ->setIcon('fa-files-o'); // TODO: Sometimes we do have a change view, we need to look at the most // recent history entry to figure it out. @@ -130,41 +137,18 @@ final class DiffusionHistoryController extends DiffusionController { ->alter('copies', true); } - $view->addAction( - id(new PhabricatorActionView()) - ->setName($branch_name) - ->setIcon('fa-code-fork') - ->setHref($branch_uri)); + $branch_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText($branch_name) + ->setIcon('fa-code-fork') + ->setHref($branch_uri); - return $view; - } + $header = id(new PHUIHeaderView()) + ->setHeader(pht('History')) + ->addActionLink($browse_button) + ->addActionLink($branch_button); - protected function buildPropertyView( - DiffusionRequest $drequest, - PhabricatorActionListView $actions) { - - $viewer = $this->getRequest()->getUser(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $stable_commit = $drequest->getStableCommit(); - - $view->addProperty( - pht('Commit'), - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $stable_commit, - )), - ), - $drequest->getRepository()->formatCommitName($stable_commit))); - - return $view; + return $header; } } diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index 0fea238249..8a4debfcea 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -103,7 +103,7 @@ final class DiffusionLastModifiedController extends DiffusionController { $modified = DiffusionView::linkCommit( $drequest->getRepository(), $commit->getCommitIdentifier()); - $date = phabricator_datetime($epoch, $viewer); + $date = $viewer->formatShortDateTime($epoch); } else { $modified = ''; $date = ''; diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index dcbb217d99..fbd059796a 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -157,6 +157,7 @@ final class DiffusionLintController extends DiffusionController { $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Lint')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $title = array('Lint'); @@ -179,7 +180,7 @@ final class DiffusionLintController extends DiffusionController { $header = id(new PHUIHeaderView()) ->setHeader($this->renderPathLinks($drequest, 'lint')) ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()); + ->setHeaderIcon('fa-code'); $actions = $this->buildActionView($drequest); $properties = $this->buildPropertyView( $drequest, @@ -189,18 +190,28 @@ final class DiffusionLintController extends DiffusionController { $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); } else { $object_box = null; + $header = id(new PHUIHeaderView()) + ->setHeader(pht('All Lint')) + ->setHeaderIcon('fa-code'); } + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $object_box, + $content, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, + $view, )); } @@ -444,6 +455,7 @@ final class DiffusionLintController extends DiffusionController { $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Lint Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $crumbs = $this->buildCrumbs( @@ -454,6 +466,16 @@ final class DiffusionLintController extends DiffusionController { )); $pager_box = $this->renderTablePagerBox($pager); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName())) + ->setHeaderIcon('fa-code'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $content, + $pager_box, + )); return $this->newPage() ->setTitle( @@ -464,8 +486,7 @@ final class DiffusionLintController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 6504ee50e1..792bcc4249 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -16,11 +16,14 @@ final class DiffusionRepositoryController extends DiffusionController { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $content = array(); - $crumbs = $this->buildCrumbs(); + $crumbs->setBorder(true); - $content[] = $this->buildPropertiesTable($drequest->getRepository()); + $header = $this->buildHeaderView($repository); + $curtain = $this->buildCurtain($repository); + $property_table = $this->buildPropertiesTable($repository); + $description = $this->buildDescriptionView($repository); + $locate_file = $this->buildLocateFile(); // Before we do any work, make sure we're looking at a some content: we're // on a valid branch, and the repository is not empty. @@ -68,14 +71,24 @@ final class DiffusionRepositoryController extends DiffusionController { } if ($page_has_content) { - $content[] = $this->buildNormalContent($drequest); + $content = $this->buildNormalContent($drequest); } else { - $content[] = id(new PHUIInfoView()) + $content = id(new PHUIInfoView()) ->setTitle($empty_title) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors(array($empty_message)); } + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $property_table, + $description, + $locate_file, + )) + ->setFooter($content); + return $this->newPage() ->setTitle( array( @@ -83,7 +96,9 @@ final class DiffusionRepositoryController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild(array( + $view, + )); } @@ -206,13 +221,13 @@ final class DiffusionRepositoryController extends DiffusionController { return $content; } - private function buildPropertiesTable(PhabricatorRepository $repository) { - $user = $this->getRequest()->getUser(); - + private function buildHeaderView(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) ->setHeader($repository->getName()) - ->setUser($user) - ->setPolicyObject($repository); + ->setUser($viewer) + ->setPolicyObject($repository) + ->setHeaderIcon('fa-code'); if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); @@ -227,12 +242,64 @@ final class DiffusionRepositoryController extends DiffusionController { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } + return $header; + } - $actions = $this->buildActionList($repository); + private function buildCurtain(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + + $edit_uri = $repository->getPathURI('edit/'); + $curtain = $this->newCurtainView($repository); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Repository')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + if ($repository->isHosted()) { + $push_uri = $this->getApplicationURI( + 'pushlog/?repositories='.$repository->getMonogram()); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Push Logs')) + ->setIcon('fa-list-alt') + ->setHref($push_uri)); + } + + return $curtain; + } + + private function buildDescriptionView(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $description = $repository->getDetail('description'); + if (strlen($description)) { + $description = new PHUIRemarkupView($viewer, $description); + $view->addTextContent($description); + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DESCRIPTION')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); + } + return null; + } + + private function buildPropertiesTable(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setObject($repository) - ->setUser($user); + ->setUser($viewer); if ($repository->isHosted()) { $ssh_uri = $repository->getSSHCloneURIObject(); @@ -286,21 +353,10 @@ final class DiffusionRepositoryController extends DiffusionController { } } - $view->invokeWillRenderEvent(); - - $description = $repository->getDetail('description'); - if (strlen($description)) { - $description = new PHUIRemarkupView($user, $description); - $view->addSectionHeader( - pht('Description'), PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($description); - } - - $view->setActionList($actions); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($view); + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); $info = null; $drequest = $this->getDiffusionRequest(); @@ -344,7 +400,7 @@ final class DiffusionRepositoryController extends DiffusionController { } private function buildBranchListTable(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); if ($drequest->getBranch() === null) { return null; @@ -379,7 +435,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setBranches($branches) ->setCommits($commits); - $panel = new PHUIObjectBoxView(); + $panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = new PHUIHeaderView(); $header->setHeader(pht('Branches')); @@ -388,7 +445,7 @@ final class DiffusionRepositoryController extends DiffusionController { } $button = new PHUIButtonView(); - $button->setText(pht('Show All Branches')); + $button->setText(pht('Show All')); $button->setTag('a'); $button->setIcon('fa-code-fork'); $button->setHref($drequest->generateURI( @@ -404,7 +461,7 @@ final class DiffusionRepositoryController extends DiffusionController { } private function buildTagListTable(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $repository = $drequest->getRepository(); switch ($repository->getVersionControlSystem()) { @@ -469,46 +526,11 @@ final class DiffusionRepositoryController extends DiffusionController { $panel->setHeader($header); $panel->setTable($view); + $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return $panel; } - private function buildActionList(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); - - $edit_uri = $repository->getPathURI('edit/'); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($repository); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Repository')) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setWorkflow(!$can_edit) - ->setDisabled(!$can_edit)); - - if ($repository->isHosted()) { - $push_uri = $this->getApplicationURI( - 'pushlog/?repositories='.$repository->getMonogram()); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Push Logs')) - ->setIcon('fa-list-alt') - ->setHref($push_uri)); - } - - return $view; - } - private function buildHistoryTable( $history_results, $history, @@ -551,7 +573,7 @@ final class DiffusionRepositoryController extends DiffusionController { ->setIcon('fa-list-alt'); $button = id(new PHUIButtonView()) - ->setText(pht('View Full History')) + ->setText(pht('View History')) ->setHref($drequest->generateURI( array( 'action' => 'history', @@ -559,7 +581,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setTag('a') ->setIcon($icon); - $panel = new PHUIObjectBoxView(); + $panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = id(new PHUIHeaderView()) ->setHeader(pht('Recent Commits')) ->addActionLink($button); @@ -569,6 +592,46 @@ final class DiffusionRepositoryController extends DiffusionController { return $panel; } + private function buildLocateFile() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $locate_panel = null; + if ($repository->canUsePathTree()) { + Javelin::initBehavior( + 'diffusion-locate-file', + array( + 'controlID' => 'locate-control', + 'inputID' => 'locate-input', + 'browseBaseURI' => (string)$drequest->generateURI( + array( + 'action' => 'browse', + )), + 'uri' => (string)$drequest->generateURI( + array( + 'action' => 'pathtree', + )), + )); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTypeaheadControl()) + ->setHardpointID('locate-control') + ->setID('locate-input') + ->setLabel(pht('Locate File'))); + $form_box = id(new PHUIBoxView()) + ->appendChild($form->buildLayoutView()); + $locate_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Locate File')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($form_box); + } + return $locate_panel; + } + private function buildBrowseTable( $browse_results, $browse_paths, @@ -606,9 +669,10 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_uri = $drequest->generateURI(array('action' => 'browse')); - $browse_panel = new PHUIObjectBoxView(); + $browse_panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Repository')); + ->setHeader($repository->getName()); $icon = id(new PHUIIconView()) ->setIcon('fa-folder-open'); @@ -621,38 +685,6 @@ final class DiffusionRepositoryController extends DiffusionController { $header->addActionLink($button); $browse_panel->setHeader($header); - - $locate_panel = null; - if ($repository->canUsePathTree()) { - Javelin::initBehavior( - 'diffusion-locate-file', - array( - 'controlID' => 'locate-control', - 'inputID' => 'locate-input', - 'browseBaseURI' => (string)$drequest->generateURI( - array( - 'action' => 'browse', - )), - 'uri' => (string)$drequest->generateURI( - array( - 'action' => 'pathtree', - )), - )); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTypeaheadControl()) - ->setHardpointID('locate-control') - ->setID('locate-input') - ->setLabel(pht('Locate File'))); - $form_box = id(new PHUIBoxView()) - ->appendChild($form->buildLayoutView()); - $locate_panel = id(new PHUIObjectBoxView()) - ->setHeaderText('Locate File') - ->appendChild($form_box); - } - $browse_panel->setTable($browse_table); $pager->setURI($browse_uri, 'offset'); @@ -664,7 +696,6 @@ final class DiffusionRepositoryController extends DiffusionController { } return array( - $locate_panel, $browse_panel, $pager_box, ); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php index a9113fee13..ae28bf3993 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php @@ -262,10 +262,26 @@ final class DiffusionRepositoryCreateController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form); + ->appendChild($view); + } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php index 8459498374..b8f95f36fc 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php @@ -53,6 +53,10 @@ final class DiffusionRepositoryEditActionsController $title = pht('Edit Actions (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($repository) @@ -97,13 +101,21 @@ final class DiffusionRepositoryEditActionsController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Actions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); + } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php index e47bde3902..038cc0f0cd 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php @@ -49,6 +49,10 @@ final class DiffusionRepositoryEditAutomationController $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $form = id(new AphrontFormView()) ->setUser($viewer) ->appendRemarkupInstructions( @@ -69,14 +73,21 @@ final class DiffusionRepositoryEditAutomationController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Automation')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php index e23a57e655..10f4b13ef9 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -105,6 +105,10 @@ final class DiffusionRepositoryEditBasicController $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( @@ -144,16 +148,23 @@ final class DiffusionRepositoryEditBasicController ->appendChild(id(new PHUIFormDividerControl())) ->appendRemarkupInstructions($this->getReadmeInstructions()); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Basic Information')) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getReadmeInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php index c256336ec2..927af295d4 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php @@ -98,6 +98,9 @@ final class DiffusionRepositoryEditBranchesController $crumbs->addTextCrumb(pht('Edit Branches')); $title = pht('Edit Branches (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) @@ -213,14 +216,21 @@ final class DiffusionRepositoryEditBranchesController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Branches')) ->setValidationException($validation_exception) - ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); } private function processBranches($string) { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index 1e3cb553da..714568f187 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -20,6 +20,7 @@ abstract class DiffusionRepositoryEditController $crumbs->addTextCrumb(pht('Edit'), $edit_uri); } } + $crumbs->setBorder(true); return $crumbs; } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php index 0059c9d6c0..256e69ac59 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php @@ -50,6 +50,9 @@ final class DiffusionRepositoryEditEncodingController $crumbs->addTextCrumb(pht('Edit Encoding')); $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($user) @@ -65,15 +68,22 @@ final class DiffusionRepositoryEditEncodingController ->setValue(pht('Save Encoding')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Encoding')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getEncodingInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php index be40b794b3..0c759eba59 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php @@ -57,6 +57,9 @@ final class DiffusionRepositoryEditHostingController $crumbs->addTextCrumb(pht('Edit Hosting')); $title = pht('Edit Hosting (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $hosted_control = id(new AphrontFormRadioButtonControl()) ->setName('hosting') @@ -95,14 +98,21 @@ final class DiffusionRepositoryEditHostingController ->setValue(pht('Save and Continue')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Hosting')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } public function handleProtocols(PhabricatorRepository $repository) { @@ -155,7 +165,9 @@ final class DiffusionRepositoryEditHostingController $crumbs->addTextCrumb(pht('Edit Protocols')); $title = pht('Edit Protocols (%s)', $repository->getName()); - + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $rw_message = pht( 'Phabricator will serve a read-write copy of this repository.'); @@ -256,14 +268,21 @@ final class DiffusionRepositoryEditHostingController ->setValue(pht('Save Changes')) ->addCancelButton($prev_uri, pht('Back'))); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Protocols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 0ec8cdd6b8..d8a4cf40ba 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -38,16 +38,16 @@ final class DiffusionRepositoryEditMainController $title = pht('Edit %s', $repository->getName()); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); if ($repository->isTracked()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { $header->setStatus('fa-ban', 'dark', pht('Inactive')); } - $basic_actions = $this->buildBasicActions($repository); - $basic_properties = - $this->buildBasicProperties($repository, $basic_actions); + $curtain = $this->buildCurtain($repository); + $basic_properties = $this->buildBasicProperties($repository); $policy_actions = $this->buildPolicyActions($repository); $policy_properties = @@ -119,16 +119,14 @@ final class DiffusionRepositoryEditMainController $boxes = array(); - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($basic_properties); - $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Policies')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($policy_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Hosting')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($hosting_properties); if ($repository->canMirror()) { @@ -156,6 +154,7 @@ final class DiffusionRepositoryEditMainController $boxes[] = id(new PHUIObjectBoxView()) ->setFormErrors($mirror_info) ->setHeaderText(pht('Mirrors')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($mirror_properties); $boxes[] = $mirror_list; @@ -164,73 +163,88 @@ final class DiffusionRepositoryEditMainController if ($remote_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Remote')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($remote_properties); } if ($storage_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Storage')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($storage_properties); } if ($staging_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Staging')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($staging_properties); } if ($automation_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Automation')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($automation_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Text Encoding')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($encoding_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Symbols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($symbols_properties); if ($branches_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Branches')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($branches_properties); } if ($subversion_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Subversion')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($subversion_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Actions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($actions_properties); - return $this->buildApplicationPage( - array( - $crumbs, + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $basic_properties) + ->setMainColumn(array( $boxes, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } - private function buildBasicActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtain(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView($repository); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Basic Information')) ->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/')); - $view->addAction($edit); + $curtain->addAction($edit); $edit = id(new PhabricatorActionView()) ->setIcon('fa-refresh') @@ -238,7 +252,7 @@ final class DiffusionRepositoryEditMainController ->setWorkflow(true) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/update/')); - $view->addAction($edit); + $curtain->addAction($edit); $activate = id(new PhabricatorActionView()) ->setHref( @@ -255,9 +269,9 @@ final class DiffusionRepositoryEditMainController ->setName(pht('Activate Repository')); } - $view->addAction($activate); + $curtain->addAction($activate); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Repository')) ->setIcon('fa-times') @@ -266,19 +280,16 @@ final class DiffusionRepositoryEditMainController ->setDisabled(true) ->setWorkflow(true)); - return $view; + return $curtain; } private function buildBasicProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { + PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($repository) - ->setActionList($actions); + ->setUser($viewer); $type = PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()); @@ -322,7 +333,7 @@ final class DiffusionRepositoryEditMainController } private function buildEncodingActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -341,7 +352,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -358,7 +369,7 @@ final class DiffusionRepositoryEditMainController } private function buildPolicyActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -377,7 +388,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -412,7 +423,7 @@ final class DiffusionRepositoryEditMainController } private function buildBranchesActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -431,7 +442,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -461,7 +472,7 @@ final class DiffusionRepositoryEditMainController } private function buildSubversionActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -480,7 +491,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -500,7 +511,7 @@ final class DiffusionRepositoryEditMainController } private function buildActionsActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -519,7 +530,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -541,7 +552,7 @@ final class DiffusionRepositoryEditMainController } private function buildRemoteActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -560,7 +571,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -581,7 +592,7 @@ final class DiffusionRepositoryEditMainController } private function buildStorageActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -600,7 +611,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -801,7 +812,7 @@ final class DiffusionRepositoryEditMainController private function buildRepositoryStatus( PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $is_cluster = $repository->getAlmanacServicePHID(); $view = new PHUIStatusListView(); @@ -1188,7 +1199,7 @@ final class DiffusionRepositoryEditMainController private function buildMirrorActions( PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $mirror_actions = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -1211,7 +1222,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $mirror_properties = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -1262,11 +1273,14 @@ final class DiffusionRepositoryEditMainController $mirror_list->addItem($item); } - return $mirror_list; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Configured Mirrors')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($mirror_list); } private function buildSymbolsActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -1285,7 +1299,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php index deb8fad669..2d158e61ea 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php @@ -45,7 +45,10 @@ final class DiffusionRepositoryEditStagingController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Staging')); - $title = pht('Edit %s', $repository->getName()); + $title = pht('Edit Staging (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -68,14 +71,21 @@ final class DiffusionRepositoryEditStagingController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Staging')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php index 711844188a..f3a492e3b3 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php @@ -22,6 +22,9 @@ final class DiffusionRepositoryEditStorageController $crumbs->addTextCrumb(pht('Edit Storage')); $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $service_phid = $repository->getAlmanacServicePHID(); if ($service_phid) { @@ -57,15 +60,21 @@ final class DiffusionRepositoryEditStorageController id(new AphrontFormSubmitControl()) ->addCancelButton($edit_uri, pht('Done'))); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setForm($form) - ->setFormErrors($errors); + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Storage')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php index 93b9193f14..6fcc316135 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php @@ -63,6 +63,9 @@ final class DiffusionRepositoryEditSubversionController $crumbs->addTextCrumb(pht('Edit Subversion Info')); $title = pht('Edit Subversion Info (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) @@ -96,13 +99,20 @@ final class DiffusionRepositoryEditSubversionController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Subversion')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php index 6076d2df65..06ce23032a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php @@ -59,7 +59,10 @@ final class DiffusionRepositorySymbolsController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Symbols')); - $title = pht('Edit %s', $repository->getName()); + $title = pht('Edit Symbols (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -85,15 +88,22 @@ final class DiffusionRepositorySymbolsController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Symbols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 84010d76af..31bcb2cbed 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -5,7 +5,12 @@ final class DiffusionServeController extends DiffusionController { private $serviceViewer; private $serviceRepository; + private $isGitLFSRequest; + private $gitLFSToken; + public function setServiceViewer(PhabricatorUser $viewer) { + $this->getRequest()->setUser($viewer); + $this->serviceViewer = $viewer; return $this; } @@ -23,6 +28,14 @@ final class DiffusionServeController extends DiffusionController { return $this->serviceRepository; } + public function getIsGitLFSRequest() { + return $this->isGitLFSRequest; + } + + public function getGitLFSToken() { + return $this->gitLFSToken; + } + public function isVCSRequest(AphrontRequest $request) { $identifier = $this->getRepositoryIdentifierFromRequest($request); if ($identifier === null) { @@ -31,6 +44,10 @@ final class DiffusionServeController extends DiffusionController { $content_type = $request->getHTTPHeader('Content-Type'); $user_agent = idx($_SERVER, 'HTTP_USER_AGENT'); + $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type'); + + // This may have a "charset" suffix, so only match the prefix. + $lfs_pattern = '(^application/vnd\\.git-lfs\\+json(;|\z))'; $vcs = null; if ($request->getExists('service')) { @@ -46,6 +63,14 @@ final class DiffusionServeController extends DiffusionController { } else if ($content_type == 'application/x-git-receive-pack-request') { // We get this for `git-receive-pack`. $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + } else if (preg_match($lfs_pattern, $content_type)) { + // This is a Git LFS HTTP API request. + $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + $this->isGitLFSRequest = true; + } else if ($request_type == 'git-lfs') { + // This is a Git LFS object content request. + $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + $this->isGitLFSRequest = true; } else if ($request->getExists('cmd')) { // Mercurial also sends an Accept header like // "application/mercurial-0.1", and a User-Agent like @@ -142,7 +167,17 @@ final class DiffusionServeController extends DiffusionController { $username = $_SERVER['PHP_AUTH_USER']; $password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']); - $viewer = $this->authenticateHTTPRepositoryUser($username, $password); + // Try Git LFS auth first since we can usually reject it without doing + // any queries, since the username won't match the one we expect or the + // request won't be LFS. + $viewer = $this->authenticateGitLFSUser($username, $password); + + // If that failed, try normal auth. Note that we can use normal auth on + // LFS requests, so this isn't strictly an alternative to LFS auth. + if (!$viewer) { + $viewer = $this->authenticateHTTPRepositoryUser($username, $password); + } + if (!$viewer) { return new PhabricatorVCSResponse( 403, @@ -202,6 +237,11 @@ final class DiffusionServeController extends DiffusionController { } } + $response = $this->validateGitLFSRequest($repository, $viewer); + if ($response) { + return $response; + } + $this->setServiceRepository($repository); if (!$repository->isTracked()) { @@ -212,46 +252,57 @@ final class DiffusionServeController extends DiffusionController { $is_push = !$this->isReadOnlyRequest($repository); - switch ($repository->getServeOverHTTP()) { - case PhabricatorRepository::SERVE_READONLY: - if ($is_push) { + if ($this->getIsGitLFSRequest() && $this->getGitLFSToken()) { + // We allow git LFS requests over HTTP even if the repository does not + // otherwise support HTTP reads or writes, as long as the user is using a + // token from SSH. If they're using HTTP username + password auth, they + // have to obey the normal HTTP rules. + } else { + switch ($repository->getServeOverHTTP()) { + case PhabricatorRepository::SERVE_READONLY: + if ($is_push) { + return new PhabricatorVCSResponse( + 403, + pht('This repository is read-only over HTTP.')); + } + break; + case PhabricatorRepository::SERVE_READWRITE: + // We'll check for push capability below. + break; + case PhabricatorRepository::SERVE_OFF: + default: return new PhabricatorVCSResponse( 403, - pht('This repository is read-only over HTTP.')); - } - break; - case PhabricatorRepository::SERVE_READWRITE: - if ($is_push) { - $can_push = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - DiffusionPushCapability::CAPABILITY); - if (!$can_push) { - if ($viewer->isLoggedIn()) { - return new PhabricatorVCSResponse( - 403, - pht('You do not have permission to push to this repository.')); - } else { - if ($allow_auth) { - return new PhabricatorVCSResponse( - 401, - pht('You must log in to push to this repository.')); - } else { - return new PhabricatorVCSResponse( - 403, - pht( - 'Pushing to this repository requires authentication, '. - 'which is forbidden over HTTP.')); - } - } + pht('This repository is not available over HTTP.')); + } + } + + if ($is_push) { + $can_push = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + DiffusionPushCapability::CAPABILITY); + if (!$can_push) { + if ($viewer->isLoggedIn()) { + return new PhabricatorVCSResponse( + 403, + pht( + 'You do not have permission to push to this '. + 'repository.')); + } else { + if ($allow_auth) { + return new PhabricatorVCSResponse( + 401, + pht('You must log in to push to this repository.')); + } else { + return new PhabricatorVCSResponse( + 403, + pht( + 'Pushing to this repository requires authentication, '. + 'which is forbidden over HTTP.')); } } - break; - case PhabricatorRepository::SERVE_OFF: - default: - return new PhabricatorVCSResponse( - 403, - pht('This repository is not available over HTTP.')); + } } $vcs_type = $repository->getVersionControlSystem(); @@ -324,6 +375,14 @@ final class DiffusionServeController extends DiffusionController { PhabricatorRepository $repository, PhabricatorUser $viewer) { + // We can serve Git LFS requests first, since we don't need to proxy them. + // It's also important that LFS requests never fall through to standard + // service pathways, because that would let you use LFS tokens to read + // normal repository data. + if ($this->getIsGitLFSRequest()) { + return $this->serveGitLFSRequest($repository, $viewer); + } + // If this repository is hosted on a service, we need to proxy the request // to a host which can serve it. $is_cluster_request = $this->getRequest()->isProxiedClusterRequest(); @@ -363,6 +422,8 @@ final class DiffusionServeController extends DiffusionController { // TODO: This implementation is safe by default, but very incomplete. + // TODO: This doesn't get the right result for Git LFS yet. + switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $service = $request->getStr('service'); @@ -514,6 +575,52 @@ final class DiffusionServeController extends DiffusionController { return $base_path; } + private function authenticateGitLFSUser( + $username, + PhutilOpaqueEnvelope $password) { + + // Never accept these credentials for requests which aren't LFS requests. + if (!$this->getIsGitLFSRequest()) { + return null; + } + + // If we have the wrong username, don't bother checking if the token + // is right. + if ($username !== DiffusionGitLFSTemporaryTokenType::HTTP_USERNAME) { + return null; + } + + $lfs_pass = $password->openEnvelope(); + $lfs_hash = PhabricatorHash::digest($lfs_pass); + + $token = id(new PhabricatorAuthTemporaryTokenQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withTokenTypes(array(DiffusionGitLFSTemporaryTokenType::TOKENTYPE)) + ->withTokenCodes(array($lfs_hash)) + ->withExpired(false) + ->executeOne(); + if (!$token) { + return null; + } + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($token->getUserPHID())) + ->executeOne(); + + if (!$user) { + return null; + } + + if (!$user->isUserActivated()) { + return null; + } + + $this->gitLFSToken = $token; + + return $user; + } + private function authenticateHTTPRepositoryUser( $username, PhutilOpaqueEnvelope $password) { @@ -739,4 +846,291 @@ final class DiffusionServeController extends DiffusionController { ); } + private function validateGitLFSRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + if (!$this->getIsGitLFSRequest()) { + return null; + } + + if (!$repository->canUseGitLFS()) { + return new PhabricatorVCSResponse( + 403, + pht( + 'The requested repository ("%s") does not support Git LFS.', + $repository->getDisplayName())); + } + + // If this is using an LFS token, sanity check that we're using it on the + // correct repository. This shouldn't really matter since the user could + // just request a proper token anyway, but it suspicious and should not + // be permitted. + + $token = $this->getGitLFSToken(); + if ($token) { + $resource = $token->getTokenResource(); + if ($resource !== $repository->getPHID()) { + return new PhabricatorVCSResponse( + 403, + pht( + 'The authentication token provided in the request is bound to '. + 'a different repository than the requested repository ("%s").', + $repository->getDisplayName())); + } + } + + return null; + } + + private function serveGitLFSRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + + if (!$this->getIsGitLFSRequest()) { + throw new Exception(pht('This is not a Git LFS request!')); + } + + $path = $this->getGitLFSRequestPath($repository); + $matches = null; + + if (preg_match('(^upload/(.*)\z)', $path, $matches)) { + $oid = $matches[1]; + return $this->serveGitLFSUploadRequest($repository, $viewer, $oid); + } else if ($path == 'objects/batch') { + return $this->serveGitLFSBatchRequest($repository, $viewer); + } else { + return DiffusionGitLFSResponse::newErrorResponse( + 404, + pht( + 'Git LFS operation "%s" is not supported by this server.', + $path)); + } + } + + private function serveGitLFSBatchRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + + $input = PhabricatorStartup::getRawInput(); + $input = phutil_json_decode($input); + + $operation = idx($input, 'operation'); + switch ($operation) { + case 'upload': + $want_upload = true; + break; + case 'download': + $want_upload = false; + break; + default: + return DiffusionGitLFSResponse::newErrorResponse( + 404, + pht( + 'Git LFS batch operation "%s" is not supported by this server.', + $operation)); + } + + $objects = idx($input, 'objects', array()); + + $hashes = array(); + foreach ($objects as $object) { + $hashes[] = idx($object, 'oid'); + } + + if ($hashes) { + $refs = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes($hashes) + ->execute(); + $refs = mpull($refs, null, 'getObjectHash'); + } else { + $refs = array(); + } + + $file_phids = mpull($refs, 'getFilePHID'); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + $authorization = null; + $output = array(); + foreach ($objects as $object) { + $oid = idx($object, 'oid'); + $size = idx($object, 'size'); + $ref = idx($refs, $oid); + $error = null; + + // NOTE: If we already have a ref for this object, we only emit a + // "download" action. The client should not upload the file again. + + $actions = array(); + if ($ref) { + $file = idx($files, $ref->getFilePHID()); + if ($file) { + // Git LFS may prompt users for authentication if the action does + // not provide an "Authorization" header and does not have a query + // parameter named "token". See here for discussion: + // + $no_authorization = 'Basic '.base64_encode('none'); + + $get_uri = $file->getCDNURIWithToken(); + $actions['download'] = array( + 'href' => $get_uri, + 'header' => array( + 'Authorization' => $no_authorization, + ), + ); + } else { + $error = array( + 'code' => 404, + 'message' => pht( + 'Object "%s" was previously uploaded, but no longer exists '. + 'on this server.', + $oid), + ); + } + } else if ($want_upload) { + if (!$authorization) { + // Here, we could reuse the existing authorization if we have one, + // but it's a little simpler to just generate a new one + // unconditionally. + $authorization = $this->newGitLFSHTTPAuthorization( + $repository, + $viewer, + $operation); + } + + $put_uri = $repository->getGitLFSURI("info/lfs/upload/{$oid}"); + + $actions['upload'] = array( + 'href' => $put_uri, + 'header' => array( + 'Authorization' => $authorization, + 'X-Phabricator-Request-Type' => 'git-lfs', + ), + ); + } + + $object = array( + 'oid' => $oid, + 'size' => $size, + ); + + if ($actions) { + $object['actions'] = $actions; + } + + if ($error) { + $object['error'] = $error; + } + + $output[] = $object; + } + + $output = array( + 'objects' => $output, + ); + + return id(new DiffusionGitLFSResponse()) + ->setContent($output); + } + + private function serveGitLFSUploadRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer, + $oid) { + + $ref = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes(array($oid)) + ->executeOne(); + if ($ref) { + return DiffusionGitLFSResponse::newErrorResponse( + 405, + pht( + 'Content for object "%s" is already known to this server. It can '. + 'not be uploaded again.', + $oid)); + } + + $request_stream = new AphrontRequestStream(); + $request_iterator = $request_stream->getIterator(); + $hashing_iterator = id(new PhutilHashingIterator($request_iterator)) + ->setAlgorithm('sha256'); + + $source = id(new PhabricatorIteratorFileUploadSource()) + ->setName('lfs-'.$oid) + ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE) + ->setIterator($hashing_iterator); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file = $source->uploadFile(); + unset($unguarded); + + $hash = $hashing_iterator->getHash(); + if ($hash !== $oid) { + return DiffusionGitLFSResponse::newErrorResponse( + 400, + pht( + 'Uploaded data is corrupt or invalid. Expected hash "%s", actual '. + 'hash "%s".', + $oid, + $hash)); + } + + $ref = id(new PhabricatorRepositoryGitLFSRef()) + ->setRepositoryPHID($repository->getPHID()) + ->setObjectHash($hash) + ->setByteSize($file->getByteSize()) + ->setAuthorPHID($viewer->getPHID()) + ->setFilePHID($file->getPHID()); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + // Attach the file to the repository to give users permission + // to access it. + $file->attachToObject($repository->getPHID()); + $ref->save(); + unset($unguarded); + + // This is just a plain HTTP 200 with no content, which is what `git lfs` + // expects. + return new DiffusionGitLFSResponse(); + } + + private function newGitLFSHTTPAuthorization( + PhabricatorRepository $repository, + PhabricatorUser $viewer, + $operation) { + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( + $repository, + $viewer, + $operation); + + unset($unguarded); + + return $authorization; + } + + private function getGitLFSRequestPath(PhabricatorRepository $repository) { + $request_path = $this->getRequestDirectoryPath($repository); + + $matches = null; + if (preg_match('(^/info/lfs(?:\z|/)(.*))', $request_path, $matches)) { + return $matches[1]; + } + + return null; + } + } diff --git a/src/applications/diffusion/controller/DiffusionSymbolController.php b/src/applications/diffusion/controller/DiffusionSymbolController.php index defe09d58c..3aaccffd22 100644 --- a/src/applications/diffusion/controller/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/DiffusionSymbolController.php @@ -134,17 +134,24 @@ final class DiffusionSymbolController extends DiffusionController { $table->setNoDataString( pht('No matching symbol could be found in any indexed repository.')); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Similar Symbols')) - ->setTable($table); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Similar Symbols')) + ->setHeaderIcon('fa-bullseye'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Find Symbol')); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $table, + )); return $this->newPage() ->setTitle(pht('Find Symbol')) ->setCrumbs($crumbs) - ->appendChild($panel); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index 4749675bbb..5abd93c37d 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -45,6 +45,11 @@ final class DiffusionTagListController extends DiffusionController { $tags = $pager->sliceResults($tags); $content = null; + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Tags')) + ->setHeaderIcon('fa-tags'); + if (!$tags) { $content = $this->renderStatusMessage( pht('No Tags'), @@ -69,11 +74,7 @@ final class DiffusionTagListController extends DiffusionController { $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Tags')) - ->appendChild($view); - - $content = $panel; + $content = $view; } $crumbs = $this->buildCrumbs( @@ -81,9 +82,22 @@ final class DiffusionTagListController extends DiffusionController { 'tags' => true, 'commit' => $drequest->getSymbolicCommit(), )); + $crumbs->setBorder(true); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getDisplayName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($view); $pager_box = $this->renderTablePagerBox($pager); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -91,11 +105,7 @@ final class DiffusionTagListController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild( - array( - $content, - $pager_box, - )); + ->appendChild($view); } } diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php new file mode 100644 index 0000000000..362a4887ec --- /dev/null +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php @@ -0,0 +1,108 @@ +setName('git-lfs-authenticate'); + $this->setArguments( + array( + array( + 'name' => 'argv', + 'wildcard' => true, + ), + )); + } + + protected function identifyRepository() { + return $this->loadRepositoryWithPath($this->getLFSPathArgument()); + } + + private function getLFSPathArgument() { + return $this->getLFSArgument(0); + } + + private function getLFSOperationArgument() { + return $this->getLFSArgument(1); + } + + private function getLFSArgument($position) { + $args = $this->getArgs(); + $argv = $args->getArg('argv'); + + if (!isset($argv[$position])) { + throw new Exception( + pht( + 'Expected `git-lfs-authenticate `, but received '. + 'too few arguments.')); + } + + return $argv[$position]; + } + + protected function executeRepositoryOperations() { + $operation = $this->getLFSOperationArgument(); + + // NOTE: We aren't checking write access here, even for "upload". The + // HTTP endpoint should be able to do that for us. + + switch ($operation) { + case 'upload': + case 'download': + break; + default: + throw new Exception( + pht( + 'Git LFS operation "%s" is not supported by this server.', + $operation)); + } + + $repository = $this->getRepository(); + + if (!$repository->isGit()) { + throw new Exception( + pht( + 'Repository "%s" is not a Git repository. Git LFS is only '. + 'supported for Git repositories.', + $repository->getDisplayName())); + } + + if (!$repository->canUseGitLFS()) { + throw new Exception( + pht('Git LFS is not enabled for this repository.')); + } + + // NOTE: This is usually the same as the default URI (which does not + // need to be specified in the response), but the protocol or domain may + // differ in some situations. + + $lfs_uri = $repository->getGitLFSURI('info/lfs'); + + // Generate a temporary token to allow the user to acces LFS over HTTP. + // This works even if normal HTTP repository operations are not available + // on this host, and does not require the user to have a VCS password. + + $user = $this->getUser(); + + $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( + $repository, + $user, + $operation); + + $headers = array( + 'authorization' => $authorization, + ); + + $result = array( + 'header' => $headers, + 'href' => $lfs_uri, + ); + $result = phutil_json_encode($result); + + $this->writeIO($result); + $this->waitForGitClient(); + + return 0; + } + +} diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php new file mode 100644 index 0000000000..0973072ba4 --- /dev/null +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php @@ -0,0 +1,42 @@ +setTokenResource($repository->getPHID()) + ->setTokenType(self::TOKENTYPE) + ->setTokenCode($lfs_hash) + ->setUserPHID($viewer->getPHID()) + ->setTemporaryTokenProperty('lfs.operation', $operation) + ->setTokenExpires($ttl) + ->save(); + + $authorization_header = base64_encode($lfs_user.':'.$lfs_pass); + return 'Basic '.$authorization_header; + } + +} diff --git a/src/applications/diffusion/response/DiffusionGitLFSResponse.php b/src/applications/diffusion/response/DiffusionGitLFSResponse.php new file mode 100644 index 0000000000..513dc577e9 --- /dev/null +++ b/src/applications/diffusion/response/DiffusionGitLFSResponse.php @@ -0,0 +1,37 @@ +setHTTPResponseCode($code) + ->setContent( + array( + 'message' => $message, + )); + } + + public function setContent(array $content) { + $this->content = phutil_json_encode($content); + return $this; + } + + public function buildResponseString() { + return $this->content; + } + + public function getHeaders() { + $headers = array( + array('Content-Type', 'application/vnd.git-lfs+json'), + ); + + return array_merge(parent::getHeaders(), $headers); + } + +} diff --git a/src/applications/diffusion/view/DiffusionBranchTableView.php b/src/applications/diffusion/view/DiffusionBranchTableView.php index 462f296bcb..b75228a0ce 100644 --- a/src/applications/diffusion/view/DiffusionBranchTableView.php +++ b/src/applications/diffusion/view/DiffusionBranchTableView.php @@ -39,7 +39,7 @@ final class DiffusionBranchTableView extends DiffusionView { $commit = idx($commits, $branch->getCommitIdentifier()); if ($commit) { $details = $commit->getSummary(); - $datetime = phabricator_datetime($commit->getEpoch(), $viewer); + $datetime = $viewer->formatShortDateTime($commit->getEpoch()); $buildable = idx($buildables, $commit->getPHID()); if ($buildable) { $build_status = $this->renderBuildable($buildable); @@ -147,7 +147,7 @@ final class DiffusionBranchTableView extends DiffusionView { '', 'wide', '', - '', + 'right', )); $view->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index 6354c5dc5e..ffc5ce61ed 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -90,7 +90,6 @@ final class DiffusionBrowseTableView extends DiffusionView { $browse_link, idx($dict, 'lint'), $dict['commit'], - $dict['author'], $dict['details'], $dict['date'], ); @@ -120,7 +119,6 @@ final class DiffusionBrowseTableView extends DiffusionView { pht('Path'), ($lint ? $lint : pht('Lint')), pht('Modified'), - pht('Author/Committer'), pht('Details'), pht('Committed'), )); @@ -130,9 +128,8 @@ final class DiffusionBrowseTableView extends DiffusionView { '', '', '', - '', 'wide', - '', + 'right', )); $view->setColumnVisibility( array( @@ -142,7 +139,6 @@ final class DiffusionBrowseTableView extends DiffusionView { true, true, true, - true, )); $view->setDeviceVisibility( @@ -150,7 +146,6 @@ final class DiffusionBrowseTableView extends DiffusionView { true, true, false, - true, false, true, false, diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index e62c4382c9..a9fb34e683 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -95,7 +95,7 @@ final class DiffusionHistoryTableView extends DiffusionView { $epoch = $history->getEpoch(); if ($epoch) { - $committed = phabricator_datetime($epoch, $viewer); + $committed = $viewer->formatShortDateTime($epoch); } else { $committed = null; } @@ -195,7 +195,7 @@ final class DiffusionHistoryTableView extends DiffusionView { '', '', 'wide', - '', + 'right', )); $view->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionPushLogListView.php b/src/applications/diffusion/view/DiffusionPushLogListView.php index 7cc02a49b4..73a44794e8 100644 --- a/src/applications/diffusion/view/DiffusionPushLogListView.php +++ b/src/applications/diffusion/view/DiffusionPushLogListView.php @@ -88,7 +88,7 @@ final class DiffusionPushLogListView extends AphrontView { // TODO: Make these human-readable. $log->getChangeFlags(), $log->getPushEvent()->getRejectCode(), - phabricator_datetime($log->getEpoch(), $viewer), + $viewer->formatShortDateTime($log->getEpoch()), ); } @@ -119,7 +119,7 @@ final class DiffusionPushLogListView extends AphrontView { 'wide', 'n', 'n', - 'date', + 'right', )); return $table; diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index 923fa30fc5..df59925522 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -28,6 +28,7 @@ final class DiffusionTagListView extends DiffusionView { public function render() { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + $viewer = $this->getViewer(); $buildables = $this->loadBuildables($this->commits); $has_builds = false; @@ -100,7 +101,7 @@ final class DiffusionTagListView extends DiffusionView { $build, $author, $description, - phabricator_datetime($tag->getEpoch(), $this->getViewer()), + $viewer->formatShortDateTime($tag->getEpoch()), ); } @@ -123,6 +124,7 @@ final class DiffusionTagListView extends DiffusionView { '', '', 'wide', + 'right', )) ->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 331c172866..5aa1fad33c 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -95,7 +95,9 @@ abstract class DiffusionView extends AphrontView { } $icon = DifferentialChangeType::getIconForFileType($file_type); - $icon_view = id(new PHUIIconView())->setIcon($icon); + $color = DifferentialChangeType::getIconColorForFileType($file_type); + $icon_view = id(new PHUIIconView()) + ->setIcon($icon.' '.$color); // If we're rendering a file or directory name, don't show the tooltip. if ($display_name !== null) { diff --git a/src/applications/drydock/controller/DrydockAuthorizationViewController.php b/src/applications/drydock/controller/DrydockAuthorizationViewController.php index bc34154e4f..80a0e5d1ef 100644 --- a/src/applications/drydock/controller/DrydockAuthorizationViewController.php +++ b/src/applications/drydock/controller/DrydockAuthorizationViewController.php @@ -26,16 +26,14 @@ final class DrydockAuthorizationViewController ->setUser($viewer) ->setPolicyObject($authorization); - $state = $authorization->getBlueprintAuthorizationState(); $icon = DrydockAuthorization::getBlueprintStateIcon($state); $name = DrydockAuthorization::getBlueprintStateName($state); $header->setStatus($icon, null, $name); - $actions = $this->buildActionListView($authorization); + $curtain = $this->buildCurtain($authorization); $properties = $this->buildPropertyListView($authorization); - $properties->setActionList($actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( @@ -45,29 +43,32 @@ final class DrydockAuthorizationViewController $blueprint->getBlueprintName(), $this->getApplicationURI("blueprint/{$blueprint_id}/")); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockAuthorization $authorization) { + private function buildCurtain(DrydockAuthorization $authorization) { $viewer = $this->getViewer(); $id = $authorization->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($authorization); + $curtain = $this->newCurtainView($authorization); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -84,7 +85,7 @@ final class DrydockAuthorizationViewController $can_authorize = $can_edit && ($state != $state_authorized); $can_decline = $can_edit && ($state != $state_declined); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($authorize_uri) ->setName(pht('Approve Authorization')) @@ -92,7 +93,7 @@ final class DrydockAuthorizationViewController ->setWorkflow(true) ->setDisabled(!$can_authorize)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($decline_uri) ->setName(pht('Decline Authorization')) @@ -100,7 +101,7 @@ final class DrydockAuthorizationViewController ->setWorkflow(true) ->setDisabled(!$can_decline)); - return $view; + return $curtain; } private function buildPropertyListView(DrydockAuthorization $authorization) { diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 21f3a395d3..d3f73bf78c 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -19,7 +19,8 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($blueprint); + ->setPolicyObject($blueprint) + ->setHeaderIcon('fa-map-o'); if ($blueprint->getIsDisabled()) { $header->setStatus('fa-ban', 'red', pht('Disabled')); @@ -27,15 +28,12 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } - $actions = $this->buildActionListView($blueprint); - $properties = $this->buildPropertyListView($blueprint, $actions); + $curtain = $this->buildCurtain($blueprint); + $properties = $this->buildPropertyListView($blueprint); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID())); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $field_list = PhabricatorCustomField::getObjectFields( $blueprint, @@ -49,9 +47,8 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $viewer, $properties); - $resource_box = $this->buildResourceBox($blueprint); - - $authorizations_box = $this->buildAuthorizationsBox($blueprint); + $resources = $this->buildResourceBox($blueprint); + $authorizations = $this->buildAuthorizationsBox($blueprint); $timeline = $this->buildTransactionTimeline( $blueprint, @@ -61,33 +58,36 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $log_query = id(new DrydockLogQuery()) ->withBlueprintPHIDs(array($blueprint->getPHID())); - $log_box = $this->buildLogBox( + $logs = $this->buildLogBox( $log_query, $this->getApplicationURI("blueprint/{$id}/logs/query/all/")); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $resource_box, - $authorizations_box, - $log_box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties) + ->setMainColumn(array( + $resources, + $authorizations, + $logs, $timeline, - ), - array( - 'title' => $title, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockBlueprint $blueprint) { + private function buildCurtain(DrydockBlueprint $blueprint) { $viewer = $this->getViewer(); $id = $blueprint->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($blueprint); - + $curtain = $this->newCurtainView($blueprint); $edit_uri = $this->getApplicationURI("blueprint/edit/{$id}/"); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -95,7 +95,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $blueprint, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($edit_uri) ->setName(pht('Edit Blueprint')) @@ -113,7 +113,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $disable_uri = $this->getApplicationURI("blueprint/{$id}/enable/"); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($disable_uri) ->setName($disable_name) @@ -121,19 +121,15 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { ->setWorkflow(true) ->setDisabled(!$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockBlueprint $blueprint, - PhabricatorActionListView $actions) { + DrydockBlueprint $blueprint) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($blueprint); - - $view->setActionList($actions); + ->setUser($viewer); $view->addProperty( pht('Type'), @@ -177,6 +173,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { return id(new PHUIObjectBoxView()) ->setHeader($resource_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($resource_list); } @@ -242,6 +239,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { return id(new PHUIObjectBoxView()) ->setHeader($authorizations_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($authorization_list); } diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index 17b8d34916..872a78f66f 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -102,6 +102,7 @@ abstract class DrydockController extends PhabricatorController { ->setText(pht('View All'))); return id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeader($log_header) ->setTable($log_table); } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 088b197a5f..7166b0bef6 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -21,53 +21,59 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $title = pht('Lease %d', $lease->getID()); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-link'); if ($lease->isReleasing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); } - $actions = $this->buildActionListView($lease); - $properties = $this->buildPropertyListView($lease, $actions); + $curtain = $this->buildCurtain($lease); + $properties = $this->buildPropertyListView($lease); $log_query = id(new DrydockLogQuery()) ->withLeasePHIDs(array($lease->getPHID())); - $log_box = $this->buildLogBox( + $logs = $this->buildLogBox( $log_query, $this->getApplicationURI("lease/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $lease_uri); + $crumbs->setBorder(true); $locks = $this->buildLocksTab($lease->getPHID()); $commands = $this->buildCommandsTab($lease->getPHID()); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Properties')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $object_box, - $log_box, - ), - array( - 'title' => $title, + $logs, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockLease $lease) { + private function buildCurtain(DrydockLease $lease) { $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($lease); - + $curtain = $this->newCurtainView($lease); $id = $lease->getID(); $can_release = $lease->canRelease(); @@ -80,7 +86,7 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $lease, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Release Lease')) ->setIcon('fa-times') @@ -88,16 +94,14 @@ final class DrydockLeaseViewController extends DrydockLeaseController { ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockLease $lease, - PhabricatorActionListView $actions) { + DrydockLease $lease) { $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); - $view->setActionList($actions); $view->addProperty( pht('Status'), diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php b/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php index f8c210368a..e7fbf07b35 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php @@ -25,50 +25,52 @@ final class DrydockRepositoryOperationViewController $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($operation); + ->setPolicyObject($operation) + ->setHeaderIcon('fa-fighter-jet'); $state = $operation->getOperationState(); $icon = DrydockRepositoryOperation::getOperationStateIcon($state); $name = DrydockRepositoryOperation::getOperationStateName($state); $header->setStatus($icon, null, $name); - $actions = $this->buildActionListView($operation); + $curtain = $this->buildCurtain($operation); $properties = $this->buildPropertyListView($operation); - $properties->setActionList($actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Operations'), $this->getApplicationURI('operation/')); $crumbs->addTextCrumb($title); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $status_view = id(new DrydockRepositoryOperationStatusView()) ->setUser($viewer) ->setOperation($operation); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties) + ->setMainColumn(array( + $status_view, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $status_view, - )); + $view, + )); } - private function buildActionListView(DrydockRepositoryOperation $operation) { + private function buildCurtain(DrydockRepositoryOperation $operation) { $viewer = $this->getViewer(); $id = $operation->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($operation); + $curtain = $this->newCurtainView($operation); - return $view; + return $curtain; } private function buildPropertyListView( diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 61319fbdbc..c2ab4337f5 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -23,14 +23,15 @@ final class DrydockResourceViewController extends DrydockResourceController { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($resource) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-map'); if ($resource->isReleasing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); } - $actions = $this->buildActionListView($resource); - $properties = $this->buildPropertyListView($resource, $actions); + $curtain = $this->buildCurtain($resource); + $properties = $this->buildPropertyListView($resource); $id = $resource->getID(); $resource_uri = $this->getApplicationURI("resource/{$id}/"); @@ -44,37 +45,42 @@ final class DrydockResourceViewController extends DrydockResourceController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); + $crumbs->setBorder(true); $locks = $this->buildLocksTab($resource->getPHID()); $commands = $this->buildCommandsTab($resource->getPHID()); + $lease_box = $this->buildLeaseBox($resource); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Properties')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - $lease_box = $this->buildLeaseBox($resource); - - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $object_box, $lease_box, $log_box, - ), - array( - 'title' => $title, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockResource $resource) { + private function buildCurtain(DrydockResource $resource) { $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($resource); + $curtain = $this->newCurtainView($resource); $can_release = $resource->canRelease(); if ($resource->isReleasing()) { @@ -89,7 +95,7 @@ final class DrydockResourceViewController extends DrydockResourceController { $uri = '/resource/'.$resource->getID().'/release/'; $uri = $this->getApplicationURI($uri); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($uri) ->setName(pht('Release Resource')) @@ -97,17 +103,14 @@ final class DrydockResourceViewController extends DrydockResourceController { ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockResource $resource, - PhabricatorActionListView $actions) { + DrydockResource $resource) { $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setActionList($actions); - + $view = new PHUIPropertyListView(); $status = $resource->getStatus(); $status = DrydockResourceStatus::getNameForStatus($status); @@ -179,6 +182,7 @@ final class DrydockResourceViewController extends DrydockResourceController { return id(new PHUIObjectBoxView()) ->setHeader($lease_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($lease_list); } diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php index d84b4cc0e7..aa7d1e3873 100644 --- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -48,6 +48,7 @@ final class DrydockRepositoryOperationStatusView $box_view = $this->getBoxView(); if (!$box_view) { $box_view = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeaderText(pht('Operation Status')); } $box_view->setObjectList($list); diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 28328796ce..f1d4c11bd7 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -26,7 +26,6 @@ final class PhabricatorFile extends PhabricatorFileDAO PhabricatorPolicyInterface, PhabricatorDestructibleInterface { - const ONETIME_TEMPORARY_TOKEN_TYPE = 'file:onetime'; const STORAGE_FORMAT_RAW = 'raw'; const METADATA_IMAGE_WIDTH = 'width'; @@ -1119,12 +1118,13 @@ final class PhabricatorFile extends PhabricatorFileDAO protected function generateOneTimeToken() { $key = Filesystem::readRandomCharacters(16); + $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE; // Save the new secret. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $token = id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($this->getPHID()) - ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) + ->setTokenResource($this->getPHID()) + ->setTokenType($token_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); @@ -1134,10 +1134,12 @@ final class PhabricatorFile extends PhabricatorFileDAO } public function validateOneTimeToken($token_code) { + $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE; + $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($this->getPHID())) - ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) + ->withTokenResources(array($this->getPHID())) + ->withTokenTypes(array($token_type)) ->withExpired(false) ->withTokenCodes(array(PhabricatorHash::digest($token_code))) ->executeOne(); diff --git a/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php b/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php new file mode 100644 index 0000000000..73b5cd8c09 --- /dev/null +++ b/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php @@ -0,0 +1,17 @@ +rewind(); $this->didRewind = true; } else { - $data->next(); + if ($data->valid()) { + $data->next(); + } } if (!$data->valid()) { diff --git a/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php new file mode 100644 index 0000000000..b8d18936e6 --- /dev/null +++ b/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php @@ -0,0 +1,25 @@ +iterator = $iterator; + return $this; + } + + public function getIterator() { + return $this->iterator; + } + + protected function newDataIterator() { + return $this->getIterator(); + } + + protected function getDataLength() { + return null; + } + +} diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php index 75de4b20df..49efdc3e25 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -7,6 +7,7 @@ final class HarbormasterUnitSummaryView extends AphrontView { private $limit; private $excuse; private $showViewAll; + private $background; public function setBuildable(HarbormasterBuildable $buildable) { $this->buildable = $buildable; @@ -33,6 +34,11 @@ final class HarbormasterUnitSummaryView extends AphrontView { return $this; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + public function render() { $messages = $this->messages; $buildable = $this->buildable; @@ -54,9 +60,14 @@ final class HarbormasterUnitSummaryView extends AphrontView { $tag_icon = 'fa-ban'; } + $tag = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade($tag_color) + ->setIcon($tag_icon) + ->setName($tag_text); + $header = id(new PHUIHeaderView()) - ->setHeader(pht('Unit Tests')) - ->setStatus($tag_icon, $tag_color, $tag_text); + ->setHeader(array(pht('Unit Tests'), $tag)); if ($this->showViewAll) { $view_all = id(new PHUIButtonView()) @@ -98,6 +109,10 @@ final class HarbormasterUnitSummaryView extends AphrontView { $box->setTable($table); + if ($this->background) { + $box->setBackground($this->background); + } + return $box; } diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index d7885ef101..3370fb428b 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -700,7 +700,7 @@ final class PhabricatorUserEditor extends PhabricatorEditor { } } - private function revokePasswordResetLinks(PhabricatorUser $user) { + public function revokePasswordResetLinks(PhabricatorUser $user) { // Revoke any outstanding password reset links. If an attacker compromises // an account, changes the email address, and sends themselves a password // reset link, it could otherwise remain live for a short period of time @@ -710,8 +710,8 @@ final class PhabricatorUserEditor extends PhabricatorEditor { $user, array($user->getPHID()), array( - PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE, - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE, + PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE, + PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE, )); } diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 07c5695945..ed6fceba1e 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -742,6 +742,38 @@ final class PhabricatorUser return new DateTimeZone($this->getTimezoneIdentifier()); } + public function formatShortDateTime($when, $now = null) { + if ($now === null) { + $now = PhabricatorTime::getNow(); + } + + try { + $when = new DateTime('@'.$when); + $now = new DateTime('@'.$now); + } catch (Exception $ex) { + return null; + } + + $zone = $this->getTimeZone(); + + $when->setTimeZone($zone); + $now->setTimeZone($zone); + + if ($when->format('Y') !== $now->format('Y')) { + // Different year, so show "Feb 31 2075". + $format = 'M j Y'; + } else if ($when->format('Ymd') !== $now->format('Ymd')) { + // Same year but different month and day, so show "Feb 31". + $format = 'M j'; + } else { + // Same year, month and day so show a time of day. + $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; + $format = $this->getPreference($pref_time); + } + + return $when->format($format); + } + public function getPreference($key) { $preferences = $this->loadPreferences(); diff --git a/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php b/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php new file mode 100644 index 0000000000..35920e16e0 --- /dev/null +++ b/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php @@ -0,0 +1,109 @@ +getEngine(); + $viewer = $engine->getConfig('viewer'); + + if (!$this->isFlatText($matches[0])) { + return $matches[0]; + } + + $phid_type = phid_get_type($matches[1]); + if ($phid_type == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { + return $matches[0]; + } + + $token = $engine->storeText($matches[0]); + if ($engine->isTextMode()) { + return $token; + } + + $original_key = self::KEY_RULE_HANDLE_ORIGINAL; + $original = $engine->getTextMetadata($original_key, array()); + $original[$token] = $matches[0]; + $engine->setTextMetadata($original_key, $original); + + $metadata_key = self::KEY_RULE_HANDLE; + $metadata = $engine->getTextMetadata($metadata_key, array()); + $phid = $matches[1]; + if (empty($metadata[$phid])) { + $metadata[$phid] = array(); + } + $metadata[$phid][] = $token; + $engine->setTextMetadata($metadata_key, $metadata); + + return $token; + } + + public function didMarkupText() { + $engine = $this->getEngine(); + + $metadata_key = self::KEY_RULE_HANDLE; + + $metadata = $engine->getTextMetadata($metadata_key, array()); + if (empty($metadata)) { + // No mentions, or we already processed them. + return; + } + + $original_key = self::KEY_RULE_HANDLE_ORIGINAL; + $original = $engine->getTextMetadata($original_key, array()); + + $phids = array_keys($metadata); + + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->getEngine()->getConfig('viewer')) + ->withPHIDs($phids) + ->execute(); + + foreach ($metadata as $phid => $tokens) { + $handle = idx($handles, $phid); + + if ($handle->isComplete()) { + if ($engine->isHTMLMailMode()) { + $href = $handle->getURI(); + $href = PhabricatorEnv::getProductionURI($href); + + $link = phutil_tag( + 'a', + array( + 'href' => $href, + 'style' => ' + border-color: #f1f7ff; + color: #19558d; + background-color: #f1f7ff; + border: 1px solid transparent; + border-radius: 3px; + font-weight: bold; + padding: 0 4px;', + ), + $handle->getLinkName()); + } else { + $link = $handle->renderTag(); + $link->setPHID($phid); + } + foreach ($tokens as $token) { + $engine->overwriteStoredText($token, $link); + } + } else { + foreach ($tokens as $token) { + $engine->overwriteStoredText($token, idx($original, $token)); + } + } + } + + $engine->setTextMetadata($metadata_key, array()); + } +} diff --git a/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php b/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php index aca7c14dc7..4ef59b300c 100644 --- a/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php +++ b/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php @@ -18,7 +18,9 @@ final class PhabricatorPhurlLinkRemarkupRule extends PhutilRemarkupRule { public function markupLink(array $matches) { $engine = $this->getEngine(); $viewer = $engine->getConfig('viewer'); + $text_mode = $engine->isTextMode(); + $html_mode = $engine->isHTMLMailMode(); if (!$this->isFlatText($matches[0])) { return $matches[0]; @@ -28,46 +30,45 @@ final class PhabricatorPhurlLinkRemarkupRule extends PhutilRemarkupRule { $monogram = null; $is_monogram = '/^U(?P[1-9]\d*)/'; + $query = id(new PhabricatorPhurlURLQuery()) + ->setViewer($viewer); + if (preg_match($is_monogram, $ref, $monogram)) { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withIDs(array($monogram[1])) - ->execute(); + $query->withIDs(array($monogram[1])); } else if (ctype_digit($ref)) { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withIDs(array($ref)) - ->execute(); + $query->withIDs(array($ref)); } else { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withAliases(array($ref)) - ->execute(); + $query->withAliases(array($ref)); } - $phurl = head($phurls); + $phurl = $query->executeOne(); + if (!$phurl) { + return $matches[0]; + } - if ($phurl) { - if ($text_mode) { - return $phurl->getDisplayName(). - ' <'. - $phurl->getRedirectURI(). - '>'; - } + $uri = $phurl->getRedirectURI(); + $name = $phurl->getDisplayName(); + if ($text_mode || $html_mode) { + $uri = PhabricatorEnv::getProductionURI($uri); + } + + if ($text_mode) { + return pht( + '%s <%s>', + $name, + $uri); + } else { $link = phutil_tag( 'a', array( - 'href' => $phurl->getRedirectURI(), + 'href' => $uri, 'target' => '_blank', ), - $phurl->getDisplayName()); - - return $this->getEngine()->storeText($link); - } else { - return $matches[0]; + $name); } + + return $this->getEngine()->storeText($link); } - } diff --git a/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php b/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php new file mode 100644 index 0000000000..cd693be605 --- /dev/null +++ b/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php @@ -0,0 +1,64 @@ +ids = $ids; + return $this; + } + + public function withRepositoryPHIDs(array $phids) { + $this->repositoryPHIDs = $phids; + return $this; + } + + public function withObjectHashes(array $hashes) { + $this->objectHashes = $hashes; + return $this; + } + + public function newResultObject() { + return new PhabricatorRepositoryGitLFSRef(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + if ($this->objectHashes !== null) { + $where[] = qsprintf( + $conn, + 'objectHash IN (%Ls)', + $this->objectHashes); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorDiffusionApplication'; + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 9896a8ef29..7068657acc 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1518,6 +1518,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return null; } + return $this->getRawHTTPCloneURIObject(); + } + + private function getRawHTTPCloneURIObject() { $uri = PhabricatorEnv::getProductionURI($this->getURI()); $uri = new PhutilURI($uri); @@ -1819,6 +1823,38 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return !$this->isSVN(); } + public function canUseGitLFS() { + if (!$this->isGit()) { + return false; + } + + if (!$this->isHosted()) { + return false; + } + + // TODO: Unprototype this feature. + if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + return false; + } + + return true; + } + + public function getGitLFSURI($path = null) { + if (!$this->canUseGitLFS()) { + throw new Exception( + pht( + 'This repository does not support Git LFS, so Git LFS URIs can '. + 'not be generated for it.')); + } + + $uri = $this->getRawHTTPCloneURIObject(); + $uri = (string)$uri; + $uri = $uri.'/'.$path; + + return $uri; + } + public function canMirror() { if ($this->isGit() || $this->isHg()) { return true; @@ -2399,6 +2435,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $engine->destroyObject($atom); } + $lfs_refs = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($engine->getViewer()) + ->withRepositoryPHIDs(array($phid)) + ->execute(); + foreach ($lfs_refs as $ref) { + $engine->destroyObject($ref); + } + $this->saveTransaction(); } diff --git a/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php b/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php new file mode 100644 index 0000000000..507af99cb4 --- /dev/null +++ b/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php @@ -0,0 +1,72 @@ + array( + 'objectHash' => 'bytes64', + 'byteSize' => 'uint64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_hash' => array( + 'columns' => array('repositoryPHID', 'objectHash'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $file_phid = $this->getFilePHID(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($engine->getViewer()) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if ($file) { + $engine->destroyObject($file); + } + + $this->delete(); + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php b/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php index 4471151d4e..8ff2ed0c9b 100644 --- a/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php +++ b/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php @@ -155,9 +155,6 @@ final class PhabricatorRepositorySchemaSpec 'repositoryID' => array( 'columns' => array('repositoryID', 'pathID', 'commitSequence'), ), - 'key_history' => array( - 'columns' => array('commitID', 'isDirect', 'changeType'), - ), )); $this->buildRawSchema( diff --git a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php index 56b8fad76a..f2db373945 100644 --- a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php @@ -40,13 +40,14 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel { // the workflow from a password reset email. $key = $request->getStr('key'); + $password_type = PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE; + $token = null; if ($key) { $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) - ->withTokenTypes( - array(PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE)) + ->withTokenResources(array($user->getPHID())) + ->withTokenTypes(array($password_type)) ->withTokenCodes(array(PhabricatorHash::digest($key))) ->withExpired(false) ->executeOne(); diff --git a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php index a92026333a..030ad37116 100644 --- a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php @@ -23,7 +23,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { $tokens = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs(array($viewer->getPHID())) + ->withTokenResources(array($viewer->getPHID())) ->execute(); $rows = array(); diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index f032e34017..3330620148 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -4,6 +4,9 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { private $items = array(); private $authorityPackages; + private $header; + private $infoView; + private $background; public function addItem(PHUIDiffTableOfContentsItemView $item) { $this->items[] = $item; @@ -20,6 +23,21 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { return $this->authorityPackages; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + + public function setHeader(PHUIHeaderView $header) { + $this->header = $header; + return $this; + } + + public function setInfoView(PHUIInfoView $infoview) { + $this->infoView = $infoview; + return $this; + } + public function render() { $this->requireResource('differential-core-view-css'); $this->requireResource('differential-table-of-contents-css'); @@ -142,11 +160,24 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { ->setAnchorName('toc') ->setNavigationMarker(true); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Table of Contents')) + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Table of Contents')); + + if ($this->header) { + $header = $this->header; + } + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground($this->background) ->setTable($table) ->appendChild($anchor) ->appendChild($buttons); + + if ($this->infoView) { + $box->setInfoView($this->infoView); + } + return $box; } } diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index c3d9dab02a..f32971646a 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -494,6 +494,7 @@ final class PhabricatorMarkupEngine extends Phobject { $rules[] = new PhabricatorIconRemarkupRule(); $rules[] = new PhabricatorEmojiRemarkupRule(); + $rules[] = new PhabricatorHandleRemarkupRule(); $applications = PhabricatorApplication::getAllInstalledApplications(); foreach ($applications as $application) { diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php index 0da471574c..92481bc614 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/form/PHUIInfoView.php @@ -14,6 +14,7 @@ final class PHUIInfoView extends AphrontView { private $id; private $buttons = array(); private $isHidden; + private $flush; public function setTitle($title) { $this->title = $title; @@ -40,6 +41,11 @@ final class PHUIInfoView extends AphrontView { return $this; } + public function setFlush($flush) { + $this->flush = $flush; + return $this; + } + public function addButton(PHUIButtonView $button) { $this->buttons[] = $button; return $this; @@ -87,6 +93,9 @@ final class PHUIInfoView extends AphrontView { $classes[] = 'phui-info-view'; $classes[] = 'phui-info-severity-'.$this->severity; $classes[] = 'grouped'; + if ($this->flush) { + $classes[] = 'phui-info-view-flush'; + } $classes = implode(' ', $classes); $children = $this->renderChildren(); diff --git a/src/view/form/PHUIPagedFormView.php b/src/view/form/PHUIPagedFormView.php index b5f6ad7066..cfd18add58 100644 --- a/src/view/form/PHUIPagedFormView.php +++ b/src/view/form/PHUIPagedFormView.php @@ -263,6 +263,7 @@ final class PHUIPagedFormView extends AphrontView { $form->appendChild($submit); $box = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setFormErrors($errors) ->setForm($form); diff --git a/src/view/phui/PHUIHeadThingView.php b/src/view/phui/PHUIHeadThingView.php index 4ff0290578..dd788399d4 100644 --- a/src/view/phui/PHUIHeadThingView.php +++ b/src/view/phui/PHUIHeadThingView.php @@ -35,6 +35,9 @@ final class PHUIHeadThingView extends AphrontTagView { $classes = array(); $classes[] = 'phui-head-thing-view'; + if ($this->image) { + $classes[] = 'phui-head-has-image'; + } if ($this->size) { $classes[] = $this->size; @@ -57,8 +60,11 @@ final class PHUIHeadThingView extends AphrontTagView { 'href' => $this->imageHref, )); - return array($image, $this->content); - + if ($this->image) { + return array($image, $this->content); + } else { + return $this->content; + } } diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 5174767e28..6c50ed603e 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -83,6 +83,26 @@ final class PhabricatorStartup { * @task info */ public static function getRawInput() { + if (self::$rawInput === null) { + $stream = new AphrontRequestStream(); + + if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { + $encoding = trim($_SERVER['HTTP_CONTENT_ENCODING']); + $stream->setEncoding($encoding); + } + + $input = ''; + do { + $bytes = $stream->readData(); + if ($bytes === null) { + break; + } + $input .= $bytes; + } while (true); + + self::$rawInput = $input; + } + return self::$rawInput; } @@ -128,47 +148,6 @@ final class PhabricatorStartup { self::detectPostMaxSizeTriggered(); self::beginOutputCapture(); - - if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { - $encoding = trim($_SERVER['HTTP_CONTENT_ENCODING']); - } else { - $encoding = null; - } - - $input_stream = fopen('php://input', 'rb'); - if (!$input_stream) { - self::didFatal( - 'Unable to open "php://input" to read HTTP request body.'); - } - - if ($encoding === 'gzip') { - $ok = stream_filter_append( - $input_stream, - 'zlib.inflate', - STREAM_FILTER_READ, - array( - 'window' => 30, - )); - - if (!$ok) { - self::didFatal( - 'Failed to append gzip inflate filter to HTTP request body input '. - 'stream.'); - } - } - - $input_data = ''; - while (!feof($input_stream)) { - $read_bytes = fread($input_stream, 16 * 1024); - if ($read_bytes === false) { - self::didFatal( - 'Failed to read input bytes from HTTP request body.'); - } - $input_data .= $read_bytes; - } - fclose($input_stream); - - self::$rawInput = $input_data; } diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 71cfa3170b..85eb5ab437 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -6,7 +6,7 @@ .differential-changeset { position: relative; margin: 0; - padding-top: 32px; + padding-top: 16px; overflow-x: auto; /* Fixes what seems to be a layout bug in Firefox which causes scrollbars, @@ -265,7 +265,7 @@ td.cov-I { .differential-changeset h1 { font-size: {$biggestfontsize}; - padding: 2px 0 12px 12px; + padding: 2px 0 20px 12px; line-height: 20px; color: #000; } @@ -322,7 +322,7 @@ td.cov-I { .differential-changeset-buttons { float: right; - margin-right: 8px; + margin-right: 12px; } .device-phone .differential-changeset-buttons { @@ -362,3 +362,7 @@ tr.differential-inline-hidden { tr.differential-inline-loading { opacity: 0.5; } + +.differential-review-stage { + position: relative; +} diff --git a/webroot/rsrc/css/application/differential/core.css b/webroot/rsrc/css/application/differential/core.css index 7ccd633f3f..2dcc02bb18 100644 --- a/webroot/rsrc/css/application/differential/core.css +++ b/webroot/rsrc/css/application/differential/core.css @@ -3,7 +3,7 @@ */ .differential-primary-pane { - margin-bottom: 32px; + margin-top: -20px; } .differential-panel { @@ -23,3 +23,7 @@ -ms-user-select: none; user-select: none; } + +.differential-content-hidden { + margin: 0 0 24px 0; +} diff --git a/webroot/rsrc/css/application/diffusion/diffusion-icons.css b/webroot/rsrc/css/application/diffusion/diffusion-icons.css index 087ce9362e..c735e3d842 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-icons.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-icons.css @@ -20,3 +20,11 @@ input.diffusion-clone-uri { .diffusion-link-icon + .diffusion-link-icon { margin-left: 6px; } + +.diffusion-search-boxen { + padding: 16px; +} + +.diffusion-search-boxen .phui-form-view { + padding: 0; +} diff --git a/webroot/rsrc/css/application/diffusion/diffusion-readme.css b/webroot/rsrc/css/application/diffusion/diffusion-readme.css index f7f8506e2d..bb661bb2c8 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-readme.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-readme.css @@ -2,6 +2,7 @@ * @provides diffusion-readme-css */ +.device .diffusion-readme-view .phui-document-fluid .phui-document-view, .device-desktop .diffusion-readme-view .phui-document-fluid .phui-document-view { margin: 0; diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index ca52d53a79..7f404e3b9b 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -4,7 +4,6 @@ .diffusion-source { width: 100%; - font: 10px/13px "Menlo", "Consolas", "Monaco", monospace; background: #fff; } @@ -21,13 +20,12 @@ } .diffusion-source td { - letter-spacing: 0.0083334px; - vertical-align: top; - white-space: pre-wrap; - padding-bottom: 1px; - padding-left: 8px; - line-height: 16px; - width: 100%; + vertical-align: top; + white-space: pre-wrap; + padding-top: 1px; + padding-bottom: 1px; + padding-left: 8px; + width: 100%; } .diffusion-browse-type-form { diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 1d42cde8fa..35f5067fec 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -66,10 +66,6 @@ padding: 0; } -.phui-box.phui-box-blue-property .phui-header-header { - text-transform: uppercase; -} - .phui-box.phui-box-blue-property .phui-header-header .phui-header-icon { margin-right: 6px; } @@ -90,11 +86,30 @@ padding: 6px 16px; } -.device .phui-box.phui-box-blue-property .phui-header-shell { - padding: 6px 12px; +.device .phui-box.phui-box-blue-property .phui-header-shell, +.device .phui-box-blue-property.phui-object-box.phui-object-box-collapsed + .phui-header-shell { + padding: 6px 12px; } .phui-box.phui-box-blue-property .phui-header-header { font-size: 13px; color: {$bluetext}; } + +.phui-box-blue-property .phui-object-item-list-view.phui-object-list-flush { + padding: 2px 8px; +} + +body .phui-box-blue-property.phui-object-box.phui-object-box-collapsed { + padding: 0; +} + +body .phui-box-blue-property .phui-header-shell + .phui-object-box { + margin-bottom: 0; +} + +.phui-box-blue-property .phui-header-shell + .phui-object-box + .phui-header-shell { + background: #fff; +} diff --git a/webroot/rsrc/css/phui/phui-head-thing.css b/webroot/rsrc/css/phui/phui-head-thing.css index b82c9321a3..bf491c4f1f 100644 --- a/webroot/rsrc/css/phui/phui-head-thing.css +++ b/webroot/rsrc/css/phui/phui-head-thing.css @@ -6,6 +6,9 @@ height: 24px; line-height: 22px; color: {$greytext}; +} + +.phui-head-thing-view.phui-head-has-image { position: relative; padding-left: 32px; } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index a61a73fc88..2a1731eb61 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -324,3 +324,7 @@ body .phui-header-shell.phui-bleed-header .phui-profile-header .phui-header-col3 { vertical-align: top; } + +.phui-header-view .phui-tag-shade-indigo a { + color: {$sh-indigotext}; +} diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index 59405995a5..d3b12dd4fe 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -10,6 +10,10 @@ border-radius: 3px; } +.phui-info-view.phui-info-view-flush { + margin: 0 0 20px 0; +} + .device .phui-info-view { margin: 8px; } diff --git a/webroot/rsrc/css/phui/phui-object-box.css b/webroot/rsrc/css/phui/phui-object-box.css index f9f06e2d18..dee0525944 100644 --- a/webroot/rsrc/css/phui/phui-object-box.css +++ b/webroot/rsrc/css/phui/phui-object-box.css @@ -52,6 +52,16 @@ div.phui-object-box.phui-object-box-flush { margin: 8px 8px 0 8px; } +.phui-object-box .phui-header-header .phui-tag-view { + margin-left: 8px; +} + +.phui-object-box .phui-header-header .phui-tag-core { + border-color: transparent; + padding: 1px 6px; + font-size: {$normalfontsize}; +} + /* - Object Box Colors ------------------------------------------------------ */ .phui-box-border.phui-object-box-green { diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css index 52e3ee0944..ab250a3cc5 100644 --- a/webroot/rsrc/css/phui/phui-property-list-view.css +++ b/webroot/rsrc/css/phui/phui-property-list-view.css @@ -199,7 +199,6 @@ .phui-property-list-image-content img { margin: 20px auto; background: url('/rsrc/image/checker_light.png'); - border: 1px solid {$lightblueborder}; } .device-desktop .phui-property-list-image-content img:hover { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 090fafb63a..7997b196fe 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -79,10 +79,6 @@ margin: 0 0 20px 0; } -.phui-two-column-view .phui-object-box.phui-object-box-collapsed { - padding: 0; -} - /* Timeline */ .phui-two-column-view .phui-timeline-view { @@ -115,12 +111,12 @@ .device-desktop .phui-main-column .phui-property-list-key { margin-left: 0; - width: 140px; + width: 160px; } .device-desktop .phui-main-column .phui-property-list-value { margin-left: 8px; - width: calc(100% - 180px); + width: calc(100% - 200px); } @@ -132,13 +128,23 @@ } .device-desktop .phui-two-column-view .phui-property-list-container { - padding: 12px 16px; + padding: 16px 0; +} + +.device-desktop .phui-two-column-view + .phui-property-list-properties-wrap.phui-property-list-stacked { + padding: 0 16px; } .device .phui-two-column-view .phui-property-list-container { padding: 12px 8px; } +.phui-two-column-view .phui-property-list-container + .keyboard-shortcuts-available { + display: none; +} + .phui-two-column-properties.phui-object-box { border: 1px solid rgba({$alphablue}, .2); } @@ -182,12 +188,12 @@ /* Info View */ -.phui-two-column-view .phui-two-column-content .phui-info-view { +.phui-two-column-view .phui-two-column-row .phui-info-view { margin: 0 0 20px 0; padding: 16px; } -.phui-two-column-view .phui-two-column-content .phui-object-box +.phui-two-column-view .phui-two-column-row .phui-object-box .phui-info-view { margin: 0; }