diff --git a/.arcconfig b/.arcconfig index 70a43b9d9a..e6aa540bd3 100644 --- a/.arcconfig +++ b/.arcconfig @@ -1,6 +1,5 @@ { - "project.name" : "phabricator", - "phabricator.uri" : "https://secure.phabricator.com/", - "unit.engine" : "PhutilUnitTestEngine", - "load" : ["src/"] + "phabricator.uri": "https://secure.phabricator.com/", + "unit.engine": "PhutilUnitTestEngine", + "load": ["src/"] } diff --git a/.arclint b/.arclint index e8b804fedb..0077bdf518 100644 --- a/.arclint +++ b/.arclint @@ -74,6 +74,7 @@ "type": "xhpast", "include": "(\\.php$)", "severity": { + "16": "advice", "34": "error" }, "xhpast.blacklisted.function": { diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e63593901f..56608dfcff 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,11 +7,11 @@ */ return array( 'names' => array( - 'core.pkg.css' => '55901d68', - 'core.pkg.js' => 'e4f47dfd', + 'core.pkg.css' => 'f85a5ce0', + 'core.pkg.js' => 'fbf1d615', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => 'bb338e4b', - 'differential.pkg.js' => '63a77807', + 'differential.pkg.css' => '30602b8c', + 'differential.pkg.js' => '8c98ce21', 'diffusion.pkg.css' => '385e85b3', 'diffusion.pkg.js' => '0115b37c', 'maniphest.pkg.css' => '4845691a', @@ -26,7 +26,7 @@ return array( 'rsrc/css/aphront/pager-view.css' => '2e3539af', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => '0ecd30a1', - 'rsrc/css/aphront/table-view.css' => 'fb17602c', + 'rsrc/css/aphront/table-view.css' => '15fedf7a', 'rsrc/css/aphront/tokenizer.css' => '04875312', 'rsrc/css/aphront/tooltip.css' => '7672b60f', 'rsrc/css/aphront/two-column.css' => '16ab3ad2', @@ -60,7 +60,7 @@ return array( 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => 'e19cfd6e', 'rsrc/css/application/differential/core.css' => '7ac3cabc', - 'rsrc/css/application/differential/phui-inline-comment.css' => '2174771a', + 'rsrc/css/application/differential/phui-inline-comment.css' => 'aa16f165', 'rsrc/css/application/differential/results-table.css' => '181aa9d9', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', @@ -111,7 +111,7 @@ return array( 'rsrc/css/core/core.css' => 'bbc7187b', 'rsrc/css/core/remarkup.css' => 'ea91b3ee', 'rsrc/css/core/syntax.css' => '6b7b24d9', - 'rsrc/css/core/z-index.css' => '8c8c40aa', + 'rsrc/css/core/z-index.css' => '63689f49', 'rsrc/css/diviner/diviner-shared.css' => '38813222', 'rsrc/css/font/font-awesome.css' => 'e2e712fe', 'rsrc/css/font/font-source-sans-pro.css' => '8906c07b', @@ -120,7 +120,7 @@ return array( 'rsrc/css/layout/phabricator-hovercard-view.css' => '0d665853', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'a440478a', 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', - 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'c0cf782a', + 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', @@ -132,7 +132,7 @@ return array( 'rsrc/css/phui/phui-document.css' => '8be7a5e3', 'rsrc/css/phui/phui-feed-story.css' => 'e5682f4c', 'rsrc/css/phui/phui-fontkit.css' => 'b664ac96', - 'rsrc/css/phui/phui-form-view.css' => '87263b05', + 'rsrc/css/phui/phui-form-view.css' => 'a0e8f168', 'rsrc/css/phui/phui-form.css' => 'f535f938', 'rsrc/css/phui/phui-header-view.css' => 'd7612da3', 'rsrc/css/phui/phui-icon.css' => '88ba9081', @@ -325,9 +325,10 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/event-all-day.js' => 'ca5fa62a', - 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', + 'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2', + 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', + 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => '16c695bf', 'rsrc/js/application/conpherence/behavior-menu.js' => 'c0348cac', @@ -346,7 +347,7 @@ return array( 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '2035b9cb', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'e723c323', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '037b59eb', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492', 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', 'rsrc/js/application/differential/behavior-show-field-details.js' => 'bba9eedf', @@ -363,7 +364,7 @@ return array( 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', - 'rsrc/js/application/herald/HeraldRuleEditor.js' => '9229e764', + 'rsrc/js/application/herald/HeraldRuleEditor.js' => '271ffdd7', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => 'f5d1233b', @@ -436,7 +437,7 @@ return array( 'rsrc/js/core/behavior-device.js' => 'a205cf28', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', - 'rsrc/js/core/behavior-fancy-datepicker.js' => '5c0f680f', + 'rsrc/js/core/behavior-fancy-datepicker.js' => '510b5809', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', @@ -482,7 +483,7 @@ return array( 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-pager-view-css' => '2e3539af', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => 'fb17602c', + 'aphront-table-view-css' => '15fedf7a', 'aphront-tokenizer-control-css' => '04875312', 'aphront-tooltip-css' => '7672b60f', 'aphront-two-column-view-css' => '16ab3ad2', @@ -497,7 +498,7 @@ return array( 'conpherence-menu-css' => 'f9f1d143', 'conpherence-message-pane-css' => '7cbf4cbb', 'conpherence-notification-css' => '919974b6', - 'conpherence-thread-manager' => '10246726', + 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '42a457f6', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '77096740', @@ -519,7 +520,7 @@ return array( 'global-drag-and-drop-css' => '697324ad', 'harbormaster-css' => '49d64eb4', 'herald-css' => '826075fa', - 'herald-rule-editor' => '9229e764', + 'herald-rule-editor' => '271ffdd7', 'herald-test-css' => '778b008e', 'inline-comment-summary-css' => 'eb5f8e8c', 'javelin-aphlict' => '5359e785', @@ -535,7 +536,7 @@ return array( 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-choose-control' => '6153c708', - 'javelin-behavior-config-reorder-fields' => '14a827de', + 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', 'javelin-behavior-conpherence-menu' => 'c0348cac', 'javelin-behavior-conpherence-pontificate' => '21ba5861', @@ -546,12 +547,13 @@ return array( 'javelin-behavior-dashboard-move-panels' => '82439934', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', + 'javelin-behavior-day-view' => '5c46cff2', 'javelin-behavior-device' => 'a205cf28', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-dropdown-menus' => '2035b9cb', - 'javelin-behavior-differential-edit-inline-comments' => 'e723c323', + 'javelin-behavior-differential-edit-inline-comments' => '037b59eb', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '2c426492', 'javelin-behavior-differential-populate' => '8694b1df', @@ -566,8 +568,8 @@ return array( 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-durable-column' => '16c695bf', 'javelin-behavior-error-log' => '6882e80a', - 'javelin-behavior-event-all-day' => 'ca5fa62a', - 'javelin-behavior-fancy-datepicker' => '5c0f680f', + 'javelin-behavior-event-all-day' => '38dcf3c8', + 'javelin-behavior-fancy-datepicker' => '510b5809', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', @@ -743,7 +745,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '8c8c40aa', + 'phabricator-zindex-css' => '63689f49', 'phame-css' => '88bd4705', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', @@ -757,7 +759,7 @@ return array( 'phui-box-css' => 'a5bb366d', 'phui-button-css' => 'b995182d', 'phui-calendar-css' => 'ccabe893', - 'phui-calendar-day-css' => 'c0cf782a', + 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-crumbs-view-css' => '3840dc4c', @@ -766,13 +768,13 @@ return array( 'phui-font-icon-base-css' => '3dad2ae3', 'phui-fontkit-css' => 'b664ac96', 'phui-form-css' => 'f535f938', - 'phui-form-view-css' => '87263b05', + 'phui-form-view-css' => 'a0e8f168', 'phui-header-view-css' => 'd7612da3', 'phui-icon-view-css' => '88ba9081', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '33e54618', - 'phui-inline-comment-view-css' => '2174771a', + 'phui-inline-comment-view-css' => 'aa16f165', 'phui-list-view-css' => 'e448b6ba', 'phui-object-box-css' => '8eacbeed', 'phui-object-item-list-view-css' => '24ed8d94', @@ -815,9 +817,28 @@ return array( 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( + '01774ab2' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '029a133d' => array( 'aphront-dialog-view-css', ), + '037b59eb' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'differential-inline-comment-editor', + ), '048330fa' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -874,17 +895,6 @@ return array( 'javelin-install', 'javelin-util', ), - 10246726 => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '13c739ea' => array( 'javelin-behavior', 'javelin-stratcom', @@ -899,13 +909,6 @@ return array( 'javelin-dom', 'javelin-history', ), - '14a827de' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-json', - 'phabricator-draggable-list', - ), '14ac66f5' => array( 'javelin-install', 'javelin-dom', @@ -982,6 +985,15 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), + '271ffdd7' => array( + 'multirow-row-manager', + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-json', + 'phabricator-prefab', + ), '2818f5ce' => array( 'javelin-install', 'javelin-util', @@ -1147,6 +1159,13 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), + '510b5809' => array( + 'javelin-behavior', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '519705ea' => array( 'javelin-install', 'javelin-dom', @@ -1224,13 +1243,6 @@ return array( 'javelin-uri', 'javelin-routable', ), - '5c0f680f' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1505,15 +1517,6 @@ return array( 'javelin-dom', 'javelin-stratcom', ), - '9229e764' => array( - 'multirow-row-manager', - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-json', - 'phabricator-prefab', - ), 93568464 => array( 'javelin-behavior', 'javelin-dom', @@ -1706,6 +1709,13 @@ return array( 'javelin-dom', 'javelin-util', ), + 'b6993408' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-json', + 'phabricator-draggable-list', + ), 'ba4fa35c' => array( 'javelin-behavior', 'javelin-dom', @@ -1913,14 +1923,6 @@ return array( 'e6e25838' => array( 'javelin-install', ), - 'e723c323' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/resources/sql/autopatches/20150521.releephrepository.sql b/resources/sql/autopatches/20150521.releephrepository.sql new file mode 100644 index 0000000000..6626cb8730 --- /dev/null +++ b/resources/sql/autopatches/20150521.releephrepository.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_releeph.releeph_project + MODIFY arcanistProjectID int(10) unsigned NULL; diff --git a/resources/sql/autopatches/20150525.diff.hidden.1.sql b/resources/sql/autopatches/20150525.diff.hidden.1.sql new file mode 100644 index 0000000000..d6b3df6440 --- /dev/null +++ b/resources/sql/autopatches/20150525.diff.hidden.1.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_differential.differential_hiddencomment ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + userPHID VARBINARY(64) NOT NULL, + commentID INT UNSIGNED NOT NULL, + UNIQUE KEY `key_user` (userPHID, commentID), + KEY `key_comment` (commentID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150526.owners.mailkey.1.sql b/resources/sql/autopatches/20150526.owners.mailkey.1.sql new file mode 100644 index 0000000000..6e83129ec3 --- /dev/null +++ b/resources/sql/autopatches/20150526.owners.mailkey.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_owners.owners_package + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20150526.owners.mailkey.2.php b/resources/sql/autopatches/20150526.owners.mailkey.2.php new file mode 100644 index 0000000000..c6bb5f8266 --- /dev/null +++ b/resources/sql/autopatches/20150526.owners.mailkey.2.php @@ -0,0 +1,18 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $package) { + $id = $package->getID(); + + echo pht('Adding mail key for package %d...', $id); + echo "\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} diff --git a/resources/sql/autopatches/20150526.owners.xaction.sql b/resources/sql/autopatches/20150526.owners.xaction.sql new file mode 100644 index 0000000000..f25b3dfdb6 --- /dev/null +++ b/resources/sql/autopatches/20150526.owners.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_packagetransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0bd498fe56..653d79b889 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -374,6 +374,7 @@ phutil_register_library_map(array( 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php', 'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php', + 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', 'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php', @@ -461,7 +462,6 @@ phutil_register_library_map(array( 'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php', 'DifferentialUpdateUnitResultsConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateUnitResultsConduitAPIMethod.php', 'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php', - 'DiffusionArcanistProjectDatasource' => 'applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php', 'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', @@ -596,6 +596,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php', 'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php', 'DiffusionRepositoryEditMainController' => 'applications/diffusion/controller/DiffusionRepositoryEditMainController.php', + 'DiffusionRepositoryEditStagingController' => 'applications/diffusion/controller/DiffusionRepositoryEditStagingController.php', 'DiffusionRepositoryEditStorageController' => 'applications/diffusion/controller/DiffusionRepositoryEditStorageController.php', 'DiffusionRepositoryEditSubversionController' => 'applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php', 'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php', @@ -1176,6 +1177,7 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php', 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', + 'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php', 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', @@ -1226,10 +1228,6 @@ phutil_register_library_map(array( 'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php', 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', - 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', - 'PackageDeleteMail' => 'applications/owners/mail/PackageDeleteMail.php', - 'PackageMail' => 'applications/owners/mail/PackageMail.php', - 'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php', 'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php', 'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php', 'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php', @@ -1496,6 +1494,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', 'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php', 'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php', + 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 'PhabricatorCalendarEventEditIconController' => 'applications/calendar/controller/PhabricatorCalendarEventEditIconController.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', @@ -1538,6 +1537,8 @@ phutil_register_library_map(array( 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', 'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php', 'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php', + 'PhabricatorCommitMergedCommitsField' => 'applications/repository/customfield/PhabricatorCommitMergedCommitsField.php', + 'PhabricatorCommitRepositoryField' => 'applications/repository/customfield/PhabricatorCommitRepositoryField.php', 'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php', 'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php', 'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php', @@ -2160,19 +2161,21 @@ phutil_register_library_map(array( 'PhabricatorOwnersConfigOptions' => 'applications/owners/config/PhabricatorOwnersConfigOptions.php', 'PhabricatorOwnersController' => 'applications/owners/controller/PhabricatorOwnersController.php', 'PhabricatorOwnersDAO' => 'applications/owners/storage/PhabricatorOwnersDAO.php', - 'PhabricatorOwnersDeleteController' => 'applications/owners/controller/PhabricatorOwnersDeleteController.php', 'PhabricatorOwnersDetailController' => 'applications/owners/controller/PhabricatorOwnersDetailController.php', 'PhabricatorOwnersEditController' => 'applications/owners/controller/PhabricatorOwnersEditController.php', 'PhabricatorOwnersListController' => 'applications/owners/controller/PhabricatorOwnersListController.php', 'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php', 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', 'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php', - 'PhabricatorOwnersPackageEditor' => 'applications/owners/editor/PhabricatorOwnersPackageEditor.php', 'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php', - 'PhabricatorOwnersPackagePathValidator' => 'applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php', 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', + 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', + 'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php', + 'PhabricatorOwnersPackageTransactionEditor' => 'applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php', + 'PhabricatorOwnersPackageTransactionQuery' => 'applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php', 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', + 'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php', 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', @@ -2383,8 +2386,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php', 'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php', 'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/PhabricatorRepositoryArcanistProject.php', - 'PhabricatorRepositoryArcanistProjectDeleteController' => 'applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php', - 'PhabricatorRepositoryArcanistProjectEditController' => 'applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php', 'PhabricatorRepositoryArcanistProjectPHIDType' => 'applications/repository/phid/PhabricatorRepositoryArcanistProjectPHIDType.php', 'PhabricatorRepositoryArcanistProjectQuery' => 'applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php', 'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/PhabricatorRepositoryAuditRequest.php', @@ -3146,7 +3147,6 @@ phutil_register_library_map(array( 'ReleephProductTransactionQuery' => 'applications/releeph/query/ReleephProductTransactionQuery.php', 'ReleephProductViewController' => 'applications/releeph/controller/product/ReleephProductViewController.php', 'ReleephProject' => 'applications/releeph/storage/ReleephProject.php', - 'ReleephProjectInfoConduitAPIMethod' => 'applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php', 'ReleephQueryBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php', 'ReleephQueryProductsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php', 'ReleephQueryRequestsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php', @@ -3614,6 +3614,7 @@ phutil_register_library_map(array( 'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialGitSVNIDField' => 'DifferentialCustomField', + 'DifferentialHiddenComment' => 'DifferentialDAO', 'DifferentialHostField' => 'DifferentialCustomField', 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy', @@ -3706,7 +3707,6 @@ phutil_register_library_map(array( 'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialUpdateUnitResultsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialViewPolicyField' => 'DifferentialCoreCustomField', - 'DiffusionArcanistProjectDatasource' => 'PhabricatorTypeaheadDatasource', 'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', @@ -3831,6 +3831,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryEditStagingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditStorageController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditSubversionController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController', @@ -4502,6 +4503,7 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentView' => 'AphrontView', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', + 'PHUIDiffRevealIconView' => 'AphrontView', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDocumentExample' => 'PhabricatorUIExample', 'PHUIDocumentView' => 'AphrontTagView', @@ -4552,10 +4554,6 @@ phutil_register_library_map(array( 'PHUITypeaheadExample' => 'PhabricatorUIExample', 'PHUIWorkboardView' => 'AphrontTagView', 'PHUIWorkpanelView' => 'AphrontTagView', - 'PackageCreateMail' => 'PackageMail', - 'PackageDeleteMail' => 'PackageMail', - 'PackageMail' => 'PhabricatorMail', - 'PackageModifyMail' => 'PackageMail', 'PassphraseAbstractKey' => 'Phobject', 'PassphraseConduitAPIMethod' => 'ConduitAPIMethod', 'PassphraseController' => 'PhabricatorController', @@ -4849,6 +4847,7 @@ phutil_register_library_map(array( ), 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditIconController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', @@ -4900,6 +4899,8 @@ phutil_register_library_map(array( 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitCustomField' => 'PhabricatorCustomField', + 'PhabricatorCommitMergedCommitsField' => 'PhabricatorCommitCustomField', + 'PhabricatorCommitRepositoryField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField', 'PhabricatorCommonPasswords' => 'Phobject', @@ -5560,7 +5561,6 @@ phutil_register_library_map(array( 'PhabricatorOwnersConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorOwnersController' => 'PhabricatorController', 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', - 'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController', 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', 'PhabricatorOwnersListController' => 'PhabricatorOwnersController', @@ -5568,13 +5568,18 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackage' => array( 'PhabricatorOwnersDAO', 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', - 'PhabricatorOwnersPackageEditor' => 'PhabricatorEditor', 'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType', 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', + 'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorOwnersPackageTransactionEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorOwnersPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', + 'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController', 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPHPASTApplication' => 'PhabricatorApplication', 'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck', @@ -5825,8 +5830,6 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), - 'PhabricatorRepositoryArcanistProjectDeleteController' => 'PhabricatorRepositoryController', - 'PhabricatorRepositoryArcanistProjectEditController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryArcanistProjectPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryArcanistProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryAuditRequest' => array( @@ -6724,7 +6727,6 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), - 'ReleephProjectInfoConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephQueryBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephQueryProductsConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephQueryRequestsConduitAPIMethod' => 'ReleephConduitAPIMethod', diff --git a/src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php b/src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php index ee5cbbff2b..c89bcf7845 100644 --- a/src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php +++ b/src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php @@ -7,8 +7,12 @@ final class ArcanistProjectInfoConduitAPIMethod return 'arcanist.projectinfo'; } + public function getMethodStatus() { + return self::METHOD_STATUS_DEPRECATED; + } + public function getMethodDescription() { - return pht('Get information about Arcanist projects.'); + return pht('Arcanist projects are deprecated.'); } protected function defineParamTypes() { diff --git a/src/applications/audit/storage/PhabricatorAuditInlineComment.php b/src/applications/audit/storage/PhabricatorAuditInlineComment.php index 292e0274a5..8779b5ffdd 100644 --- a/src/applications/audit/storage/PhabricatorAuditInlineComment.php +++ b/src/applications/audit/storage/PhabricatorAuditInlineComment.php @@ -23,6 +23,14 @@ final class PhabricatorAuditInlineComment return $this->proxy; } + public function supportsHiding() { + return false; + } + + public function isHidden() { + return false; + } + public function getTransactionCommentForSave() { $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_LEGACY, diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 534b11d2e8..d41577a3af 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -54,6 +54,8 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { => 'PhabricatorCalendarEventEditController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventEditController', + 'drag/(?P[1-9]\d*)/' + => 'PhabricatorCalendarEventDragController', 'cancel/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventCancelController', '(?Pjoin|decline|accept)/(?P[1-9]\d*)/' diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php index 08af8971ea..2e088ecf49 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarController.php @@ -5,11 +5,23 @@ abstract class PhabricatorCalendarController extends PhabricatorController { protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); + $actions = id(new PhabricatorActionListView()) + ->setUser($this->getViewer()) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Create Private Event')) + ->setHref('/calendar/event/create/?mode=private')) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Create Public Event')) + ->setHref('/calendar/event/create/?mode=public')); + $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Event')) ->setHref($this->getApplicationURI().'event/create/') - ->setIcon('fa-plus-square')); + ->setIcon('fa-plus-square') + ->setDropdownMenu($actions)); return $crumbs; } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php new file mode 100644 index 0000000000..7b813cdc44 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php @@ -0,0 +1,66 @@ +getViewer(); + + $event = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$event) { + return new Aphront404Response(); + } + + if (!$request->validateCSRF()) { + return new Aphront400Response(); + } + + if ($event->getIsAllDay()) { + return new Aphront400Response(); + } + + $xactions = array(); + + $duration = $event->getDateTo() - $event->getDateFrom(); + + $start = $request->getInt('start'); + $start_value = id(AphrontFormDateControlValue::newFromEpoch( + $viewer, + $start)); + + $end = $start + $duration; + $end_value = id(AphrontFormDateControlValue::newFromEpoch( + $viewer, + $end)); + + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_START_DATE) + ->setNewValue($start_value); + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_END_DATE) + ->setNewValue($end_value); + + + $editor = id(new PhabricatorCalendarEventEditor()) + ->setActor($viewer) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + $xactions = $editor->applyTransactions($event, $xactions); + + return id(new AphrontReloadResponse()); + } +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 41744edecf..109aa692bd 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -14,8 +14,8 @@ final class PhabricatorCalendarEventEditController } public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); - $user_phid = $user->getPHID(); + $viewer = $request->getViewer(); + $user_phid = $viewer->getPHID(); $error_name = true; $error_start_date = true; $error_end_date = true; @@ -25,9 +25,44 @@ final class PhabricatorCalendarEventEditController $start_date_id = celerity_generate_unique_node_id(); $end_date_id = null; + $next_workflow = $request->getStr('next'); + $uri_query = $request->getStr('query'); + if ($this->isCreate()) { - $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); - list($start_value, $end_value) = $this->getDefaultTimeValues($user); + $mode = $request->getStr('mode'); + $event = PhabricatorCalendarEvent::initializeNewCalendarEvent( + $viewer, + $mode); + + $create_start_year = $request->getInt('year'); + $create_start_month = $request->getInt('month'); + $create_start_day = $request->getInt('day'); + $create_start_time = $request->getStr('time'); + + if ($create_start_year) { + $start = AphrontFormDateControlValue::newFromParts( + $viewer, + $create_start_year, + $create_start_month, + $create_start_day, + $create_start_time); + if (!$start->isValid()) { + return new Aphront400Response(); + } + $start_value = AphrontFormDateControlValue::newFromEpoch( + $viewer, + $start->getEpoch()); + + $end = clone $start_value->getDateTime(); + $end->modify('+1 hour'); + $end_value = AphrontFormDateControlValue::newFromEpoch( + $viewer, + $end->format('U')); + + } else { + list($start_value, $end_value) = $this->getDefaultTimeValues($viewer); + } + $submit_label = pht('Create'); $page_title = pht('Create Event'); @@ -38,7 +73,7 @@ final class PhabricatorCalendarEventEditController $end_date_id = celerity_generate_unique_node_id(); } else { $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($this->id)) ->requireCapabilities( array( @@ -51,10 +86,10 @@ final class PhabricatorCalendarEventEditController } $end_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $event->getDateTo()); $start_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $event->getDateFrom()); $submit_label = pht('Update'); @@ -81,7 +116,7 @@ final class PhabricatorCalendarEventEditController $icon = $event->getIcon(); $current_policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setObject($event) ->execute(); @@ -106,9 +141,9 @@ final class PhabricatorCalendarEventEditController $new_invitees = $this->getNewInviteeList($invitees, $event); $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; if ($this->isCreate()) { - $status = idx($new_invitees, $user->getPHID()); + $status = idx($new_invitees, $viewer->getPHID()); if ($status) { - $new_invitees[$user->getPHID()] = $status_attending; + $new_invitees[$viewer->getPHID()] = $status_attending; } } @@ -161,14 +196,29 @@ final class PhabricatorCalendarEventEditController ->setNewValue($request->getStr('editPolicy')); $editor = id(new PhabricatorCalendarEventEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $xactions = $editor->applyTransactions($event, $xactions); $response = id(new AphrontRedirectResponse()); - return $response->setURI('/E'.$event->getID()); + switch ($next_workflow) { + case 'day': + if (!$uri_query) { + $uri_query = 'month'; + } + $year = $start_value->getDateTime()->format('Y'); + $month = $start_value->getDateTime()->format('m'); + $day = $start_value->getDateTime()->format('d'); + $response->setURI( + '/calendar/query/'.$uri_query.'/'.$year.'/'.$month.'/'.$day.'/'); + break; + default: + $response->setURI('/E'.$event->getID()); + break; + } + return $response; } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $error_name = $ex->getShortMessage( @@ -204,7 +254,7 @@ final class PhabricatorCalendarEventEditController $all_day_id); $start_control = id(new AphrontFormDateControl()) - ->setUser($user) + ->setUser($viewer) ->setName('start') ->setLabel(pht('Start')) ->setError($error_start_date) @@ -214,7 +264,7 @@ final class PhabricatorCalendarEventEditController ->setEndDateID($end_date_id); $end_control = id(new AphrontFormDateControl()) - ->setUser($user) + ->setUser($viewer) ->setName('end') ->setLabel(pht('End')) ->setError($error_end_date) @@ -228,13 +278,13 @@ final class PhabricatorCalendarEventEditController ->setValue($description); $view_policies = id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($event) ->setPolicies($current_policies) ->setName('viewPolicy'); $edit_policies = id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($event) ->setPolicies($current_policies) @@ -244,14 +294,14 @@ final class PhabricatorCalendarEventEditController ->setLabel(pht('Subscribers')) ->setName('subscribers') ->setValue($subscribers) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); $invitees = id(new AphrontFormTokenizerControl()) ->setLabel(pht('Invitees')) ->setName('invitees') ->setValue($invitees) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); if ($this->isCreate()) { @@ -269,7 +319,9 @@ final class PhabricatorCalendarEventEditController ->setValue($icon); $form = id(new AphrontFormView()) - ->setUser($user) + ->addHiddenInput('next', $next_workflow) + ->addHiddenInput('query', $uri_query) + ->setUser($viewer) ->appendChild($name) ->appendChild($all_day_checkbox) ->appendChild($start_control) @@ -351,19 +403,19 @@ final class PhabricatorCalendarEventEditController return $new; } - private function getDefaultTimeValues($user) { + private function getDefaultTimeValues($viewer) { $start = new DateTime('@'.time()); - $start->setTimeZone($user->getTimeZone()); + $start->setTimeZone($viewer->getTimeZone()); $start->setTime($start->format('H'), 0, 0); $start->modify('+1 hour'); $end = id(clone $start)->modify('+1 hour'); $start_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $start->format('U')); $end_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $end->format('U')); return array($start_value, $end_value); diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 474ac49a46..bdc87aaf26 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -180,6 +180,21 @@ final class PhabricatorCalendarEventQuery protected function willFilterPage(array $events) { + $range_start = $this->rangeBegin; + $range_end = $this->rangeEnd; + + foreach ($events as $key => $event) { + $event_start = $event->getDateFrom(); + $event_end = $event->getDateTo(); + + if ($range_start && $event_end < $range_start) { + unset($events[$key]); + } + if ($range_end && $event_start > $range_end) { + unset($events[$key]); + } + } + $phids = array(); foreach ($events as $event) { @@ -197,6 +212,8 @@ final class PhabricatorCalendarEventQuery $event->attachInvitees($event_invitees); } + $events = msort($events, 'getDateFrom'); + return $events; } diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index b9ca2b81bf..b42b5dcdf8 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -390,12 +390,13 @@ final class PhabricatorCalendarEventSearchEngine list($start_year, $start_month, $start_day) = $this->getDisplayYearAndMonthAndDay($query); - $day_view = new PHUICalendarDayView( + $day_view = id(new PHUICalendarDayView( $this->getDateFrom($query), $this->getDateTo($query), $start_year, $start_month, - $start_day); + $start_day)) + ->setQuery($query->getQueryKey()); $day_view->setUser($viewer); @@ -408,7 +409,13 @@ final class PhabricatorCalendarEventSearchEngine $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $status, + PhabricatorPolicyCapability::CAN_EDIT); + $event = new AphrontCalendarEventView(); + $event->setCanEdit($can_edit); $event->setEventID($status->getID()); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); $event->setIsAllDay($status->getIsAllDay()); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 3055e5e4e6..cd829369d8 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -28,18 +28,26 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO private $invitees = self::ATTACHABLE; private $appliedViewer; - public static function initializeNewCalendarEvent(PhabricatorUser $actor) { + public static function initializeNewCalendarEvent( + PhabricatorUser $actor, + $mode) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorCalendarApplication')) ->executeOne(); + if ($mode == 'public') { + $view_policy = PhabricatorPolicies::getMostOpenPolicy(); + } else { + $view_policy = $actor->getPHID(); + } + return id(new PhabricatorCalendarEvent()) ->setUserPHID($actor->getPHID()) ->setIsCancelled(0) ->setIsAllDay(0) ->setIcon(self::DEFAULT_ICON) - ->setViewPolicy($actor->getPHID()) + ->setViewPolicy($view_policy) ->setEditPolicy($actor->getPHID()) ->attachInvitees(array()) ->applyViewerTimezone($actor); diff --git a/src/applications/calendar/view/AphrontCalendarEventView.php b/src/applications/calendar/view/AphrontCalendarEventView.php index fadf5eb889..5bc0b71e31 100644 --- a/src/applications/calendar/view/AphrontCalendarEventView.php +++ b/src/applications/calendar/view/AphrontCalendarEventView.php @@ -12,6 +12,7 @@ final class AphrontCalendarEventView extends AphrontView { private $uri; private $isAllDay; private $icon; + private $canEdit; public function setURI($uri) { $this->uri = $uri; @@ -97,6 +98,14 @@ final class AphrontCalendarEventView extends AphrontView { return $this->icon; } + public function setCanEdit($can_edit) { + $this->canEdit = $can_edit; + return $this; + } + + public function getCanEdit() { + return $this->canEdit; + } public function getMultiDay() { $nextday = strtotime('12:00 AM Tomorrow', $this->getEpochStart()); diff --git a/src/applications/conduit/controller/PhabricatorConduitLogController.php b/src/applications/conduit/controller/PhabricatorConduitLogController.php index 495c51ece0..0c408982f8 100644 --- a/src/applications/conduit/controller/PhabricatorConduitLogController.php +++ b/src/applications/conduit/controller/PhabricatorConduitLogController.php @@ -98,7 +98,7 @@ final class PhabricatorConduitLogController array($call->getMethod(), $client), $status, $call->getError(), - pht('%d us', number_format($call->getDuration())), + pht('%s us', new PhutilNumber($call->getDuration())), phabricator_datetime($call->getDateCreated(), $viewer), ); } diff --git a/src/applications/console/plugin/DarkConsoleServicesPlugin.php b/src/applications/console/plugin/DarkConsoleServicesPlugin.php index 6a59c7e129..055315d641 100644 --- a/src/applications/console/plugin/DarkConsoleServicesPlugin.php +++ b/src/applications/console/plugin/DarkConsoleServicesPlugin.php @@ -198,7 +198,7 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin { $summary[] = array( $type, number_format($counts[$type]), - pht('%d us', number_format((int)(1000000 * $totals[$type]))), + pht('%s us', new PhutilNumber((int)(1000000 * $totals[$type]))), sprintf('%.1f%%', 100 * $totals[$type] / $page_total), ); } @@ -258,10 +258,12 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin { break; } + $offset = ($row['begin'] - $data['start']); + $rows[] = array( $row['type'], - pht('+%d ms', number_format(1000 * ($row['begin'] - $data['start']))), - pht('%d us', number_format(1000000 * $row['duration'])), + pht('+%s ms', new PhutilNumber(1000 * $offset)), + pht('%s us', new PhutilNumber(1000000 * $row['duration'])), $info, $analysis, ); diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php index 1c627760b4..beaabae074 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php @@ -50,7 +50,7 @@ final class PhabricatorDaemonConsoleController $rows[] = array( $class, number_format($info['n']), - pht('%d us', number_format((int)($info['duration'] / $info['n']))), + pht('%s us', new PhutilNumber((int)($info['duration'] / $info['n']))), ); } diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php index 57951ccb91..66925ffd36 100644 --- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php +++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php @@ -141,7 +141,7 @@ final class PhabricatorWorkerTaskDetailController $expires); if ($task->isArchived()) { - $duration = pht('%d us', number_format($task->getDuration())); + $duration = pht('%s us', new PhutilNumber($task->getDuration())); } else { $duration = phutil_tag('em', array(), pht('Not Completed')); } diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php index 0638578f94..2373d3ef2c 100644 --- a/src/applications/differential/controller/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/DifferentialChangesetViewController.php @@ -191,6 +191,7 @@ final class DifferentialChangesetViewController extends DifferentialController { if ($revision) { $query = id(new DifferentialInlineCommentQuery()) ->setViewer($viewer) + ->needHidden(true) ->withRevisionPHIDs(array($revision->getPHID())); $inlines = $query->execute(); $inlines = $query->adjustInlinesForChangesets( diff --git a/src/applications/differential/controller/DifferentialInlineCommentEditController.php b/src/applications/differential/controller/DifferentialInlineCommentEditController.php index 13aac1776c..deba6af01b 100644 --- a/src/applications/differential/controller/DifferentialInlineCommentEditController.php +++ b/src/applications/differential/controller/DifferentialInlineCommentEditController.php @@ -42,6 +42,7 @@ final class DifferentialInlineCommentEditController ->setViewer($this->getViewer()) ->withIDs(array($id)) ->withDeletedDrafts(true) + ->needHidden(true) ->executeOne(); } @@ -50,6 +51,7 @@ final class DifferentialInlineCommentEditController ->setViewer($this->getViewer()) ->withPHIDs(array($phid)) ->withDeletedDrafts(true) + ->needHidden(true) ->executeOne(); } @@ -152,4 +154,38 @@ final class DifferentialInlineCommentEditController return $this->loadRevision()->getAuthorPHID(); } + protected function hideComments(array $ids) { + $viewer = $this->getViewer(); + $table = new DifferentialHiddenComment(); + $conn_w = $table->establishConnection('w'); + + $sql = array(); + foreach ($ids as $id) { + $sql[] = qsprintf( + $conn_w, + '(%s, %d)', + $viewer->getPHID(), + $id); + } + + queryfx( + $conn_w, + 'INSERT IGNORE INTO %T (userPHID, commentID) VALUES %Q', + $table->getTableName(), + implode(', ', $sql)); + } + + protected function showComments(array $ids) { + $viewer = $this->getViewer(); + $table = new DifferentialHiddenComment(); + $conn_w = $table->establishConnection('w'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE userPHID = %s AND commentID IN (%Ld)', + $table->getTableName(), + $viewer->getPHID(), + $ids); + } + } diff --git a/src/applications/differential/controller/DifferentialInlineCommentPreviewController.php b/src/applications/differential/controller/DifferentialInlineCommentPreviewController.php index 846cab5439..519b08bb63 100644 --- a/src/applications/differential/controller/DifferentialInlineCommentPreviewController.php +++ b/src/applications/differential/controller/DifferentialInlineCommentPreviewController.php @@ -19,6 +19,7 @@ extends PhabricatorInlineCommentPreviewController { ->withDrafts(true) ->withAuthorPHIDs(array($viewer->getPHID())) ->withRevisionPHIDs(array($revision->getPHID())) + ->needHidden(true) ->execute(); } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index df84af4526..eb0e38aec5 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -175,6 +175,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $query = id(new DifferentialInlineCommentQuery()) ->setViewer($user) + ->needHidden(true) ->withRevisionPHIDs(array($revision->getPHID())); $inlines = $query->execute(); $inlines = $query->adjustInlinesForChangesets( diff --git a/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php b/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php index c805083cd4..b4ff727cbe 100644 --- a/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php +++ b/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php @@ -47,6 +47,11 @@ final class DifferentialGitHubLandingStrategy DifferentialRevision $revision, PhabricatorRepository $repository) { + // TODO: This temporarily disables this action, because it doesn't work + // and is confusing to users. If you want to use it, comment out this line + // for now and we'll provide real support eventually. + return; + $vcs = $repository->getVersionControlSystem(); if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { return; diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index bca4098ca1..a34fe85064 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -906,11 +906,10 @@ final class DifferentialChangesetParser { $shield = $renderer->renderShield( pht('This file was completely deleted.')); } else if ($this->changeset->getAffectedLineCount() > 2500) { - $lines = number_format($this->changeset->getAffectedLineCount()); $shield = $renderer->renderShield( pht( 'This file has a very large number of changes (%s lines).', - $lines)); + new PhutilNumber($this->changeset->getAffectedLineCount()))); } } diff --git a/src/applications/differential/query/DifferentialInlineCommentQuery.php b/src/applications/differential/query/DifferentialInlineCommentQuery.php index 3fb26739c0..7e986bdc60 100644 --- a/src/applications/differential/query/DifferentialInlineCommentQuery.php +++ b/src/applications/differential/query/DifferentialInlineCommentQuery.php @@ -15,6 +15,7 @@ final class DifferentialInlineCommentQuery private $authorPHIDs; private $revisionPHIDs; private $deletedDrafts; + private $needHidden; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -55,6 +56,11 @@ final class DifferentialInlineCommentQuery return $this; } + public function needHidden($need) { + $this->needHidden = $need; + return $this; + } + public function execute() { $table = new DifferentialTransactionComment(); $conn_r = $table->establishConnection('r'); @@ -68,6 +74,26 @@ final class DifferentialInlineCommentQuery $comments = $table->loadAllFromArray($data); + if ($this->needHidden) { + $viewer_phid = $this->getViewer()->getPHID(); + if ($viewer_phid && $comments) { + $hidden = queryfx_all( + $conn_r, + 'SELECT commentID FROM %T WHERE userPHID = %s + AND commentID IN (%Ls)', + id(new DifferentialHiddenComment())->getTableName(), + $viewer_phid, + mpull($comments, 'getID')); + $hidden = array_fuse(ipull($hidden, 'commentID')); + } else { + $hidden = array(); + } + + foreach ($comments as $inline) { + $inline->attachIsHidden(isset($hidden[$inline->getID()])); + } + } + foreach ($comments as $key => $value) { $comments[$key] = DifferentialInlineComment::newFromModernComment( $value); diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index 2634d037d7..dfc990d4d4 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -41,8 +41,10 @@ final class DifferentialChangesetOneUpRenderer $column_width = 4; + $hidden = new PHUIDiffRevealIconView(); + $out = array(); - foreach ($primitives as $p) { + foreach ($primitives as $k => $p) { $type = $p['type']; switch ($type) { case 'old': @@ -51,6 +53,27 @@ final class DifferentialChangesetOneUpRenderer case 'new-file': $is_old = ($type == 'old' || $type == 'old-file'); + $o_hidden = array(); + $n_hidden = array(); + + for ($look = $k + 1; isset($primitives[$look]); $look++) { + $next = $primitives[$look]; + switch ($next['type']) { + case 'inline': + $comment = $next['comment']; + if ($comment->isHidden()) { + if ($next['right']) { + $n_hidden[] = $comment; + } else { + $o_hidden[] = $comment; + } + } + break; + default: + break 2; + } + } + $cells = array(); if ($is_old) { if ($p['htype']) { @@ -68,7 +91,13 @@ final class DifferentialChangesetOneUpRenderer } else { $left_id = null; } - $cells[] = phutil_tag('th', array('id' => $left_id), $p['line']); + + $line = $p['line']; + if ($o_hidden) { + $line = array($hidden, $line); + } + + $cells[] = phutil_tag('th', array('id' => $left_id), $line); $cells[] = phutil_tag('th', array()); $cells[] = $no_copy; @@ -85,7 +114,13 @@ final class DifferentialChangesetOneUpRenderer } else { $left_id = null; } - $cells[] = phutil_tag('th', array('id' => $left_id), $p['oline']); + + $oline = $p['oline']; + if ($o_hidden) { + $oline = array($hidden, $oline); + } + + $cells[] = phutil_tag('th', array('id' => $left_id), $oline); } if ($type == 'new-file') { @@ -97,8 +132,13 @@ final class DifferentialChangesetOneUpRenderer } else { $right_id = null; } - $cells[] = phutil_tag('th', array('id' => $right_id), $p['line']); + $line = $p['line']; + if ($n_hidden) { + $line = array($hidden, $line); + } + + $cells[] = phutil_tag('th', array('id' => $right_id), $line); $cells[] = $no_copy; $cells[] = phutil_tag('td', array('class' => $class), $p['render']); diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php index 181e3d1306..16752b9402 100644 --- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php @@ -69,6 +69,8 @@ final class DifferentialChangesetTwoUpRenderer $depths = $this->getDepths(); $mask = $this->getMask(); + $hidden = new PHUIDiffRevealIconView(); + for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { if (empty($mask[$ii])) { // If we aren't going to show this line, we've just entered a gap. @@ -235,6 +237,69 @@ final class DifferentialChangesetTwoUpRenderer $n_id = null; } + $old_comments = $this->getOldComments(); + $new_comments = $this->getNewComments(); + $scaffolds = array(); + + $o_hidden = array(); + $n_hidden = array(); + + if ($o_num && isset($old_comments[$o_num])) { + foreach ($old_comments[$o_num] as $comment) { + $inline = $this->buildInlineComment( + $comment, + $on_right = false); + $scaffold = $this->getRowScaffoldForInline($inline); + + if ($comment->isHidden()) { + $o_hidden[] = $comment; + } + + if ($n_num && isset($new_comments[$n_num])) { + foreach ($new_comments[$n_num] as $key => $new_comment) { + if ($comment->isCompatible($new_comment)) { + $companion = $this->buildInlineComment( + $new_comment, + $on_right = true); + + if ($new_comment->isHidden()) { + $n_hidden = $new_comment; + } + + $scaffold->addInlineView($companion); + unset($new_comments[$n_num][$key]); + break; + } + } + } + + + $scaffolds[] = $scaffold; + } + } + + if ($n_num && isset($new_comments[$n_num])) { + foreach ($new_comments[$n_num] as $comment) { + $inline = $this->buildInlineComment( + $comment, + $on_right = true); + + if ($comment->isHidden()) { + $n_hidden[] = $comment; + } + + $scaffolds[] = $this->getRowScaffoldForInline($inline); + } + } + + if ($o_hidden) { + $o_num = array($hidden, $o_num); + } + + if ($n_hidden) { + $n_num = array($hidden, $n_num); + } + // NOTE: This is a unicode zero-width space, which we use as a hint when // intercepting 'copy' events to make sure sensible text ends up on the // clipboard. See the 'phabricator-oncopy' behavior. @@ -259,40 +324,8 @@ final class DifferentialChangesetTwoUpRenderer $html[] = $context_not_available; } - $old_comments = $this->getOldComments(); - $new_comments = $this->getNewComments(); - - if ($o_num && isset($old_comments[$o_num])) { - foreach ($old_comments[$o_num] as $comment) { - $inline = $this->buildInlineComment( - $comment, - $on_right = false); - $scaffold = $this->getRowScaffoldForInline($inline); - - if ($n_num && isset($new_comments[$n_num])) { - foreach ($new_comments[$n_num] as $key => $new_comment) { - if ($comment->isCompatible($new_comment)) { - $companion = $this->buildInlineComment( - $new_comment, - $on_right = true); - - $scaffold->addInlineView($companion); - unset($new_comments[$n_num][$key]); - break; - } - } - } - - $html[] = $scaffold; - } - } - if ($n_num && isset($new_comments[$n_num])) { - foreach ($new_comments[$n_num] as $comment) { - $inline = $this->buildInlineComment( - $comment, - $on_right = true); - $html[] = $this->getRowScaffoldForInline($inline); - } + foreach ($scaffolds as $scaffold) { + $html[] = $scaffold; } } diff --git a/src/applications/differential/storage/DifferentialHiddenComment.php b/src/applications/differential/storage/DifferentialHiddenComment.php new file mode 100644 index 0000000000..90dbb527f2 --- /dev/null +++ b/src/applications/differential/storage/DifferentialHiddenComment.php @@ -0,0 +1,24 @@ + false, + self::CONFIG_KEY_SCHEMA => array( + 'key_user' => array( + 'columns' => array('userPHID', 'commentID'), + 'unique' => true, + ), + 'key_comment' => array( + 'columns' => array('commentID'), + ), + ), + ) + parent::getConfiguration(); + } + +} diff --git a/src/applications/differential/storage/DifferentialInlineComment.php b/src/applications/differential/storage/DifferentialInlineComment.php index 65ea64773b..4af5ea5681 100644 --- a/src/applications/differential/storage/DifferentialInlineComment.php +++ b/src/applications/differential/storage/DifferentialInlineComment.php @@ -24,6 +24,7 @@ final class DifferentialInlineComment ->setViewPolicy('public') ->setEditPolicy($this->getAuthorPHID()) ->setContentSource($content_source) + ->attachIsHidden(false) ->setCommentVersion(1); return $this->proxy; @@ -49,6 +50,20 @@ final class DifferentialInlineComment return $this; } + public function supportsHiding() { + if ($this->getSyntheticAuthor()) { + return false; + } + return true; + } + + public function isHidden() { + if (!$this->supportsHiding()) { + return false; + } + return $this->proxy->getIsHidden(); + } + public function getID() { return $this->proxy->getID(); } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 7630814882..0a4d459568 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -522,6 +522,7 @@ final class DifferentialRevision extends DifferentialDAO } $query = id(new DifferentialInlineCommentQuery()) + ->needHidden(true) ->setViewer($viewer); // NOTE: This is a bit sketchy: this method adjusts the inlines as a diff --git a/src/applications/differential/storage/DifferentialTransactionComment.php b/src/applications/differential/storage/DifferentialTransactionComment.php index 0d1e6ea33a..5a9b031dad 100644 --- a/src/applications/differential/storage/DifferentialTransactionComment.php +++ b/src/applications/differential/storage/DifferentialTransactionComment.php @@ -13,6 +13,7 @@ final class DifferentialTransactionComment protected $replyToCommentPHID; private $replyToComment = self::ATTACHABLE; + private $isHidden = self::ATTACHABLE; public function getApplicationTransactionObject() { return new DifferentialTransaction(); @@ -99,4 +100,13 @@ final class DifferentialTransactionComment return $inline_groups; } + public function getIsHidden() { + return $this->assertAttached($this->isHidden); + } + + public function attachIsHidden($hidden) { + $this->isHidden = $hidden; + return $this; + } + } diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 7ac45cc02b..f39526a21e 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -232,8 +232,9 @@ final class DifferentialChangesetListView extends AphrontView { if ($this->inlineURI) { Javelin::initBehavior('differential-edit-inline-comments', array( - 'uri' => $this->inlineURI, - 'stage' => 'differential-review-stage', + 'uri' => $this->inlineURI, + 'stage' => 'differential-review-stage', + 'revealIcon' => hsprintf('%s', new PHUIDiffRevealIconView()), )); } diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 14a3338f5e..0f44db3d13 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -101,6 +101,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '(?Pserve)/' => 'DiffusionRepositoryEditHostingController', 'update/' => 'DiffusionRepositoryEditUpdateController', 'symbol/' => 'DiffusionRepositorySymbolsController', + 'staging/' => 'DiffusionRepositoryEditStagingController', ), 'pathtree/(?P.*)' => 'DiffusionPathTreeController', 'mirror/' => array( diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php index 28a9e1b670..9c3d7eff81 100644 --- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php +++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php @@ -20,6 +20,22 @@ final class PhabricatorDiffusionConfigOptions } public function getOptions() { + $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; + + $fields = array( + new PhabricatorCommitRepositoryField(), + new PhabricatorCommitBranchesField(), + new PhabricatorCommitTagsField(), + new PhabricatorCommitMergedCommitsField(), + ); + + $default_fields = array(); + foreach ($fields as $field) { + $default_fields[$field->getFieldKey()] = array( + 'disabled' => $field->shouldDisableByDefault(), + ); + } + return array( $this->newOption( 'metamta.diffusion.subject-prefix', @@ -124,6 +140,12 @@ final class PhabricatorDiffusionConfigOptions 'from web traffic (for example, if you use different SSH and '. 'web load balancers), you can set the SSH hostname here. This '. 'is an advanced option.')), + $this->newOption('diffusion.fields', $custom_field_type, $default_fields) + ->setCustomData( + id(new PhabricatorRepositoryCommit()) + ->getCustomFieldBaseClass()) + ->setDescription( + pht('Select and reorder Diffusion fields.')), ); } diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 24f600740b..6caf42d8ee 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -240,10 +240,10 @@ final class DiffusionCommitController extends DiffusionController { $change_panel = new PHUIObjectBoxView(); $header = new PHUIHeaderView(); - $header->setHeader(pht('Changes (%d', number_format($count))); + $header->setHeader(pht('Changes (%s)', new PhutilNumber($count))); $change_panel->setID('toc'); - if ($count > self::CHANGES_LIMIT && !$show_all_details) { + if ($count > self::CHANGES_LIMIT && !$show_all_details) { $icon = id(new PHUIIconView()) ->setIconFont('fa-files-o'); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 905021ab23..824a7afe1d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -30,6 +30,7 @@ final class DiffusionRepositoryEditMainController $has_branches = ($is_git || $is_hg); $has_local = $repository->usesLocalWorkingCopy(); + $supports_staging = $repository->supportsStaging(); $crumbs = $this->buildApplicationCrumbs($is_main = true); @@ -92,6 +93,13 @@ final class DiffusionRepositoryEditMainController $this->buildStorageActions($repository)); } + $staging_properties = null; + if ($supports_staging) { + $staging_properties = $this->buildStagingProperties( + $repository, + $this->buildStagingActions($repository)); + } + $actions_properties = $this->buildActionsProperties( $repository, $this->buildActionsActions($repository)); @@ -157,6 +165,12 @@ final class DiffusionRepositoryEditMainController ->addPropertyList($storage_properties); } + if ($staging_properties) { + $boxes[] = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Staging')) + ->addPropertyList($staging_properties); + } + $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Text Encoding')) ->addPropertyList($encoding_properties); @@ -609,6 +623,45 @@ final class DiffusionRepositoryEditMainController return $view; } + + private function buildStagingActions(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + + $view = id(new PhabricatorActionListView()) + ->setObjectURI($this->getRequest()->getRequestURI()) + ->setUser($viewer); + + $edit = id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Staging')) + ->setHref( + $this->getRepositoryControllerURI($repository, 'edit/staging/')); + $view->addAction($edit); + + return $view; + } + + private function buildStagingProperties( + PhabricatorRepository $repository, + PhabricatorActionListView $actions) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setActionList($actions); + + $staging_uri = $repository->getStagingURI(); + if (!$staging_uri) { + $staging_uri = phutil_tag('em', array(), pht('No Staging Area')); + } + + $view->addProperty( + pht('Staging Area'), + $staging_uri); + + return $view; + } + private function buildHostingActions(PhabricatorRepository $repository) { $user = $this->getRequest()->getUser(); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php new file mode 100644 index 0000000000..9fcfd767c0 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php @@ -0,0 +1,92 @@ +getUser(); + $drequest = $this->diffusionRequest; + $repository = $drequest->getRepository(); + + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($user) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->withIDs(array($repository->getID())) + ->executeOne(); + if (!$repository) { + return new Aphront404Response(); + } + + if (!$repository->supportsStaging()) { + return new Aphront404Response(); + } + + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + + $v_area = $repository->getHumanReadableDetail('staging-uri'); + if ($request->isFormPost()) { + $v_area = $request->getStr('area'); + + $xactions = array(); + $template = id(new PhabricatorRepositoryTransaction()); + + $type_encoding = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; + + $xactions[] = id(clone $template) + ->setTransactionType($type_encoding) + ->setNewValue($v_area); + + id(new PhabricatorRepositoryEditor()) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->setActor($user) + ->applyTransactions($repository, $xactions); + + return id(new AphrontRedirectResponse())->setURI($edit_uri); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Edit Staging')); + + $title = pht('Edit %s', $repository->getName()); + + $form = id(new AphrontFormView()) + ->setUser($user) + ->appendRemarkupInstructions( + pht( + "To make it easier to run integration tests and builds on code ". + "under review, you can configure a **Staging Area**. When `arc` ". + "creates a diff, it will push a copy of the changes to the ". + "configured staging area with a corresponding tag.". + "\n\n". + "IMPORTANT: This feature is new, experimental, and not supported. ". + "Use it at your own risk.")) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Staging Area URI')) + ->setName('area') + ->setValue($v_area)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save')) + ->addCancelButton($edit_uri)); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setForm($form); + + return $this->buildApplicationPage( + array( + $crumbs, + $object_box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php b/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php deleted file mode 100644 index 4f58acfa59..0000000000 --- a/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php +++ /dev/null @@ -1,39 +0,0 @@ -getViewer(); - $raw_query = $this->getRawQuery(); - - $results = array(); - - $arcprojs = id(new PhabricatorRepositoryArcanistProject())->loadAll(); - foreach ($arcprojs as $proj) { - $results[] = id(new PhabricatorTypeaheadResult()) - ->setName($proj->getName()) - ->setPHID($proj->getPHID()); - } - - return $results; - } - -} diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 300af2c383..f494ad95fc 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -42,7 +42,6 @@ abstract class HeraldAdapter { const FIELD_APPLICATION_EMAIL = 'applicaton-email'; const FIELD_TASK_PRIORITY = 'taskpriority'; const FIELD_TASK_STATUS = 'taskstatus'; - const FIELD_ARCANIST_PROJECT = 'arcanist-project'; const FIELD_PUSHER_IS_COMMITTER = 'pusher-is-committer'; const FIELD_PATH = 'path'; @@ -100,7 +99,6 @@ abstract class HeraldAdapter { const VALUE_BUILD_PLAN = 'buildplan'; const VALUE_TASK_PRIORITY = 'taskpriority'; const VALUE_TASK_STATUS = 'taskstatus'; - const VALUE_ARCANIST_PROJECT = 'arcanistprojects'; const VALUE_LEGAL_DOCUMENTS = 'legaldocuments'; const VALUE_APPLICATION_EMAIL = 'applicationemail'; @@ -385,7 +383,6 @@ abstract class HeraldAdapter { self::FIELD_APPLICATION_EMAIL => pht('Receiving email address'), self::FIELD_TASK_PRIORITY => pht('Task priority'), self::FIELD_TASK_STATUS => pht('Task status'), - self::FIELD_ARCANIST_PROJECT => pht('Arcanist Project'), self::FIELD_PUSHER_IS_COMMITTER => pht('Pusher same as committer'), self::FIELD_PATH => pht('Path'), ) + $this->getCustomFieldNameMap(); @@ -441,7 +438,6 @@ abstract class HeraldAdapter { case self::FIELD_PUSHER: case self::FIELD_TASK_PRIORITY: case self::FIELD_TASK_STATUS: - case self::FIELD_ARCANIST_PROJECT: return array( self::CONDITION_IS_ANY, self::CONDITION_IS_NOT_ANY, @@ -946,8 +942,6 @@ abstract class HeraldAdapter { return self::VALUE_TASK_PRIORITY; case self::FIELD_TASK_STATUS: return self::VALUE_TASK_STATUS; - case self::FIELD_ARCANIST_PROJECT: - return self::VALUE_ARCANIST_PROJECT; default: return self::VALUE_USER; } @@ -1203,7 +1197,15 @@ abstract class HeraldAdapter { $rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; $action_type = $action->getAction(); - $action_name = idx($this->getActionNameMap($rule_global), $action_type); + + $default = $this->isHeraldCustomKey($action_type) + ? pht('(Unknown Custom Action "%s") equals', $action_type) + : pht('(Unknown Action "%s") equals', $action_type); + + $action_name = idx( + $this->getActionNameMap($rule_global), + $action_type, + $default); $target = $this->renderActionTargetAsText($action, $handles); @@ -1525,7 +1527,9 @@ abstract class HeraldAdapter { $supported = $this->getActions($rule_type); $supported = array_fuse($supported); if (empty($supported[$action])) { - throw new Exception( + return new HeraldApplyTranscript( + $effect, + false, pht( 'Adapter "%s" does not support action "%s" for rule type "%s".', get_class($this), @@ -1548,7 +1552,9 @@ abstract class HeraldAdapter { $result = $this->handleCustomHeraldEffect($effect); if (!$result) { - throw new Exception( + return new HeraldApplyTranscript( + $effect, + false, pht( 'No custom action exists to handle rule action "%s".', $action)); diff --git a/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php b/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php index 766be528f5..2a7a576e3e 100644 --- a/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php +++ b/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php @@ -154,7 +154,10 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { pht('Blocked diff.')); break; default: - throw new Exception(pht('No rules to handle action "%s"!', $action)); + $result[] = new HeraldApplyTranscript( + $effect, + false, + pht('No rules to handle action "%s"!', $action)); } } diff --git a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php index ff14686636..e7ba56cf06 100644 --- a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php @@ -80,7 +80,6 @@ final class HeraldDifferentialRevisionAdapter self::FIELD_AFFECTED_PACKAGE, self::FIELD_AFFECTED_PACKAGE_OWNER, self::FIELD_IS_NEW_OBJECT, - self::FIELD_ARCANIST_PROJECT, ), parent::getFields()); } @@ -259,8 +258,6 @@ final class HeraldDifferentialRevisionAdapter $packages = $this->loadAffectedPackages(); return PhabricatorOwnersOwner::loadAffiliatedUserPHIDs( mpull($packages, 'getID')); - case self::FIELD_ARCANIST_PROJECT: - return $this->revision->getArcanistProjectPHID(); } return parent::getHeraldField($field); diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index f52a849d1e..a2fbe7a108 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -320,7 +320,7 @@ final class HeraldRuleController extends HeraldController { try { $adapter->willSaveAction($rule, $obj); } catch (HeraldInvalidActionException $ex) { - $errors[] = $ex; + $errors[] = $ex->getMessage(); } $actions[] = $obj; @@ -354,7 +354,6 @@ final class HeraldRuleController extends HeraldController { if ($rule->getConditions()) { $serial_conditions = array(); foreach ($rule->getConditions() as $condition) { - $value = $condition->getValue(); switch ($condition->getFieldName()) { case HeraldAdapter::FIELD_TASK_PRIORITY: @@ -394,10 +393,10 @@ final class HeraldRuleController extends HeraldController { $serial_actions = array( array('default', ''), ); + if ($rule->getActions()) { $serial_actions = array(); foreach ($rule->getActions() as $action) { - switch ($action->getAction()) { case HeraldAdapter::ACTION_FLAG: case HeraldAdapter::ACTION_BLOCK: @@ -438,21 +437,39 @@ final class HeraldRuleController extends HeraldController { // names of, so that saving a rule without touching anything doesn't change // it. foreach ($rule->getConditions() as $condition) { - if (empty($field_map[$condition->getFieldName()])) { - $field_map[$condition->getFieldName()] = pht(''); + $field_name = $condition->getFieldName(); + + if (empty($field_map[$field_name])) { + $field_map[$field_name] = pht('', $field_name); } } $actions = $adapter->getActions($rule->getRuleType()); $action_map = array_select_keys($all_actions, $actions); + // Populate any actions which exist in the rule but which we don't know the + // names of, so that saving a rule without touching anything doesn't change + // it. + foreach ($rule->getActions() as $action) { + $action_name = $action->getAction(); + + if (empty($action_map[$action_name])) { + $action_map[$action_name] = pht('', $action_name); + } + } + + $config_info = array(); $config_info['fields'] = $field_map; $config_info['conditions'] = $all_conditions; $config_info['actions'] = $action_map; foreach ($config_info['fields'] as $field => $name) { - $field_conditions = $adapter->getConditionsForField($field); + try { + $field_conditions = $adapter->getConditionsForField($field); + } catch (Exception $ex) { + $field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY); + } $config_info['conditionMap'][$field] = $field_conditions; } @@ -468,9 +485,15 @@ final class HeraldRuleController extends HeraldController { $config_info['rule_type'] = $rule->getRuleType(); foreach ($config_info['actions'] as $action => $name) { - $config_info['targets'][$action] = $adapter->getValueTypeForAction( - $action, - $rule->getRuleType()); + try { + $action_value = $adapter->getValueTypeForAction( + $action, + $rule->getRuleType()); + } catch (Exception $ex) { + $action_value = array(HeraldAdapter::VALUE_NONE); + } + + $config_info['targets'][$action] = $action_value; } $changeflag_options = @@ -603,7 +626,6 @@ final class HeraldRuleController extends HeraldController { 'taskpriority' => new ManiphestTaskPriorityDatasource(), 'taskstatus' => new ManiphestTaskStatusDatasource(), 'buildplan' => new HarbormasterBuildPlanDatasource(), - 'arcanistprojects' => new DiffusionArcanistProjectDatasource(), 'package' => new PhabricatorOwnersPackageDatasource(), 'project' => new PhabricatorProjectDatasource(), 'user' => new PhabricatorPeopleDatasource(), diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index f20d620cca..a915e55b3d 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -380,7 +380,10 @@ final class HeraldTranscriptController extends HeraldController { $item->setState(PHUIObjectItemView::STATE_FAIL); } - $rule = idx($action_names, $apply_xscript->getAction(), pht('Unknown')); + $rule = idx( + $action_names, + $apply_xscript->getAction(), + pht('Unknown Action "%s"', $apply_xscript->getAction())); $item->setHeader(pht('%s: %s', $rule, $target)); $item->addAttribute($apply_xscript->getReason()); diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 5f1189fff6..4e986987d5 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -272,6 +272,16 @@ final class HeraldEngine { $result = false; } else { foreach ($conditions as $condition) { + try { + $object->getHeraldField($condition->getFieldName()); + } catch (Exception $ex) { + $reason = pht( + 'Field "%s" does not exist!', + $condition->getFieldName()); + $result = false; + break; + } + $match = $this->doesConditionMatch($rule, $condition, $object); if (!$all && $match) { diff --git a/src/applications/herald/query/HeraldTranscriptSearchEngine.php b/src/applications/herald/query/HeraldTranscriptSearchEngine.php index 1fd01c3e0d..f9433e0c02 100644 --- a/src/applications/herald/query/HeraldTranscriptSearchEngine.php +++ b/src/applications/herald/query/HeraldTranscriptSearchEngine.php @@ -125,7 +125,7 @@ final class HeraldTranscriptSearchEngine } $item->addAttribute($handles[$xscript->getObjectPHID()]->renderLink()); $item->addAttribute( - pht('%d ms', number_format((int)(1000 * $xscript->getDuration())))); + pht('%s ms', new PhutilNumber((int)(1000 * $xscript->getDuration())))); $item->addIcon( 'none', phabricator_datetime($xscript->getTime(), $viewer)); diff --git a/src/applications/owners/application/PhabricatorOwnersApplication.php b/src/applications/owners/application/PhabricatorOwnersApplication.php index ba44293016..3ed14212dc 100644 --- a/src/applications/owners/application/PhabricatorOwnersApplication.php +++ b/src/applications/owners/application/PhabricatorOwnersApplication.php @@ -42,12 +42,11 @@ final class PhabricatorOwnersApplication extends PhabricatorApplication { public function getRoutes() { return array( '/owners/' => array( - '' => 'PhabricatorOwnersListController', - 'view/(?P[^/]+)/' => 'PhabricatorOwnersListController', + '(?:query/(?P[^/]+)/)?' => 'PhabricatorOwnersListController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorOwnersEditController', 'new/' => 'PhabricatorOwnersEditController', 'package/(?P[1-9]\d*)/' => 'PhabricatorOwnersDetailController', - 'delete/(?P[1-9]\d*)/' => 'PhabricatorOwnersDeleteController', + 'paths/(?P[1-9]\d*)/' => 'PhabricatorOwnersPathsController', ), ); } diff --git a/src/applications/owners/controller/PhabricatorOwnersController.php b/src/applications/owners/controller/PhabricatorOwnersController.php index 4066b297dc..aa2e618775 100644 --- a/src/applications/owners/controller/PhabricatorOwnersController.php +++ b/src/applications/owners/controller/PhabricatorOwnersController.php @@ -1,70 +1,3 @@ filter; - } - protected function setSideNavFilter($filter) { - $this->filter = $filter; - return $this; - } - - public function buildSideNavView() { - $nav = new AphrontSideNavFilterView(); - $base_uri = new PhutilURI('/owners/'); - $nav->setBaseURI($base_uri); - - $nav->addLabel(pht('Packages')); - $this->getExtraPackageViews($nav); - $nav->addFilter('view/owned', pht('Owned')); - $nav->addFilter('view/projects', pht('Projects')); - $nav->addFilter('view/all', pht('All')); - - $nav->selectFilter($this->getSideNavFilter(), 'view/owned'); - - $filter = $nav->getSelectedFilter(); - switch ($filter) { - case 'view/owned': - $title = pht('Owned Packages'); - break; - case 'view/all': - $title = pht('All Packages'); - break; - case 'view/projects': - $title = pht('Projects'); - break; - case 'new': - $title = pht('New Package'); - break; - default: - $title = pht('Package'); - break; - } - - return $nav; - } - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Package')) - ->setHref('/owners/new/') - ->setIcon('fa-plus-square')); - - return $crumbs; - } - - public function buildApplicationMenu() { - return $this->buildSideNavView()->getMenu(); - } - - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { - return; - } - -} +abstract class PhabricatorOwnersController extends PhabricatorController {} diff --git a/src/applications/owners/controller/PhabricatorOwnersDeleteController.php b/src/applications/owners/controller/PhabricatorOwnersDeleteController.php deleted file mode 100644 index 7b275b7407..0000000000 --- a/src/applications/owners/controller/PhabricatorOwnersDeleteController.php +++ /dev/null @@ -1,44 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $package = id(new PhabricatorOwnersPackage())->load($this->id); - if (!$package) { - return new Aphront404Response(); - } - - if ($request->isDialogFormPost()) { - id(new PhabricatorOwnersPackageEditor()) - ->setActor($user) - ->setPackage($package) - ->delete(); - return id(new AphrontRedirectResponse())->setURI('/owners/'); - } - - $text = pht( - 'Are you sure you want to delete the "%s" package? This '. - 'operation can not be undone.', - $package->getName()); - $dialog = id(new AphrontDialogView()) - ->setUser($user) - ->setTitle(pht('Really delete this package?')) - ->appendChild(phutil_tag('p', array(), $text)) - ->addSubmitButton(pht('Delete')) - ->addCancelButton('/owners/package/'.$package->getID().'/') - ->setSubmitURI($request->getRequestURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 4b28fb62c3..7935031389 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -3,25 +3,22 @@ final class PhabricatorOwnersDetailController extends PhabricatorOwnersController { - private $id; - private $package; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; + public function shouldAllowPublic() { + return true; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); - $package = id(new PhabricatorOwnersPackage())->load($this->id); + $package = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); if (!$package) { return new Aphront404Response(); } - $this->package = $package; $paths = $package->loadPaths(); - $owners = $package->loadOwners(); $repository_phids = array(); foreach ($paths as $path) { @@ -30,7 +27,7 @@ final class PhabricatorOwnersDetailController if ($repository_phids) { $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array_keys($repository_phids)) ->execute(); $repositories = mpull($repositories, null, 'getPHID'); @@ -38,101 +35,18 @@ final class PhabricatorOwnersDetailController $repositories = array(); } - $phids = array(); - foreach ($owners as $owner) { - $phids[$owner->getUserPHID()] = true; - } - $phids = array_keys($phids); + $actions = $this->buildPackageActionView($package); + $properties = $this->buildPackagePropertyView($package); + $properties->setActionList($actions); - $handles = $this->loadViewerHandles($phids); + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($package->getName()) + ->setPolicyObject($package); - $rows = array(); - - $rows[] = array(pht('Name'), $package->getName()); - $rows[] = array(pht('Description'), $package->getDescription()); - - $primary_owner = null; - $primary_phid = $package->getPrimaryOwnerPHID(); - if ($primary_phid && isset($handles[$primary_phid])) { - $primary_owner = phutil_tag( - 'strong', - array(), - $handles[$primary_phid]->renderLink()); - } - $rows[] = array(pht('Primary Owner'), $primary_owner); - - $owner_links = array(); - foreach ($owners as $owner) { - $owner_links[] = $handles[$owner->getUserPHID()]->renderLink(); - } - $owner_links = phutil_implode_html(phutil_tag('br'), $owner_links); - $rows[] = array(pht('Owners'), $owner_links); - - $rows[] = array( - pht('Auditing'), - $package->getAuditingEnabled() ? - pht('Enabled') : - pht('Disabled'), - ); - - $path_links = array(); - foreach ($paths as $path) { - $repo = idx($repositories, $path->getRepositoryPHID()); - if (!$repo) { - continue; - } - $href = DiffusionRequest::generateDiffusionURI( - array( - 'callsign' => $repo->getCallsign(), - 'branch' => $repo->getDefaultBranch(), - 'path' => $path->getPath(), - 'action' => 'browse', - )); - $repo_name = phutil_tag('strong', array(), $repo->getName()); - $path_link = phutil_tag( - 'a', - array( - 'href' => (string)$href, - ), - $path->getPath()); - $path_links[] = hsprintf( - '%s %s %s', - ($path->getExcluded() ? "\xE2\x80\x93" : '+'), - $repo_name, - $path_link); - } - $path_links = phutil_implode_html(phutil_tag('br'), $path_links); - $rows[] = array(pht('Paths'), $path_links); - - $table = new AphrontTableView($rows); - $table->setColumnClasses( - array( - 'header', - 'wide', - )); - - $panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader( - pht('Package Details for "%s"', $package->getName())); - $header->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setHref('/owners/delete/'.$package->getID().'/') - ->addSigil('workflow') - ->setText(pht('Delete Package'))); - - $header->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setHref('/owners/edit/'.$package->getID().'/') - ->setText(pht('Edit Package'))); - - $panel->setHeader($header); - $panel->setTable($table); - - $key = 'package/'.$package->getID(); - $this->setSideNavFilter($key); + $panel = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($properties); $commit_views = array(); @@ -151,7 +65,7 @@ final class PhabricatorOwnersDetailController ->execute(); if ($attention_commits) { $view = id(new PhabricatorAuditListView()) - ->setUser($user) + ->setUser($viewer) ->setCommits($attention_commits); $commit_views[] = array( @@ -172,7 +86,7 @@ final class PhabricatorOwnersDetailController ->execute(); $view = id(new PhabricatorAuditListView()) - ->setUser($user) + ->setUser($viewer) ->setCommits($all_commits) ->setNoDataString(pht('No commits in this package.')); @@ -210,21 +124,165 @@ final class PhabricatorOwnersDetailController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($package->getName()); - $nav = $this->buildSideNavView(); - $nav->appendChild($crumbs); - $nav->appendChild($panel); - $nav->appendChild($commit_panels); + $timeline = $this->buildTransactionTimeline( + $package, + new PhabricatorOwnersPackageTransactionQuery()); + $timeline->setShouldTerminate(true); return $this->buildApplicationPage( - $nav, array( - 'title' => pht('Package %s', $package->getName()), + $crumbs, + $panel, + $this->renderPathsTable($paths, $repositories), + $commit_panels, + $timeline, + ), + array( + 'title' => $package->getName(), )); } - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { - $package = $this->package; - $view->addFilter('package/'.$package->getID(), pht('Details')); + + private function buildPackagePropertyView(PhabricatorOwnersPackage $package) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $primary_phid = $package->getPrimaryOwnerPHID(); + if ($primary_phid) { + $primary_owner = $viewer->renderHandle($primary_phid); + } else { + $primary_owner = phutil_tag('em', array(), pht('None')); + } + $view->addProperty(pht('Primary Owner'), $primary_owner); + + // TODO: needOwners() this on the Query. + $owners = $package->loadOwners(); + if ($owners) { + $owner_list = $viewer->renderHandleList(mpull($owners, 'getUserPHID')); + } else { + $owner_list = phutil_tag('em', array(), pht('None')); + } + $view->addProperty(pht('Owners'), $owner_list); + + if ($package->getAuditingEnabled()) { + $auditing = pht('Enabled'); + } else { + $auditing = pht('Disabled'); + } + $view->addProperty(pht('Auditing'), $auditing); + + $description = $package->getDescription(); + if (strlen($description)) { + $view->addSectionHeader(pht('Description')); + $view->addTextContent( + $output = PhabricatorMarkupEngine::renderOneObject( + id(new PhabricatorMarkupOneOff())->setContent($description), + 'default', + $viewer)); + } + + return $view; + } + + private function buildPackageActionView(PhabricatorOwnersPackage $package) { + $viewer = $this->getViewer(); + + // TODO: Implement this capability. + $can_edit = true; + + $id = $package->getID(); + $edit_uri = $this->getApplicationURI("/edit/{$id}/"); + $paths_uri = $this->getApplicationURI("/paths/{$id}/"); + + $view = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($package) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Package')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Paths')) + ->setIcon('fa-folder-open') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($paths_uri)); + + return $view; + } + + private function renderPathsTable(array $paths, array $repositories) { + $viewer = $this->getViewer(); + + $rows = array(); + foreach ($paths as $path) { + $repo = idx($repositories, $path->getRepositoryPHID()); + if (!$repo) { + continue; + } + $href = DiffusionRequest::generateDiffusionURI( + array( + 'callsign' => $repo->getCallsign(), + 'branch' => $repo->getDefaultBranch(), + 'path' => $path->getPath(), + 'action' => 'browse', + )); + + $path_link = phutil_tag( + 'a', + array( + 'href' => (string)$href, + ), + $path->getPath()); + + $rows[] = array( + ($path->getExcluded() ? '-' : '+'), + $repo->getName(), + $path_link, + ); + } + + $info = null; + if (!$paths) { + $info = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors( + array( + pht( + 'This package does not contain any paths yet. Use '. + '"Edit Paths" to add some.'), + )); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + null, + pht('Repository'), + pht('Path'), + )) + ->setColumnClasses( + array( + null, + null, + 'wide', + )); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Paths')) + ->appendChild($table); + + if ($info) { + $box->setInfoView($info); + } + + return $box; } } diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 3a0baaa06e..9f1987814d 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -3,180 +3,132 @@ final class PhabricatorOwnersEditController extends PhabricatorOwnersController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getUser(); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - if ($this->id) { - $package = id(new PhabricatorOwnersPackage())->load($this->id); + $id = $request->getURIData('id'); + if ($id) { + $package = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + // TODO: Support this capability. + // PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); if (!$package) { return new Aphront404Response(); } + $is_new = false; } else { - $package = new PhabricatorOwnersPackage(); - $package->setPrimaryOwnerPHID($user->getPHID()); + $package = PhabricatorOwnersPackage::initializeNewPackage($viewer); + $is_new = true; } $e_name = true; $e_primary = true; + $v_name = $package->getName(); + $v_primary = $package->getPrimaryOwnerPHID(); + // TODO: Pull these off needOwners() on the Query. + $v_owners = mpull($package->loadOwners(), 'getUserPHID'); + $v_auditing = $package->getAuditingEnabled(); + $v_description = $package->getDescription(); + + $errors = array(); - if ($request->isFormPost()) { - $package->setName($request->getStr('name')); - $package->setDescription($request->getStr('description')); - $old_auditing_enabled = $package->getAuditingEnabled(); - $package->setAuditingEnabled( - ($request->getStr('auditing') === 'enabled') - ? 1 - : 0); + $xactions = array(); - $primary = $request->getArr('primary'); - $primary = reset($primary); - $old_primary = $package->getPrimaryOwnerPHID(); - $package->setPrimaryOwnerPHID($primary); + $v_name = $request->getStr('name'); + $v_primary = head($request->getArr('primary')); + $v_owners = $request->getArr('owners'); + $v_auditing = ($request->getStr('auditing') == 'enabled'); + $v_description = $request->getStr('description'); - $owners = $request->getArr('owners'); - if ($primary) { - array_unshift($owners, $primary); + if ($v_primary) { + $v_owners[] = $v_primary; + $v_owners = array_unique($v_owners); } - $owners = array_unique($owners); - $paths = $request->getArr('path'); - $repos = $request->getArr('repo'); - $excludes = $request->getArr('exclude'); + $type_name = PhabricatorOwnersPackageTransaction::TYPE_NAME; + $type_primary = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; + $type_owners = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; + $type_auditing = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; + $type_description = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; - $path_refs = array(); - for ($ii = 0; $ii < count($paths); $ii++) { - if (empty($paths[$ii]) || empty($repos[$ii])) { - continue; + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_primary) + ->setNewValue($v_primary); + + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_owners) + ->setNewValue($v_owners); + + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_auditing) + ->setNewValue($v_auditing); + + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_description) + ->setNewValue($v_description); + + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($package, $xactions); + + $id = $package->getID(); + if ($is_new) { + $next_uri = '/owners/paths/'.$id.'/'; + } else { + $next_uri = '/owners/package/'.$id.'/'; } - $path_refs[] = array( - 'repositoryPHID' => $repos[$ii], - 'path' => $paths[$ii], - 'excluded' => $excludes[$ii], - ); - } - if (!strlen($package->getName())) { - $e_name = pht('Required'); - $errors[] = pht('Package name is required.'); - } else { - $e_name = null; - } + return id(new AphrontRedirectResponse())->setURI($next_uri); + } catch (AphrontDuplicateKeyQueryException $ex) { + $e_name = pht('Duplicate'); + $errors[] = pht('Package name must be unique.'); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; - if (!$package->getPrimaryOwnerPHID()) { - $e_primary = pht('Required'); - $errors[] = pht('Package must have a primary owner.'); - } else { - $e_primary = null; - } - - if (!$path_refs) { - $errors[] = pht('Package must include at least one path.'); - } - - if (!$errors) { - $package->attachUnsavedOwners($owners); - $package->attachUnsavedPaths($path_refs); - $package->attachOldAuditingEnabled($old_auditing_enabled); - $package->attachOldPrimaryOwnerPHID($old_primary); - try { - id(new PhabricatorOwnersPackageEditor()) - ->setActor($user) - ->setPackage($package) - ->save(); - return id(new AphrontRedirectResponse()) - ->setURI('/owners/package/'.$package->getID().'/'); - } catch (AphrontDuplicateKeyQueryException $ex) { - $e_name = pht('Duplicate'); - $errors[] = pht('Package name must be unique.'); - } - } - } else { - $owners = $package->loadOwners(); - $owners = mpull($owners, 'getUserPHID'); - - $paths = $package->loadPaths(); - $path_refs = array(); - foreach ($paths as $path) { - $path_refs[] = array( - 'repositoryPHID' => $path->getRepositoryPHID(), - 'path' => $path->getPath(), - 'excluded' => $path->getExcluded(), - ); + $e_name = $ex->getShortMessage($type_name); + $e_primary = $ex->getShortMessage($type_primary); } } - $primary = $package->getPrimaryOwnerPHID(); - if ($primary) { - $value_primary_owner = array($primary); + if ($v_primary) { + $value_primary_owner = array($v_primary); } else { $value_primary_owner = array(); } - if ($package->getID()) { - $title = pht('Edit Package'); - $side_nav_filter = 'edit/'.$this->id; - } else { + if ($is_new) { + $cancel_uri = '/owners/'; $title = pht('New Package'); - $side_nav_filter = 'new'; + $button_text = pht('Continue'); + } else { + $cancel_uri = '/owners/package/'.$package->getID().'/'; + $title = pht('Edit Package'); + $button_text = pht('Save Package'); } - $this->setSideNavFilter($side_nav_filter); - - $repos = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->execute(); - - $default_paths = array(); - foreach ($repos as $repo) { - $default_path = $repo->getDetail('default-owners-path'); - if ($default_path) { - $default_paths[$repo->getPHID()] = $default_path; - } - } - - $repos = mpull($repos, 'getCallsign', 'getPHID'); - asort($repos); - - $template = new AphrontTypeaheadTemplateView(); - $template = $template->render(); - - Javelin::initBehavior( - 'owners-path-editor', - array( - 'root' => 'path-editor', - 'table' => 'paths', - 'add_button' => 'addpath', - 'repositories' => $repos, - 'input_template' => $template, - 'pathRefs' => $path_refs, - - 'completeURI' => '/diffusion/services/path/complete/', - 'validateURI' => '/diffusion/services/path/validate/', - - 'repositoryDefaultPaths' => $default_paths, - )); - - require_celerity_resource('owners-path-editor-css'); - - $cancel_uri = $package->getID() - ? '/owners/package/'.$package->getID().'/' - : '/owners/'; $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') - ->setValue($package->getName()) + ->setValue($v_name) ->setError($e_name)) ->appendControl( id(new AphrontFormTokenizerControl()) @@ -191,7 +143,7 @@ final class PhabricatorOwnersEditController ->setDatasource(new PhabricatorProjectOrUserDatasource()) ->setLabel(pht('Owners')) ->setName('owners') - ->setValue($owners)) + ->setValue($v_owners)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('auditing') @@ -202,47 +154,22 @@ final class PhabricatorOwnersEditController 'this package will be reviewed to make sure an owner '. 'of the package is involved and the commit message has '. 'a valid revision, reviewed by, and author.')) - ->setOptions(array( - 'disabled' => pht('Disabled'), - 'enabled' => pht('Enabled'), - )) - ->setValue( - $package->getAuditingEnabled() - ? 'enabled' - : 'disabled')) + ->setOptions( + array( + 'disabled' => pht('Disabled'), + 'enabled' => pht('Enabled'), + )) + ->setValue(($v_auditing ? 'enabled' : 'disabled'))) ->appendChild( - id(new PHUIFormInsetView()) - ->setTitle(pht('Paths')) - ->addDivAttributes(array('id' => 'path-editor')) - ->setRightButton(javelin_tag( - 'a', - array( - 'href' => '#', - 'class' => 'button green', - 'sigil' => 'addpath', - 'mustcapture' => true, - ), - pht('Add New Path'))) - ->setDescription( - pht( - 'Specify the files and directories which comprise '. - 'this package.')) - ->setContent(javelin_tag( - 'table', - array( - 'class' => 'owners-path-editor-table', - 'sigil' => 'paths', - ), - ''))) - ->appendChild( - id(new AphrontFormTextAreaControl()) + id(new PhabricatorRemarkupControl()) + ->setUser($viewer) ->setLabel(pht('Description')) ->setName('description') - ->setValue($package->getDescription())) + ->setValue($v_description)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) - ->setValue(pht('Save Package'))); + ->setValue($button_text)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) @@ -251,29 +178,22 @@ final class PhabricatorOwnersEditController $crumbs = $this->buildApplicationCrumbs(); if ($package->getID()) { - $crumbs->addTextCrumb(pht('Edit %s', $package->getName())); + $crumbs->addTextCrumb( + $package->getName(), + $this->getApplicationURI('package/'.$package->getID().'/')); + $crumbs->addTextCrumb(pht('Edit')); } else { $crumbs->addTextCrumb(pht('New Package')); } - $nav = $this->buildSideNavView(); - $nav->appendChild($crumbs); - $nav->appendChild($form_box); - return $this->buildApplicationPage( array( - $nav, + $crumbs, + $form_box, ), array( 'title' => $title, )); } - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { - if ($this->id) { - $view->addFilter('edit/'.$this->id, pht('Edit')); - } else { - $view->addFilter('new', pht('New')); - } - } } diff --git a/src/applications/owners/controller/PhabricatorOwnersListController.php b/src/applications/owners/controller/PhabricatorOwnersListController.php index 2f73f38425..7beb842c82 100644 --- a/src/applications/owners/controller/PhabricatorOwnersListController.php +++ b/src/applications/owners/controller/PhabricatorOwnersListController.php @@ -3,340 +3,52 @@ final class PhabricatorOwnersListController extends PhabricatorOwnersController { - protected $view; - - public function willProcessRequest(array $data) { - $this->view = idx($data, 'view', 'owned'); - $this->setSideNavFilter('view/'.$this->view); + public function shouldAllowPublic() { + return true; } - public function processRequest() { + public function handleRequest(AphrontRequest $request) { + $controller = id(new PhabricatorApplicationSearchController()) + ->setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new PhabricatorOwnersPackageSearchEngine()) + ->setNavigation($this->buildSideNavView()); - $request = $this->getRequest(); - $user = $request->getUser(); - - $package = new PhabricatorOwnersPackage(); - $owner = new PhabricatorOwnersOwner(); - $path = new PhabricatorOwnersPath(); - - $repository_phid = ''; - if ($request->getStr('repository') != '') { - $repository_phid = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->withCallsigns(array($request->getStr('repository'))) - ->executeOne() - ->getPHID(); - } - - switch ($this->view) { - case 'search': - $packages = array(); - - $conn_r = $package->establishConnection('r'); - - $where = array('1 = 1'); - $join = array(); - $having = ''; - - if ($request->getStr('name')) { - $where[] = qsprintf( - $conn_r, - 'p.name LIKE %~', - $request->getStr('name')); - } - - if ($repository_phid || $request->getStr('path')) { - - $join[] = qsprintf( - $conn_r, - 'JOIN %T path ON path.packageID = p.id', - $path->getTableName()); - - if ($repository_phid) { - $where[] = qsprintf( - $conn_r, - 'path.repositoryPHID = %s', - $repository_phid); - } - - if ($request->getStr('path')) { - $where[] = qsprintf( - $conn_r, - '(path.path LIKE %~ AND NOT path.excluded) OR - %s LIKE CONCAT(REPLACE(path.path, %s, %s), %s)', - $request->getStr('path'), - $request->getStr('path'), - '_', - '\_', - '%'); - $having = 'HAVING MAX(path.excluded) = 0'; - } - - } - - if ($request->getArr('owner')) { - $join[] = qsprintf( - $conn_r, - 'JOIN %T o ON o.packageID = p.id', - $owner->getTableName()); - $where[] = qsprintf( - $conn_r, - 'o.userPHID IN (%Ls)', - $request->getArr('owner')); - } - - $data = queryfx_all( - $conn_r, - 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id %Q', - $package->getTableName(), - implode(' ', $join), - '('.implode(') AND (', $where).')', - $having); - $packages = $package->loadAllFromArray($data); - - $header = pht('Search Results'); - $nodata = pht('No packages match your query.'); - break; - case 'owned': - $data = queryfx_all( - $package->establishConnection('r'), - 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID - WHERE o.userPHID = %s GROUP BY p.id', - $package->getTableName(), - $owner->getTableName(), - $user->getPHID()); - $packages = $package->loadAllFromArray($data); - - $header = pht('Owned Packages'); - $nodata = pht('No owned packages'); - break; - case 'projects': - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($user) - ->withMemberPHIDs(array($user->getPHID())) - ->withStatus(PhabricatorProjectQuery::STATUS_ANY) - ->execute(); - $owner_phids = mpull($projects, 'getPHID'); - if ($owner_phids) { - $data = queryfx_all( - $package->establishConnection('r'), - 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID - WHERE o.userPHID IN (%Ls) GROUP BY p.id', - $package->getTableName(), - $owner->getTableName(), - $owner_phids); - } else { - $data = array(); - } - $packages = $package->loadAllFromArray($data); - - $header = pht('Project Packages'); - $nodata = pht('No owned packages'); - break; - case 'all': - $packages = $package->loadAll(); - - $header = pht('All Packages'); - $nodata = pht('There are no defined packages.'); - break; - } - - $content = $this->renderPackageTable( - $packages, - $header, - $nodata); - - $filter = new AphrontListFilterView(); - - $owner_phids = $request->getArr('owner'); - - $callsigns = array('' => pht('(Any Repository)')); - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->setOrder('callsign') - ->execute(); - foreach ($repositories as $repository) { - $callsigns[$repository->getCallsign()] = - $repository->getCallsign().': '.$repository->getName(); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->setAction('/owners/view/search/') - ->setMethod('GET') - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($request->getStr('name'))) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectOrUserDatasource()) - ->setLimit(1) - ->setName('owner') - ->setLabel(pht('Owner')) - ->setValue($owner_phids)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('repository') - ->setLabel(pht('Repository')) - ->setOptions($callsigns) - ->setValue($request->getStr('repository'))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('path') - ->setLabel(pht('Path')) - ->setValue($request->getStr('path'))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Search for Packages'))); - - $filter->appendChild($form); - $title = pht('Package Index'); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($header); - - $nav = $this->buildSideNavView(); - $nav->appendChild($crumbs); - $nav->appendChild($filter); - $nav->appendChild($content); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Package Index'), - )); + return $this->delegateToController($controller); } - private function renderPackageTable(array $packages, $header, $nodata) { - assert_instances_of($packages, 'PhabricatorOwnersPackage'); + public function buildSideNavView($for_app = false) { + $viewer = $this->getViewer(); - if ($packages) { - $package_ids = mpull($packages, 'getID'); + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( - 'packageID IN (%Ld)', - $package_ids); - - $paths = id(new PhabricatorOwnersPath())->loadAllWhere( - 'packageID in (%Ld)', - $package_ids); - - $phids = array(); - foreach ($owners as $owner) { - $phids[$owner->getUserPHID()] = true; - } - $phids = array_keys($phids); - $handles = $this->loadViewerHandles($phids); - - $repository_phids = array(); - foreach ($paths as $path) { - $repository_phids[$path->getRepositoryPHID()] = true; - } - - if ($repository_phids) { - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($this->getRequest()->getUser()) - ->withPHIDs(array_keys($repository_phids)) - ->execute(); - } else { - $repositories = array(); - } - - $repositories = mpull($repositories, null, 'getPHID'); - $owners = mgroup($owners, 'getPackageID'); - $paths = mgroup($paths, 'getPackageID'); - } else { - $handles = array(); - $repositories = array(); - $owners = array(); - $paths = array(); + if ($for_app) { + $nav->addFilter('new/', pht('Create Package')); } - $rows = array(); - foreach ($packages as $package) { + id(new PhabricatorOwnersPackageSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); - $pkg_owners = idx($owners, $package->getID(), array()); - foreach ($pkg_owners as $key => $owner) { - $pkg_owners[$key] = $handles[$owner->getUserPHID()]->renderLink(); - if ($owner->getUserPHID() == $package->getPrimaryOwnerPHID()) { - $pkg_owners[$key] = phutil_tag('strong', array(), $pkg_owners[$key]); - } - } - $pkg_owners = phutil_implode_html(phutil_tag('br'), $pkg_owners); + $nav->selectFilter(null); - $pkg_paths = idx($paths, $package->getID(), array()); - foreach ($pkg_paths as $key => $path) { - $repo = idx($repositories, $path->getRepositoryPHID()); - if ($repo) { - $href = DiffusionRequest::generateDiffusionURI( - array( - 'callsign' => $repo->getCallsign(), - 'branch' => $repo->getDefaultBranch(), - 'path' => $path->getPath(), - 'action' => 'browse', - )); - $pkg_paths[$key] = hsprintf( - '%s %s%s', - ($path->getExcluded() ? "\xE2\x80\x93" : '+'), - phutil_tag('strong', array(), $repo->getName()), - phutil_tag( - 'a', - array( - 'href' => (string)$href, - ), - $path->getPath())); - } else { - $pkg_paths[$key] = $path->getPath(); - } - } - $pkg_paths = phutil_implode_html(phutil_tag('br'), $pkg_paths); - - $rows[] = array( - phutil_tag( - 'a', - array( - 'href' => '/owners/package/'.$package->getID().'/', - ), - $package->getName()), - $pkg_owners, - $pkg_paths, - phutil_tag( - 'a', - array( - 'href' => '/audit/?auditorPHIDs='.$package->getPHID(), - ), - pht('Related Commits')), - ); - } - - $table = new AphrontTableView($rows); - $table->setHeaders( - array( - pht('Name'), - pht('Owners'), - pht('Paths'), - pht('Related Commits'), - )); - $table->setColumnClasses( - array( - 'pri', - '', - 'wide wrap', - 'narrow', - )); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText($header); - $panel->setTable($table); - - return $panel; + return $nav; } - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { - if ($this->view == 'search') { - $view->addFilter('view/search', pht('Search Results')); - } + public function buildApplicationMenu() { + return $this->buildSideNavView(true)->getMenu(); } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Package')) + ->setHref($this->getApplicationURI('new/')) + ->setIcon('fa-plus-square')); + + return $crumbs; + } + } diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php new file mode 100644 index 0000000000..95df2cb807 --- /dev/null +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -0,0 +1,165 @@ +getUser(); + + $package = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + // TODO: Support this capability. + // PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$package) { + return new Aphront404Response(); + } + + if ($request->isFormPost()) { + $paths = $request->getArr('path'); + $repos = $request->getArr('repo'); + $excludes = $request->getArr('exclude'); + + $path_refs = array(); + foreach ($paths as $key => $path) { + if (!isset($repos[$key])) { + throw new Exception( + pht( + 'No repository PHID for path "%s"!', + $key)); + } + + if (!isset($excludes[$key])) { + throw new Exception( + pht( + 'No exclusion value for path "%s"!', + $key)); + } + + $path_refs[] = array( + 'repositoryPHID' => $repos[$key], + 'path' => $path, + 'excluded' => (int)$excludes[$key], + ); + } + + $type_paths = PhabricatorOwnersPackageTransaction::TYPE_PATHS; + + $xactions = array(); + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_paths) + ->setNewValue($path_refs); + + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($package, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI('/owners/package/'.$package->getID().'/'); + } else { + $paths = $package->loadPaths(); + $path_refs = mpull($paths, 'getRef'); + } + + $repos = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->execute(); + + $default_paths = array(); + foreach ($repos as $repo) { + $default_path = $repo->getDetail('default-owners-path'); + if ($default_path) { + $default_paths[$repo->getPHID()] = $default_path; + } + } + + $repos = mpull($repos, 'getCallsign', 'getPHID'); + asort($repos); + + $template = new AphrontTypeaheadTemplateView(); + $template = $template->render(); + + Javelin::initBehavior( + 'owners-path-editor', + array( + 'root' => 'path-editor', + 'table' => 'paths', + 'add_button' => 'addpath', + 'repositories' => $repos, + 'input_template' => $template, + 'pathRefs' => $path_refs, + + 'completeURI' => '/diffusion/services/path/complete/', + 'validateURI' => '/diffusion/services/path/validate/', + + 'repositoryDefaultPaths' => $default_paths, + )); + + require_celerity_resource('owners-path-editor-css'); + + $cancel_uri = '/owners/package/'.$package->getID().'/'; + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new PHUIFormInsetView()) + ->setTitle(pht('Paths')) + ->addDivAttributes(array('id' => 'path-editor')) + ->setRightButton(javelin_tag( + 'a', + array( + 'href' => '#', + 'class' => 'button green', + 'sigil' => 'addpath', + 'mustcapture' => true, + ), + pht('Add New Path'))) + ->setDescription( + pht( + 'Specify the files and directories which comprise '. + 'this package.')) + ->setContent(javelin_tag( + 'table', + array( + 'class' => 'owners-path-editor-table', + 'sigil' => 'paths', + ), + ''))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue(pht('Save Paths'))); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Edit Paths')) + ->setForm($form); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + $package->getName(), + $this->getApplicationURI('package/'.$package->getID().'/')); + $crumbs->addTextCrumb(pht('Edit Paths')); + + return $this->buildApplicationPage( + array( + $crumbs, + $form_box, + ), + array( + 'title' => array( + $package->getName(), + pht('Edit Paths'), + ), + )); + } + +} diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditor.php deleted file mode 100644 index 160c9039aa..0000000000 --- a/src/applications/owners/editor/PhabricatorOwnersPackageEditor.php +++ /dev/null @@ -1,198 +0,0 @@ -package = $package; - return $this; - } - - public function getPackage() { - return $this->package; - } - - public function save() { - $actor = $this->getActor(); - $package = $this->getPackage(); - $package->attachActorPHID($actor->getPHID()); - - if ($package->getID()) { - $is_new = false; - } else { - $is_new = true; - } - - $package->openTransaction(); - - $ret = $package->save(); - - $add_owners = array(); - $remove_owners = array(); - $all_owners = array(); - if ($package->getUnsavedOwners()) { - $new_owners = array_fill_keys($package->getUnsavedOwners(), true); - $cur_owners = array(); - foreach ($package->loadOwners() as $owner) { - if (empty($new_owners[$owner->getUserPHID()])) { - $remove_owners[$owner->getUserPHID()] = true; - $owner->delete(); - continue; - } - $cur_owners[$owner->getUserPHID()] = true; - } - - $add_owners = array_diff_key($new_owners, $cur_owners); - $all_owners = array_merge( - array($package->getPrimaryOwnerPHID() => true), - $new_owners, - $remove_owners); - foreach ($add_owners as $phid => $ignored) { - $owner = new PhabricatorOwnersOwner(); - $owner->setPackageID($package->getID()); - $owner->setUserPHID($phid); - $owner->save(); - } - $package->attachUnsavedOwners(array()); - } - - $add_paths = array(); - $remove_paths = array(); - $touched_repos = array(); - if ($package->getUnsavedPaths()) { - $new_paths = igroup( - $package->getUnsavedPaths(), - 'repositoryPHID', - 'path'); - $cur_paths = $package->loadPaths(); - foreach ($cur_paths as $key => $path) { - $repository_phid = $path->getRepositoryPHID(); - $new_path = head(idx( - idx($new_paths, $repository_phid, array()), - $path->getPath(), - array())); - $excluded = $path->getExcluded(); - if ($new_path === false || - idx($new_path, 'excluded') != $excluded) { - $touched_repos[$repository_phid] = true; - $remove_paths[$repository_phid][$path->getPath()] = $excluded; - $path->delete(); - unset($cur_paths[$key]); - } - } - - $cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath'); - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($actor) - ->withPHIDs(array_keys($cur_paths)) - ->execute(); - $repositories = mpull($repositories, null, 'getPHID'); - foreach ($new_paths as $repository_phid => $paths) { - $repository = idx($repositories, $repository_phid); - if (!$repository) { - continue; - } - foreach ($paths as $path => $dicts) { - $path = ltrim($path, '/'); - // build query to validate path - $drequest = DiffusionRequest::newFromDictionary( - array( - 'user' => $actor, - 'repository' => $repository, - 'path' => $path, - )); - $results = DiffusionBrowseResultSet::newFromConduit( - DiffusionQuery::callConduitWithDiffusionRequest( - $actor, - $drequest, - 'diffusion.browsequery', - array( - 'commit' => $drequest->getCommit(), - 'path' => $path, - 'needValidityOnly' => true, - ))); - $valid = $results->isValidResults(); - $is_directory = true; - if (!$valid) { - switch ($results->getReasonForEmptyResultSet()) { - case DiffusionBrowseResultSet::REASON_IS_FILE: - $valid = true; - $is_directory = false; - break; - case DiffusionBrowseResultSet::REASON_IS_EMPTY: - $valid = true; - break; - } - } - if ($is_directory && substr($path, -1) != '/') { - $path .= '/'; - } - if (substr($path, 0, 1) != '/') { - $path = '/'.$path; - } - if (empty($cur_paths[$repository_phid][$path]) && $valid) { - $touched_repos[$repository_phid] = true; - $excluded = idx(reset($dicts), 'excluded', 0); - $add_paths[$repository_phid][$path] = $excluded; - $obj = new PhabricatorOwnersPath(); - $obj->setPackageID($package->getID()); - $obj->setRepositoryPHID($repository_phid); - $obj->setPath($path); - $obj->setExcluded($excluded); - $obj->save(); - } - } - } - $package->attachUnsavedPaths(array()); - } - - $package->saveTransaction(); - - if ($is_new) { - $mail = new PackageCreateMail($package); - } else { - $mail = new PackageModifyMail( - $package, - array_keys($add_owners), - array_keys($remove_owners), - array_keys($all_owners), - array_keys($touched_repos), - $add_paths, - $remove_paths); - } - $mail->setActor($actor); - $mail->send(); - - return $ret; - } - - public function delete() { - $actor = $this->getActor(); - $package = $this->getPackage(); - $package->attachActorPHID($actor->getPHID()); - - $mails = id(new PackageDeleteMail($package)) - ->setActor($actor) - ->prepareMails(); - - $package->openTransaction(); - - foreach ($package->loadOwners() as $owner) { - $owner->delete(); - } - foreach ($package->loadPaths() as $path) { - $path->delete(); - } - $ret = $package->delete(); - - $package->saveTransaction(); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } - - return $ret; - } - -} diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php new file mode 100644 index 0000000000..8ce1e48c29 --- /dev/null +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -0,0 +1,290 @@ +getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + return $object->getName(); + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + return $object->getPrimaryOwnerPHID(); + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + // TODO: needOwners() this on the Query. + $phids = mpull($object->loadOwners(), 'getUserPHID'); + $phids = array_values($phids); + return $phids; + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + return (int)$object->getAuditingEnabled(); + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + return $object->getDescription(); + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + // TODO: needPaths() this on the query + $paths = $object->loadPaths(); + return mpull($paths, 'getRef'); + } + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + return $xaction->getNewValue(); + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + return (int)$xaction->getNewValue(); + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + $phids = $xaction->getNewValue(); + $phids = array_unique($phids); + $phids = array_values($phids); + return $phids; + } + } + + protected function transactionHasEffect( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); + list($rem, $add) = $diffs; + + return ($rem || $add); + } + + return parent::transactionHasEffect($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + $object->setPrimaryOwnerPHID($xaction->getNewValue()); + return; + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + $object->setDescription($xaction->getNewValue()); + return; + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + $object->setAuditingEnabled($xaction->getNewValue()); + return; + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + return; + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + // TODO: needOwners this + $owners = $object->loadOwners(); + $owners = mpull($owners, null, 'getUserPHID'); + + $rem = array_diff($old, $new); + foreach ($rem as $phid) { + if (isset($owners[$phid])) { + $owners[$phid]->delete(); + unset($owners[$phid]); + } + } + + $add = array_diff($new, $old); + foreach ($add as $phid) { + $owners[$phid] = id(new PhabricatorOwnersOwner()) + ->setPackageID($object->getID()) + ->setUserPHID($phid) + ->save(); + } + + // TODO: Attach owners here + return; + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + // TODO: needPaths this + $paths = $object->loadPaths(); + + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); + list($rem, $add) = $diffs; + + $set = PhabricatorOwnersPath::getSetFromTransactionValue($rem); + foreach ($paths as $path) { + $ref = $path->getRef(); + if (PhabricatorOwnersPath::isRefInSet($ref, $set)) { + $path->delete(); + } + } + + foreach ($add as $ref) { + $path = PhabricatorOwnersPath::newFromRef($ref) + ->setPackageID($object->getID()) + ->save(); + } + + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Package name is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + $missing = $this->validateIsEmptyTextField( + $object->getPrimaryOwnerPHID(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Packages must have a primary owner.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + + protected function extractFilePHIDsFromCustomTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + return array($xaction->getNewValue()); + } + + return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); + } + + protected function shouldSendMail( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function getMailSubjectPrefix() { + return PhabricatorEnv::getEnvConfig('metamta.package.subject-prefix'); + } + + protected function getMailTo(PhabricatorLiskDAO $object) { + return array( + $object->getPrimaryOwnerPHID(), + $this->requireActor()->getPHID(), + ); + } + + protected function getMailCC(PhabricatorLiskDAO $object) { + // TODO: needOwners() this + return mpull($object->loadOwners(), 'getUserPHID'); + } + + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new OwnersPackageReplyHandler()) + ->setMailReceiver($object); + } + + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $name = $object->getName(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject($name) + ->addHeader('Thread-Topic', $object->getPHID()); + } + + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $body = parent::buildMailBody($object, $xactions); + + $detail_uri = PhabricatorEnv::getProductionURI( + '/owners/package/'.$object->getID().'/'); + + $body->addLinkSection( + pht('PACKAGE DETAIL'), + $detail_uri); + + return $body; + } + +} diff --git a/src/applications/owners/mail/PackageCreateMail.php b/src/applications/owners/mail/PackageCreateMail.php deleted file mode 100644 index 7f447d7d88..0000000000 --- a/src/applications/owners/mail/PackageCreateMail.php +++ /dev/null @@ -1,12 +0,0 @@ -package = $package; - } - - abstract protected function getVerb(); - - abstract protected function isNewThread(); - - final protected function getPackage() { - return $this->package; - } - - final protected function getHandles() { - return $this->handles; - } - - final protected function getOwners() { - return $this->owners; - } - - final protected function getPaths() { - return $this->paths; - } - - final protected function getMailTo() { - return $this->mailTo; - } - - final protected function renderPackageTitle() { - return $this->getPackage()->getName(); - } - - final protected function renderRepoSubSection($repository_phid, $paths) { - $handles = $this->getHandles(); - $section = array(); - $section[] = ' '. - pht('In repository %s', $handles[$repository_phid]->getName()). - ' - '.PhabricatorEnv::getProductionURI($handles[$repository_phid] - ->getURI()); - foreach ($paths as $path => $excluded) { - $section[] = ' '. - ($excluded ? pht('Excluded') : pht('Included')).' '.$path; - } - - return implode("\n", $section); - } - - protected function needSend() { - return true; - } - - protected function loadData() { - $package = $this->getPackage(); - $owners = $package->loadOwners(); - $this->owners = $owners; - - $owner_phids = mpull($owners, 'getUserPHID'); - $primary_owner_phid = $package->getPrimaryOwnerPHID(); - $mail_to = $owner_phids; - if (!in_array($primary_owner_phid, $owner_phids)) { - $mail_to[] = $primary_owner_phid; - } - $this->mailTo = $mail_to; - - $this->paths = array(); - $repository_paths = mgroup($package->loadPaths(), 'getRepositoryPHID'); - foreach ($repository_paths as $repository_phid => $paths) { - $this->paths[$repository_phid] = mpull($paths, 'getExcluded', 'getPath'); - } - - $phids = array_merge( - $this->mailTo, - array($package->getActorPHID()), - array_keys($this->paths)); - $this->handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs($phids) - ->execute(); - } - - final protected function renderSummarySection() { - $package = $this->getPackage(); - $handles = $this->getHandles(); - $section = array(); - $section[] = $handles[$package->getActorPHID()]->getName().' '. - strtolower($this->getVerb()).' '.$this->renderPackageTitle().'.'; - $section[] = ''; - - $section[] = pht('PACKAGE DETAIL'); - $section[] = ' '.PhabricatorEnv::getProductionURI( - '/owners/package/'.$package->getID().'/'); - - return implode("\n", $section); - } - - protected function renderDescriptionSection() { - return pht('PACKAGE DESCRIPTION')."\n ". - $this->getPackage()->getDescription(); - } - - protected function renderPrimaryOwnerSection() { - $handles = $this->getHandles(); - return pht('PRIMARY OWNER')."\n ". - $handles[$this->getPackage()->getPrimaryOwnerPHID()]->getName(); - } - - protected function renderOwnersSection() { - $handles = $this->getHandles(); - $owners = $this->getOwners(); - if (!$owners) { - return null; - } - - $owners = mpull($owners, 'getUserPHID'); - $owners = array_select_keys($handles, $owners); - $owners = mpull($owners, 'getName'); - return pht('OWNERS')."\n ".implode(', ', $owners); - } - - protected function renderAuditingEnabledSection() { - return pht('AUDITING ENABLED STATUS')."\n ". - ($this->getPackage()->getAuditingEnabled() - ? pht('Enabled') - : pht('Disabled')); - } - - protected function renderPathsSection() { - $section = array(); - $section[] = pht('PATHS'); - foreach ($this->paths as $repository_phid => $paths) { - $section[] = $this->renderRepoSubSection($repository_phid, $paths); - } - - return implode("\n", $section); - } - - final protected function renderBody() { - $body = array(); - $body[] = $this->renderSummarySection(); - $body[] = $this->renderDescriptionSection(); - $body[] = $this->renderPrimaryOwnerSection(); - $body[] = $this->renderOwnersSection(); - $body[] = $this->renderAuditingEnabledSection(); - $body[] = $this->renderPathsSection(); - $body = array_filter($body); - return implode("\n\n", $body)."\n"; - } - - final public function send() { - $mails = $this->prepareMails(); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } - } - - final public function prepareMails() { - if (!$this->needSend()) { - return array(); - } - - $this->loadData(); - - $package = $this->getPackage(); - $prefix = PhabricatorEnv::getEnvConfig('metamta.package.subject-prefix'); - $verb = $this->getVerb(); - $threading = $this->getMailThreading(); - list($thread_id, $thread_topic) = $threading; - - $template = id(new PhabricatorMetaMTAMail()) - ->setSubject($this->renderPackageTitle()) - ->setSubjectPrefix($prefix) - ->setVarySubjectPrefix("[{$verb}]") - ->setFrom($package->getActorPHID()) - ->setThreadID($thread_id, $this->isNewThread()) - ->addHeader('Thread-Topic', $thread_topic) - ->setRelatedPHID($package->getPHID()) - ->setIsBulk(true) - ->setBody($this->renderBody()); - - $reply_handler = $this->newReplyHandler(); - $mails = $reply_handler->multiplexMail( - $template, - array_select_keys($this->getHandles(), $this->getMailTo()), - array()); - return $mails; - } - - private function getMailThreading() { - return array( - 'package-'.$this->getPackage()->getPHID(), - 'Package '.$this->getPackage()->getOriginalName(), - ); - } - - private function newReplyHandler() { - $reply_handler = new OwnersPackageReplyHandler(); - $reply_handler->setMailReceiver($this->getPackage()); - return $reply_handler; - } - -} diff --git a/src/applications/owners/mail/PackageModifyMail.php b/src/applications/owners/mail/PackageModifyMail.php deleted file mode 100644 index cd96fb0c70..0000000000 --- a/src/applications/owners/mail/PackageModifyMail.php +++ /dev/null @@ -1,160 +0,0 @@ -package = $package; - - $this->addOwners = $add_owners; - $this->removeOwners = $remove_owners; - $this->allOwners = $all_owners; - $this->touchedRepos = $touched_repos; - $this->addPaths = $add_paths; - $this->removePaths = $remove_paths; - } - - protected function getVerb() { - return pht('Modified'); - } - - protected function isNewThread() { - return false; - } - - protected function needSend() { - $package = $this->getPackage(); - if ($package->getOldPrimaryOwnerPHID() !== $package->getPrimaryOwnerPHID() - || $package->getOldAuditingEnabled() != $package->getAuditingEnabled() - || $this->addOwners - || $this->removeOwners - || $this->addPaths - || $this->removePaths) { - return true; - } else { - return false; - } - } - - protected function loadData() { - $this->mailTo = $this->allOwners; - - $phids = array_merge( - $this->allOwners, - $this->touchedRepos, - array( - $this->getPackage()->getActorPHID(), - )); - $this->handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs($phids) - ->execute(); - } - - protected function renderDescriptionSection() { - return null; - } - - protected function renderPrimaryOwnerSection() { - $package = $this->getPackage(); - $handles = $this->getHandles(); - - $old_primary_owner_phid = $package->getOldPrimaryOwnerPHID(); - $primary_owner_phid = $package->getPrimaryOwnerPHID(); - if ($old_primary_owner_phid == $primary_owner_phid) { - return null; - } - - $section = array(); - $section[] = pht('PRIMARY OWNER CHANGE'); - $section[] = ' '.pht('Old owner:').' '. - $handles[$old_primary_owner_phid]->getName(); - $section[] = ' '.pht('New owner:').' '. - $handles[$primary_owner_phid]->getName(); - - return implode("\n", $section); - } - - protected function renderOwnersSection() { - $section = array(); - $add_owners = $this->addOwners; - $remove_owners = $this->removeOwners; - $handles = $this->getHandles(); - - if ($add_owners) { - $add_owners = array_select_keys($handles, $add_owners); - $add_owners = mpull($add_owners, 'getName'); - $section[] = pht('ADDED OWNERS'); - $section[] = ' '.implode(', ', $add_owners); - } - - if ($remove_owners) { - if ($add_owners) { - $section[] = ''; - } - $remove_owners = array_select_keys($handles, $remove_owners); - $remove_owners = mpull($remove_owners, 'getName'); - $section[] = pht('REMOVED OWNERS'); - $section[] = ' '.implode(', ', $remove_owners); - } - - if ($section) { - return implode("\n", $section); - } else { - return null; - } - } - - protected function renderAuditingEnabledSection() { - $package = $this->getPackage(); - $old_auditing_enabled = $package->getOldAuditingEnabled(); - $auditing_enabled = $package->getAuditingEnabled(); - if ($old_auditing_enabled == $auditing_enabled) { - return null; - } - - $section = array(); - $section[] = pht('AUDITING ENABLED STATUS CHANGE'); - $section[] = ' '.pht('Old value:').' '. - ($old_auditing_enabled ? pht('Enabled') : pht('Disabled')); - $section[] = ' '.pht('New value:').' '. - ($auditing_enabled ? pht('Enabled') : pht('Disabled')); - return implode("\n", $section); - } - - protected function renderPathsSection() { - $section = array(); - if ($this->addPaths) { - $section[] = pht('ADDED PATHS'); - foreach ($this->addPaths as $repository_phid => $paths) { - $section[] = $this->renderRepoSubSection($repository_phid, $paths); - } - } - - if ($this->removePaths) { - if ($this->addPaths) { - $section[] = ''; - } - $section[] = pht('REMOVED PATHS'); - foreach ($this->removePaths as $repository_phid => $paths) { - $section[] = $this->renderRepoSubSection($repository_phid, $paths); - } - } - return implode("\n", $section); - } - -} diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index 3bbb3b8fe2..64ed2f4098 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -3,8 +3,10 @@ final class PhabricatorOwnersPackageQuery extends PhabricatorCursorPagedPolicyAwareQuery { + private $ids; private $phids; private $ownerPHIDs; + private $repositoryPHIDs; /** * Owners are direct owners, and members of owning projects. @@ -19,6 +21,16 @@ final class PhabricatorOwnersPackageQuery return $this; } + public function withIDs(array $ids) { + $this->ids = $ids; + return $this; + } + + public function withRepositoryPHIDs(array $phids) { + $this->repositoryPHIDs = $phids; + return $this; + } + protected function loadPage() { $table = new PhabricatorOwnersPackage(); $conn_r = $table->establishConnection('r'); @@ -38,27 +50,48 @@ final class PhabricatorOwnersPackageQuery protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { $joins = array(); - if ($this->ownerPHIDs) { + if ($this->ownerPHIDs !== null) { $joins[] = qsprintf( $conn_r, 'JOIN %T o ON o.packageID = p.id', id(new PhabricatorOwnersOwner())->getTableName()); } + if ($this->repositoryPHIDs !== null) { + $joins[] = qsprintf( + $conn_r, + 'JOIN %T rpath ON rpath.packageID = p.id', + id(new PhabricatorOwnersPath())->getTableName()); + } + return implode(' ', $joins); } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn_r, 'p.phid IN (%Ls)', $this->phids); } - if ($this->ownerPHIDs) { + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'p.id IN (%Ld)', + $this->ids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'rpath.repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + if ($this->ownerPHIDs !== null) { $base_phids = $this->ownerPHIDs; $query = new PhabricatorProjectQuery(); diff --git a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php new file mode 100644 index 0000000000..f421e3d3ce --- /dev/null +++ b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php @@ -0,0 +1,134 @@ +setParameter( + 'ownerPHIDs', + $this->readUsersFromRequest( + $request, + 'owners', + array( + PhabricatorProjectProjectPHIDType::TYPECONST, + ))); + + $saved->setParameter( + 'repositoryPHIDs', + $this->readPHIDsFromRequest( + $request, + 'repositories', + array( + PhabricatorRepositoryRepositoryPHIDType::TYPECONST, + ))); + + return $saved; + } + + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $query = id(new PhabricatorOwnersPackageQuery()); + + $owner_phids = $saved->getParameter('ownerPHIDs', array()); + if ($owner_phids) { + $query->withOwnerPHIDs($owner_phids); + } + + $repository_phids = $saved->getParameter('repositoryPHIDs', array()); + if ($repository_phids) { + $query->withRepositoryPHIDs($repository_phids); + } + + return $query; + } + + public function buildSearchForm( + AphrontFormView $form, + PhabricatorSavedQuery $saved) { + + $owner_phids = $saved->getParameter('ownerPHIDs', array()); + $repository_phids = $saved->getParameter('repositoryPHIDs', array()); + + $form + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorProjectOrUserDatasource()) + ->setName('owners') + ->setLabel(pht('Owners')) + ->setValue($owner_phids)) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new DiffusionRepositoryDatasource()) + ->setName('repositories') + ->setLabel(pht('Repositories')) + ->setValue($repository_phids)); + } + + protected function getURI($path) { + return '/owners/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array(); + + if ($this->requireViewer()->isLoggedIn()) { + $names['owned'] = pht('Owned'); + } + + $names += array( + 'all' => pht('All Packages'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + case 'owned': + return $query->setParameter( + 'ownerPHIDs', + array($this->requireViewer()->getPHID())); + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $packages, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($packages, 'PhabricatorOwnersPackage'); + + $viewer = $this->requireViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + foreach ($packages as $package) { + $id = $package->getID(); + + $item = id(new PHUIObjectItemView()) + ->setObject($package) + ->setObjectName(pht('Package %d', $id)) + ->setHeader($package->getName()) + ->setHref('/owners/package/'.$id.'/'); + + $list->addItem($item); + } + + return $list; + } +} diff --git a/src/applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php new file mode 100644 index 0000000000..e0e7c2792f --- /dev/null +++ b/src/applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php @@ -0,0 +1,10 @@ +setAuditingEnabled(0) + ->setPrimaryOwnerPHID($actor->getPHID()); + } public function getCapabilities() { return array( @@ -37,13 +41,14 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO return array( // This information is better available from the history table. self::CONFIG_TIMESTAMPS => false, - self::CONFIG_AUX_PHID => true, + self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'originalName' => 'text255', 'description' => 'text', 'primaryOwnerPHID' => 'phid?', 'auditingEnabled' => 'bool', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -60,52 +65,16 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO } public function generatePHID() { - return PhabricatorPHID::generateNewPHID('OPKG'); + return PhabricatorPHID::generateNewPHID( + PhabricatorOwnersPackagePHIDType::TYPECONST); } - public function attachUnsavedOwners(array $owners) { - $this->unsavedOwners = $owners; - return $this; - } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } - public function getUnsavedOwners() { - return $this->assertAttached($this->unsavedOwners); - } - - public function attachUnsavedPaths(array $paths) { - $this->unsavedPaths = $paths; - return $this; - } - - public function getUnsavedPaths() { - return $this->assertAttached($this->unsavedPaths); - } - - public function attachActorPHID($actor_phid) { - $this->actorPHID = $actor_phid; - return $this; - } - - public function getActorPHID() { - return $this->actorPHID; - } - - public function attachOldPrimaryOwnerPHID($old_primary) { - $this->oldPrimaryOwnerPHID = $old_primary; - return $this; - } - - public function getOldPrimaryOwnerPHID() { - return $this->oldPrimaryOwnerPHID; - } - - public function attachOldAuditingEnabled($auditing_enabled) { - $this->oldAuditingEnabled = $auditing_enabled; - return $this; - } - - public function getOldAuditingEnabled() { - return $this->oldAuditingEnabled; + return parent::save(); } public function setName($name) { @@ -143,15 +112,15 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO } return self::loadPackagesForPaths($repository, $paths); - } + } - public static function loadOwningPackages($repository, $path) { + public static function loadOwningPackages($repository, $path) { if (empty($path)) { return array(); } return self::loadPackagesForPaths($repository, array($path), 1); - } + } private static function loadPackagesForPaths( PhabricatorRepository $repository, @@ -268,4 +237,27 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO } return $result; } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorOwnersPackageTransactionEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorOwnersPackageTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php new file mode 100644 index 0000000000..3686125887 --- /dev/null +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -0,0 +1,211 @@ +getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_PRIMARY: + if ($old) { + $phids[] = $old; + } + if ($new) { + $phids[] = $new; + } + break; + case self::TYPE_OWNERS: + $add = array_diff($new, $old); + foreach ($add as $phid) { + $phids[] = $phid; + } + $rem = array_diff($old, $new); + foreach ($rem as $phid) { + $phids[] = $phid; + } + break; + } + + return $phids; + } + + public function shouldHide() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($old === null); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $author_phid = $this->getAuthorPHID(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this package.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this package from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + case self::TYPE_PRIMARY: + return pht( + '%s changed the primary owner for this package from %s to %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + case self::TYPE_OWNERS: + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && !$rem) { + return pht( + '%s added %s owner(s): %s.', + $this->renderHandleLink($author_phid), + count($add), + $this->renderHandleList($add)); + } else if ($rem && !$add) { + return pht( + '%s removed %s owner(s): %s.', + $this->renderHandleLink($author_phid), + count($rem), + $this->renderHandleList($rem)); + } else { + return pht( + '%s changed %s package owner(s), added %s: %s; removed %s: %s.', + $this->renderHandleLink($author_phid), + count($add) + count($rem), + count($add), + $this->renderHandleList($add), + count($rem), + $this->renderHandleList($rem)); + } + case self::TYPE_AUDITING: + if ($new) { + return pht( + '%s enabled auditing for this package.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s disabled auditing for this package.', + $this->renderHandleLink($author_phid)); + } + case self::TYPE_DESCRIPTION: + return pht( + '%s updated the description for this package.', + $this->renderHandleLink($author_phid)); + case self::TYPE_PATHS: + // TODO: Flesh this out. + return pht( + '%s updated paths for this package.', + $this->renderHandleLink($author_phid)); + } + + return parent::getTitle(); + } + + public function hasChangeDetails() { + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($this->getOldValue() !== null); + case self::TYPE_PATHS: + return true; + } + + return parent::hasChangeDetails(); + } + + public function renderChangeDetails(PhabricatorUser $viewer) { + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return $this->renderTextCorpusChangeDetails( + $viewer, + $old, + $new); + case self::TYPE_PATHS: + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); + list($rem, $add) = $diffs; + + $rows = array(); + foreach ($rem as $ref) { + $rows[] = array( + 'class' => 'diff-removed', + 'change' => '-', + ) + $ref; + } + + foreach ($add as $ref) { + $rows[] = array( + 'class' => 'diff-added', + 'change' => '+', + ) + $ref; + } + + $rowc = array(); + foreach ($rows as $key => $row) { + $rowc[] = $row['class']; + $rows[$key] = array( + $row['change'], + $row['excluded'] ? pht('Exclude') : pht('Include'), + $viewer->renderHandle($row['repositoryPHID']), + $row['path'], + ); + } + + $table = id(new AphrontTableView($rows)) + ->setRowClasses($rowc) + ->setHeaders( + array( + null, + pht('Type'), + pht('Repository'), + pht('Path'), + )) + ->setColumnClasses( + array( + null, + null, + null, + 'wide', + )); + + return $table; + } + + return parent::renderChangeDetails($viewer); + } + +} diff --git a/src/applications/owners/storage/PhabricatorOwnersPath.php b/src/applications/owners/storage/PhabricatorOwnersPath.php index 0cc987e6bb..f65d6052db 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPath.php +++ b/src/applications/owners/storage/PhabricatorOwnersPath.php @@ -22,4 +22,52 @@ final class PhabricatorOwnersPath extends PhabricatorOwnersDAO { ) + parent::getConfiguration(); } + + public static function newFromRef(array $ref) { + $path = new PhabricatorOwnersPath(); + $path->repositoryPHID = $ref['repositoryPHID']; + $path->path = $ref['path']; + $path->excluded = $ref['excluded']; + return $path; + } + + public function getRef() { + return array( + 'repositoryPHID' => $this->getRepositoryPHID(), + 'path' => $this->getPath(), + 'excluded' => (int)$this->getExcluded(), + ); + } + + public static function getTransactionValueChanges(array $old, array $new) { + return array( + self::getTransactionValueDiff($old, $new), + self::getTransactionValueDiff($new, $old), + ); + } + + private static function getTransactionValueDiff(array $u, array $v) { + $set = self::getSetFromTransactionValue($v); + + foreach ($u as $key => $ref) { + if (self::isRefInSet($ref, $set)) { + unset($u[$key]); + } + } + + return $u; + } + + public static function getSetFromTransactionValue(array $v) { + $set = array(); + foreach ($v as $ref) { + $set[$ref['repositoryPHID']][$ref['path']][$ref['excluded']] = true; + } + return $set; + } + + public static function isRefInSet(array $ref, array $set) { + return isset($set[$ref['repositoryPHID']][$ref['path']][$ref['excluded']]); + } + } diff --git a/src/applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php deleted file mode 100644 index 0dc45391cd..0000000000 --- a/src/applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php +++ /dev/null @@ -1,100 +0,0 @@ - 'optional string', - ); - } - - protected function defineReturnType() { - return 'dict'; - } - - protected function defineErrorTypes() { - return array( - 'ERR_UNKNOWN_ARC' => pht( - "The given Arcanist project name doesn't exist in the ". - "installation of Phabricator you are accessing."), - ); - } - - protected function execute(ConduitAPIRequest $request) { - $arc_project_name = $request->getValue('arcProjectName'); - if ($arc_project_name) { - $arc_project = id(new PhabricatorRepositoryArcanistProject()) - ->loadOneWhere('name = %s', $arc_project_name); - if (!$arc_project) { - throw id(new ConduitException('ERR_UNKNOWN_ARC')) - ->setErrorDescription( - pht( - "Unknown Arcanist project '%s': ". - "are you using the correct Conduit URI?", - $arc_project_name)); - } - - $releeph_projects = id(new ReleephProject()) - ->loadAllWhere('arcanistProjectID = %d', $arc_project->getID()); - } else { - $releeph_projects = id(new ReleephProject())->loadAll(); - } - - $releeph_projects = mfilter($releeph_projects, 'getIsActive'); - - $result = array(); - foreach ($releeph_projects as $releeph_project) { - $selector = $releeph_project->getReleephFieldSelector(); - $fields = $selector->getFieldSpecifications(); - - $fields_info = array(); - foreach ($fields as $field) { - $field->setReleephProject($releeph_project); - if ($field->isEditable()) { - $key = $field->getKeyForConduit(); - $fields_info[$key] = array( - 'class' => get_class($field), - 'name' => $field->getName(), - 'key' => $key, - 'arcHelp' => $field->renderHelpForArcanist(), - ); - } - } - - $releeph_branches = mfilter( - id(new ReleephBranch()) - ->loadAllWhere('releephProjectID = %d', $releeph_project->getID()), - 'getIsActive'); - - $releeph_branches_struct = array(); - foreach ($releeph_branches as $branch) { - $releeph_branches_struct[] = array( - 'branchName' => $branch->getName(), - 'projectName' => $releeph_project->getName(), - 'projectPHID' => $releeph_project->getPHID(), - 'branchPHID' => $branch->getPHID(), - ); - } - - $result[] = array( - 'projectName' => $releeph_project->getName(), - 'projectPHID' => $releeph_project->getPHID(), - 'branches' => $releeph_branches_struct, - 'fields' => $fields_info, - ); - } - - return $result; - } - -} diff --git a/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php b/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php index 6eefc0dc83..bf429f9034 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php @@ -13,10 +13,10 @@ final class ReleephBranchNamePreviewController $template = ReleephBranchTemplate::getDefaultTemplate(); } - $arc_project_id = $request->getInt('arcProjectID'); + $repository_phid = $request->getInt('repositoryPHID'); $fake_commit_handle = ReleephBranchTemplate::getFakeCommitHandleFor( - $arc_project_id, + $repository_phid, $request->getUser()); list($name, $errors) = id(new ReleephBranchTemplate()) diff --git a/src/applications/releeph/controller/product/ReleephProductCreateController.php b/src/applications/releeph/controller/product/ReleephProductCreateController.php index 9412f292ed..6d5f29ff37 100644 --- a/src/applications/releeph/controller/product/ReleephProductCreateController.php +++ b/src/applications/releeph/controller/product/ReleephProductCreateController.php @@ -6,9 +6,7 @@ final class ReleephProductCreateController extends ReleephProductController { $request = $this->getRequest(); $name = trim($request->getStr('name')); $trunk_branch = trim($request->getStr('trunkBranch')); - $arc_pr_id = $request->getInt('arcPrID'); - - $arc_projects = $this->loadArcProjects(); + $repository_phid = $request->getStr('repositoryPHID'); $e_name = true; $e_trunk_branch = true; @@ -27,14 +25,10 @@ final class ReleephProductCreateController extends ReleephProductController { 'You must specify which branch you will be picking from.'); } - $arc_project = $arc_projects[$arc_pr_id]; - $pr_repository = null; - if ($arc_project->getRepositoryID()) { - $pr_repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($request->getUser()) - ->withIDs(array($arc_project->getRepositoryID())) - ->executeOne(); - } + $pr_repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($request->getUser()) + ->withPHIDs(array($repository_phid)) + ->executeOne(); if (!$errors) { @@ -42,7 +36,6 @@ final class ReleephProductCreateController extends ReleephProductController { ->setName($name) ->setTrunkBranch($trunk_branch) ->setRepositoryPHID($pr_repository->getPHID()) - ->setArcanistProjectID($arc_project->getID()) ->setCreatedByUserPHID($request->getUser()->getPHID()) ->setIsActive(1); @@ -58,7 +51,7 @@ final class ReleephProductCreateController extends ReleephProductController { } } - $arc_project_options = $this->getArcProjectSelectOptions($arc_projects); + $repo_options = $this->getRepositorySelectOptions(); $product_name_input = id(new AphrontFormTextControl()) ->setLabel(pht('Name')) @@ -68,32 +61,23 @@ final class ReleephProductCreateController extends ReleephProductController { ->setError($e_name) ->setCaption(pht('A name like "Thrift" but not "Thrift releases".')); - $arc_project_input = id(new AphrontFormSelectControl()) - ->setLabel(pht('Arc Project')) - ->setName('arcPrID') - ->setValue($arc_pr_id) - ->setCaption(pht( - "If your Arc project isn't listed, associate it with a repository %s.", - phutil_tag( - 'a', - array( - 'href' => '/repository/', - 'target' => '_blank', - ), - 'here'))) - ->setOptions($arc_project_options); + $repository_input = id(new AphrontFormSelectControl()) + ->setLabel(pht('Repository')) + ->setName('repositoryPHID') + ->setValue($repository_phid) + ->setOptions($repo_options); $branch_name_preview = id(new ReleephBranchPreviewView()) ->setLabel(pht('Example Branch')) ->addControl('projectName', $product_name_input) - ->addControl('arcProjectID', $arc_project_input) + ->addControl('repositoryPHID', $repository_input) ->addStatic('template', '') ->addStatic('isSymbolic', false); $form = id(new AphrontFormView()) ->setUser($request->getUser()) ->appendChild($product_name_input) - ->appendChild($arc_project_input) + ->appendChild($repository_input) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Trunk')) @@ -126,43 +110,23 @@ final class ReleephProductCreateController extends ReleephProductController { )); } - private function loadArcProjects() { - $viewer = $this->getRequest()->getUser(); - - $projects = id(new PhabricatorRepositoryArcanistProjectQuery()) - ->setViewer($viewer) - ->needRepositories(true) + private function getRepositorySelectOptions() { + $repos = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getRequest()->getUser()) ->execute(); - $projects = mfilter($projects, 'getRepository'); - $projects = msort($projects, 'getName'); - - return $projects; - } - - private function getArcProjectSelectOptions(array $arc_projects) { - assert_instances_of($arc_projects, 'PhabricatorRepositoryArcanistProject'); - - $repos = mpull($arc_projects, 'getRepository'); + $repos = msort($repos, 'getName'); $repos = mpull($repos, null, 'getID'); - $groups = array(); - foreach ($arc_projects as $arc_project) { - $id = $arc_project->getID(); - $repo_id = $arc_project->getRepository()->getID(); - $groups[$repo_id][$id] = $arc_project->getName(); - } - $choices = array(); - foreach ($groups as $repo_id => $group) { - $repo_name = $repos[$repo_id]->getName(); - $callsign = $repos[$repo_id]->getCallsign(); - $name = "r{$callsign} ({$repo_name})"; - $choices[$name] = $group; + + foreach ($repos as $repo_id => $repo) { + $repo_name = $repo->getName(); + $callsign = $repo->getCallsign(); + $choices[$repo->getPHID()] = "r{$callsign} ({$repo_name})"; } ksort($choices); - return $choices; } diff --git a/src/applications/releeph/controller/product/ReleephProductEditController.php b/src/applications/releeph/controller/product/ReleephProductEditController.php index 41b405030d..d0769c7b28 100644 --- a/src/applications/releeph/controller/product/ReleephProductEditController.php +++ b/src/applications/releeph/controller/product/ReleephProductEditController.php @@ -15,7 +15,6 @@ final class ReleephProductEditController extends ReleephProductController { $product = id(new ReleephProductQuery()) ->setViewer($viewer) ->withIDs(array($this->productID)) - ->needArcanistProjects(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -48,7 +47,7 @@ final class ReleephProductEditController extends ReleephProductController { $test_paths = $product->getDetail('testPaths', array()); } - $arc_project_id = $product->getArcanistProjectID(); + $repository_phid = $product->getRepositoryPHID(); if ($request->isFormPost()) { $pusher_phids = $request->getArr('pushers'); @@ -92,8 +91,9 @@ final class ReleephProductEditController extends ReleephProductController { ->setDetail('branchTemplate', $branch_template) ->setDetail('testPaths', $test_paths); - $fake_commit_handle = - ReleephBranchTemplate::getFakeCommitHandleFor($arc_project_id, $viewer); + $fake_commit_handle = ReleephBranchTemplate::getFakeCommitHandleFor( + $repository_phid, + $viewer); if ($branch_template) { list($branch_name, $template_errors) = id(new ReleephBranchTemplate()) @@ -136,9 +136,9 @@ final class ReleephProductEditController extends ReleephProductController { $product->getRepository()->getName())) ->appendChild( id(new AphrontFormStaticControl()) - ->setLabel(pht('Arc Project')) + ->setLabel(pht('Repository')) ->setValue( - $product->getArcanistProject()->getName())) + $product->getRepository()->getName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Releeph Project PHID')) @@ -179,7 +179,7 @@ final class ReleephProductEditController extends ReleephProductController { $branch_template_preview = id(new ReleephBranchPreviewView()) ->setLabel(pht('Preview')) ->addControl('template', $branch_template_input) - ->addStatic('arcProjectID', $arc_project_id) + ->addStatic('repositoryPHID', $repository_phid) ->addStatic('isSymbolic', false) ->addStatic('projectName', $product->getName()); diff --git a/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php b/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php index e938fda6c3..3c2dc3d735 100644 --- a/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php +++ b/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php @@ -25,19 +25,18 @@ final class ReleephRequestDifferentialCreateController } $this->revision = $diff_rev; - $arc_project = id(new PhabricatorRepositoryArcanistProject()) - ->loadOneWhere('phid = %s', $this->revision->getArcanistProjectPHID()); + $repository = $this->revision->getRepository(); $projects = id(new ReleephProject())->loadAllWhere( - 'arcanistProjectID = %d AND isActive = 1', - $arc_project->getID()); + 'repositoryPHID = %s AND isActive = 1', + $repository->getPHID()); if (!$projects) { throw new Exception( pht( - "%s belongs to the '%s' Arcanist project, ". + "%s belongs to the '%s' repository, ". "which is not part of any Releeph project!", 'D'.$this->revision->getID(), - $arc_project->getName())); + $repository->getMonogram())); } $branches = id(new ReleephBranch())->loadAllWhere( diff --git a/src/applications/releeph/query/ReleephProductQuery.php b/src/applications/releeph/query/ReleephProductQuery.php index 6fe2b90db3..acfc39c1c2 100644 --- a/src/applications/releeph/query/ReleephProductQuery.php +++ b/src/applications/releeph/query/ReleephProductQuery.php @@ -8,8 +8,6 @@ final class ReleephProductQuery private $phids; private $repositoryPHIDs; - private $needArcanistProjects; - const ORDER_ID = 'order-id'; const ORDER_NAME = 'order-name'; @@ -47,11 +45,6 @@ final class ReleephProductQuery return $this; } - public function needArcanistProjects($need) { - $this->needArcanistProjects = $need; - return $this; - } - protected function loadPage() { $table = new ReleephProject(); $conn_r = $table->establishConnection('r'); @@ -90,27 +83,6 @@ final class ReleephProductQuery return $projects; } - protected function didFilterPage(array $products) { - if ($this->needArcanistProjects) { - $project_ids = array_filter(mpull($products, 'getArcanistProjectID')); - if ($project_ids) { - $projects = id(new PhabricatorRepositoryArcanistProject()) - ->loadAllWhere('id IN (%Ld)', $project_ids); - $projects = mpull($projects, null, 'getID'); - } else { - $projects = array(); - } - - foreach ($products as $product) { - $project_id = $product->getArcanistProjectID(); - $project = idx($projects, $project_id); - $product->attachArcanistProject($project); - } - } - - return $products; - } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); diff --git a/src/applications/releeph/query/ReleephProductSearchEngine.php b/src/applications/releeph/query/ReleephProductSearchEngine.php index 37aecea858..d105568bd3 100644 --- a/src/applications/releeph/query/ReleephProductSearchEngine.php +++ b/src/applications/releeph/query/ReleephProductSearchEngine.php @@ -21,8 +21,7 @@ final class ReleephProductSearchEngine public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new ReleephProductQuery()) - ->setOrder(ReleephProductQuery::ORDER_NAME) - ->needArcanistProjects(true); + ->setOrder(ReleephProductQuery::ORDER_NAME); $active = $saved->getParameter('active'); $value = idx($this->getActiveValues(), $active); @@ -119,11 +118,6 @@ final class ReleephProductSearchEngine ), 'r'.$repo->getCallsign())); - $arc = $product->getArcanistProject(); - if ($arc) { - $item->addAttribute($arc->getName()); - } - $list->addItem($item); } diff --git a/src/applications/releeph/storage/ReleephProject.php b/src/applications/releeph/storage/ReleephProject.php index ae34894658..4dd91b38ab 100644 --- a/src/applications/releeph/storage/ReleephProject.php +++ b/src/applications/releeph/storage/ReleephProject.php @@ -35,6 +35,7 @@ final class ReleephProject extends ReleephDAO 'name' => 'text128', 'trunkBranch' => 'text255', 'isActive' => 'bool', + 'arcanistProjectID' => 'id?', ), self::CONFIG_KEY_SCHEMA => array( 'projectName' => array( diff --git a/src/applications/releeph/view/branch/ReleephBranchPreviewView.php b/src/applications/releeph/view/branch/ReleephBranchPreviewView.php index afb3dffd4c..462ed73db3 100644 --- a/src/applications/releeph/view/branch/ReleephBranchPreviewView.php +++ b/src/applications/releeph/view/branch/ReleephBranchPreviewView.php @@ -24,7 +24,7 @@ final class ReleephBranchPreviewView extends AphrontFormControl { protected function renderInput() { static $required_params = array( - 'arcProjectID', + 'repositoryPHID', 'projectName', 'isSymbolic', 'template', @@ -43,9 +43,9 @@ final class ReleephBranchPreviewView extends AphrontFormControl { $output_id = celerity_generate_unique_node_id(); Javelin::initBehavior('releeph-preview-branch', array( - 'uri' => '/releeph/branch/preview/', - 'outputID' => $output_id, - 'params' => array( + 'uri' => '/releeph/branch/preview/', + 'outputID' => $output_id, + 'params' => array( 'static' => $this->statics, 'dynamic' => $this->dynamics, ), diff --git a/src/applications/releeph/view/branch/ReleephBranchTemplate.php b/src/applications/releeph/view/branch/ReleephBranchTemplate.php index 79d19b8589..7efaaad9fb 100644 --- a/src/applications/releeph/view/branch/ReleephBranchTemplate.php +++ b/src/applications/releeph/view/branch/ReleephBranchTemplate.php @@ -20,25 +20,14 @@ final class ReleephBranchTemplate { } public static function getFakeCommitHandleFor( - $arc_project_id, + $repository_phid, PhabricatorUser $viewer) { - $arc_project = id(new PhabricatorRepositoryArcanistProject()) - ->load($arc_project_id); - if (!$arc_project) { - throw new Exception( - pht( - "No Arc project found with id '%s'!", - $arc_project_id)); - } + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withPHIDs(array($repository_phid)) + ->executeOne(); - $repository = null; - if ($arc_project->getRepositoryID()) { - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->withIDs(array($arc_project->getRepositoryID())) - ->executeOne(); - } $fake_handle = 'SOFAKE'; if ($repository) { $fake_handle = id(new PhabricatorObjectHandle()) diff --git a/src/applications/repository/application/PhabricatorRepositoriesApplication.php b/src/applications/repository/application/PhabricatorRepositoriesApplication.php index 534db063e7..f740d05dcd 100644 --- a/src/applications/repository/application/PhabricatorRepositoriesApplication.php +++ b/src/applications/repository/application/PhabricatorRepositoriesApplication.php @@ -30,10 +30,6 @@ final class PhabricatorRepositoriesApplication extends PhabricatorApplication { return array( '/repository/' => array( '' => 'PhabricatorRepositoryListController', - 'project/edit/(?P[1-9]\d*)/' - => 'PhabricatorRepositoryArcanistProjectEditController', - 'project/delete/(?P[1-9]\d*)/' - => 'PhabricatorRepositoryArcanistProjectDeleteController', ), ); } diff --git a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php b/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php deleted file mode 100644 index 04c2008897..0000000000 --- a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - - $arc_project = - id(new PhabricatorRepositoryArcanistProject())->load($this->id); - if (!$arc_project) { - return new Aphront404Response(); - } - - $request = $this->getRequest(); - - if ($request->isDialogFormPost()) { - $arc_project->delete(); - return id(new AphrontRedirectResponse())->setURI('/repository/'); - } - - $dialog = new AphrontDialogView(); - $dialog - ->setUser($request->getUser()) - ->setTitle(pht('Really delete this arcanist project?')) - ->appendChild( - pht( - 'Really delete the "%s" arcanist project? '. - 'This operation can not be undone.', - $arc_project->getName())) - ->setSubmitURI('/repository/project/delete/'.$this->id.'/') - ->addSubmitButton(pht('Delete Arcanist Project')) - ->addCancelButton('/repository/'); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } -} diff --git a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php b/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php deleted file mode 100644 index c453ebb057..0000000000 --- a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php +++ /dev/null @@ -1,85 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - - $request = $this->getRequest(); - $user = $request->getUser(); - - $project = id(new PhabricatorRepositoryArcanistProject())->load($this->id); - if (!$project) { - return new Aphront404Response(); - } - - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->execute(); - $repos = array( - 0 => 'None', - ); - foreach ($repositories as $repository) { - $callsign = $repository->getCallsign(); - $name = $repository->getname(); - $repos[$repository->getID()] = "r{$callsign} ({$name})"; - } - // note "None" will still be first thanks to 'r' prefix - asort($repos); - - if ($request->isFormPost()) { - $repo_id = $request->getInt('repository', 0); - if (isset($repos[$repo_id])) { - $project->setRepositoryID($repo_id); - $project->save(); - - return id(new AphrontRedirectResponse()) - ->setURI('/repository/'); - } - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Name')) - ->setValue($project->getName())) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('PHID') - ->setValue($project->getPHID())) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Repository')) - ->setOptions($repos) - ->setName('repository') - ->setValue($project->getRepositoryID())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton('/repository/') - ->setValue(pht('Save'))); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Edit Arcanist Project')); - $panel->setForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Project')); - - return $this->buildApplicationPage( - array( - $crumbs, - $panel, - ), - array( - 'title' => pht('Edit Project'), - )); - } - -} diff --git a/src/applications/repository/controller/PhabricatorRepositoryListController.php b/src/applications/repository/controller/PhabricatorRepositoryListController.php index c1d87b060f..2394ce6fa4 100644 --- a/src/applications/repository/controller/PhabricatorRepositoryListController.php +++ b/src/applications/repository/controller/PhabricatorRepositoryListController.php @@ -85,68 +85,6 @@ final class PhabricatorRepositoryListController $panel->setHeader($header); $panel->setTable($table); - $projects = id(new PhabricatorRepositoryArcanistProject())->loadAll(); - - $rows = array(); - foreach ($projects as $project) { - $repo = idx($repos, $project->getRepositoryID()); - if ($repo) { - $repo_name = $repo->getName(); - } else { - $repo_name = '-'; - } - - $rows[] = array( - $project->getName(), - $repo_name, - phutil_tag( - 'a', - array( - 'href' => '/repository/project/edit/'.$project->getID().'/', - 'class' => 'button grey small', - ), - pht('Edit')), - javelin_tag( - 'a', - array( - 'href' => '/repository/project/delete/'.$project->getID().'/', - 'class' => 'button grey small', - 'sigil' => 'workflow', - ), - pht('Delete')), - ); - - } - - $project_table = new AphrontTableView($rows); - $project_table->setNoDataString(pht('No Arcanist Projects')); - $project_table->setHeaders( - array( - pht('Project ID'), - pht('Repository'), - '', - '', - )); - $project_table->setColumnClasses( - array( - '', - 'wide', - 'action', - 'action', - )); - - $project_table->setColumnVisibility( - array( - true, - true, - $is_admin, - $is_admin, - )); - - $project_panel = new PHUIObjectBoxView(); - $project_panel->setHeaderText(pht('Arcanist Projects')); - $project_panel->setTable($project_table); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Repository List')); @@ -154,7 +92,6 @@ final class PhabricatorRepositoryListController array( $crumbs, $panel, - $project_panel, ), array( 'title' => pht('Repository List'), diff --git a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php index ed07a8fb29..0d430f36a6 100644 --- a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php +++ b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php @@ -7,13 +7,22 @@ final class PhabricatorCommitBranchesField return 'diffusion:branches'; } - public function shouldAppearInApplicationTransactions() { + public function getFieldName() { + return pht('Branches'); + } + + public function getFieldDescription() { + return pht('Shows branches a commit appears on in email.'); + } + + public function shouldAppearInTransactionMail() { return true; } - public function buildApplicationTransactionMailBody( - PhabricatorApplicationTransaction $xaction, - PhabricatorMetaMTAMailBody $body) { + public function updateTransactionMailBody( + PhabricatorMetaMTAMailBody $body, + PhabricatorApplicationTransactionEditor $editor, + array $xactions) { $params = array( 'contains' => $this->getObject()->getCommitIdentifier(), diff --git a/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php new file mode 100644 index 0000000000..267b440dce --- /dev/null +++ b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php @@ -0,0 +1,74 @@ +getObject(); + + try { + $merges = DiffusionPathChange::newFromConduit( + id(new ConduitCall('diffusion.mergedcommitsquery', array( + 'commit' => $commit->getCommitIdentifier(), + 'limit' => $limit + 1, + 'callsign' => $commit->getRepository()->getCallsign(), + ))) + ->setUser($this->getViewer()) + ->execute()); + + if (count($merges) > $limit) { + $merges = array_slice($merges, 0, $limit); + $merges_caption = + pht("This commit merges more than %d changes. Only the first ". + "%d are shown.\n", $limit, $limit); + } + + if ($merges) { + $merge_commits = array(); + foreach ($merges as $merge) { + $merge_commits[] = $merge->getAuthorName(). + ': '. + $merge->getSummary(); + } + $body->addTextSection( + pht('MERGED COMMITS'), + $merges_caption.implode("\n", $merge_commits)); + } + } catch (ConduitException $ex) { + // Log the exception into the email body + $body->addTextSection( + pht('MERGED COMMITS'), + pht('Error generating merged commits: ').$ex->getMessage()); + } + + } + +} diff --git a/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php b/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php new file mode 100644 index 0000000000..c600a8cb6c --- /dev/null +++ b/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php @@ -0,0 +1,38 @@ +getObject()->getRepository(); + + $body->addTextSection( + pht('REPOSITORY'), + $repository->getMonogram().' '.$repository->getName()); + } + +} diff --git a/src/applications/repository/customfield/PhabricatorCommitTagsField.php b/src/applications/repository/customfield/PhabricatorCommitTagsField.php index 25b2a4ee4e..b69d246d85 100644 --- a/src/applications/repository/customfield/PhabricatorCommitTagsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitTagsField.php @@ -7,13 +7,22 @@ final class PhabricatorCommitTagsField return 'diffusion:tags'; } - public function shouldAppearInApplicationTransactions() { + public function getFieldName() { + return pht('Tags'); + } + + public function getFieldDescription() { + return pht('Shows commit tags in email.'); + } + + public function shouldAppearInTransactionMail() { return true; } - public function buildApplicationTransactionMailBody( - PhabricatorApplicationTransaction $xaction, - PhabricatorMetaMTAMailBody $body) { + public function updateTransactionMailBody( + PhabricatorMetaMTAMailBody $body, + PhabricatorApplicationTransactionEditor $editor, + array $xactions) { $params = array( 'commit' => $this->getObject()->getCommitIdentifier(), diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 69012bdcd2..2e769975ab 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -43,6 +43,7 @@ final class PhabricatorRepositoryEditor $types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE; $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE; $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES; + $types[] = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -104,6 +105,8 @@ final class PhabricatorRepositoryEditor return $object->getSymbolLanguages(); case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: return $object->getSymbolSources(); + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: + return $object->getDetail('staging-uri'); } } @@ -139,6 +142,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: return $xaction->getNewValue(); case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: @@ -219,6 +223,9 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: $object->setDetail('symbol-sources', $xaction->getNewValue()); return; + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: + $object->setDetail('staging-uri', $xaction->getNewValue()); + return; case PhabricatorRepositoryTransaction::TYPE_ENCODING: // Make sure the encoding is valid by converting to UTF-8. This tests // that the user has mbstring installed, and also that they didn't type @@ -330,6 +337,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php index 275577ea33..e06c6ca089 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php @@ -241,8 +241,8 @@ final class PhabricatorRepositoryManagementReparseWorkflow '**NOTE**: This script will queue tasks to reparse the data. Once the '. 'tasks have been queued, you need to run Taskmaster daemons to '. 'execute them.'."\n\n". - "QUEUEING TASKS (%d Commits):", - number_format(count($commits)))); + "QUEUEING TASKS (%s Commits):", + new PhutilNumber(count($commits)))); } $progress = new PhutilConsoleProgressBar(); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 547fdb484d..13dd10a878 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -135,6 +135,12 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'isActive' => $this->isTracked(), 'isHosted' => $this->isHosted(), 'isImporting' => $this->isImporting(), + 'encoding' => $this->getDetail('encoding'), + 'staging' => array( + 'supported' => $this->supportsStaging(), + 'prefix' => 'phabricator', + 'uri' => $this->getStagingURI(), + ), ); } @@ -1796,6 +1802,22 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } +/* -( Staging )-------------------------------------------------------------*/ + + + public function supportsStaging() { + return $this->isGit(); + } + + + public function getStagingURI() { + if (!$this->supportsStaging()) { + return null; + } + return $this->getDetail('staging-uri', null); + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index a3b4bf0609..18328758ba 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -349,9 +349,7 @@ final class PhabricatorRepositoryCommit public function getCustomFieldSpecificationForRole($role) { - // TODO: We could make this configurable eventually, but just use the - // defaults for now. - return array(); + return PhabricatorEnv::getEnvConfig('diffusion.fields'); } public function getCustomFieldBaseClass() { diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index 9907cb0a01..de1310f320 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -27,6 +27,7 @@ final class PhabricatorRepositoryTransaction const TYPE_SERVICE = 'repo:service'; const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source'; const TYPE_SYMBOLS_LANGUAGE = 'repo:symbol-language'; + const TYPE_STAGING_URI = 'repo:staging-uri'; // TODO: Clean up these legacy transaction types. const TYPE_SSH_LOGIN = 'repo:ssh-login'; @@ -412,9 +413,29 @@ final class PhabricatorRepositoryTransaction case self::TYPE_SYMBOLS_LANGUAGE: return pht('%s changed indexed languages from %s to %s.', + $this->renderHandleLink($author_phid), + $old ? implode(', ', $old) : pht('Any'), + $new ? implode(', ', $new) : pht('Any')); + + case self::TYPE_STAGING_URI: + if (!$old) { + return pht( + '%s set "%s" as the staging area for this repository.', $this->renderHandleLink($author_phid), - $old ? implode(', ', $old) : pht('Any'), - $new ? implode(', ', $new) : pht('Any')); + $new); + } else if (!$new) { + return pht( + '%s removed "%s" as the staging area for this repository.', + $this->renderHandleLink($author_phid), + $old); + } else { + return pht( + '%s changed the staging area for this repository from '. + '"%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } } return parent::getTitle(); diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php b/src/applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php deleted file mode 100644 index 55b34e7fef..0000000000 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php +++ /dev/null @@ -1,108 +0,0 @@ -setViewer($actor) - ->withIDs(array($commit->getRepositoryID())) - ->executeOne(); - if (!$repository) { - return; - } - $changes = self::loadDiffusionChangesForCommit( - $repository, - $commit, - $actor); - - if (!$changes) { - return; - } - - $move_map = array(); - foreach ($changes as $change) { - if ($change->getChangeType() == DifferentialChangeType::TYPE_MOVE_HERE) { - $from_path = '/'.$change->getTargetPath(); - $to_path = '/'.$change->getPath(); - if ($change->getFileType() == DifferentialChangeType::FILE_DIRECTORY) { - $to_path = $to_path.'/'; - $from_path = $from_path.'/'; - } - $move_map[$from_path] = $to_path; - } - } - - if ($move_map) { - self::updateAffectedPackages($repository, $move_map); - } - } - - private static function updateAffectedPackages($repository, array $move_map) { - $paths = array_keys($move_map); - if ($paths) { - $packages = PhabricatorOwnersPackage::loadAffectedPackages($repository, - $paths); - foreach ($packages as $package) { - self::updatePackagePaths($package, $move_map); - } - } - } - - private static function updatePackagePaths($package, array $move_map) { - $paths = array_keys($move_map); - $pkg_paths = $package->loadPaths(); - $new_paths = array(); - foreach ($pkg_paths as $pkg_path) { - $path_changed = false; - - foreach ($paths as $old_path) { - if (strncmp($pkg_path->getPath(), $old_path, strlen($old_path)) === 0) { - $new_paths[] = array ( - 'packageID' => $package->getID(), - 'repositoryPHID' => $pkg_path->getRepositoryPHID(), - 'path' => str_replace($pkg_path->getPath(), $old_path, - $move_map[$old_path]), - ); - $path_changed = true; - } - } - - if (!$path_changed) { - $new_paths[] = array ( - 'packageID' => $package->getID(), - 'repositoryPHID' => $pkg_path->getRepositoryPHID(), - 'path' => $pkg_path->getPath(), - ); - } - } - - if ($new_paths) { - $package->attachOldPrimaryOwnerPHID($package->getPrimaryOwnerPHID()); - $package->attachUnsavedPaths($new_paths); - $package->save(); // save the changes and notify the owners. - } - } - - private static function loadDiffusionChangesForCommit( - PhabricatorRepository $repository, - PhabricatorRepositoryCommit $commit, - PhabricatorUser $actor) { - $data = array( - 'user' => $actor, - 'repository' => $repository, - 'commit' => $commit->getCommitIdentifier(), - ); - $drequest = DiffusionRequest::newFromDictionary($data); - $change_query = - DiffusionPathChangeQuery::newFromDiffusionRequest($drequest); - return $change_query->loadChanges(); - } -} diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php index 92ba0d8c5b..9fcaeb7254 100644 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php @@ -96,9 +96,6 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker id(new PhabricatorSearchIndexer()) ->queueDocumentForIndexing($commit->getPHID()); - PhabricatorOwnersPackagePathValidator::updateOwnersPackagePaths( - $commit, - PhabricatorUser::getOmnipotentUser()); if ($this->shouldQueueFollowupTasks()) { $this->queueTask( 'PhabricatorRepositoryCommitOwnersWorker', diff --git a/src/docs/user/userguide/arcanist_new_project.diviner b/src/docs/user/userguide/arcanist_new_project.diviner index fbb2ad4a45..448d3ea947 100644 --- a/src/docs/user/userguide/arcanist_new_project.diviner +++ b/src/docs/user/userguide/arcanist_new_project.diviner @@ -49,10 +49,6 @@ Other options include: See below for details about path resolution, or see @{article:libphutil Libraries User Guide} for a general introduction to libphutil libraries. - - **project.name**: name an "Arcanist Project" to associate this working - copy (Git, Mercurial) or directory (SVN) with. Previously, this was a - required option, but `arc` can now usually operate without it in Git and - Mercurial. This option was previously called `project_id`. - **https.cabundle**: specifies the path to an alternate certificate bundle for use when making HTTPS connections. - **lint.engine**: the name of a subclass of diff --git a/src/docs/user/userguide/arcanist_quick_start.diviner b/src/docs/user/userguide/arcanist_quick_start.diviner index 261056bbeb..98633bfaef 100644 --- a/src/docs/user/userguide/arcanist_quick_start.diviner +++ b/src/docs/user/userguide/arcanist_quick_start.diviner @@ -44,12 +44,9 @@ Create a `.arcconfig` file in your project's working copy: yourproject/ $ $EDITOR .arcconfig yourproject/ $ cat .arcconfig { - "project.name" : "yourprojectname", "phabricator.uri" : "https://phabricator.example.com/" } -Set `project.name` to a string that identifies the project. - Set `phabricator.uri` to the URI for your Phabricator install (where `arc` should send changes to). diff --git a/src/docs/user/userguide/libraries.diviner b/src/docs/user/userguide/libraries.diviner index 33f6f57b67..656b167d8d 100644 --- a/src/docs/user/userguide/libraries.diviner +++ b/src/docs/user/userguide/libraries.diviner @@ -83,8 +83,7 @@ Phabricator. For example, you might write this file to `libcustom/.arcconfig`: { - "project.name" : "libcustom", - "load" : [ + "load": [ "phabricator/src/" ] } diff --git a/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php b/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php index 6ff9eae1d0..1de963c177 100644 --- a/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php +++ b/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php @@ -12,9 +12,6 @@ final class PhabricatorCustomFieldConfigOptionType $storage_value = $request->getStr('value'); $in_value = phutil_json_decode($storage_value); - if (!is_array($in_value)) { - $in_value = array(); - } // When we submit from JS, we submit a list (since maps are not guaranteed // to retain order). Convert it into a map for storage (since it's far more @@ -37,20 +34,16 @@ final class PhabricatorCustomFieldConfigOptionType $field_spec = PhabricatorEnv::getEnvConfig($option->getKey()); } - // Get all of the fields (including disabled fields) by querying for them - // with a faux spec where no fields are disabled. - $faux_spec = $field_spec; - foreach ($faux_spec as $key => $spec) { - unset($faux_spec[$key]['disabled']); - } - // TODO: We might need to build a real object here eventually. $faux_object = null; $fields = PhabricatorCustomField::buildFieldList( $field_base_class, - $faux_spec, - $faux_object); + $field_spec, + $faux_object, + array( + 'withDisabled' => true, + )); $list_id = celerity_generate_unique_node_id(); $input_id = celerity_generate_unique_node_id(); @@ -66,7 +59,8 @@ final class PhabricatorCustomFieldConfigOptionType ->addAttribute($field->getFieldDescription()) ->setHeader($field->getFieldName()); - $is_disabled = !empty($field_spec[$key]['disabled']); + $spec = idx($field_spec, $key, array()); + $is_disabled = idx($spec, 'disabled', $field->shouldDisableByDefault()); $disabled_item = clone $item; $enabled_item = clone $item; @@ -113,7 +107,7 @@ final class PhabricatorCustomFieldConfigOptionType 'id' => $input_id, 'type' => 'hidden', 'name' => 'value', - 'value' => json_encode($display_value), + 'value' => '', )); Javelin::initBehavior( diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index 28110e7334..b154e0e065 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -105,7 +105,18 @@ abstract class PhabricatorCustomField { /** * @task apps */ - public static function buildFieldList($base_class, array $spec, $object) { + public static function buildFieldList( + $base_class, + array $spec, + $object, + array $options = array()) { + + PhutilTypeSpec::checkMap( + $options, + array( + 'withDisabled' => 'optional bool', + )); + $field_objects = id(new PhutilSymbolLoader()) ->setAncestorClass($base_class) ->loadObjects(); @@ -135,13 +146,16 @@ abstract class PhabricatorCustomField { $fields = array_select_keys($fields, array_keys($spec)) + $fields; - foreach ($spec as $key => $config) { - if (empty($fields[$key])) { - continue; - } - if (!empty($config['disabled'])) { - if ($fields[$key]->canDisableField()) { - unset($fields[$key]); + if (empty($options['withDisabled'])) { + foreach ($fields as $key => $field) { + $config = idx($spec, $key, array()) + array( + 'disabled' => $field->shouldDisableByDefault(), + ); + + if (!empty($config['disabled'])) { + if ($field->canDisableField()) { + unset($fields[$key]); + } } } } @@ -1055,22 +1069,6 @@ abstract class PhabricatorCustomField { return false; } - /** - * TODO: this is only used by Diffusion right now and everything is completely - * faked since Diffusion doesn't use ApplicationTransactions yet. This should - * get fleshed out as we have more use cases. - * - * @task appxaction - */ - public function buildApplicationTransactionMailBody( - PhabricatorApplicationTransaction $xaction, - PhabricatorMetaMTAMailBody $body) { - if ($this->proxy) { - return $this->proxy->buildApplicationTransactionMailBody($xaction, $body); - } - return; - } - /* -( Transaction Mail )--------------------------------------------------- */ diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index 4608ebeffa..343319b3d8 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -15,6 +15,14 @@ abstract class PhabricatorInlineCommentController abstract protected function saveComment( PhabricatorInlineCommentInterface $inline); + protected function hideComments(array $ids) { + throw new PhutilMethodNotImplementedException(); + } + + protected function showComments(array $ids) { + throw new PhutilMethodNotImplementedException(); + } + private $changesetID; private $isNewFile; private $isOnRight; @@ -84,6 +92,22 @@ abstract class PhabricatorInlineCommentController $op = $this->getOperation(); switch ($op) { + case 'hide': + case 'show': + if (!$request->validateCSRF()) { + return new Aphront404Response(); + } + + $ids = $request->getStrList('ids'); + if ($ids) { + if ($op == 'hide') { + $this->hideComments($ids); + } else { + $this->showComments($ids); + } + } + + return id(new AphrontAjaxResponse())->setContent(array()); case 'done': if (!$request->validateCSRF()) { return new Aphront404Response(); diff --git a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php index 8ba648f2b6..13bf3ad83b 100644 --- a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php +++ b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php @@ -57,4 +57,7 @@ interface PhabricatorInlineCommentInterface extends PhabricatorMarkupInterface { public function setIsGhost($is_ghost); public function getIsGhost(); + public function supportsHiding(); + public function isHidden(); + } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index e265cafad1..de17b5dd2b 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -18,6 +18,10 @@ final class PHUIDiffInlineCommentDetailView return $this; } + public function isHidden() { + return $this->inlineComment->isHidden(); + } + public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; @@ -192,6 +196,8 @@ final class PHUIDiffInlineCommentDetailView if (!$this->preview) { $nextprev = new PHUIButtonBarView(); $nextprev->addClass('mml'); + + $up = id(new PHUIButtonView()) ->setTag('a') ->setColor(PHUIButtonView::SIMPLE) @@ -208,6 +214,18 @@ final class PHUIDiffInlineCommentDetailView ->addSigil('differential-inline-next') ->setMustCapture(true); + $hide = id(new PHUIButtonView()) + ->setTag('a') + ->setColor(PHUIButtonView::SIMPLE) + ->setTooltip(pht('Hide Comment')) + ->setIconFont('fa-times') + ->addSigil('hide-inline') + ->setMustCapture(true); + + if ($viewer_phid && $inline->getID() && $inline->supportsHiding()) { + $nextprev->addButton($hide); + } + $nextprev->addButton($up); $nextprev->addButton($down); diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php index 194ff6f5cc..b0c0b4ccac 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php @@ -23,8 +23,18 @@ abstract class PHUIDiffInlineCommentRowScaffold extends AphrontView { protected function getRowAttributes() { // TODO: This is semantic information used by the JS when placing comments // and using keyboard navigation; we should move it out of class names. + + $style = null; + foreach ($this->getInlineViews() as $view) { + if ($view->isHidden()) { + $style = 'display: none'; + } + } + return array( 'class' => 'inline', + 'sigil' => 'inline-row', + 'style' => $style, ); } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php index f2f82471d6..b62160e232 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php @@ -17,4 +17,8 @@ abstract class PHUIDiffInlineCommentView extends AphrontView { return null; } + public function isHidden() { + return false; + } + } diff --git a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php index 6b8cb0a32e..708b70b360 100644 --- a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php @@ -28,7 +28,7 @@ final class PHUIDiffOneUpInlineCommentRowScaffold phutil_tag('td', $attrs, $inline), ); - return phutil_tag('tr', $this->getRowAttributes(), $cells); + return javelin_tag('tr', $this->getRowAttributes(), $cells); } } diff --git a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php new file mode 100644 index 0000000000..b2c879a8b5 --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php @@ -0,0 +1,27 @@ +setIconFont('fa-comment') + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => pht('Show Hidden Comments'), + 'align' => 'E', + 'size' => 275, + )); + + return javelin_tag( + 'a', + array( + 'href' => '#', + 'class' => 'reveal-inlines', + 'sigil' => 'reveal-inlines', + 'mustcapture' => true, + ), + $icon); + } + +} diff --git a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php index e2c2183856..4fac5088d1 100644 --- a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php @@ -68,7 +68,7 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold phutil_tag('td', $right_attrs, $right_side), ); - return phutil_tag('tr', $this->getRowAttributes(), $cells); + return javelin_tag('tr', $this->getRowAttributes(), $cells); } } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 61dd34fbe2..111e38857f 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1078,6 +1078,24 @@ final class PhabricatorUSEnglishTranslation 'Are you absolutely certain you want to destroy these objects?', ), + '%s added %s owner(s): %s.' => array( + array( + '%s added an owner: %3$s.', + '%s added owners: %3$s.', + ), + ), + + '%s removed %s owner(s): %s.' => array( + array( + '%s removed an owner: %3$s.', + '%s removed owners: %3$s.', + ), + ), + + '%s changed %s package owner(s), added %s: %s; removed %s: %s.' => array( + '%s changed package owners, added: %4$s; removed: %6$s.', + ), + ); } diff --git a/src/infrastructure/time/PhabricatorTime.php b/src/infrastructure/time/PhabricatorTime.php index e31222cf76..2adcc95d49 100644 --- a/src/infrastructure/time/PhabricatorTime.php +++ b/src/infrastructure/time/PhabricatorTime.php @@ -72,4 +72,10 @@ final class PhabricatorTime { return $today; } + public static function getDateTimeFromEpoch($epoch, PhabricatorUser $viewer) { + $datetime = new DateTime('@'.$epoch); + $datetime->setTimeZone($viewer->getTimeZone()); + return $datetime; + } + } diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index d829a29093..ac026d410b 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -5,9 +5,7 @@ final class AphrontFormDateControl extends AphrontFormControl { private $initialTime; private $zone; - private $valueDay; - private $valueMonth; - private $valueYear; + private $valueDate; private $valueTime; private $allowNull; private $continueOnInvalidDate = false; @@ -41,9 +39,7 @@ final class AphrontFormDateControl extends AphrontFormControl { } public function readValueFromRequest(AphrontRequest $request) { - $day = $request->getInt($this->getDayInputName()); - $month = $request->getInt($this->getMonthInputName()); - $year = $request->getInt($this->getYearInputName()); + $date = $request->getStr($this->getDateInputName()); $time = $request->getStr($this->getTimeInputName()); $enabled = $request->getBool($this->getCheckboxInputName()); @@ -55,10 +51,8 @@ final class AphrontFormDateControl extends AphrontFormControl { $err = $this->getError(); - if ($day || $month || $year || $time) { - $this->valueDay = $day; - $this->valueMonth = $month; - $this->valueYear = $year; + if ($date || $time) { + $this->valueDate = $date; $this->valueTime = $time; // Assume invalid. @@ -67,8 +61,8 @@ final class AphrontFormDateControl extends AphrontFormControl { $zone = $this->getTimezone(); try { - $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone); - $value = $date->format('U'); + $datetime = new DateTime("{$date} {$time}", $zone); + $value = $datetime->format('U'); } catch (Exception $ex) { $value = null; } @@ -100,9 +94,7 @@ final class AphrontFormDateControl extends AphrontFormControl { public function setValue($epoch) { if ($epoch instanceof AphrontFormDateControlValue) { $this->continueOnInvalidDate = true; - $this->valueYear = $epoch->getValueYear(); - $this->valueMonth = $epoch->getValueMonth(); - $this->valueDay = $epoch->getValueDay(); + $this->valueDate = $epoch->getValueDate(); $this->valueTime = $epoch->getValueTime(); $this->allowNull = $epoch->getOptional(); $this->isDisabled = $epoch->isDisabled(); @@ -119,42 +111,18 @@ final class AphrontFormDateControl extends AphrontFormControl { $readable = $this->formatTime($epoch, 'Y!m!d!g:i A'); $readable = explode('!', $readable, 4); - $this->valueYear = $readable[0]; - $this->valueMonth = $readable[1]; - $this->valueDay = $readable[2]; + $year = $readable[0]; + $month = $readable[1]; + $day = $readable[2]; + + $this->valueDate = $month.'/'.$day.'/'.$year; $this->valueTime = $readable[3]; return $result; } - private function getMinYear() { - $cur_year = $this->formatTime( - time(), - 'Y'); - $val_year = $this->getYearInputValue(); - - return min($cur_year, $val_year) - 3; - } - - private function getMaxYear() { - $cur_year = $this->formatTime( - time(), - 'Y'); - $val_year = $this->getYearInputValue(); - - return max($cur_year, $val_year) + 3; - } - - private function getDayInputValue() { - return $this->valueDay; - } - - private function getMonthInputValue() { - return $this->valueMonth; - } - - private function getYearInputValue() { - return $this->valueYear; + private function getDateInputValue() { + return $this->valueDate; } private function getTimeInputValue() { @@ -168,18 +136,10 @@ final class AphrontFormDateControl extends AphrontFormControl { $fmt); } - private function getDayInputName() { + private function getDateInputName() { return $this->getName().'_d'; } - private function getMonthInputName() { - return $this->getName().'_m'; - } - - private function getYearInputName() { - return $this->getName().'_y'; - } - private function getTimeInputName() { return $this->getName().'_t'; } @@ -202,27 +162,6 @@ final class AphrontFormDateControl extends AphrontFormControl { $disabled = 'disabled'; } - $min_year = $this->getMinYear(); - $max_year = $this->getMaxYear(); - - $days = range(1, 31); - $days = array_fuse($days); - - $months = array( - 1 => pht('Jan'), - 2 => pht('Feb'), - 3 => pht('Mar'), - 4 => pht('Apr'), - 5 => pht('May'), - 6 => pht('Jun'), - 7 => pht('Jul'), - 8 => pht('Aug'), - 9 => pht('Sep'), - 10 => pht('Oct'), - 11 => pht('Nov'), - 12 => pht('Dec'), - ); - $checkbox = null; if ($this->allowNull) { $checkbox = javelin_tag( @@ -237,32 +176,24 @@ final class AphrontFormDateControl extends AphrontFormControl { )); } - $years = range($this->getMinYear(), $this->getMaxYear()); - $years = array_fuse($years); - - $days_sel = AphrontFormSelectControl::renderSelectTag( - $this->getDayInputValue(), - $days, + $date_sel = javelin_tag( + 'input', array( - 'name' => $this->getDayInputName(), - 'sigil' => 'day-input', - )); + 'autocomplete' => 'off', + 'name' => $this->getDateInputName(), + 'sigil' => 'date-input', + 'value' => $this->getDateInputValue(), + 'type' => 'text', + 'class' => 'aphront-form-date-input', + ), + ''); - $months_sel = AphrontFormSelectControl::renderSelectTag( - $this->getMonthInputValue(), - $months, + $date_div = javelin_tag( + 'div', array( - 'name' => $this->getMonthInputName(), - 'sigil' => 'month-input', - )); - - $years_sel = AphrontFormSelectControl::renderSelectTag( - $this->getYearInputValue(), - $years, - array( - 'name' => $this->getYearInputName(), - 'sigil' => 'year-input', - )); + 'class' => 'aphront-form-date-input-container', + ), + $date_sel); $cicon = id(new PHUIIconView()) ->setIconFont('fa-calendar'); @@ -294,7 +225,7 @@ final class AphrontFormDateControl extends AphrontFormControl { 'sigil' => 'time-input', 'value' => $this->getTimeInputValue(), 'type' => 'text', - 'class' => 'aphront-form-date-time-input', + 'class' => 'aphront-form-time-input', ), ''); @@ -302,7 +233,7 @@ final class AphrontFormDateControl extends AphrontFormControl { 'div', array( 'id' => $time_id, - 'class' => 'aphront-form-date-time-input-container', + 'class' => 'aphront-form-time-input-container', ), $time_sel); @@ -329,9 +260,7 @@ final class AphrontFormDateControl extends AphrontFormControl { ), array( $checkbox, - $days_sel, - $months_sel, - $years_sel, + $date_div, $cal_icon, $time_div, )); diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index 3fb6a07fb4..43fb42664e 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -2,9 +2,7 @@ final class AphrontFormDateControlValue extends Phobject { - private $valueDay; - private $valueMonth; - private $valueYear; + private $valueDate; private $valueTime; private $valueEnabled; @@ -12,16 +10,8 @@ final class AphrontFormDateControlValue extends Phobject { private $zone; private $optional; - public function getValueDay() { - return $this->valueDay; - } - - public function getValueMonth() { - return $this->valueMonth; - } - - public function getValueYear() { - return $this->valueYear; + public function getValueDate() { + return $this->valueDate; } public function getValueTime() { @@ -36,15 +26,7 @@ final class AphrontFormDateControlValue extends Phobject { } public function isEmpty() { - if ($this->valueDay) { - return false; - } - - if ($this->valueMonth) { - return false; - } - - if ($this->valueYear) { + if ($this->valueDate) { return false; } @@ -83,9 +65,7 @@ final class AphrontFormDateControlValue extends Phobject { $value = new AphrontFormDateControlValue(); $value->viewer = $viewer; - $value->valueYear = $year; - $value->valueMonth = $month; - $value->valueDay = $day; + $value->valueDate = $month.'/'.$day.'/'.$year; $value->valueTime = coalesce($time, '12:00 AM'); $value->valueEnabled = $enabled; @@ -95,10 +75,7 @@ final class AphrontFormDateControlValue extends Phobject { public static function newFromRequest(AphrontRequest $request, $key) { $value = new AphrontFormDateControlValue(); $value->viewer = $request->getViewer(); - - $value->valueDay = $request->getInt($key.'_d'); - $value->valueMonth = $request->getInt($key.'_m'); - $value->valueYear = $request->getInt($key.'_y'); + $value->valueDate = $request->getStr($key.'_d'); $value->valueTime = $request->getStr($key.'_t'); $value->valueEnabled = $request->getStr($key.'_e'); @@ -111,11 +88,14 @@ final class AphrontFormDateControlValue extends Phobject { $readable = $value->formatTime($epoch, 'Y!m!d!g:i A'); $readable = explode('!', $readable, 4); - $value->valueYear = $readable[0]; - $value->valueMonth = $readable[1]; - $value->valueDay = $readable[2]; + $year = $readable[0]; + $month = $readable[1]; + $day = $readable[2]; + + $value->valueDate = $month.'/'.$day.'/'.$year; $value->valueTime = $readable[3]; + return $value; } @@ -125,9 +105,7 @@ final class AphrontFormDateControlValue extends Phobject { $value = new AphrontFormDateControlValue(); $value->viewer = $viewer; - $value->valueYear = idx($dictionary, 'y'); - $value->valueMonth = idx($dictionary, 'm'); - $value->valueDay = idx($dictionary, 'd'); + $value->valueDate = idx($dictionary, 'd'); $value->valueTime = idx($dictionary, 't'); $value->valueEnabled = idx($dictionary, 'e'); @@ -149,9 +127,7 @@ final class AphrontFormDateControlValue extends Phobject { public function getDictionary() { return array( - 'y' => $this->valueYear, - 'm' => $this->valueMonth, - 'd' => $this->valueDay, + 'd' => $this->valueDate, 't' => $this->valueTime, 'e' => $this->valueEnabled, ); @@ -176,9 +152,7 @@ final class AphrontFormDateControlValue extends Phobject { return null; } - $year = $this->valueYear; - $month = $this->valueMonth; - $day = $this->valueDay; + $date = $this->valueDate; $time = $this->valueTime; $zone = $this->getTimezone(); @@ -203,14 +177,27 @@ final class AphrontFormDateControlValue extends Phobject { } try { - $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone); - $value = $date->format('U'); + $datetime = new DateTime("{$date} {$time}", $zone); + $value = $datetime->format('U'); } catch (Exception $ex) { $value = null; } return $value; } + public function getDateTime() { + $epoch = $this->getEpoch(); + $date = null; + + if ($epoch) { + $zone = $this->getTimezone(); + $date = new DateTime('@'.$epoch); + $date->setTimeZone($zone); + } + + return $date; + } + private function getTimezone() { if ($this->zone) { return $this->zone; diff --git a/src/view/phui/PHUICrumbsView.php b/src/view/phui/PHUICrumbsView.php index 95d78a268c..11ec90fafe 100644 --- a/src/view/phui/PHUICrumbsView.php +++ b/src/view/phui/PHUICrumbsView.php @@ -84,6 +84,7 @@ final class PHUICrumbsView extends AphrontView { 'class' => implode(' ', $action_classes), 'sigil' => implode(' ', $action_sigils), 'style' => $action->getStyle(), + 'meta' => $action->getMetadata(), ), array( $icon, diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index 79eb6c0f44..e2e347306f 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -29,6 +29,18 @@ final class PHUIListItemView extends AphrontTagView { private $aural; private $profileImage; + public function setDropdownMenu(PhabricatorActionListView $actions) { + Javelin::initBehavior('phui-dropdown-menu'); + + $this->addSigil('phui-dropdown-menu'); + $this->setMetadata( + array( + 'items' => $actions, + )); + + return $this; + } + public function setAural($aural) { $this->aural = $aural; return $this; diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 337ef50738..844cd31222 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -8,8 +8,8 @@ final class PHUICalendarDayView extends AphrontView { private $month; private $year; private $browseURI; + private $query; private $events = array(); - private $todayEvents = array(); private $allDayEvents = array(); @@ -26,6 +26,14 @@ final class PHUICalendarDayView extends AphrontView { return $this->browseURI; } + public function setQuery($query) { + $this->query = $query; + return $this; + } + private function getQuery() { + return $this->query; + } + public function __construct( $range_start, $range_end, @@ -44,13 +52,22 @@ final class PHUICalendarDayView extends AphrontView { public function render() { require_celerity_resource('phui-calendar-day-css'); + $viewer = $this->getUser(); + $hours = $this->getHoursOfDay(); - $hourly_events = array(); + $js_hours = array(); + $js_today_events = array(); + + foreach ($hours as $hour) { + $js_hours[] = array( + 'hour' => $hour->format('G'), + 'hour_meridian' => $hour->format('g A'), + ); + } $first_event_hour = null; - + $js_today_all_day_events = array(); $all_day_events = $this->getAllDayEvents(); - $today_all_day_events = array(); $day_start = $this->getDateTime(); $day_end = id(clone $day_start)->modify('+1 day'); @@ -63,123 +80,101 @@ final class PHUICalendarDayView extends AphrontView { $all_day_end = $all_day_event->getEpochEnd(); if ($all_day_start < $day_end_epoch && $all_day_end > $day_start_epoch) { - $today_all_day_events[] = $all_day_event; - } - } - - foreach ($hours as $hour) { - $current_hour_events = array(); - $hour_start = $hour->format('U'); - $hour_end = id(clone $hour)->modify('+1 hour')->format('U'); - - foreach ($this->events as $event) { - if ($event->getIsAllDay()) { - continue; - } - if (($hour == $day_start && - $event->getEpochStart() <= $hour_start && - $event->getEpochEnd() > $day_start_epoch) || - ($event->getEpochStart() >= $hour_start - && $event->getEpochStart() < $hour_end)) { - $current_hour_events[] = $event; - $this->todayEvents[] = $event; - } - } - foreach ($current_hour_events as $event) { - $day_start_epoch = $this->getDateTime()->format('U'); - $event_start = max($event->getEpochStart(), $day_start_epoch); - $event_end = min($event->getEpochEnd(), $day_end_epoch); - - $top = (($event_start - $hour_start) / ($hour_end - $hour_start)) - * 100; - $top = max(0, $top); - - $height = (($event_end - $event_start) / ($hour_end - $hour_start)) - * 100; - $height = min(2400, $height); - - if ($first_event_hour === null) { - $first_event_hour = $hour; - } - - $hourly_events[$event->getEventID()] = array( - 'hour' => $hour, - 'event' => $event, - 'offset' => '0', - 'width' => '100%', - 'top' => $top.'%', - 'height' => $height.'%', + $js_today_all_day_events[] = array( + 'name' => $all_day_event->getName(), + 'id' => $all_day_event->getEventID(), + 'viewerIsInvited' => $all_day_event->getViewerIsInvited(), + 'uri' => $all_day_event->getURI(), ); } } - $clusters = $this->findTodayClusters(); - foreach ($clusters as $cluster) { - $hourly_events = $this->updateEventsFromCluster( - $cluster, - $hourly_events); - } + $this->events = msort($this->events, 'getEpochStart'); + $first_event_hour = $this->getDateTime()->setTime(8, 0, 0); + $midnight = $this->getDateTime()->setTime(0, 0, 0); - $rows = array(); - - foreach ($hours as $hour) { - $early_hours = array(8); - if ($first_event_hour) { - $early_hours[] = $first_event_hour->format('G'); - } - if ($hour->format('G') < min($early_hours)) { + foreach ($this->events as $event) { + if ($event->getIsAllDay()) { continue; } + if ($event->getEpochStart() <= $day_end_epoch && + $event->getEpochEnd() > $day_start_epoch) { - $drawn_hourly_events = array(); - $cell_time = phutil_tag( - 'td', - array('class' => 'phui-calendar-day-hour'), - $hour->format('g A')); - - foreach ($hourly_events as $hourly_event) { - if ($hourly_event['hour'] == $hour) { - - $drawn_hourly_events[] = $this->drawEvent( - $hourly_event['event'], - $hourly_event['offset'], - $hourly_event['width'], - $hourly_event['top'], - $hourly_event['height']); + if ($event->getEpochStart() < $midnight->format('U') && + $event->getEpochEnd() > $midnight->format('U')) { + $first_event_hour = clone $midnight; } + + if ($event->getEpochStart() < $first_event_hour->format('U') && + $event->getEpochStart() > $midnight->format('U')) { + $first_event_hour = PhabricatorTime::getDateTimeFromEpoch( + $event->getEpochStart(), + $viewer); + $first_event_hour->setTime($first_event_hour->format('h'), 0, 0); + } + + $event_start = max($event->getEpochStart(), $day_start_epoch); + $event_end = min($event->getEpochEnd(), $day_end_epoch); + + $day_duration = ($day_end_epoch - $first_event_hour->format('U')) / 60; + + $top = (($event_start - $first_event_hour->format('U')) + / ($day_end_epoch - $first_event_hour->format('U'))) + * $day_duration; + $top = max(0, $top); + + $height = (($event_end - $event_start) + / ($day_end_epoch - $first_event_hour->format('U'))) + * $day_duration; + $height = min($day_duration, $height); + + $js_today_events[] = array( + 'eventStartEpoch' => $event->getEpochStart(), + 'eventEndEpoch' => $event->getEpochEnd(), + 'eventName' => $event->getName(), + 'eventID' => $event->getEventID(), + 'viewerIsInvited' => $event->getViewerIsInvited(), + 'uri' => $event->getURI(), + 'offset' => '0', + 'width' => '100%', + 'top' => $top.'px', + 'height' => $height.'px', + 'canEdit' => $event->getCanEdit(), + ); } - $cell_event = phutil_tag( - 'td', - array('class' => 'phui-calendar-day-events'), - $drawn_hourly_events); - - $row = phutil_tag( - 'tr', - array(), - array($cell_time, $cell_event)); - - $rows[] = $row; - } - - $table = phutil_tag( - 'table', - array('class' => 'phui-calendar-day-view'), - $rows); - - $all_day_event_box = new PHUIBoxView(); - foreach ($today_all_day_events as $all_day_event) { - $all_day_event_box->appendChild( - $this->drawAllDayEvent($all_day_event)); } $header = $this->renderDayViewHeader(); $sidebar = $this->renderSidebar(); $warnings = $this->getQueryRangeWarning(); + $table_id = celerity_generate_unique_node_id(); + + $table_wrapper = phutil_tag( + 'div', + array( + 'id' => $table_id, + ), + ''); + + Javelin::initBehavior( + 'day-view', + array( + 'year' => $first_event_hour->format('Y'), + 'month' => $first_event_hour->format('m'), + 'day' => $first_event_hour->format('d'), + 'query' => $this->getQuery(), + 'allDayEvents' => $js_today_all_day_events, + 'todayEvents' => $js_today_events, + 'hours' => $js_hours, + 'firstEventHour' => $first_event_hour->format('G'), + 'firstEventHourEpoch' => $first_event_hour->format('U'), + 'tableID' => $table_id, + )); + $table_box = id(new PHUIObjectBoxView()) ->setHeader($header) - ->appendChild($all_day_event_box) - ->appendChild($table) + ->appendChild($table_wrapper) ->setFormErrors($warnings) ->setFlush(true); @@ -391,75 +386,6 @@ final class PHUICalendarDayView extends AphrontView { return $hourly_events; } - private function drawAllDayEvent(AphrontCalendarEventView $event) { - $class = 'day-view-all-day'; - if ($event->getViewerIsInvited()) { - $class = $class.' viewer-invited-day-event'; - } - - $name = phutil_tag( - 'a', - array( - 'class' => $class, - 'href' => $event->getURI(), - ), - $event->getName()); - - $all_day_label = phutil_tag( - 'span', - array( - 'class' => 'phui-calendar-all-day-label', - ), - pht('All Day')); - - $div = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-day-event', - ), - array( - $all_day_label, - $name, - )); - - return $div; - } - - private function drawEvent( - AphrontCalendarEventView $event, - $offset, - $width, - $top, - $height) { - - $class = 'phui-calendar-day-event-link'; - if ($event->getViewerIsInvited()) { - $class = $class.' viewer-invited-day-event'; - } - - $name = phutil_tag( - 'a', - array( - 'class' => $class, - 'href' => $event->getURI(), - ), - $event->getName()); - - $div = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-day-event', - 'style' => 'left: '.$offset - .'; width: '.$width - .'; top: '.$top - .'; height: '.$height - .';', - ), - $name); - - return $div; - } - // returns DateTime of each hour in the day private function getHoursOfDay() { $included_datetimes = array(); diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index e1f37fa1ae..fa998f11a6 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -150,8 +150,11 @@ final class PHUICalendarListView extends AphrontTagView { $this->getUser(), $event->getEpochEnd())); + $start_date = $start->getDateTime()->format('m d Y'); + $end_date = $end->getDateTime()->format('m d Y'); + if ($event->getIsAllDay()) { - if ($start->getValueDay() == $end->getValueDay()) { + if ($start_date == $end_date) { $tip = pht('All day'); } else { $tip = pht( @@ -160,9 +163,7 @@ final class PHUICalendarListView extends AphrontTagView { $end->getValueAsFormat('M j, Y')); } } else { - if ($start->getValueDay() == $end->getValueDay() && - $start->getValueMonth() == $end->getValueMonth() && - $start->getValueYear() == $end->getValueYear()) { + if ($start->getValueDate() == $end->getValueDate()) { $tip = pht( '%s - %s', $start->getValueAsFormat('g:i A'), diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index 4677f61ad9..ad69943bf5 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -219,6 +219,16 @@ span.single-display-line-content { background: {$sh-yellowbackground}; } +.aphront-table-view tr.diff-removed, +.aphront-table-view tr.alt-diff-removed { + background: {$lightred} +} + +.aphront-table-view tr.diff-added, +.aphront-table-view tr.alt-diff-added { + background: {$lightgreen} +} + .aphront-table-view tr.no-data td { padding: 12px; text-align: center; diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 5b6e2d79ee..632635ba3a 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -434,3 +434,21 @@ border-color: {$lightgreyborder}; color: {$lightgreytext}; } + + +/* - Hiding Inlines ------------------------------------------------------------ +*/ + +.reveal-inlines { + float: left; + margin-left: 4px; + color: {$lightbluetext}; +} + +.reveal-inlines span.phui-icon-view { + color: {$lightbluetext}; +} + +.reveal-inlines:hover span.phui-icon-view { + color: {$darkbluetext}; +} diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index c69638eede..d03e7bc3fa 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -32,6 +32,10 @@ z-index: 2; } +div.phui-calendar-day-event { + z-index: 2; +} + .slowvote-above-the-bar { z-index: 3; } diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css index 715bd6fd3b..e012be0afa 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -29,13 +29,22 @@ .phui-calendar-day-view td { position: relative; + cursor: pointer; +} + +.phui-calendar-day-view td:hover { + background: {$lightbluebackground}; } .phui-calendar-day-view tr + tr td.phui-calendar-day-events { border-top: 1px solid {$lightgreyborder}; } -.phui-calendar-day-view td div.phui-calendar-day-event { +.phui-drag { + opacity: .25; +} + +div.phui-calendar-day-event { width: 100%; position: absolute; top: 0; @@ -43,11 +52,19 @@ min-height: 30px; } +.can-drag a { + cursor: move; +} + +div.phui-calendar-day-event.all-day { + position: relative; +} + .phui-calendar-day-event-link { padding: 8px; border: 1px solid {$greyborder}; background-color: {$darkgreybackground}; - margin: 0 4px; + margin: 0 1px; position: absolute; left: 0; right: 0; diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 5a115d9064..37223c98fc 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -334,17 +334,19 @@ table.aphront-form-control-checkbox-layout th { font-size: 16px; } -.aphront-form-date-container .aphront-form-date-time-input-container { +.aphront-form-date-container .aphront-form-time-input-container, +.aphront-form-date-container .aphront-form-date-input-container { position: relative; display: inline-block; width: 7em; } -.aphront-form-date-container input.aphront-form-date-time-input { +.aphront-form-date-container input.aphront-form-time-input, +.aphront-form-date-container input.aphront-form-date-input { width: 7em; } -.aphront-form-date-time-input-container div.jx-typeahead-results a.jx-result { +.aphront-form-time-input-container div.jx-typeahead-results a.jx-result { border: none; } @@ -470,7 +472,7 @@ properly, and submit values. */ opacity: 0.5; } -.aphront-form-date-container.no-time .aphront-form-date-time-input{ +.aphront-form-date-container.no-time .aphront-form-time-input{ display: none; } diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js new file mode 100644 index 0000000000..5be51ce99c --- /dev/null +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -0,0 +1,362 @@ +/** + * @provides javelin-behavior-day-view + */ + + +JX.behavior('day-view', function(config) { + + function findTodayClusters() { + var events = today_events.sort(function(x, y){ + return (x.eventStartEpoch - y.eventStartEpoch); + }); + + var clusters = []; + + for (var i=0; i < events.length; i++) { + var today_event = events[i]; + + var destination_cluster_index = null; + var event_start = today_event.eventStartEpoch - (60); + var event_end = today_event.eventEndEpoch + (60); + + for (var j=0; j < clusters.length; j++) { + var cluster = clusters[j]; + + for(var k=0; k < cluster.length; k++) { + var clustered_event = cluster[k]; + var compare_event_start = clustered_event.eventStartEpoch; + var compare_event_end = clustered_event.eventEndEpoch; + + if (event_start < compare_event_end && + event_end > compare_event_start) { + destination_cluster_index = j; + break; + } + } + + if (destination_cluster_index !== null) { + break; + } + } + + if (destination_cluster_index !== null) { + clusters[destination_cluster_index].push(today_event); + destination_cluster_index = null; + } else { + var next_cluster = []; + next_cluster.push(today_event); + clusters.push(next_cluster); + } + } + + return clusters; + } + + function updateEventsFromCluster(cluster) { + var cluster_size = cluster.length; + var n = 0; + for(var i=0; i < cluster.length; i++) { + var cluster_member = cluster[i]; + + var event_id = cluster_member.eventID; + var offset = ((n / cluster_size) * 100) + '%'; + var width = ((1 / cluster_size) * 100) + '%'; + + for (var j=0; j < today_events.length; j++) { + if (today_events[j].eventID == event_id) { + + today_events[j]['offset'] = offset; + today_events[j]['width'] = width; + } + } + n++; + } + + return today_events; + } + + function drawEvent(e) { + var name = e['eventName']; + var eventID = e['eventID']; + var viewerIsInvited = e['viewerIsInvited']; + var offset = e['offset']; + var width = e['width']; + var top = e['top']; + var height = e['height']; + var uri = e['uri']; + + var sigil = 'phui-calendar-day-event'; + var link_class = 'phui-calendar-day-event-link'; + + if (viewerIsInvited) { + link_class = link_class + ' viewer-invited-day-event'; + } + + var name_link = JX.$N( + 'a', + { + className : link_class, + href: uri + }, + name); + + var class_name = 'phui-calendar-day-event'; + if (e.canEdit) { + class_name = class_name + ' can-drag'; + } + + var div = JX.$N( + 'div', + { + className: class_name, + sigil: sigil, + meta: {eventID: eventID, record: e, uri: uri}, + style: { + left: offset, + width: width, + top: top, + height: height + } + }, + name_link); + + return div; + } + + function drawAllDayEvent( + viewerIsInvited, + uri, + name) { + var class_name = 'day-view-all-day'; + if (viewerIsInvited) { + class_name = class_name + ' viewer-invited-day-event'; + } + + name = JX.$N( + 'a', + { + className: class_name, + href: uri + }, + name); + + var all_day_label = JX.$N( + 'span', + {className: 'phui-calendar-all-day-label'}, + 'All Day'); + + var div_all_day = JX.$N( + 'div', + {className: 'phui-calendar-day-event all-day'}, + [all_day_label, name]); + + return div_all_day; + } + + function drawRows() { + var rows = []; + var early_hours = [8]; + if (first_event_hour) { + early_hours.push(first_event_hour); + } + var min_early_hour = Math.min(early_hours[0], early_hours[1]); + + + for(var i=0; i < hours.length; i++) { + if (hours[i]['hour'] < min_early_hour) { + continue; + } + var cell_time = JX.$N( + 'td', + {className: 'phui-calendar-day-hour'}, + hours[i]['hour_meridian']); + + var cell_event = JX.$N( + 'td', + { + meta: { + time: hours[i]['hour_meridian'] + }, + className: 'phui-calendar-day-events', + sigil: 'phui-calendar-day-event-cell' + }); + + var row = JX.$N( + 'tr', + {}, + [cell_time, cell_event]); + rows.push(row); + } + return rows; + } + + function clusterAndDrawEvents() { + var today_clusters = findTodayClusters(); + for(var i=0; i < today_clusters.length; i++) { + today_events = updateEventsFromCluster(today_clusters[i]); + } + var drawn_hourly_events = []; + for (i=0; i < today_events.length; i++) { + drawn_hourly_events.push(drawEvent(today_events[i])); + } + + JX.DOM.setContent(hourly_events_wrapper, drawn_hourly_events); + + } + + var year = config.year; + var month = config.month; + var day = config.day; + var query = config.query; + + var hours = config.hours; + var first_event_hour = config.firstEventHour; + var first_event_hour_epoch = parseInt(config.firstEventHourEpoch, 10); + var today_events = config.todayEvents; + var today_all_day_events = config.allDayEvents; + var table_wrapper = JX.$(config.tableID); + var rows = drawRows(); + + var all_day_events = []; + for(i=0; i < today_all_day_events.length; i++) { + var all_day_event = today_all_day_events[i]; + all_day_events.push(drawAllDayEvent( + all_day_event['viewerIsInvited'], + all_day_event['uri'], + all_day_event['name'])); + } + + var table = JX.$N( + 'table', + {className: 'phui-calendar-day-view'}, + rows); + + var dragging = false; + var origin = null; + + var offset_top = null; + var new_top = null; + + var click_time = null; + + JX.DOM.listen( + table_wrapper, + 'mousedown', + 'phui-calendar-day-event', + function(e){ + + if (!e.isNormalMouseEvent()) { + return; + } + var data = e.getNodeData('phui-calendar-day-event'); + if (!data.record.canEdit) { + return; + } + e.kill(); + dragging = e.getNode('phui-calendar-day-event'); + JX.DOM.alterClass(dragging, 'phui-drag', true); + + click_time = new Date(); + + origin = JX.$V(e); + + var outer = JX.Vector.getPos(table); + var inner = JX.Vector.getPos(dragging); + + offset_top = inner.y - outer.y; + new_top = offset_top; + + dragging.style.top = offset_top + 'px'; + }); + JX.Stratcom.listen('mousemove', null, function(e){ + if (!dragging) { + return; + } + var cursor = JX.$V(e); + + new_top = cursor.y - origin.y + offset_top; + new_top = Math.min(new_top, 1320); + new_top = Math.max(new_top, 0); + new_top = Math.floor(new_top/15) * 15; + + dragging.style.top = new_top + 'px'; + }); + JX.Stratcom.listen('mouseup', null, function(){ + if (!dragging) { + return; + } + + var data = JX.Stratcom.getData(dragging); + var record = data.record; + + if (new_top == offset_top) { + var now = new Date(); + if (now.getTime() - click_time.getTime() < 250) { + JX.$U(record.uri).go(); + } + + JX.DOM.alterClass(dragging, 'phui-drag', false); + dragging = false; + return; + } + var new_time = first_event_hour_epoch + (new_top * 60); + var id = data.eventID; + var duration = record.eventEndEpoch - record.eventStartEpoch; + record.eventStartEpoch = new_time; + record.eventEndEpoch = new_time + duration; + record.top = new_top + 'px'; + + new JX.Workflow( + '/calendar/event/drag/' + id + '/', + {start: new_time}) + .start(); + + JX.DOM.alterClass(dragging, 'phui-drag', false); + dragging = false; + + clusterAndDrawEvents(); + }); + + JX.DOM.listen(table_wrapper, 'click', 'phui-calendar-day-event', function(e){ + if (e.isNormalClick()) { + e.kill(); + } + }); + + JX.DOM.listen(table, 'click', 'phui-calendar-day-event-cell', function(e){ + if (!e.isNormalClick()) { + return; + } + var data = e.getNodeData('phui-calendar-day-event-cell'); + var time = data.time; + new JX.Workflow( + '/calendar/event/create/', + { + year: year, + month: month, + day: day, + time: time, + next: 'day', + query: query + }) + .start(); + }); + + var hourly_events_wrapper = JX.$N( + 'div', + {style: { + position: 'absolute', + left: '69px', + right: 0 + }}); + + clusterAndDrawEvents(); + + var daily_wrapper = JX.$N( + 'div', + {style: {position: 'relative'}}, + [hourly_events_wrapper, table]); + + JX.DOM.setContent(table_wrapper, [all_day_events, daily_wrapper]); + +}); diff --git a/webroot/rsrc/js/application/calendar/event-all-day.js b/webroot/rsrc/js/application/calendar/behavior-event-all-day.js similarity index 100% rename from webroot/rsrc/js/application/calendar/event-all-day.js rename to webroot/rsrc/js/application/calendar/behavior-event-all-day.js diff --git a/webroot/rsrc/js/application/config/behavior-reorder-fields.js b/webroot/rsrc/js/application/config/behavior-reorder-fields.js index 948b248afe..1c041e85f8 100644 --- a/webroot/rsrc/js/application/config/behavior-reorder-fields.js +++ b/webroot/rsrc/js/application/config/behavior-reorder-fields.js @@ -54,4 +54,5 @@ JX.behavior('config-reorder-fields', function(config) { JX.$(config.inputID).value = JX.JSON.stringify(order); }; + write_state_to_form(); }); diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index 71c1af139b..0a39863c74 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -447,9 +447,20 @@ JX.install('ConpherenceThreadManager', { }, sendMessage: function(form, params) { + var inputs = JX.DOM.scry(form, 'input'); + var block_empty = true; + for (var i = 0; i < inputs.length; i++) { + if (inputs[i].type != 'hidden') { + continue; + } + if (inputs[i].name == 'action' && inputs[i].value == 'join_room') { + block_empty = false; + continue; + } + } // don't bother sending up text if there is nothing to submit var textarea = JX.DOM.find(form, 'textarea'); - if (!textarea.value.length) { + if (block_empty && !textarea.value.length) { return; } params = this._getParams(params); diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 0a572a999a..821d279f50 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -395,4 +395,87 @@ JX.behavior('differential-edit-inline-comments', function(config) { handle_inline_action(data.node, data.op); }); + // Respond to the user clicking the "Hide Inline" button on an inline + // comment. + JX.Stratcom.listen('click', 'hide-inline', function(e) { + e.kill(); + + var row = e.getNode('inline-row'); + JX.DOM.hide(row); + + var prev = row.previousSibling; + while (prev && JX.Stratcom.hasSigil(prev, 'inline-row')) { + prev = prev.previousSibling; + } + + if (!prev) { + return; + } + + var comment = e.getNodeData('differential-inline-comment'); + + var slots = []; + for (var ii = 0; ii < prev.childNodes.length; ii++) { + if (JX.DOM.isType(prev.childNodes[ii], 'th')) { + slots.push(prev.childNodes[ii]); + } + } + + // Select the right-hand side if the comment is on the right. + var slot = (comment.on_right && slots[1]) || slots[0]; + + var reveal = JX.DOM.scry(slot, 'a', 'reveal-inlines')[0]; + if (!reveal) { + reveal = JX.$N( + 'a', + { + className: 'reveal-inlines', + sigil: 'reveal-inlines' + }, + JX.$H(config.revealIcon)); + + JX.DOM.prependContent(slot, reveal); + } + + new JX.Workflow(config.uri, {op: 'hide', ids: comment.id}) + .setHandler(JX.bag) + .start(); + }); + + JX.Stratcom.listen('click', 'reveal-inlines', function(e) { + e.kill(); + + var row = e.getNode('tag:tr'); + var next = row.nextSibling; + + var ids = []; + var ii; + + // Show any hidden inline comment rows directly below this one. + while (next && JX.Stratcom.hasSigil(next, 'inline-row')) { + JX.DOM.show(next); + + var comments = JX.DOM.scry(next, 'div', 'differential-inline-comment'); + for (ii = 0; ii < comments.length; ii++) { + var id = JX.Stratcom.getData(comments[ii]).id; + if (id) { + ids.push(id); + } + } + + next = next.nextSibling; + } + + // Remove any "reveal" icons on the row. + var reveals = JX.DOM.scry(row, 'a', 'reveal-inlines'); + for (ii = 0; ii < reveals.length; ii++) { + JX.DOM.remove(reveals[ii]); + } + + new JX.Workflow(config.uri, {op: 'show', ids: ids.join(',')}) + .setHandler(JX.bag) + .start(); + }); + + }); diff --git a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js index abf645ba29..e7300528d6 100644 --- a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js +++ b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js @@ -220,7 +220,6 @@ JX.install('HeraldRuleEditor', { case 'buildplan': case 'taskpriority': case 'taskstatus': - case 'arcanistprojects': case 'legaldocuments': case 'applicationemail': var tokenizer = this._newTokenizer(type); diff --git a/webroot/rsrc/js/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/core/behavior-fancy-datepicker.js index f2d092faee..d00f6cc77f 100644 --- a/webroot/rsrc/js/core/behavior-fancy-datepicker.js +++ b/webroot/rsrc/js/core/behavior-fancy-datepicker.js @@ -64,7 +64,9 @@ JX.behavior('fancy-datepicker', function() { JX.DOM.remove(picker); picker = null; JX.DOM.alterClass(root, 'picker-open', false); - e.kill(); + if (e) { + e.kill(); + } root = null; }; @@ -78,25 +80,23 @@ JX.behavior('fancy-datepicker', function() { var get_inputs = function() { return { - y: JX.DOM.find(root, 'select', 'year-input'), - m: JX.DOM.find(root, 'select', 'month-input'), - d: JX.DOM.find(root, 'select', 'day-input'), + d: JX.DOM.find(root, 'input', 'date-input'), t: JX.DOM.find(root, 'input', 'time-input') }; }; var read_date = function() { var i = get_inputs(); - value_y = +i.y.value; - value_m = +i.m.value; - value_d = +i.d.value; + var date = i.d.value; + var parts = date.split('/'); + value_y = +parts[2]; + value_m = +parts[0]; + value_d = +parts[1]; }; var write_date = function() { var i = get_inputs(); - i.y.value = value_y; - i.m.value = value_m; - i.d.value = value_d; + i.d.value = value_m + '/' + value_d + '/' + value_y; }; var render = function() { @@ -133,9 +133,12 @@ JX.behavior('fancy-datepicker', function() { return JX.$N('td', {meta: {value: value}, className: class_name}, label); }; - // Render the top bar which allows you to pick a month and year. var render_month = function() { + var valid_date = getValidDate(); + var month = valid_date.getMonth(); + var year = valid_date.getYear() + 1900; + var months = [ 'January', 'February', @@ -152,7 +155,7 @@ JX.behavior('fancy-datepicker', function() { var buttons = [ cell('\u25C0', 'm:-1', false, 'lrbutton'), - cell(months[value_m - 1] + ' ' + value_y, null), + cell(months[month] + ' ' + year, null), cell('\u25B6', 'm:1', false, 'lrbutton')]; return JX.$N( @@ -161,9 +164,26 @@ JX.behavior('fancy-datepicker', function() { JX.$N('tr', {}, buttons)); }; + function getValidDate() { + var written_date = new Date(value_y, value_m-1, value_d); + if (isNaN(written_date.getTime())) { + return new Date(); + } else { + //year 01 should be 2001, not 1901 + if (written_date.getYear() < 70) { + value_y += 2000; + written_date = new Date(value_y, value_m-1, value_d); + } + return written_date; + } + } + // Render the day-of-week and calendar views. var render_day = function() { + var today = new Date(); + var valid_date = getValidDate(); + var weeks = []; // First, render the weekday names. @@ -179,16 +199,21 @@ JX.behavior('fancy-datepicker', function() { // Render the calendar itself. NOTE: Javascript uses 0-based month indexes // while we use 1-based month indexes, so we have to adjust for that. var days = []; - var start = new Date(value_y, value_m - 1, 1).getDay(); + var start = new Date( + valid_date.getYear() + 1900, + valid_date.getMonth(), + 1).getDay(); + while (start--) { days.push(cell('', null, false, 'day-placeholder')); } - var today = new Date(); - for (ii = 1; ii <= 31; ii++) { - var date = new Date(value_y, value_m - 1, ii); - if (date.getMonth() != (value_m - 1)) { + var date = new Date( + valid_date.getYear() + 1900, + valid_date.getMonth(), + ii); + if (date.getMonth() != (valid_date.getMonth())) { // We've spilled over into the next month, so stop rendering. break; } @@ -206,7 +231,11 @@ JX.behavior('fancy-datepicker', function() { classes.push('weekend'); } - days.push(cell(ii, 'd:'+ii, value_d == ii, classes.join(' '))); + days.push(cell( + ii, + 'd:'+ii, + valid_date.getDate() == ii, + classes.join(' '))); } // Slice the days into weeks. @@ -232,6 +261,11 @@ JX.behavior('fancy-datepicker', function() { return; } + var valid_date = getValidDate(); + value_y = valid_date.getYear() + 1900; + value_m = valid_date.getMonth() + 1; + value_d = valid_date.getDate(); + var p = data.value.split(':'); switch (p[0]) { case 'm': @@ -263,4 +297,11 @@ JX.behavior('fancy-datepicker', function() { render(); }); + JX.Stratcom.listen('click', null, function(e){ + if (e.getNode('phabricator-datepicker')) { + return; + } + onclose(); + }); + });