diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 38399fc9d4..528f1bc886 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,10 +9,10 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '84d8ab9a', - 'core.pkg.js' => '2058ec09', + 'core.pkg.css' => '5efaf405', + 'core.pkg.js' => 'b5a949ca', 'differential.pkg.css' => '06dc617c', - 'differential.pkg.js' => 'ef19e026', + 'differential.pkg.js' => 'c1cfa143', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'maniphest.pkg.css' => '4845691a', @@ -75,7 +75,7 @@ return array( 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', 'rsrc/css/application/flag/flag.css' => 'bba8f811', - 'rsrc/css/application/harbormaster/harbormaster.css' => '730a4a3c', + 'rsrc/css/application/harbormaster/harbormaster.css' => '7446ce72', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald.css' => 'cd8d0134', 'rsrc/css/application/maniphest/report.css' => '9b9580b7', @@ -122,7 +122,7 @@ return array( 'rsrc/css/layout/phabricator-source-code-view.css' => '2ab25dfa', 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', - 'rsrc/css/phui/button/phui-button.css' => '1863cc6e', + 'rsrc/css/phui/button/phui-button.css' => '6ccb303c', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf', @@ -146,15 +146,15 @@ return array( 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '10728aaa', 'rsrc/css/phui/phui-curtain-view.css' => '2bdaf026', - 'rsrc/css/phui/phui-document-pro.css' => '8af7ea27', + 'rsrc/css/phui/phui-document-pro.css' => 'dd79b5df', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', - 'rsrc/css/phui/phui-document.css' => '878c2f52', + 'rsrc/css/phui/phui-document.css' => 'c4ac41f9', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', - 'rsrc/css/phui/phui-form-view.css' => 'b446e8ff', + 'rsrc/css/phui/phui-form-view.css' => '739c78df', 'rsrc/css/phui/phui-form.css' => '7aaa04e3', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => 'edeb9252', + 'rsrc/css/phui/phui-header-view.css' => '1ba8b707', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => 'cf24ceec', @@ -375,12 +375,12 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'b49b59d6', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'f0ffe8c3', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'e0b984b5', 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-populate.js' => '419998ab', + 'rsrc/js/application/differential/behavior-populate.js' => 'f0eb6708', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '00676f00', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -395,7 +395,7 @@ return array( 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '549459b8', 'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e', - 'rsrc/js/application/herald/PathTypeahead.js' => '662e9cea', + 'rsrc/js/application/herald/PathTypeahead.js' => '6d8c7912', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e', 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876', @@ -425,7 +425,7 @@ return array( 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', - 'rsrc/js/application/transactions/behavior-comment-actions.js' => '9a6dd75c', + 'rsrc/js/application/transactions/behavior-comment-actions.js' => '038bf27f', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '8f29b364', @@ -475,7 +475,7 @@ return array( 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', - 'rsrc/js/core/behavior-phabricator-nav.js' => '94b7c320', + 'rsrc/js/core/behavior-phabricator-nav.js' => '9d32bc88', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee', 'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207', 'rsrc/js/core/behavior-redirect.js' => '0213259f', @@ -508,7 +508,7 @@ return array( 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '8d4a8c72', 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'df1bbd34', - 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', + 'rsrc/js/phuix/PHUIXButtonView.js' => '85ac9772', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 'rsrc/js/phuix/PHUIXFormControl.js' => '210a16c1', @@ -556,7 +556,7 @@ return array( 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => 'b556a948', - 'harbormaster-css' => '730a4a3c', + 'harbormaster-css' => '7446ce72', 'herald-css' => 'cd8d0134', 'herald-rule-editor' => 'dca75c0e', 'herald-test-css' => 'a52e323e', @@ -577,7 +577,7 @@ return array( 'javelin-behavior-bulk-job-reload' => 'edf8a145', 'javelin-behavior-calendar-month-view' => 'fe33e256', 'javelin-behavior-choose-control' => '327a00d1', - 'javelin-behavior-comment-actions' => '9a6dd75c', + 'javelin-behavior-comment-actions' => '038bf27f', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-menu' => '4047cd35', 'javelin-behavior-conpherence-participant-pane' => 'd057e45a', @@ -596,7 +596,7 @@ return array( 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => '51c5ad07', - 'javelin-behavior-differential-populate' => '419998ab', + 'javelin-behavior-differential-populate' => 'f0eb6708', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', 'javelin-behavior-diffusion-commit-graph' => '75b83cbb', @@ -633,7 +633,7 @@ return array( 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', 'javelin-behavior-phabricator-line-linker' => '66a62306', - 'javelin-behavior-phabricator-nav' => '94b7c320', + 'javelin-behavior-phabricator-nav' => '9d32bc88', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '77c1f0b0', 'javelin-behavior-phabricator-oncopy' => '2926fff2', @@ -741,7 +741,7 @@ return array( 'owners-path-editor' => 'c96502cf', 'owners-path-editor-css' => '9c136c29', 'paste-css' => '9fcc9773', - 'path-typeahead' => '662e9cea', + 'path-typeahead' => '6d8c7912', 'people-picture-menu-item-css' => 'a06f7f34', 'people-profile-css' => '4df76faf', 'phabricator-action-list-view-css' => '0bcd9a45', @@ -754,7 +754,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'b49b59d6', - 'phabricator-diff-changeset-list' => 'f0ffe8c3', + 'phabricator-diff-changeset-list' => 'e0b984b5', 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -803,7 +803,7 @@ return array( 'phui-box-css' => '4bd6cdb9', 'phui-bulk-editor-css' => '9a81e5d5', 'phui-button-bar-css' => 'f1ff5494', - 'phui-button-css' => '1863cc6e', + 'phui-button-css' => '6ccb303c', 'phui-button-simple-css' => '8e1baf68', 'phui-calendar-css' => 'f1ddf11c', 'phui-calendar-day-css' => '572b1893', @@ -816,15 +816,15 @@ return array( 'phui-crumbs-view-css' => '10728aaa', 'phui-curtain-view-css' => '2bdaf026', 'phui-document-summary-view-css' => '9ca48bdf', - 'phui-document-view-css' => '878c2f52', - 'phui-document-view-pro-css' => '8af7ea27', + 'phui-document-view-css' => 'c4ac41f9', + 'phui-document-view-pro-css' => 'dd79b5df', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', 'phui-form-css' => '7aaa04e3', - 'phui-form-view-css' => 'b446e8ff', + 'phui-form-view-css' => '739c78df', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => 'edeb9252', + 'phui-header-view-css' => '1ba8b707', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', @@ -861,7 +861,7 @@ return array( 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8d4a8c72', 'phuix-autocomplete' => 'df1bbd34', - 'phuix-button-view' => '8a91e1ac', + 'phuix-button-view' => '85ac9772', 'phuix-dropdown-menu' => '04b2ae03', 'phuix-form-control-view' => '210a16c1', 'phuix-icon-view' => 'bff6884b', @@ -908,6 +908,15 @@ return array( 'javelin-behavior', 'javelin-uri', ), + '038bf27f' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-dom', + 'phuix-form-control-view', + 'phuix-icon-view', + 'javelin-behavior-phabricator-gesture', + ), '040fce04' => array( 'javelin-behavior', 'javelin-request', @@ -1133,14 +1142,6 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), - '419998ab' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '4250a34e' => array( 'javelin-behavior', 'javelin-dom', @@ -1361,14 +1362,6 @@ return array( 'javelin-workflow', 'javelin-dom', ), - '662e9cea' => array( - 'javelin-install', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-request', - 'javelin-typeahead-ondemand-source', - 'javelin-util', - ), 66888767 => array( 'javelin-behavior', 'javelin-stratcom', @@ -1444,6 +1437,14 @@ return array( 'javelin-typeahead', 'javelin-uri', ), + '6d8c7912' => array( + 'javelin-install', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-request', + 'javelin-typeahead-ondemand-source', + 'javelin-util', + ), '70baed2f' => array( 'javelin-install', 'javelin-dom', @@ -1540,6 +1541,10 @@ return array( 'javelin-dom', 'javelin-stratcom', ), + '85ac9772' => array( + 'javelin-install', + 'javelin-dom', + ), '85ee8ce6' => array( 'aphront-dialog-view-css', ), @@ -1567,10 +1572,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '8a91e1ac' => array( - 'javelin-install', - 'javelin-dom', - ), '8ce821c5' => array( 'phabricator-notification', 'javelin-stratcom', @@ -1630,16 +1631,6 @@ return array( 'javelin-resource', 'javelin-routable', ), - '94b7c320' => array( - 'javelin-behavior', - 'javelin-behavior-device', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-magical-init', - 'javelin-vector', - 'javelin-request', - 'javelin-util', - ), '960f6a39' => array( 'javelin-behavior', 'javelin-dom', @@ -1647,15 +1638,6 @@ return array( 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), - '9a6dd75c' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-dom', - 'phuix-form-control-view', - 'phuix-icon-view', - 'javelin-behavior-phabricator-gesture', - ), '9a860428' => array( 'javelin-behavior', 'javelin-dom', @@ -1669,6 +1651,16 @@ return array( 'javelin-workflow', 'javelin-stratcom', ), + '9d32bc88' => array( + 'javelin-behavior', + 'javelin-behavior-device', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-magical-init', + 'javelin-vector', + 'javelin-request', + 'javelin-util', + ), '9d9685d6' => array( 'phui-oi-list-view-css', ), @@ -2028,6 +2020,10 @@ return array( 'phuix-icon-view', 'phabricator-prefab', ), + 'e0b984b5' => array( + 'javelin-install', + 'phuix-button-view', + ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2120,9 +2116,13 @@ return array( 'javelin-workflow', 'javelin-json', ), - 'f0ffe8c3' => array( - 'javelin-install', - 'phuix-button-view', + 'f0eb6708' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', ), 'f1ff5494' => array( 'phui-button-css', diff --git a/resources/sql/autopatches/20180809.repo_identities.activity.php b/resources/sql/autopatches/20180809.repo_identities.activity.php new file mode 100644 index 0000000000..e1077d4ddb --- /dev/null +++ b/resources/sql/autopatches/20180809.repo_identities.activity.php @@ -0,0 +1,20 @@ +loadAllWhere('authorIdentityPHID IS NULL LIMIT 1'); + +if (!$commits) { + return; +} + +try { + id(new PhabricatorConfigManualActivity()) + ->setActivityType(PhabricatorConfigManualActivity::TYPE_IDENTITIES) + ->save(); +} catch (AphrontDuplicateKeyQueryException $ex) { + // If we've already noted that this activity is required, just move on. +} diff --git a/resources/sql/autopatches/20180827.drydock.01.acquired.sql b/resources/sql/autopatches/20180827.drydock.01.acquired.sql new file mode 100644 index 0000000000..55948391c9 --- /dev/null +++ b/resources/sql/autopatches/20180827.drydock.01.acquired.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_lease + ADD acquiredEpoch INT UNSIGNED; diff --git a/resources/sql/autopatches/20180827.drydock.02.activated.sql b/resources/sql/autopatches/20180827.drydock.02.activated.sql new file mode 100644 index 0000000000..552f7b6b24 --- /dev/null +++ b/resources/sql/autopatches/20180827.drydock.02.activated.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_lease + ADD activatedEpoch INT UNSIGNED; diff --git a/resources/sql/autopatches/20180828.phriction.01.contentphid.sql b/resources/sql/autopatches/20180828.phriction.01.contentphid.sql new file mode 100644 index 0000000000..cf3d78ebdf --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.01.contentphid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_document + ADD contentPHID VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20180828.phriction.02.documentphid.sql b/resources/sql/autopatches/20180828.phriction.02.documentphid.sql new file mode 100644 index 0000000000..c15b4b17b8 --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.02.documentphid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_content + ADD documentPHID VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20180828.phriction.03.editedepoch.sql b/resources/sql/autopatches/20180828.phriction.03.editedepoch.sql new file mode 100644 index 0000000000..eae31fc0ba --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.03.editedepoch.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_document + ADD editedEpoch INT UNSIGNED NOT NULL; diff --git a/resources/sql/autopatches/20180828.phriction.04.migrate.php b/resources/sql/autopatches/20180828.phriction.04.migrate.php new file mode 100644 index 0000000000..461eae2dad --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.04.migrate.php @@ -0,0 +1,57 @@ +establishConnection('w'); + +$document_iterator = new LiskRawMigrationIterator( + $conn, + $document_table->getTableName()); +foreach ($document_iterator as $row) { + $content_id = $row['contentID']; + + $content_row = queryfx_one( + $conn, + 'SELECT phid, dateCreated FROM %T WHERE id = %d', + $content_table->getTableName(), + $content_id); + + if (!$content_row) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET contentPHID = %s, editedEpoch = %d WHERE id = %d', + $document_table->getTableName(), + $content_row['phid'], + $content_row['dateCreated'], + $row['id']); +} + +$content_iterator = new LiskRawMigrationIterator( + $conn, + $content_table->getTableName()); +foreach ($content_iterator as $row) { + $document_id = $row['documentID']; + + $document_row = queryfx_one( + $conn, + 'SELECT phid FROM %T WHERE id = %d', + $document_table->getTableName(), + $document_id); + if (!$document_row) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET documentPHID = %s WHERE id = %d', + $content_table->getTableName(), + $document_row['phid'], + $row['id']); +} diff --git a/resources/sql/autopatches/20180828.phriction.05.contentid.sql b/resources/sql/autopatches/20180828.phriction.05.contentid.sql new file mode 100644 index 0000000000..d6cba741a3 --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.05.contentid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_document + DROP contentID; diff --git a/resources/sql/autopatches/20180828.phriction.06.c.documentid.php b/resources/sql/autopatches/20180828.phriction.06.c.documentid.php new file mode 100644 index 0000000000..474643d620 --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.06.c.documentid.php @@ -0,0 +1,20 @@ +establishConnection('w'); + +try { + queryfx( + $conn, + 'ALTER TABLE %T DROP KEY documentID', + $table->getTableName()); +} catch (AphrontQueryException $ex) { + // Ignore. +} diff --git a/resources/sql/autopatches/20180828.phriction.06.documentid.sql b/resources/sql/autopatches/20180828.phriction.06.documentid.sql new file mode 100644 index 0000000000..2323154b3e --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.06.documentid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_content + DROP documentID; diff --git a/resources/sql/autopatches/20180828.phriction.07.c.documentuniq.sql b/resources/sql/autopatches/20180828.phriction.07.c.documentuniq.sql new file mode 100644 index 0000000000..d086cc6141 --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.07.c.documentuniq.sql @@ -0,0 +1 @@ +DELETE FROM {$NAMESPACE}_phriction.phriction_content WHERE documentPHID = ''; diff --git a/resources/sql/autopatches/20180828.phriction.07.documentkey.sql b/resources/sql/autopatches/20180828.phriction.07.documentkey.sql new file mode 100644 index 0000000000..aea3c97130 --- /dev/null +++ b/resources/sql/autopatches/20180828.phriction.07.documentkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_content + ADD UNIQUE KEY `key_version` (documentPHID, version); diff --git a/resources/sql/autopatches/20180829.phriction.01.mailkey.php b/resources/sql/autopatches/20180829.phriction.01.mailkey.php new file mode 100644 index 0000000000..cb85a3c5ef --- /dev/null +++ b/resources/sql/autopatches/20180829.phriction.01.mailkey.php @@ -0,0 +1,26 @@ +establishConnection('w'); +$document_name = $document_table->getTableName(); + +$properties_table = new PhabricatorMetaMTAMailProperties(); +$conn = $properties_table->establishConnection('w'); + +$iterator = new LiskRawMigrationIterator($document_conn, $document_name); +foreach ($iterator as $row) { + queryfx( + $conn, + 'INSERT IGNORE INTO %T + (objectPHID, mailProperties, dateCreated, dateModified) + VALUES + (%s, %s, %d, %d)', + $properties_table->getTableName(), + $row['phid'], + phutil_json_encode( + array( + 'mailKey' => $row['mailKey'], + )), + PhabricatorTime::getNow(), + PhabricatorTime::getNow()); +} diff --git a/resources/sql/autopatches/20180829.phriction.02.rmkey.sql b/resources/sql/autopatches/20180829.phriction.02.rmkey.sql new file mode 100644 index 0000000000..8199287db7 --- /dev/null +++ b/resources/sql/autopatches/20180829.phriction.02.rmkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_document + DROP mailKey; diff --git a/resources/sql/autopatches/20180830.phriction.01.maxversion.sql b/resources/sql/autopatches/20180830.phriction.01.maxversion.sql new file mode 100644 index 0000000000..f6f24e8333 --- /dev/null +++ b/resources/sql/autopatches/20180830.phriction.01.maxversion.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_document + ADD maxVersion INT UNSIGNED NOT NULL; diff --git a/resources/sql/autopatches/20180830.phriction.02.maxes.php b/resources/sql/autopatches/20180830.phriction.02.maxes.php new file mode 100644 index 0000000000..97abf010db --- /dev/null +++ b/resources/sql/autopatches/20180830.phriction.02.maxes.php @@ -0,0 +1,30 @@ +establishConnection('w'); + +$iterator = new LiskRawMigrationIterator( + $conn, + $document_table->getTableName()); +foreach ($iterator as $row) { + $content = queryfx_one( + $conn, + 'SELECT MAX(version) max FROM %T WHERE documentPHID = %s', + $content_table->getTableName(), + $row['phid']); + if (!$content) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET maxVersion = %d WHERE id = %d', + $document_table->getTableName(), + $content['max'], + $row['id']); +} diff --git a/resources/sql/autopatches/20180910.audit.01.searches.php b/resources/sql/autopatches/20180910.audit.01.searches.php new file mode 100644 index 0000000000..f68e76fe45 --- /dev/null +++ b/resources/sql/autopatches/20180910.audit.01.searches.php @@ -0,0 +1,54 @@ +establishConnection('w'); + +$status_map = array( + 0 => 'none', + 1 => 'needs-audit', + 2 => 'concern-raised', + 3 => 'partially-audited', + 4 => 'audited', + 5 => 'needs-verification', +); + +foreach (new LiskMigrationIterator($table) as $query) { + if ($query->getEngineClassName() !== 'PhabricatorCommitSearchEngine') { + continue; + } + + $parameters = $query->getParameters(); + $status = idx($parameters, 'statuses'); + + if (!$status) { + // No saved "status" constraint. + continue; + } + + if (!is_array($status)) { + // Saved constraint isn't a list. + continue; + } + + // Migrate old integer values to new string values. + $old_status = $status; + foreach ($status as $key => $value) { + if (is_numeric($value)) { + $status[$key] = $status_map[$value]; + } + } + + if ($status === $old_status) { + // Nothing changed. + continue; + } + + $parameters['statuses'] = $status; + + queryfx( + $conn, + 'UPDATE %T SET parameters = %s WHERE id = %d', + $table->getTableName(), + phutil_json_encode($parameters), + $query->getID()); +} diff --git a/resources/sql/autopatches/20180910.audit.02.string.sql b/resources/sql/autopatches/20180910.audit.02.string.sql new file mode 100644 index 0000000000..4caa4a1724 --- /dev/null +++ b/resources/sql/autopatches/20180910.audit.02.string.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_commit + CHANGE auditStatus auditStatus VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20180910.audit.03.status.php b/resources/sql/autopatches/20180910.audit.03.status.php new file mode 100644 index 0000000000..b7a0bf9f58 --- /dev/null +++ b/resources/sql/autopatches/20180910.audit.03.status.php @@ -0,0 +1,28 @@ +establishConnection('w'); + +$status_map = array( + 0 => 'none', + 1 => 'needs-audit', + 2 => 'concern-raised', + 3 => 'partially-audited', + 4 => 'audited', + 5 => 'needs-verification', +); + +foreach (new LiskMigrationIterator($table) as $commit) { + $status = $commit->getAuditStatus(); + + if (!isset($status_map[$status])) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET auditStatus = %s WHERE id = %d', + $table->getTableName(), + $status_map[$status], + $commit->getID()); +} diff --git a/resources/sql/autopatches/20180910.audit.04.xactions.php b/resources/sql/autopatches/20180910.audit.04.xactions.php new file mode 100644 index 0000000000..1ecf9ef320 --- /dev/null +++ b/resources/sql/autopatches/20180910.audit.04.xactions.php @@ -0,0 +1,48 @@ +establishConnection('w'); + +$status_map = array( + 0 => 'none', + 1 => 'needs-audit', + 2 => 'concern-raised', + 3 => 'partially-audited', + 4 => 'audited', + 5 => 'needs-verification', +); + +$state_type = DiffusionCommitStateTransaction::TRANSACTIONTYPE; + +foreach (new LiskMigrationIterator($table) as $xaction) { + if ($xaction->getTransactionType() !== $state_type) { + continue; + } + + $old_value = $xaction->getOldValue(); + $new_value = $xaction->getNewValue(); + + $any_change = false; + + if (isset($status_map[$old_value])) { + $old_value = $status_map[$old_value]; + $any_change = true; + } + + if (isset($status_map[$new_value])) { + $new_value = $status_map[$new_value]; + $any_change = true; + } + + if (!$any_change) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET oldValue = %s, newValue = %s WHERE id = %d', + $table->getTableName(), + phutil_json_encode($old_value), + phutil_json_encode($new_value), + $xaction->getID()); +} diff --git a/resources/sql/autopatches/20180914.audit.01.mailkey.php b/resources/sql/autopatches/20180914.audit.01.mailkey.php new file mode 100644 index 0000000000..de8419a3c4 --- /dev/null +++ b/resources/sql/autopatches/20180914.audit.01.mailkey.php @@ -0,0 +1,26 @@ +establishConnection('w'); +$commit_name = $commit_table->getTableName(); + +$properties_table = new PhabricatorMetaMTAMailProperties(); +$conn = $properties_table->establishConnection('w'); + +$iterator = new LiskRawMigrationIterator($commit_conn, $commit_name); +foreach ($iterator as $commit) { + queryfx( + $conn, + 'INSERT IGNORE INTO %T + (objectPHID, mailProperties, dateCreated, dateModified) + VALUES + (%s, %s, %d, %d)', + $properties_table->getTableName(), + $commit['phid'], + phutil_json_encode( + array( + 'mailKey' => $commit['mailKey'], + )), + PhabricatorTime::getNow(), + PhabricatorTime::getNow()); +} diff --git a/resources/sql/autopatches/20180914.audit.02.rmkey.sql b/resources/sql/autopatches/20180914.audit.02.rmkey.sql new file mode 100644 index 0000000000..3187da4258 --- /dev/null +++ b/resources/sql/autopatches/20180914.audit.02.rmkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_commit + DROP mailKey; diff --git a/resources/sql/autopatches/20180914.drydock.01.operationphid.sql b/resources/sql/autopatches/20180914.drydock.01.operationphid.sql new file mode 100644 index 0000000000..bdfe02b0df --- /dev/null +++ b/resources/sql/autopatches/20180914.drydock.01.operationphid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_log + ADD operationPHID VARBINARY(64); diff --git a/scripts/repository/rebuild_summaries.php b/scripts/repository/rebuild_summaries.php deleted file mode 100755 index de3d5acca0..0000000000 --- a/scripts/repository/rebuild_summaries.php +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env php -establishConnection('w'); -$sizes = queryfx_all( - $conn_w, - 'SELECT repositoryID, count(*) N FROM %T GROUP BY repositoryID', - $commit->getTableName()); -$sizes = ipull($sizes, 'N', 'repositoryID'); - -$maxes = queryfx_all( - $conn_w, - 'SELECT repositoryID, max(epoch) maxEpoch FROM %T GROUP BY repositoryID', - $commit->getTableName()); -$maxes = ipull($maxes, 'maxEpoch', 'repositoryID'); - - -$repository_ids = array_keys($sizes + $maxes); - -echo pht('Updating %d repositories', count($repository_ids)); - -foreach ($repository_ids as $repository_id) { - $last_commit = queryfx_one( - $conn_w, - 'SELECT id FROM %T WHERE repositoryID = %d AND epoch = %d LIMIT 1', - $commit->getTableName(), - $repository_id, - idx($maxes, $repository_id, 0)); - if ($last_commit) { - $last_commit = $last_commit['id']; - } else { - $last_commit = 0; - } - queryfx( - $conn_w, - 'INSERT INTO %T (repositoryID, lastCommitID, size, epoch) - VALUES (%d, %d, %d, %d) ON DUPLICATE KEY UPDATE - lastCommitID = VALUES(lastCommitID), - size = VALUES(size), - epoch = VALUES(epoch)', - PhabricatorRepository::TABLE_SUMMARY, - $repository_id, - $last_commit, - idx($sizes, $repository_id, 0), - idx($maxes, $repository_id, 0)); - echo '.'; -} -echo "\n".pht('Done.')."\n"; diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth.php index b25905668b..3c4f3f2b33 100755 --- a/scripts/ssh/ssh-auth.php +++ b/scripts/ssh/ssh-auth.php @@ -2,13 +2,27 @@ getKey($authfile_key); +$authstruct_key = PhabricatorAuthSSHKeyQuery::AUTHSTRUCT_CACHEKEY; +$authstruct_raw = $cache->getKey($authstruct_key); -if ($authfile === null) { +$authstruct = null; + +if (strlen($authstruct_raw)) { + try { + $authstruct = phutil_json_decode($authstruct_raw); + } catch (Exception $ex) { + // Ignore any issues with the cached data; we'll just rebuild the + // structure below. + } +} + +if ($authstruct === null) { $keys = id(new PhabricatorAuthSSHKeyQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withIsActive(true) @@ -19,7 +33,7 @@ if ($authfile === null) { exit(1); } - $bin = $root.'/bin/ssh-exec'; + $key_list = array(); foreach ($keys as $ssh_key) { $key_argv = array(); $object = $ssh_key->getObject(); @@ -42,18 +56,7 @@ if ($authfile === null) { $key_argv[] = '--phabricator-ssh-key'; $key_argv[] = $ssh_key->getID(); - $cmd = csprintf('%s %Ls', $bin, $key_argv); - - $instance = PhabricatorEnv::getEnvConfig('cluster.instance'); - if (strlen($instance)) { - $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd); - } - - // This is additional escaping for the SSH 'command="..."' string. - $cmd = addcslashes($cmd, '"\\'); - // Strip out newlines and other nonsense from the key type and key body. - $type = $ssh_key->getKeyType(); $type = preg_replace('@[\x00-\x20]+@', '', $type); if (!strlen($type)) { @@ -66,22 +69,54 @@ if ($authfile === null) { continue; } - $options = array( - 'command="'.$cmd.'"', - 'no-port-forwarding', - 'no-X11-forwarding', - 'no-agent-forwarding', - 'no-pty', + $key_list[] = array( + 'argv' => $key_argv, + 'type' => $type, + 'key' => $key, ); - $options = implode(',', $options); - - $lines[] = $options.' '.$type.' '.$key."\n"; } - $authfile = implode('', $lines); + $authstruct = array( + 'keys' => $key_list, + ); + + $authstruct_raw = phutil_json_encode($authstruct); $ttl = phutil_units('24 hours in seconds'); - $cache->setKey($authfile_key, $authfile, $ttl); + $cache->setKey($authstruct_key, $authstruct_raw, $ttl); } +$bin = $root.'/bin/ssh-exec'; +$instance = PhabricatorEnv::getEnvConfig('cluster.instance'); + +$lines = array(); +foreach ($authstruct['keys'] as $key_struct) { + $key_argv = $key_struct['argv']; + $key = $key_struct['key']; + $type = $key_struct['type']; + + $cmd = csprintf('%s %Ls', $bin, $key_argv); + + if (strlen($instance)) { + $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd); + } + + // This is additional escaping for the SSH 'command="..."' string. + $cmd = addcslashes($cmd, '"\\'); + + $options = array( + 'command="'.$cmd.'"', + 'no-port-forwarding', + 'no-X11-forwarding', + 'no-agent-forwarding', + 'no-pty', + ); + $options = implode(',', $options); + + $lines[] = $options.' '.$type.' '.$key."\n"; +} + +$authfile = implode('', $lines); + echo $authfile; + exit(0); diff --git a/scripts/ssh/ssh-connect.php b/scripts/ssh/ssh-connect.php index 9757f3cbbe..0d87f16973 100755 --- a/scripts/ssh/ssh-connect.php +++ b/scripts/ssh/ssh-connect.php @@ -27,7 +27,15 @@ $args->parsePartial( 'param' => pht('port'), 'help' => pht('Port number to connect to.'), ), + array( + 'name' => 'options', + 'short' => 'o', + 'param' => pht('options'), + 'repeat' => true, + 'help' => pht('SSH options.'), + ), )); + $unconsumed_argv = $args->getUnconsumedArgumentVector(); if (function_exists('pcntl_signal')) { @@ -113,6 +121,25 @@ if ($port) { $arguments[] = $port; } +$options = $args->getArg('options'); +$allowed_ssh_options = array('SendEnv=GIT_PROTOCOL'); + +if (!empty($options)) { + foreach ($options as $option) { + if (array_search($option, $allowed_ssh_options) !== false) { + $pattern[] = '-o %s'; + $arguments[] = $option; + } else { + throw new Exception( + pht( + 'Disallowed ssh option "%s" given with "-o". '. + 'Allowed options are: %s.', + $option, + implode(', ', $allowed_ssh_options))); + } + } +} + $pattern[] = '--'; $pattern[] = '%s'; diff --git a/scripts/symbols/generate_ctags_symbols.php b/scripts/symbols/generate_ctags_symbols.php index 8d77bfc478..e93b0c5cbc 100755 --- a/scripts/symbols/generate_ctags_symbols.php +++ b/scripts/symbols/generate_ctags_symbols.php @@ -39,6 +39,10 @@ $data = array(); $futures = array(); foreach (explode("\n", trim($input)) as $file) { + if (!strlen($file)) { + continue; + } + $file = Filesystem::readablePath($file); $futures[$file] = ctags_get_parser_future($file); } diff --git a/scripts/symbols/generate_php_symbols.php b/scripts/symbols/generate_php_symbols.php index db8412764e..af87d580d8 100755 --- a/scripts/symbols/generate_php_symbols.php +++ b/scripts/symbols/generate_php_symbols.php @@ -27,6 +27,10 @@ $data = array(); $futures = array(); foreach (explode("\n", trim($input)) as $file) { + if (!strlen($file)) { + continue; + } + $file = Filesystem::readablePath($file); $data[$file] = Filesystem::readFile($file); $futures[$file] = PhutilXHPASTBinary::getParserFuture($data[$file]); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f2480ae9d1..a9c1c35f89 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -313,6 +313,7 @@ phutil_register_library_map(array( 'ConduitCallTestCase' => 'applications/conduit/call/__tests__/ConduitCallTestCase.php', 'ConduitColumnsParameterType' => 'applications/conduit/parametertype/ConduitColumnsParameterType.php', 'ConduitConnectConduitAPIMethod' => 'applications/conduit/method/ConduitConnectConduitAPIMethod.php', + 'ConduitConstantDescription' => 'applications/conduit/data/ConduitConstantDescription.php', 'ConduitEpochParameterType' => 'applications/conduit/parametertype/ConduitEpochParameterType.php', 'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php', 'ConduitGetCapabilitiesConduitAPIMethod' => 'applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php', @@ -644,6 +645,7 @@ phutil_register_library_map(array( 'DifferentialRevisionStatusTransaction' => 'applications/differential/xaction/DifferentialRevisionStatusTransaction.php', 'DifferentialRevisionSummaryHeraldField' => 'applications/differential/herald/DifferentialRevisionSummaryHeraldField.php', 'DifferentialRevisionSummaryTransaction' => 'applications/differential/xaction/DifferentialRevisionSummaryTransaction.php', + 'DifferentialRevisionTestPlanHeraldField' => 'applications/differential/herald/DifferentialRevisionTestPlanHeraldField.php', 'DifferentialRevisionTestPlanTransaction' => 'applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php', 'DifferentialRevisionTitleHeraldField' => 'applications/differential/herald/DifferentialRevisionTitleHeraldField.php', 'DifferentialRevisionTitleTransaction' => 'applications/differential/xaction/DifferentialRevisionTitleTransaction.php', @@ -702,6 +704,7 @@ phutil_register_library_map(array( 'DiffusionCommitAcceptTransaction' => 'applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php', 'DiffusionCommitActionTransaction' => 'applications/diffusion/xaction/DiffusionCommitActionTransaction.php', 'DiffusionCommitAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionCommitAffectedFilesHeraldField.php', + 'DiffusionCommitAuditStatus' => 'applications/diffusion/DiffusionCommitAuditStatus.php', 'DiffusionCommitAuditTransaction' => 'applications/diffusion/xaction/DiffusionCommitAuditTransaction.php', 'DiffusionCommitAuditorsHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuditorsHeraldField.php', 'DiffusionCommitAuditorsTransaction' => 'applications/diffusion/xaction/DiffusionCommitAuditorsTransaction.php', @@ -1165,6 +1168,7 @@ phutil_register_library_map(array( 'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', 'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php', + 'DrydockOperationWorkLogType' => 'applications/drydock/logtype/DrydockOperationWorkLogType.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php', 'DrydockRepositoryOperationController' => 'applications/drydock/controller/DrydockRepositoryOperationController.php', @@ -1202,6 +1206,7 @@ phutil_register_library_map(array( 'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php', 'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php', 'DrydockTestRepositoryOperation' => 'applications/drydock/operation/DrydockTestRepositoryOperation.php', + 'DrydockTextLogType' => 'applications/drydock/logtype/DrydockTextLogType.php', 'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', 'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php', 'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', @@ -1646,13 +1651,8 @@ phutil_register_library_map(array( 'ManiphestDAO' => 'applications/maniphest/storage/ManiphestDAO.php', 'ManiphestDefaultEditCapability' => 'applications/maniphest/capability/ManiphestDefaultEditCapability.php', 'ManiphestDefaultViewCapability' => 'applications/maniphest/capability/ManiphestDefaultViewCapability.php', - 'ManiphestEditAssignCapability' => 'applications/maniphest/capability/ManiphestEditAssignCapability.php', 'ManiphestEditConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestEditConduitAPIMethod.php', 'ManiphestEditEngine' => 'applications/maniphest/editor/ManiphestEditEngine.php', - 'ManiphestEditPoliciesCapability' => 'applications/maniphest/capability/ManiphestEditPoliciesCapability.php', - 'ManiphestEditPriorityCapability' => 'applications/maniphest/capability/ManiphestEditPriorityCapability.php', - 'ManiphestEditProjectsCapability' => 'applications/maniphest/capability/ManiphestEditProjectsCapability.php', - 'ManiphestEditStatusCapability' => 'applications/maniphest/capability/ManiphestEditStatusCapability.php', 'ManiphestEmailCommand' => 'applications/maniphest/command/ManiphestEmailCommand.php', 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php', 'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php', @@ -1918,7 +1918,7 @@ phutil_register_library_map(array( 'PHUIDiffTableOfContentsListView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsListView.php', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 'PHUIDocumentSummaryView' => 'view/phui/PHUIDocumentSummaryView.php', - 'PHUIDocumentViewPro' => 'view/phui/PHUIDocumentViewPro.php', + 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', 'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php', 'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php', 'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php', @@ -2048,6 +2048,7 @@ phutil_register_library_map(array( 'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php', 'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php', 'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php', + 'PeopleDisableUsersCapability' => 'applications/people/capability/PeopleDisableUsersCapability.php', 'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php', 'PeopleMainMenuBarExtension' => 'applications/people/engineextension/PeopleMainMenuBarExtension.php', 'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php', @@ -2152,7 +2153,6 @@ phutil_register_library_map(array( 'PhabricatorAuditActionConstants' => 'applications/audit/constants/PhabricatorAuditActionConstants.php', 'PhabricatorAuditApplication' => 'applications/audit/application/PhabricatorAuditApplication.php', 'PhabricatorAuditCommentEditor' => 'applications/audit/editor/PhabricatorAuditCommentEditor.php', - 'PhabricatorAuditCommitStatusConstants' => 'applications/audit/constants/PhabricatorAuditCommitStatusConstants.php', 'PhabricatorAuditController' => 'applications/audit/controller/PhabricatorAuditController.php', 'PhabricatorAuditEditor' => 'applications/audit/editor/PhabricatorAuditEditor.php', 'PhabricatorAuditInlineComment' => 'applications/audit/storage/PhabricatorAuditInlineComment.php', @@ -4567,6 +4567,8 @@ phutil_register_library_map(array( 'PhabricatorUserCustomFieldNumericIndex' => 'applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php', 'PhabricatorUserCustomFieldStringIndex' => 'applications/people/storage/PhabricatorUserCustomFieldStringIndex.php', 'PhabricatorUserDAO' => 'applications/people/storage/PhabricatorUserDAO.php', + 'PhabricatorUserDisableTransaction' => 'applications/people/xaction/PhabricatorUserDisableTransaction.php', + 'PhabricatorUserEditEngine' => 'applications/people/editor/PhabricatorUserEditEngine.php', 'PhabricatorUserEditor' => 'applications/people/editor/PhabricatorUserEditor.php', 'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php', 'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php', @@ -4588,7 +4590,6 @@ phutil_register_library_map(array( 'PhabricatorUserPreferencesTransaction' => 'applications/settings/storage/PhabricatorUserPreferencesTransaction.php', 'PhabricatorUserPreferencesTransactionQuery' => 'applications/settings/query/PhabricatorUserPreferencesTransactionQuery.php', 'PhabricatorUserProfile' => 'applications/people/storage/PhabricatorUserProfile.php', - 'PhabricatorUserProfileEditor' => 'applications/people/editor/PhabricatorUserProfileEditor.php', 'PhabricatorUserProfileImageCacheType' => 'applications/people/cache/PhabricatorUserProfileImageCacheType.php', 'PhabricatorUserRealNameField' => 'applications/people/customfield/PhabricatorUserRealNameField.php', 'PhabricatorUserRolesField' => 'applications/people/customfield/PhabricatorUserRolesField.php', @@ -4598,6 +4599,8 @@ phutil_register_library_map(array( 'PhabricatorUserTestCase' => 'applications/people/storage/__tests__/PhabricatorUserTestCase.php', 'PhabricatorUserTitleField' => 'applications/people/customfield/PhabricatorUserTitleField.php', 'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php', + 'PhabricatorUserTransactionEditor' => 'applications/people/editor/PhabricatorUserTransactionEditor.php', + 'PhabricatorUserTransactionType' => 'applications/people/xaction/PhabricatorUserTransactionType.php', 'PhabricatorUsersEditField' => 'applications/transactions/editfield/PhabricatorUsersEditField.php', 'PhabricatorUsersPolicyRule' => 'applications/people/policyrule/PhabricatorUsersPolicyRule.php', 'PhabricatorUsersSearchField' => 'applications/people/searchfield/PhabricatorUsersSearchField.php', @@ -5025,6 +5028,9 @@ phutil_register_library_map(array( 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', 'PhrictionDocumentDatasource' => 'applications/phriction/typeahead/PhrictionDocumentDatasource.php', 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', + 'PhrictionDocumentDraftTransaction' => 'applications/phriction/xaction/PhrictionDocumentDraftTransaction.php', + 'PhrictionDocumentEditEngine' => 'applications/phriction/editor/PhrictionDocumentEditEngine.php', + 'PhrictionDocumentEditTransaction' => 'applications/phriction/xaction/PhrictionDocumentEditTransaction.php', 'PhrictionDocumentFerretEngine' => 'applications/phriction/search/PhrictionDocumentFerretEngine.php', 'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php', 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', @@ -5035,6 +5041,7 @@ phutil_register_library_map(array( 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', 'PhrictionDocumentPolicyCodex' => 'applications/phriction/codex/PhrictionDocumentPolicyCodex.php', + 'PhrictionDocumentPublishTransaction' => 'applications/phriction/xaction/PhrictionDocumentPublishTransaction.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', 'PhrictionDocumentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php', 'PhrictionDocumentSearchEngine' => 'applications/phriction/query/PhrictionDocumentSearchEngine.php', @@ -5042,8 +5049,10 @@ phutil_register_library_map(array( 'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php', 'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php', 'PhrictionDocumentTransactionType' => 'applications/phriction/xaction/PhrictionDocumentTransactionType.php', + 'PhrictionDocumentVersionTransaction' => 'applications/phriction/xaction/PhrictionDocumentVersionTransaction.php', 'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php', 'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php', + 'PhrictionEditEngineController' => 'applications/phriction/controller/PhrictionEditEngineController.php', 'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php', 'PhrictionHistoryController' => 'applications/phriction/controller/PhrictionHistoryController.php', 'PhrictionInfoConduitAPIMethod' => 'applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php', @@ -5051,6 +5060,7 @@ phutil_register_library_map(array( 'PhrictionMarkupPreviewController' => 'applications/phriction/controller/PhrictionMarkupPreviewController.php', 'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php', 'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php', + 'PhrictionPublishController' => 'applications/phriction/controller/PhrictionPublishController.php', 'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php', 'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php', 'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php', @@ -5239,6 +5249,7 @@ phutil_register_library_map(array( 'TransactionSearchConduitAPIMethod' => 'applications/transactions/conduit/TransactionSearchConduitAPIMethod.php', 'UserConduitAPIMethod' => 'applications/people/conduit/UserConduitAPIMethod.php', 'UserDisableConduitAPIMethod' => 'applications/people/conduit/UserDisableConduitAPIMethod.php', + 'UserEditConduitAPIMethod' => 'applications/people/conduit/UserEditConduitAPIMethod.php', 'UserEnableConduitAPIMethod' => 'applications/people/conduit/UserEnableConduitAPIMethod.php', 'UserFindConduitAPIMethod' => 'applications/people/conduit/UserFindConduitAPIMethod.php', 'UserQueryConduitAPIMethod' => 'applications/people/conduit/UserQueryConduitAPIMethod.php', @@ -5631,6 +5642,7 @@ phutil_register_library_map(array( 'ConduitCallTestCase' => 'PhabricatorTestCase', 'ConduitColumnsParameterType' => 'ConduitParameterType', 'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod', + 'ConduitConstantDescription' => 'Phobject', 'ConduitEpochParameterType' => 'ConduitParameterType', 'ConduitException' => 'Exception', 'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod', @@ -6008,6 +6020,7 @@ phutil_register_library_map(array( 'DifferentialRevisionStatusTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionSummaryHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionSummaryTransaction' => 'DifferentialRevisionTransactionType', + 'DifferentialRevisionTestPlanHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionTestPlanTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionTitleHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionTitleTransaction' => 'DifferentialRevisionTransactionType', @@ -6066,6 +6079,7 @@ phutil_register_library_map(array( 'DiffusionCommitAcceptTransaction' => 'DiffusionCommitAuditTransaction', 'DiffusionCommitActionTransaction' => 'DiffusionCommitTransactionType', 'DiffusionCommitAffectedFilesHeraldField' => 'DiffusionCommitHeraldField', + 'DiffusionCommitAuditStatus' => 'Phobject', 'DiffusionCommitAuditTransaction' => 'DiffusionCommitActionTransaction', 'DiffusionCommitAuditorsHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitAuditorsTransaction' => 'DiffusionCommitTransactionType', @@ -6570,6 +6584,7 @@ phutil_register_library_map(array( 'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', 'DrydockObjectAuthorizationView' => 'AphrontView', + 'DrydockOperationWorkLogType' => 'DrydockLogType', 'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DrydockRepositoryOperation' => array( 'DrydockDAO', @@ -6613,6 +6628,7 @@ phutil_register_library_map(array( 'DrydockSlotLockException' => 'Exception', 'DrydockSlotLockFailureLogType' => 'DrydockLogType', 'DrydockTestRepositoryOperation' => 'DrydockRepositoryOperationType', + 'DrydockTextLogType' => 'DrydockLogType', 'DrydockWebrootInterface' => 'DrydockInterface', 'DrydockWorker' => 'PhabricatorWorker', 'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', @@ -7155,13 +7171,8 @@ phutil_register_library_map(array( 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestDefaultEditCapability' => 'PhabricatorPolicyCapability', 'ManiphestDefaultViewCapability' => 'PhabricatorPolicyCapability', - 'ManiphestEditAssignCapability' => 'PhabricatorPolicyCapability', 'ManiphestEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ManiphestEditEngine' => 'PhabricatorEditEngine', - 'ManiphestEditPoliciesCapability' => 'PhabricatorPolicyCapability', - 'ManiphestEditPriorityCapability' => 'PhabricatorPolicyCapability', - 'ManiphestEditProjectsCapability' => 'PhabricatorPolicyCapability', - 'ManiphestEditStatusCapability' => 'PhabricatorPolicyCapability', 'ManiphestEmailCommand' => 'MetaMTAEmailTransactionCommand', 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', @@ -7466,7 +7477,7 @@ phutil_register_library_map(array( 'PHUIDiffTableOfContentsListView' => 'AphrontView', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDocumentSummaryView' => 'AphrontTagView', - 'PHUIDocumentViewPro' => 'AphrontTagView', + 'PHUIDocumentView' => 'AphrontTagView', 'PHUIFeedStoryExample' => 'PhabricatorUIExample', 'PHUIFeedStoryView' => 'AphrontView', 'PHUIFormDividerControl' => 'AphrontFormControl', @@ -7606,6 +7617,7 @@ phutil_register_library_map(array( 'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability', 'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability', + 'PeopleDisableUsersCapability' => 'PhabricatorPolicyCapability', 'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector', @@ -7722,7 +7734,6 @@ phutil_register_library_map(array( 'PhabricatorAuditActionConstants' => 'Phobject', 'PhabricatorAuditApplication' => 'PhabricatorApplication', 'PhabricatorAuditCommentEditor' => 'PhabricatorEditor', - 'PhabricatorAuditCommitStatusConstants' => 'Phobject', 'PhabricatorAuditController' => 'PhabricatorController', 'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorAuditInlineComment' => array( @@ -8305,10 +8316,7 @@ phutil_register_library_map(array( 'PhabricatorConfigManualActivity' => 'PhabricatorConfigEntryDAO', 'PhabricatorConfigModule' => 'Phobject', 'PhabricatorConfigModuleController' => 'PhabricatorConfigController', - 'PhabricatorConfigOption' => array( - 'Phobject', - 'PhabricatorMarkupInterface', - ), + 'PhabricatorConfigOption' => 'Phobject', 'PhabricatorConfigOptionType' => 'Phobject', 'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule', 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', @@ -10558,6 +10566,8 @@ phutil_register_library_map(array( 'PhabricatorUserCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'PhabricatorUserCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', + 'PhabricatorUserDisableTransaction' => 'PhabricatorUserTransactionType', + 'PhabricatorUserEditEngine' => 'PhabricatorEditEngine', 'PhabricatorUserEditor' => 'PhabricatorEditor', 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorUserEmail' => 'PhabricatorUserDAO', @@ -10587,7 +10597,6 @@ phutil_register_library_map(array( 'PhabricatorUserPreferencesTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorUserPreferencesTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', - 'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorUserProfileImageCacheType' => 'PhabricatorUserCacheType', 'PhabricatorUserRealNameField' => 'PhabricatorUserCustomField', 'PhabricatorUserRolesField' => 'PhabricatorUserCustomField', @@ -10596,7 +10605,9 @@ phutil_register_library_map(array( 'PhabricatorUserStatusField' => 'PhabricatorUserCustomField', 'PhabricatorUserTestCase' => 'PhabricatorTestCase', 'PhabricatorUserTitleField' => 'PhabricatorUserCustomField', - 'PhabricatorUserTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorUserTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorUserTransactionEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorUserTransactionType' => 'PhabricatorModularTransactionType', 'PhabricatorUsersEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorUsersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorUsersSearchField' => 'PhabricatorSearchTokenizerField', @@ -11145,29 +11156,35 @@ phutil_register_library_map(array( ), 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', - 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentEditTransaction', 'PhrictionDocumentController' => 'PhrictionController', 'PhrictionDocumentDatasource' => 'PhabricatorTypeaheadDatasource', - 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentVersionTransaction', + 'PhrictionDocumentDraftTransaction' => 'PhrictionDocumentEditTransaction', + 'PhrictionDocumentEditEngine' => 'PhabricatorEditEngine', + 'PhrictionDocumentEditTransaction' => 'PhrictionDocumentVersionTransaction', 'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine', 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', 'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup', - 'PhrictionDocumentMoveAwayTransaction' => 'PhrictionDocumentTransactionType', - 'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentMoveAwayTransaction' => 'PhrictionDocumentVersionTransaction', + 'PhrictionDocumentMoveToTransaction' => 'PhrictionDocumentVersionTransaction', 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentPolicyCodex' => 'PhabricatorPolicyCodex', + 'PhrictionDocumentPublishTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhrictionDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrictionDocumentStatus' => 'PhabricatorObjectStatus', 'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField', - 'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentVersionTransaction', 'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType', + 'PhrictionDocumentVersionTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionEditController' => 'PhrictionController', + 'PhrictionEditEngineController' => 'PhrictionController', 'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionHistoryController' => 'PhrictionController', 'PhrictionInfoConduitAPIMethod' => 'PhrictionConduitAPIMethod', @@ -11175,6 +11192,7 @@ phutil_register_library_map(array( 'PhrictionMarkupPreviewController' => 'PhabricatorController', 'PhrictionMoveController' => 'PhrictionController', 'PhrictionNewController' => 'PhrictionController', + 'PhrictionPublishController' => 'PhrictionController', 'PhrictionRemarkupRule' => 'PhutilRemarkupRule', 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', @@ -11400,6 +11418,7 @@ phutil_register_library_map(array( 'TransactionSearchConduitAPIMethod' => 'ConduitAPIMethod', 'UserConduitAPIMethod' => 'ConduitAPIMethod', 'UserDisableConduitAPIMethod' => 'UserConduitAPIMethod', + 'UserEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'UserEnableConduitAPIMethod' => 'UserConduitAPIMethod', 'UserFindConduitAPIMethod' => 'UserConduitAPIMethod', 'UserQueryConduitAPIMethod' => 'UserConduitAPIMethod', diff --git a/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php b/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php index 2c68ed0374..30c371d6f9 100644 --- a/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacInterfaceEditEngine.php @@ -127,6 +127,9 @@ final class AlmanacInterfaceEditEngine } protected function getObjectCreateCancelURI($object) { + if ($this->getDevice()) { + return $this->getDevice()->getURI(); + } return '/almanac/interface/'; } diff --git a/src/applications/audit/conduit/AuditQueryConduitAPIMethod.php b/src/applications/audit/conduit/AuditQueryConduitAPIMethod.php index f3858afbf8..d0d3779c2a 100644 --- a/src/applications/audit/conduit/AuditQueryConduitAPIMethod.php +++ b/src/applications/audit/conduit/AuditQueryConduitAPIMethod.php @@ -68,17 +68,17 @@ final class AuditQueryConduitAPIMethod extends AuditConduitAPIMethod { $status_map = array( self::AUDIT_LEGACYSTATUS_OPEN => array( - PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT, - PhabricatorAuditCommitStatusConstants::CONCERN_RAISED, + DiffusionCommitAuditStatus::NEEDS_AUDIT, + DiffusionCommitAuditStatus::CONCERN_RAISED, ), self::AUDIT_LEGACYSTATUS_CONCERN => array( - PhabricatorAuditCommitStatusConstants::CONCERN_RAISED, + DiffusionCommitAuditStatus::CONCERN_RAISED, ), self::AUDIT_LEGACYSTATUS_ACCEPTED => array( - PhabricatorAuditCommitStatusConstants::FULLY_AUDITED, + DiffusionCommitAuditStatus::AUDITED, ), self::AUDIT_LEGACYSTATUS_PARTIAL => array( - PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED, + DiffusionCommitAuditStatus::PARTIALLY_AUDITED, ), ); diff --git a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php deleted file mode 100644 index 31bb2c22f7..0000000000 --- a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php +++ /dev/null @@ -1,92 +0,0 @@ - pht('No Audits'), - self::NEEDS_AUDIT => pht('Audit Required'), - self::CONCERN_RAISED => pht('Concern Raised'), - self::NEEDS_VERIFICATION => pht('Needs Verification'), - self::PARTIALLY_AUDITED => pht('Partially Audited'), - self::FULLY_AUDITED => pht('Audited'), - ); - - return $map; - } - - public static function getStatusName($code) { - return idx(self::getStatusNameMap(), $code, pht('Unknown')); - } - - public static function getOpenStatusConstants() { - return array( - self::CONCERN_RAISED, - self::NEEDS_AUDIT, - self::NEEDS_VERIFICATION, - self::PARTIALLY_AUDITED, - ); - } - - public static function getStatusColor($code) { - switch ($code) { - case self::CONCERN_RAISED: - $color = 'red'; - break; - case self::NEEDS_AUDIT: - $color = 'orange'; - break; - case self::PARTIALLY_AUDITED: - $color = 'yellow'; - break; - case self::FULLY_AUDITED: - $color = 'green'; - break; - case self::NONE: - $color = 'bluegrey'; - break; - case self::NEEDS_VERIFICATION: - $color = 'indigo'; - break; - default: - $color = null; - break; - } - return $color; - } - - public static function getStatusIcon($code) { - switch ($code) { - case self::CONCERN_RAISED: - $icon = 'fa-times-circle'; - break; - case self::NEEDS_AUDIT: - $icon = 'fa-exclamation-circle'; - break; - case self::PARTIALLY_AUDITED: - $icon = 'fa-check-circle-o'; - break; - case self::FULLY_AUDITED: - $icon = 'fa-check-circle'; - break; - case self::NONE: - $icon = 'fa-check'; - break; - case self::NEEDS_VERIFICATION: - $icon = 'fa-refresh'; - break; - default: - $icon = null; - break; - } - return $icon; - } - -} diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 984e2c1472..cfa84b8bd7 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -206,12 +206,10 @@ final class PhabricatorAuditEditor $object->writeImportStatusFlag($import_status_flag); } - $partial_status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED; - // If the commit has changed state after this edit, add an informational // transaction about the state change. if ($old_status != $new_status) { - if ($new_status == $partial_status) { + if ($object->isAuditStatusPartiallyAudited()) { // This state isn't interesting enough to get a transaction. The // best way we could lead the user forward is something like "This // commit still requires additional audits." but that's redundant and @@ -258,19 +256,31 @@ final class PhabricatorAuditEditor $this->didExpandInlineState = true; $actor_phid = $this->getActingAsPHID(); - $actor_is_author = ($object->getAuthorPHID() == $actor_phid); - if (!$actor_is_author) { - break; - } + $author_phid = $object->getAuthorPHID(); + $actor_is_author = ($actor_phid == $author_phid); $state_map = PhabricatorTransactions::getInlineStateMap(); - $inlines = id(new DiffusionDiffInlineCommentQuery()) + $query = id(new DiffusionDiffInlineCommentQuery()) ->setViewer($this->getActor()) ->withCommitPHIDs(array($object->getPHID())) - ->withFixedStates(array_keys($state_map)) + ->withFixedStates(array_keys($state_map)); + + $inlines = array(); + + $inlines[] = id(clone $query) + ->withAuthorPHIDs(array($actor_phid)) + ->withHasTransaction(false) ->execute(); + if ($actor_is_author) { + $inlines[] = id(clone $query) + ->withHasTransaction(true) + ->execute(); + } + + $inlines = array_mergev($inlines); + if (!$inlines) { break; } diff --git a/src/applications/audit/management/PhabricatorAuditSynchronizeManagementWorkflow.php b/src/applications/audit/management/PhabricatorAuditSynchronizeManagementWorkflow.php index db6ce096c4..96d06e65c2 100644 --- a/src/applications/audit/management/PhabricatorAuditSynchronizeManagementWorkflow.php +++ b/src/applications/audit/management/PhabricatorAuditSynchronizeManagementWorkflow.php @@ -30,11 +30,11 @@ final class PhabricatorAuditSynchronizeManagementWorkflow continue; } - $old_status = $commit->getAuditStatus(); + $old_status = $commit->getAuditStatusObject(); $commit->updateAuditStatus($commit->getAudits()); - $new_status = $commit->getAuditStatus(); + $new_status = $commit->getAuditStatusObject(); - if ($old_status == $new_status) { + if ($old_status->getKey() == $new_status->getKey()) { echo tsprintf( "%s\n", pht( @@ -46,10 +46,8 @@ final class PhabricatorAuditSynchronizeManagementWorkflow pht( 'Updating "%s": "%s" -> "%s".', $commit->getDisplayName(), - PhabricatorAuditCommitStatusConstants::getStatusName( - $old_status), - PhabricatorAuditCommitStatusConstants::getStatusName( - $new_status))); + $old_status->getName(), + $new_status->getName())); $commit->save(); } diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php index 993626e1e3..ee5105f193 100644 --- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php +++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php @@ -15,6 +15,7 @@ final class PhabricatorCommitSearchEngine return id(new DiffusionCommitQuery()) ->needAuditRequests(true) ->needCommitData(true) + ->needIdentities(true) ->needDrafts(true); } @@ -67,35 +68,50 @@ final class PhabricatorCommitSearchEngine ->setKey('responsiblePHIDs') ->setConduitKey('responsible') ->setAliases(array('responsible', 'responsibles', 'responsiblePHID')) - ->setDatasource(new DifferentialResponsibleDatasource()), + ->setDatasource(new DifferentialResponsibleDatasource()) + ->setDescription( + pht( + 'Find commits where given users, projects, or packages are '. + 'responsible for the next steps in the audit workflow.')), id(new PhabricatorUsersSearchField()) ->setLabel(pht('Authors')) ->setKey('authorPHIDs') ->setConduitKey('authors') - ->setAliases(array('author', 'authors', 'authorPHID')), + ->setAliases(array('author', 'authors', 'authorPHID')) + ->setDescription(pht('Find commits authored by particular users.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Auditors')) ->setKey('auditorPHIDs') ->setConduitKey('auditors') ->setAliases(array('auditor', 'auditors', 'auditorPHID')) - ->setDatasource(new DiffusionAuditorFunctionDatasource()), + ->setDatasource(new DiffusionAuditorFunctionDatasource()) + ->setDescription( + pht( + 'Find commits where given users, projects, or packages are '. + 'auditors.')), id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Audit Status')) ->setKey('statuses') ->setAliases(array('status')) - ->setOptions(PhabricatorAuditCommitStatusConstants::getStatusNameMap()), + ->setOptions(DiffusionCommitAuditStatus::newOptions()) + ->setDeprecatedOptions( + DiffusionCommitAuditStatus::newDeprecatedOptions()) + ->setDescription(pht('Find commits with given audit statuses.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Repositories')) ->setKey('repositoryPHIDs') ->setConduitKey('repositories') ->setAliases(array('repository', 'repositories', 'repositoryPHID')) - ->setDatasource(new DiffusionRepositoryFunctionDatasource()), + ->setDatasource(new DiffusionRepositoryFunctionDatasource()) + ->setDescription(pht('Find commits in particular repositories.')), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Packages')) ->setKey('packagePHIDs') ->setConduitKey('packages') ->setAliases(array('package', 'packages', 'packagePHID')) - ->setDatasource(new PhabricatorOwnersPackageDatasource()), + ->setDatasource(new PhabricatorOwnersPackageDatasource()) + ->setDescription( + pht('Find commits which affect given packages.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Unreachable')) ->setKey('unreachable') @@ -147,7 +163,7 @@ final class PhabricatorCommitSearchEngine case 'active': $bucket_key = DiffusionCommitRequiredActionResultBucket::BUCKETKEY; - $open = PhabricatorAuditCommitStatusConstants::getOpenStatusConstants(); + $open = DiffusionCommitAuditStatus::getOpenStatusConstants(); $query ->setParameter('responsiblePHIDs', array($viewer_phid)) diff --git a/src/applications/audit/view/PhabricatorAuditListView.php b/src/applications/audit/view/PhabricatorAuditListView.php index cb9fecac3a..fb56e7cd55 100644 --- a/src/applications/audit/view/PhabricatorAuditListView.php +++ b/src/applications/audit/view/PhabricatorAuditListView.php @@ -120,14 +120,11 @@ final class PhabricatorAuditListView extends AphrontView { $commit_desc = $this->getCommitDescription($commit_phid); $committed = phabricator_datetime($commit->getEpoch(), $viewer); - $status = $commit->getAuditStatus(); + $status = $commit->getAuditStatusObject(); - $status_text = - PhabricatorAuditCommitStatusConstants::getStatusName($status); - $status_color = - PhabricatorAuditCommitStatusConstants::getStatusColor($status); - $status_icon = - PhabricatorAuditCommitStatusConstants::getStatusIcon($status); + $status_text = $status->getName(); + $status_color = $status->getColor(); + $status_icon = $status->getIcon(); $author_phid = $commit->getAuthorPHID(); if ($author_phid) { diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 82303fff2b..6e40cdde98 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -490,8 +490,7 @@ final class PhabricatorAuthSessionEngine extends Phobject { PhabricatorAuthSession $session, $force = false) { - $until = $session->getHighSecurityUntil(); - if ($until > time() || $force) { + if ($session->isHighSecuritySession() || $force) { return new PhabricatorAuthHighSecurityToken(); } diff --git a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php index 10c44aaec0..373adfbb54 100644 --- a/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php +++ b/src/applications/auth/factor/PhabricatorTOTPAuthFactor.php @@ -154,6 +154,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { id(new PHUIFormNumberControl()) ->setName($this->getParameterName($config, 'totpcode')) ->setLabel(pht('App Code')) + ->setDisableAutocomplete(true) ->setCaption(pht('Factor Name: %s', $config->getFactorName())) ->setValue(idx($validation_result, 'value')) ->setError(idx($validation_result, 'error', true))); diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php index 77b666ea44..d8474085b2 100644 --- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php @@ -3,7 +3,7 @@ final class PhabricatorAuthSSHKeyQuery extends PhabricatorCursorPagedPolicyAwareQuery { - const AUTHFILE_CACHEKEY = 'ssh.authfile'; + const AUTHSTRUCT_CACHEKEY = 'ssh.authstruct'; private $ids; private $phids; @@ -13,7 +13,7 @@ final class PhabricatorAuthSSHKeyQuery public static function deleteSSHKeyCache() { $cache = PhabricatorCaches::getMutableCache(); - $authfile_key = self::AUTHFILE_CACHEKEY; + $authfile_key = self::AUTHSTRUCT_CACHEKEY; $cache->deleteKey($authfile_key); } diff --git a/src/applications/auth/storage/PhabricatorAuthPassword.php b/src/applications/auth/storage/PhabricatorAuthPassword.php index fe538e13a7..0b094cbb2f 100644 --- a/src/applications/auth/storage/PhabricatorAuthPassword.php +++ b/src/applications/auth/storage/PhabricatorAuthPassword.php @@ -184,6 +184,7 @@ final class PhabricatorAuthPassword public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } @@ -201,7 +202,7 @@ final class PhabricatorAuthPassword public function getExtendedPolicy($capability, PhabricatorUser $viewer) { return array( - array($this->getObject(), PhabricatorPolicyCapability::CAN_VIEW), + array($this->getObject(), $capability), ); } diff --git a/src/applications/auth/storage/PhabricatorAuthSession.php b/src/applications/auth/storage/PhabricatorAuthSession.php index b0b4996c93..cf707a053d 100644 --- a/src/applications/auth/storage/PhabricatorAuthSession.php +++ b/src/applications/auth/storage/PhabricatorAuthSession.php @@ -74,6 +74,22 @@ final class PhabricatorAuthSession extends PhabricatorAuthDAO } } + public function isHighSecuritySession() { + $until = $this->getHighSecurityUntil(); + + if (!$until) { + return false; + } + + $now = PhabricatorTime::getNow(); + if ($until < $now) { + return false; + } + + return true; + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/cache/spec/PhabricatorDataCacheSpec.php b/src/applications/cache/spec/PhabricatorDataCacheSpec.php index bf6a495f7f..103b0d8394 100644 --- a/src/applications/cache/spec/PhabricatorDataCacheSpec.php +++ b/src/applications/cache/spec/PhabricatorDataCacheSpec.php @@ -106,8 +106,21 @@ final class PhabricatorDataCacheSpec extends PhabricatorCacheSpec { $cache = $info['cache_list']; $state = array(); foreach ($cache as $item) { - $info = idx($item, 'info', ''); - $key = self::getKeyPattern($info); + // Some older versions of APCu report the cachekey as "key", while + // newer APCu and APC report it as "info". Just check both indexes + // for commpatibility. See T13164 for details. + + $info = idx($item, 'info'); + if ($info === null) { + $info = idx($item, 'key'); + } + + if ($info === null) { + $key = ''; + } else { + $key = self::getKeyPattern($info); + } + if (empty($state[$key])) { $state[$key] = array( 'max' => 0, diff --git a/src/applications/conduit/data/ConduitConstantDescription.php b/src/applications/conduit/data/ConduitConstantDescription.php new file mode 100644 index 0000000000..c9b2d00f6b --- /dev/null +++ b/src/applications/conduit/data/ConduitConstantDescription.php @@ -0,0 +1,36 @@ +key = $key; + return $this; + } + + public function getKey() { + return $this->key; + } + + public function setValue($value) { + $this->value = $value; + return $this; + } + + public function getValue() { + return $this->value; + } + + public function setIsDeprecated($is_deprecated) { + $this->isDeprecated = $is_deprecated; + return $this; + } + + public function getIsDeprecated() { + return $this->isDeprecated; + } + +} diff --git a/src/applications/conduit/management/PhabricatorConduitCallManagementWorkflow.php b/src/applications/conduit/management/PhabricatorConduitCallManagementWorkflow.php index 6cb3bd2409..f9ba48b372 100644 --- a/src/applications/conduit/management/PhabricatorConduitCallManagementWorkflow.php +++ b/src/applications/conduit/management/PhabricatorConduitCallManagementWorkflow.php @@ -21,6 +21,13 @@ final class PhabricatorConduitCallManagementWorkflow 'File to read parameters from, or "-" to read from '. 'stdin.'), ), + array( + 'name' => 'as', + 'param' => 'username', + 'help' => pht( + 'Execute the call as the given user. (If omitted, the call will '. + 'be executed as an omnipotent user.)'), + ), )); } @@ -39,6 +46,22 @@ final class PhabricatorConduitCallManagementWorkflow pht('Specify a file to read parameters from with "--input".')); } + $as = $args->getArg('as'); + if (strlen($as)) { + $actor = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withUsernames(array($as)) + ->executeOne(); + if (!$actor) { + throw new PhutilArgumentUsageException( + pht( + 'No such user "%s" exists.', + $as)); + } + } else { + $actor = $viewer; + } + if ($input === '-') { fprintf(STDERR, tsprintf("%s\n", pht('Reading input from stdin...'))); $input_json = file_get_contents('php://stdin'); @@ -49,7 +72,7 @@ final class PhabricatorConduitCallManagementWorkflow $params = phutil_json_decode($input_json); $result = id(new ConduitCall($method, $params)) - ->setUser($viewer) + ->setUser($actor) ->execute(); $output = array( diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php index 84fd5bedf2..4bf51602ba 100644 --- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php @@ -84,8 +84,6 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { $issue->addPhabricatorConfig($key); } } - - $this->executeManiphestFieldChecks(); } /** @@ -361,69 +359,4 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { return $ancient_config; } - private function executeManiphestFieldChecks() { - $maniphest_appclass = 'PhabricatorManiphestApplication'; - if (!PhabricatorApplication::isClassInstalled($maniphest_appclass)) { - return; - } - - $capabilities = array( - ManiphestEditAssignCapability::CAPABILITY, - ManiphestEditPoliciesCapability::CAPABILITY, - ManiphestEditPriorityCapability::CAPABILITY, - ManiphestEditProjectsCapability::CAPABILITY, - ManiphestEditStatusCapability::CAPABILITY, - ); - - // Check for any of these capabilities set to anything other than - // "All Users". - - $any_set = false; - $app = new PhabricatorManiphestApplication(); - foreach ($capabilities as $capability) { - $setting = $app->getPolicy($capability); - if ($setting != PhabricatorPolicies::POLICY_USER) { - $any_set = true; - break; - } - } - - if (!$any_set) { - return; - } - - $issue_summary = pht( - 'Maniphest is currently configured with deprecated policy settings '. - 'which will be removed in a future version of Phabricator.'); - - - $message = pht( - 'Some policy settings in Maniphest are now deprecated and will be '. - 'removed in a future version of Phabricator. You are currently using '. - 'at least one of these settings.'. - "\n\n". - 'The deprecated settings are "Can Assign Tasks", '. - '"Can Edit Task Policies", "Can Prioritize Tasks", '. - '"Can Edit Task Projects", and "Can Edit Task Status". You can '. - 'find these settings in Applications, or follow the link below.'. - "\n\n". - 'You can find discussion of this change (including rationale and '. - 'recommendations on how to configure similar features) in the upstream, '. - 'at the link below.'. - "\n\n". - 'To resolve this issue, set all of these policies to "All Users" after '. - 'making any necessary form customization changes.'); - - $more_href = 'https://secure.phabricator.com/T10003'; - $edit_href = '/applications/view/PhabricatorManiphestApplication/'; - - $issue = $this->newIssue('maniphest.T10003-per-field-policies') - ->setShortName(pht('Deprecated Policies')) - ->setName(pht('Deprecated Maniphest Field Policies')) - ->setSummary($issue_summary) - ->setMessage($message) - ->addLink($more_href, pht('Learn More: Upstream Discussion')) - ->addLink($edit_href, pht('Edit These Settings')); - } - } diff --git a/src/applications/config/check/PhabricatorManualActivitySetupCheck.php b/src/applications/config/check/PhabricatorManualActivitySetupCheck.php index 44db1b57a6..c5c756e49b 100644 --- a/src/applications/config/check/PhabricatorManualActivitySetupCheck.php +++ b/src/applications/config/check/PhabricatorManualActivitySetupCheck.php @@ -13,63 +13,134 @@ final class PhabricatorManualActivitySetupCheck foreach ($activities as $activity) { $type = $activity->getActivityType(); - // For now, there is only one type of manual activity. It's not clear - // if we're really going to have too much more of this stuff so this - // is a bit under-designed for now. + switch ($type) { + case PhabricatorConfigManualActivity::TYPE_REINDEX: + $this->raiseSearchReindexIssue(); + break; - $activity_name = pht('Rebuild Search Index'); - $activity_summary = pht( - 'The search index algorithm has been updated and the index needs '. - 'be rebuilt.'); + case PhabricatorConfigManualActivity::TYPE_IDENTITIES: + $this->raiseRebuildIdentitiesIssue(); + break; - $message = array(); - - $message[] = pht( - 'The indexing algorithm for the fulltext search index has been '. - 'updated and the index needs to be rebuilt. Until you rebuild the '. - 'index, global search (and other fulltext search) will not '. - 'function correctly.'); - - $message[] = pht( - 'You can rebuild the search index while Phabricator is running.'); - - $message[] = pht( - 'To rebuild the index, run this command:'); - - $message[] = phutil_tag( - 'pre', - array(), - (string)csprintf( - 'phabricator/ $ ./bin/search index --all --force --background')); - - $message[] = pht( - 'You can find more information about rebuilding the search '. - 'index here: %s', - phutil_tag( - 'a', - array( - 'href' => 'https://phurl.io/u/reindex', - 'target' => '_blank', - ), - 'https://phurl.io/u/reindex')); - - $message[] = pht( - 'After rebuilding the index, run this command to clear this setup '. - 'warning:'); - - $message[] = phutil_tag( - 'pre', - array(), - (string)csprintf('phabricator/ $ ./bin/config done %R', $type)); - - $activity_message = phutil_implode_html("\n\n", $message); - - $this->newIssue('manual.'.$type) - ->setName($activity_name) - ->setSummary($activity_summary) - ->setMessage($activity_message); + default: + } } + } + private function raiseSearchReindexIssue() { + $activity_name = pht('Rebuild Search Index'); + $activity_summary = pht( + 'The search index algorithm has been updated and the index needs '. + 'be rebuilt.'); + + $message = array(); + + $message[] = pht( + 'The indexing algorithm for the fulltext search index has been '. + 'updated and the index needs to be rebuilt. Until you rebuild the '. + 'index, global search (and other fulltext search) will not '. + 'function correctly.'); + + $message[] = pht( + 'You can rebuild the search index while Phabricator is running.'); + + $message[] = pht( + 'To rebuild the index, run this command:'); + + $message[] = phutil_tag( + 'pre', + array(), + (string)csprintf( + 'phabricator/ $ ./bin/search index --all --force --background')); + + $message[] = pht( + 'You can find more information about rebuilding the search '. + 'index here: %s', + phutil_tag( + 'a', + array( + 'href' => 'https://phurl.io/u/reindex', + 'target' => '_blank', + ), + 'https://phurl.io/u/reindex')); + + $message[] = pht( + 'After rebuilding the index, run this command to clear this setup '. + 'warning:'); + + $message[] = phutil_tag( + 'pre', + array(), + 'phabricator/ $ ./bin/config done reindex'); + + $activity_message = phutil_implode_html("\n\n", $message); + + $this->newIssue('manual.reindex') + ->setName($activity_name) + ->setSummary($activity_summary) + ->setMessage($activity_message); + } + + private function raiseRebuildIdentitiesIssue() { + $activity_name = pht('Rebuild Repository Identities'); + $activity_summary = pht( + 'The mapping from VCS users to Phabricator users has changed '. + 'and must be rebuilt.'); + + $message = array(); + + $message[] = pht( + 'The way Phabricator attributes VCS activity to Phabricator users '. + 'has changed. There is a new indirection layer between the strings '. + 'that appear as VCS authors and committers (such as "John Developer '. + '") and the Phabricator user that gets associated '. + 'with VCS commits. This is to support situations where users '. + 'are incorrectly associated with commits by Phabricator making bad '. + 'guesses about the identity of the corresponding Phabricator user. '. + 'This also helps with situations where existing repositories are '. + 'imported without having created accounts for all the committers to '. + 'that repository. Until you rebuild these repository identities, you '. + 'are likely to encounter problems with future Phabricator features '. + 'which will rely on the existence of these identities.'); + + $message[] = pht( + 'You can rebuild repository identities while Phabricator is running.'); + + $message[] = pht( + 'To rebuild identities, run this command:'); + + $message[] = phutil_tag( + 'pre', + array(), + (string)csprintf( + 'phabricator/ $ ./bin/repository rebuild-identities --all')); + + $message[] = pht( + 'You can find more information about this new identity mapping '. + 'here: %s', + phutil_tag( + 'a', + array( + 'href' => 'https://phurl.io/u/repoIdentities', + 'target' => '_blank', + ), + 'https://phurl.io/u/repoIdentities')); + + $message[] = pht( + 'After rebuilding repository identities, run this command to clear '. + 'this setup warning:'); + + $message[] = phutil_tag( + 'pre', + array(), + 'phabricator/ $ ./bin/config done identities'); + + $activity_message = phutil_implode_html("\n\n", $message); + + $this->newIssue('manual.identities') + ->setName($activity_name) + ->setSummary($activity_summary) + ->setMessage($activity_message); } } diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 37f7db6c7c..224705e181 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -153,30 +153,16 @@ final class PhabricatorConfigEditController $e_value); } - $engine = new PhabricatorMarkupEngine(); - $engine->setViewer($viewer); - $engine->addObject($option, 'description'); - $engine->process(); - $description = phutil_tag( - 'div', - array( - 'class' => 'phabricator-remarkup', - ), - $engine->getOutput($option, 'description')); - $form ->setUser($viewer) ->addHiddenInput('issue', $request->getStr('issue')); - $description = $option->getDescription(); - if (strlen($description)) { - $description_view = new PHUIRemarkupView($viewer, $description); - - $form - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Description')) - ->setValue($description_view)); + $description = $option->newDescriptionRemarkupView($viewer); + if ($description) { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Description')) + ->setValue($description)); } if ($group) { diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php index 7068ddaecc..7a3f77dfea 100644 --- a/src/applications/config/controller/PhabricatorConfigGroupController.php +++ b/src/applications/config/controller/PhabricatorConfigGroupController.php @@ -60,17 +60,10 @@ final class PhabricatorConfigGroupController $db_values = mpull($db_values, null, 'getConfigKey'); } - $engine = id(new PhabricatorMarkupEngine()) - ->setViewer($this->getRequest()->getUser()); - foreach ($options as $option) { - $engine->addObject($option, 'summary'); - } - $engine->process(); - $list = new PHUIObjectItemListView(); $list->setBig(true); foreach ($options as $option) { - $summary = $engine->getOutput($option, 'summary'); + $summary = $option->getSummary(); $item = id(new PHUIObjectItemView()) ->setHeader($option->getKey()) diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php index 770f79a9f2..0ca94abe04 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -33,27 +33,26 @@ final class PhabricatorConfigIssueListController PhabricatorSetupCheck::GROUP_OTHER, 'fa-question-circle'); - $no_issues = null; - if (empty($issues)) { - $no_issues = id(new PHUIInfoView()) + $title = pht('Setup Issues'); + $header = $this->buildHeaderView($title); + + if (!$issues) { + $issue_list = id(new PHUIInfoView()) ->setTitle(pht('No Issues')) ->appendChild( pht('Your install has no current setup issues to resolve.')) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); + } else { + $issue_list = array( + $important, + $php, + $mysql, + $other, + ); + + $issue_list = $this->buildConfigBoxView(pht('Issues'), $issue_list); } - $title = pht('Setup Issues'); - $header = $this->buildHeaderView($title); - - $issue_list = array( - $important, - $php, - $mysql, - $other, - ); - - $issue_list = $this->buildConfigBoxView(pht('Issues'), $issue_list); - $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); @@ -62,10 +61,7 @@ final class PhabricatorConfigIssueListController ->setHeader($header) ->setNavigation($nav) ->setFixed(true) - ->setMainColumn(array( - $no_issues, - $issue_list, - )); + ->setMainColumn($issue_list); return $this->newPage() ->setTitle($title) diff --git a/src/applications/config/option/PhabricatorConfigOption.php b/src/applications/config/option/PhabricatorConfigOption.php index 11b8de2617..385af002c7 100644 --- a/src/applications/config/option/PhabricatorConfigOption.php +++ b/src/applications/config/option/PhabricatorConfigOption.php @@ -1,8 +1,7 @@ getKey().':'.$field; - } - - public function newMarkupEngine($field) { - return PhabricatorMarkupEngine::newMarkupEngine(array()); - } - - public function getMarkupText($field) { - switch ($field) { - case 'description': - $text = $this->getDescription(); - break; - case 'summary': - $text = $this->getSummary(); - break; + public function newDescriptionRemarkupView(PhabricatorUser $viewer) { + $description = $this->getDescription(); + if (!strlen($description)) { + return null; } - // TODO: We should probably implement this as a real Markup rule, but - // markup rules are a bit of a mess right now and it doesn't hurt us to - // fake this. - $text = preg_replace( + // TODO: Some day, we should probably implement this as a real rule. + $description = preg_replace( '/{{([^}]+)}}/', '[[/config/edit/\\1/ | \\1]]', - $text); + $description); - return $text; - } - - public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { - return $output; - } - - public function shouldUseMarkupCache($field) { - return false; + return new PHUIRemarkupView($viewer, $description); } } diff --git a/src/applications/config/option/PhabricatorPHDConfigOptions.php b/src/applications/config/option/PhabricatorPHDConfigOptions.php index bfe7c148c4..37fae45dfb 100644 --- a/src/applications/config/option/PhabricatorPHDConfigOptions.php +++ b/src/applications/config/option/PhabricatorPHDConfigOptions.php @@ -34,9 +34,14 @@ final class PhabricatorPHDConfigOptions ->setSummary(pht('Maximum taskmaster daemon pool size.')) ->setDescription( pht( - 'Maximum number of taskmaster daemons to run at once. Raising '. - 'this can increase the maximum throughput of the task queue. The '. - 'pool will automatically scale down when unutilized.')), + "Maximum number of taskmaster daemons to run at once. Raising ". + "this can increase the maximum throughput of the task queue. The ". + "pool will automatically scale down when unutilized.". + "\n\n". + "If you are running a cluster, this limit applies separately ". + "to each instance of `phd`. For example, if this limit is set ". + "to `4` and you have three hosts running daemons, the effective ". + "global limit will be 12.")), $this->newOption('phd.verbose', 'bool', false) ->setLocked(true) ->setBoolOptions( diff --git a/src/applications/config/storage/PhabricatorConfigManualActivity.php b/src/applications/config/storage/PhabricatorConfigManualActivity.php index 44c26f77dd..9952021575 100644 --- a/src/applications/config/storage/PhabricatorConfigManualActivity.php +++ b/src/applications/config/storage/PhabricatorConfigManualActivity.php @@ -7,6 +7,8 @@ final class PhabricatorConfigManualActivity protected $parameters = array(); const TYPE_REINDEX = 'reindex'; + const TYPE_IDENTITIES = 'identities'; + protected function getConfiguration() { return array( diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 3d451e78d3..3b4ad09e11 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -146,49 +146,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return $xactions; } - protected function requireCapabilities( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - parent::requireCapabilities($object, $xaction); - - switch ($xaction->getTransactionType()) { - case ConpherenceThreadParticipantsTransaction::TRANSACTIONTYPE: - $old_map = array_fuse($xaction->getOldValue()); - $new_map = array_fuse($xaction->getNewValue()); - - $add = array_keys(array_diff_key($new_map, $old_map)); - $rem = array_keys(array_diff_key($old_map, $new_map)); - - $actor_phid = $this->getActingAsPHID(); - - $is_join = (($add === array($actor_phid)) && !$rem); - $is_leave = (($rem === array($actor_phid)) && !$add); - - if ($is_join) { - // Anyone can join a thread they can see. - } else if ($is_leave) { - // Anyone can leave a thread. - } else { - // You need CAN_EDIT to add or remove participants. For additional - // discussion, see D17696 and T4411. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - } - break; - - case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: - case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - break; - } - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php b/src/applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php index 681c6d639c..c0e4c7c501 100644 --- a/src/applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php +++ b/src/applications/conpherence/xaction/ConpherenceThreadParticipantsTransaction.php @@ -114,4 +114,34 @@ final class ConpherenceThreadParticipantsTransaction return $errors; } + public function getRequiredCapabilities( + $object, + PhabricatorApplicationTransaction $xaction) { + + $old_map = array_fuse($xaction->getOldValue()); + $new_map = array_fuse($xaction->getNewValue()); + + $add = array_keys(array_diff_key($new_map, $old_map)); + $rem = array_keys(array_diff_key($old_map, $new_map)); + + $actor_phid = $this->getActingAsPHID(); + + $is_join = (($add === array($actor_phid)) && !$rem); + $is_leave = (($rem === array($actor_phid)) && !$add); + + if ($is_join) { + // Anyone can join a thread they can see. + return null; + } + + if ($is_leave) { + // Anyone can leave a thread. + return null; + } + + // You need CAN_EDIT to add or remove participants. For additional + // discussion, see D17696 and T4411. + return PhabricatorPolicyCapability::CAN_EDIT; + } + } diff --git a/src/applications/differential/application/PhabricatorDifferentialApplication.php b/src/applications/differential/application/PhabricatorDifferentialApplication.php index 52b222301a..c27d0da820 100644 --- a/src/applications/differential/application/PhabricatorDifferentialApplication.php +++ b/src/applications/differential/application/PhabricatorDifferentialApplication.php @@ -1,6 +1,7 @@ new)/' => 'DifferentialRevisionViewController', ), '/differential/' => array( - '(?:query/(?P[^/]+)/)?' - => 'DifferentialRevisionListController', + $this->getQueryRoutePattern() => 'DifferentialRevisionListController', 'diff/' => array( '(?P[1-9]\d*)/' => array( '' => 'DifferentialDiffViewController', diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php index 9d02202f9f..c41f951394 100644 --- a/src/applications/differential/controller/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/DifferentialChangesetViewController.php @@ -276,6 +276,7 @@ final class DifferentialChangesetViewController extends DifferentialController { ->setDiff($diff) ->setTitle(pht('Standalone View')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setIsStandalone(true) ->setParser($parser); if ($revision_id) { diff --git a/src/applications/differential/controller/DifferentialInlineCommentEditController.php b/src/applications/differential/controller/DifferentialInlineCommentEditController.php index 926158cb41..9741cc93ee 100644 --- a/src/applications/differential/controller/DifferentialInlineCommentEditController.php +++ b/src/applications/differential/controller/DifferentialInlineCommentEditController.php @@ -77,19 +77,17 @@ final class DifferentialInlineCommentEditController } protected function loadCommentForEdit($id) { - $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $this->getViewer(); $inline = $this->loadComment($id); - if (!$this->canEditInlineComment($user, $inline)) { + if (!$this->canEditInlineComment($viewer, $inline)) { throw new Exception(pht('That comment is not editable!')); } return $inline; } protected function loadCommentForDone($id) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $inline = $this->loadComment($id); if (!$inline) { @@ -120,19 +118,32 @@ final class DifferentialInlineCommentEditController throw new Exception(pht('Unable to load revision.')); } - if ($revision->getAuthorPHID() !== $viewer->getPHID()) { - throw new Exception(pht('You are not the revision owner.')); + $viewer_phid = $viewer->getPHID(); + $is_owner = ($viewer_phid == $revision->getAuthorPHID()); + $is_author = ($viewer_phid == $inline->getAuthorPHID()); + $is_draft = ($inline->isDraft()); + + if ($is_owner) { + // You own the revision, so you can mark the comment as "Done". + } else if ($is_author && $is_draft) { + // You made this comment and it's still a draft, so you can mark + // it as "Done". + } else { + throw new Exception( + pht( + 'You are not the revision owner, and this is not a draft comment '. + 'you authored.')); } return $inline; } private function canEditInlineComment( - PhabricatorUser $user, + PhabricatorUser $viewer, DifferentialInlineComment $inline) { // Only the author may edit a comment. - if ($inline->getAuthorPHID() != $user->getPHID()) { + if ($inline->getAuthorPHID() != $viewer->getPHID()) { return false; } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 33440e535f..8216c11557 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -463,7 +463,7 @@ final class DifferentialRevisionViewController } } - $tab_group = id(new PHUITabGroupView()); + $tab_group = new PHUITabGroupView(); if ($toc_view) { $tab_group->addTab( @@ -473,17 +473,30 @@ final class DifferentialRevisionViewController ->appendChild($toc_view)); } - $tab_group - ->addTab( - id(new PHUITabView()) - ->setName(pht('History')) - ->setKey('history') - ->appendChild($history)) - ->addTab( - id(new PHUITabView()) - ->setName(pht('Commits')) - ->setKey('commits') - ->appendChild($local_table)); + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('History')) + ->setKey('history') + ->appendChild($history)); + + $filetree_on = $viewer->compareUserSetting( + PhabricatorShowFiletreeSetting::SETTINGKEY, + PhabricatorShowFiletreeSetting::VALUE_ENABLE_FILETREE); + + $collapsed_key = PhabricatorFiletreeVisibleSetting::SETTINGKEY; + $filetree_collapsed = (bool)$viewer->getUserSetting($collapsed_key); + + // See PHI811. If the viewer has the file tree on, the files tab with the + // table of contents is redundant, so default to the "History" tab instead. + if ($filetree_on && !$filetree_collapsed) { + $tab_group->selectTab('history'); + } + + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Commits')) + ->setKey('commits') + ->appendChild($local_table)); $stack_graph = id(new DifferentialRevisionGraph()) ->setViewer($viewer) @@ -601,22 +614,15 @@ final class DifferentialRevisionViewController $crumbs->addTextCrumb($monogram); $crumbs->setBorder(true); - $filetree_on = $viewer->compareUserSetting( - PhabricatorShowFiletreeSetting::SETTINGKEY, - PhabricatorShowFiletreeSetting::VALUE_ENABLE_FILETREE); - $nav = null; if ($filetree_on && !$this->isVeryLargeDiff()) { - $collapsed_key = PhabricatorFiletreeVisibleSetting::SETTINGKEY; - $collapsed_value = $viewer->getUserSetting($collapsed_key); - $width_key = PhabricatorFiletreeWidthSetting::SETTINGKEY; $width_value = $viewer->getUserSetting($width_key); $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setTitle($monogram) ->setBaseURI(new PhutilURI($revision->getURI())) - ->setCollapsed((bool)$collapsed_value) + ->setCollapsed($filetree_collapsed) ->setWidth((int)$width_value) ->build($changesets); } @@ -1309,7 +1315,7 @@ final class DifferentialRevisionViewController } return id(new HarbormasterUnitSummaryView()) - ->setUser($viewer) + ->setViewer($viewer) ->setExcuse($excuse) ->setBuildable($diff->getBuildable()) ->setUnitMessages($diff->getUnitMessages()) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index f4dd3f9fe3..5bc33dabe1 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -248,19 +248,34 @@ final class DifferentialTransactionEditor $this->didExpandInlineState = true; $actor_phid = $this->getActingAsPHID(); - $actor_is_author = ($object->getAuthorPHID() == $actor_phid); - if (!$actor_is_author) { - break; - } + $author_phid = $object->getAuthorPHID(); + $actor_is_author = ($actor_phid == $author_phid); $state_map = PhabricatorTransactions::getInlineStateMap(); - $inlines = id(new DifferentialDiffInlineCommentQuery()) + $query = id(new DifferentialDiffInlineCommentQuery()) ->setViewer($this->getActor()) ->withRevisionPHIDs(array($object->getPHID())) - ->withFixedStates(array_keys($state_map)) + ->withFixedStates(array_keys($state_map)); + + $inlines = array(); + + // We're going to undraft any "done" marks on your own inlines. + $inlines[] = id(clone $query) + ->withAuthorPHIDs(array($actor_phid)) + ->withHasTransaction(false) ->execute(); + // If you're the author, we also undraft any "done" marks on other + // inlines. + if ($actor_is_author) { + $inlines[] = id(clone $query) + ->withHasTransaction(true) + ->execute(); + } + + $inlines = array_mergev($inlines); + if (!$inlines) { break; } @@ -621,8 +636,8 @@ final class DifferentialTransactionEditor $viewer = $this->requireActor(); - $body = new PhabricatorMetaMTAMailBody(); - $body->setViewer($this->requireActor()); + $body = id(new PhabricatorMetaMTAMailBody()) + ->setViewer($viewer); $revision_uri = $object->getURI(); $revision_uri = PhabricatorEnv::getProductionURI($revision_uri); @@ -671,7 +686,7 @@ final class DifferentialTransactionEditor $this->addCustomFieldsToMailBody($body, $object, $xactions); - if (!$this->getIsNewObject()) { + if (!$this->isFirstBroadcast()) { $body->addLinkSection(pht('CHANGES SINCE LAST ACTION'), $new_uri); } @@ -892,6 +907,17 @@ final class DifferentialTransactionEditor array $inlines, PhabricatorMetaMTAMailBody $body) { + $limit = 100; + $limit_note = null; + if (count($inlines) > $limit) { + $limit_note = pht( + '(Showing first %s of %s inline comments.)', + new PhutilNumber($limit), + phutil_count($inlines)); + + $inlines = array_slice($inlines, 0, $limit, true); + } + $section = id(new DifferentialInlineCommentMailView()) ->setViewer($this->getActor()) ->setInlines($inlines) @@ -900,6 +926,9 @@ final class DifferentialTransactionEditor $header = pht('INLINE COMMENTS'); $section_text = "\n".$section->getPlaintext(); + if ($limit_note) { + $section_text = $limit_note."\n".$section_text; + } $style = array( 'margin: 6px 0 12px 0;', @@ -912,6 +941,16 @@ final class DifferentialTransactionEditor ), $section->getHTML()); + if ($limit_note) { + $section_html = array( + phutil_tag( + 'em', + array(), + $limit_note), + $section_html, + ); + } + $body->addPlaintextSection($header, $section_text, false); $body->addHTMLSection($header, $section_html); } diff --git a/src/applications/differential/engine/DifferentialChangesetEngine.php b/src/applications/differential/engine/DifferentialChangesetEngine.php index e8a55a1b0e..d72db025ad 100644 --- a/src/applications/differential/engine/DifferentialChangesetEngine.php +++ b/src/applications/differential/engine/DifferentialChangesetEngine.php @@ -88,6 +88,20 @@ final class DifferentialChangesetEngine extends Phobject { private function detectCopiedCode(array $changesets) { + // See PHI944. If the total number of changed lines is excessively large, + // don't bother with copied code detection. This can take a lot of time and + // memory and it's not generally of any use for very large changes. + $max_size = 65535; + + $total_size = 0; + foreach ($changesets as $changeset) { + $total_size += ($changeset->getAddLines() + $changeset->getDelLines()); + } + + if ($total_size > $max_size) { + return; + } + $min_width = 30; $min_lines = 3; diff --git a/src/applications/differential/harbormaster/DifferentialBuildableEngine.php b/src/applications/differential/harbormaster/DifferentialBuildableEngine.php index df38885edf..8554f7be25 100644 --- a/src/applications/differential/harbormaster/DifferentialBuildableEngine.php +++ b/src/applications/differential/harbormaster/DifferentialBuildableEngine.php @@ -54,4 +54,28 @@ final class DifferentialBuildableEngine $this->applyTransactions(array($xaction)); } + public function getAuthorIdentity() { + $object = $this->getObject(); + + if ($object instanceof DifferentialRevision) { + $object = $object->loadActiveDiff(); + } + + $authorship = $object->getDiffAuthorshipDict(); + if (!isset($authorship['authorName'])) { + return null; + } + + $name = $authorship['authorName']; + $address = idx($authorship, 'authorEmail'); + + $full = id(new PhutilEmailAddress()) + ->setDisplayName($name) + ->setAddress($address); + + return id(new PhabricatorRepositoryIdentity()) + ->setIdentityName((string)$full) + ->makeEphemeral(); + } + } diff --git a/src/applications/differential/herald/DifferentialRevisionSummaryHeraldField.php b/src/applications/differential/herald/DifferentialRevisionSummaryHeraldField.php index eafc9bc990..587468d549 100644 --- a/src/applications/differential/herald/DifferentialRevisionSummaryHeraldField.php +++ b/src/applications/differential/herald/DifferentialRevisionSummaryHeraldField.php @@ -10,10 +10,7 @@ final class DifferentialRevisionSummaryHeraldField } public function getHeraldFieldValue($object) { - // NOTE: For historical reasons, this field includes the test plan. We - // could maybe try to fix this some day, but it probably aligns reasonably - // well with user expectation without harming anything. - return $object->getSummary()."\n\n".$object->getTestPlan(); + return $object->getSummary(); } protected function getHeraldFieldStandardType() { diff --git a/src/applications/differential/herald/DifferentialRevisionTestPlanHeraldField.php b/src/applications/differential/herald/DifferentialRevisionTestPlanHeraldField.php new file mode 100644 index 0000000000..bb2e6d0910 --- /dev/null +++ b/src/applications/differential/herald/DifferentialRevisionTestPlanHeraldField.php @@ -0,0 +1,20 @@ +getTestPlan(); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_TEXT; + } + +} diff --git a/src/applications/differential/mail/DifferentialInlineCommentMailView.php b/src/applications/differential/mail/DifferentialInlineCommentMailView.php index 57958202b1..0bc914914b 100644 --- a/src/applications/differential/mail/DifferentialInlineCommentMailView.php +++ b/src/applications/differential/mail/DifferentialInlineCommentMailView.php @@ -345,7 +345,13 @@ final class DifferentialInlineCommentMailView $offset_mode = 'old'; } + // See PHI894. Use the parse cache since we can end up with a large + // rendering cost otherwise when users or bots leave hundreds of inline + // comments on diffs with long recipient lists. + $cache_key = $changeset->getID(); + $parser = id(new DifferentialChangesetParser()) + ->setRenderCacheKey($cache_key) ->setUser($viewer) ->setChangeset($changeset) ->setOffsetMode($offset_mode) @@ -440,7 +446,18 @@ final class DifferentialInlineCommentMailView 'style' => implode(' ', $link_style), 'href' => $link_href, ), - pht('View Inline')); + array( + pht('View Inline'), + + // See PHI920. Add a space after the link so we render this into + // the document: + // + // View Inline filename.txt + // + // Otherwise, we render "Inlinefilename.txt" and double-clicking + // the file name selects the word "Inline" as well. + ' ', + )); } else { $link = null; } diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 727a4ca184..bc93de2db2 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -25,6 +25,8 @@ final class DifferentialRevisionQuery private $updatedEpochMax; private $statuses; private $isOpen; + private $createdEpochMin; + private $createdEpochMax; const ORDER_MODIFIED = 'order-modified'; const ORDER_CREATED = 'order-created'; @@ -206,6 +208,12 @@ final class DifferentialRevisionQuery return $this; } + public function withCreatedEpochBetween($min, $max) { + $this->createdEpochMin = $min; + $this->createdEpochMax = $max; + return $this; + } + /** * Set whether or not the query should load the active diff for each @@ -687,6 +695,20 @@ final class DifferentialRevisionQuery $this->updatedEpochMax); } + if ($this->createdEpochMin !== null) { + $where[] = qsprintf( + $conn_r, + 'r.dateCreated >= %d', + $this->createdEpochMin); + } + + if ($this->createdEpochMax !== null) { + $where[] = qsprintf( + $conn_r, + 'r.dateCreated <= %d', + $this->createdEpochMax); + } + if ($this->statuses !== null) { $where[] = qsprintf( $conn_r, diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index ec56f6ac8a..1d682cd8a8 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -45,6 +45,12 @@ final class DifferentialRevisionSearchEngine $query->withStatuses($map['statuses']); } + if ($map['createdStart'] || $map['createdEnd']) { + $query->withCreatedEpochBetween( + $map['createdStart'], + $map['createdEnd']); + } + return $query; } @@ -84,6 +90,16 @@ final class DifferentialRevisionSearchEngine ->setDatasource(new DifferentialRevisionStatusFunctionDatasource()) ->setDescription( pht('Find revisions with particular statuses.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart') + ->setDescription( + pht('Find revisions created at or after a particular time.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd') + ->setDescription( + pht('Find revisions created at or before a particular time.')), ); } @@ -277,4 +293,77 @@ final class DifferentialRevisionSearchEngine return $result; } + protected function newExportFields() { + $fields = array( + id(new PhabricatorStringExportField()) + ->setKey('monogram') + ->setLabel(pht('Monogram')), + id(new PhabricatorPHIDExportField()) + ->setKey('authorPHID') + ->setLabel(pht('Author PHID')), + id(new PhabricatorStringExportField()) + ->setKey('author') + ->setLabel(pht('Author')), + id(new PhabricatorStringExportField()) + ->setKey('status') + ->setLabel(pht('Status')), + id(new PhabricatorStringExportField()) + ->setKey('statusName') + ->setLabel(pht('Status Name')), + id(new PhabricatorURIExportField()) + ->setKey('uri') + ->setLabel(pht('URI')), + id(new PhabricatorStringExportField()) + ->setKey('title') + ->setLabel(pht('Title')), + id(new PhabricatorStringExportField()) + ->setKey('summary') + ->setLabel(pht('Summary')), + id(new PhabricatorStringExportField()) + ->setKey('testPlan') + ->setLabel(pht('Test Plan')), + ); + + return $fields; + } + + protected function newExportData(array $revisions) { + $viewer = $this->requireViewer(); + + $phids = array(); + foreach ($revisions as $revision) { + $phids[] = $revision->getAuthorPHID(); + } + $handles = $viewer->loadHandles($phids); + + $export = array(); + foreach ($revisions as $revision) { + + $author_phid = $revision->getAuthorPHID(); + if ($author_phid) { + $author_name = $handles[$author_phid]->getName(); + } else { + $author_name = null; + } + + $status = $revision->getStatusObject(); + $status_name = $status->getDisplayName(); + $status_value = $status->getKey(); + + $export[] = array( + 'monogram' => $revision->getMonogram(), + 'authorPHID' => $author_phid, + 'author' => $author_name, + 'status' => $status_value, + 'statusName' => $status_name, + 'uri' => PhabricatorEnv::getProductionURI($revision->getURI()), + 'title' => (string)$revision->getTitle(), + 'summary' => (string)$revision->getSummary(), + 'testPlan' => (string)$revision->getTestPlan(), + ); + } + + return $export; + } + } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index a432c02a96..9b169f3c67 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -1152,6 +1152,20 @@ final class DifferentialRevision extends DifferentialDAO ->setKey('testPlan') ->setType('string') ->setDescription(pht('Revision test plan.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('isDraft') + ->setType('bool') + ->setDescription( + pht( + 'True if this revision is in any draft state, and thus not '. + 'notifying reviewers and subscribers about changes.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('holdAsDraft') + ->setType('bool') + ->setDescription( + pht( + 'True if this revision is being held as a draft. It will not be '. + 'automatically submitted for review even if tests pass.')), ); } @@ -1172,6 +1186,8 @@ final class DifferentialRevision extends DifferentialDAO 'diffPHID' => $this->getActiveDiffPHID(), 'summary' => $this->getSummary(), 'testPlan' => $this->getTestPlan(), + 'isDraft' => !$this->getShouldBroadcast(), + 'holdAsDraft' => (bool)$this->getHoldAsDraft(), ); } diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 59f73b5465..14de553e59 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -10,6 +10,7 @@ final class DifferentialChangesetListView extends AphrontView { private $whitespace; private $background; private $header; + private $isStandalone; private $standaloneURI; private $leftRawFileURI; @@ -124,6 +125,15 @@ final class DifferentialChangesetListView extends AphrontView { return $this; } + public function setIsStandalone($is_standalone) { + $this->isStandalone = $is_standalone; + return $this; + } + + public function getIsStandalone() { + return $this->isStandalone; + } + public function setBackground($background) { $this->background = $background; return $this; @@ -219,6 +229,7 @@ final class DifferentialChangesetListView extends AphrontView { 'changesetViewIDs' => $ids, 'inlineURI' => $this->inlineURI, 'inlineListURI' => $this->inlineListURI, + 'isStandalone' => $this->getIsStandalone(), 'pht' => array( 'Open in Editor' => pht('Open in Editor'), 'Show All Context' => pht('Show All Context'), @@ -298,6 +309,7 @@ final class DifferentialChangesetListView extends AphrontView { 'Show All Inlines' => pht('Show All Inlines'), 'List Inline Comments' => pht('List Inline Comments'), + 'Display Options' => pht('Display Options'), 'Hide or show all inline comments.' => pht('Hide or show all inline comments.'), diff --git a/src/applications/diffusion/DiffusionCommitAuditStatus.php b/src/applications/diffusion/DiffusionCommitAuditStatus.php new file mode 100644 index 0000000000..95d0e52213 --- /dev/null +++ b/src/applications/diffusion/DiffusionCommitAuditStatus.php @@ -0,0 +1,173 @@ + $spec) { + if (isset($spec['legacy'])) { + $modern[$spec['legacy']] = $key; + } + } + + foreach ($values as $key => $value) { + $values[$key] = idx($modern, $value, $value); + } + + return $values; + } + + public static function newForStatus($status) { + $result = new self(); + + $result->key = $status; + + $map = self::getMap(); + if (isset($map[$status])) { + $result->spec = $map[$status]; + } + + return $result; + } + + public function getKey() { + return $this->key; + } + + public function getIcon() { + return idx($this->spec, 'icon'); + } + + public function getColor() { + return idx($this->spec, 'color'); + } + + public function getAnsiColor() { + return idx($this->spec, 'color.ansi'); + } + + public function getName() { + return idx($this->spec, 'name', pht('Unknown ("%s")', $this->key)); + } + + public function isNoAudit() { + return ($this->key == self::NONE); + } + + public function isNeedsAudit() { + return ($this->key == self::NEEDS_AUDIT); + } + + public function isConcernRaised() { + return ($this->key == self::CONCERN_RAISED); + } + + public function isNeedsVerification() { + return ($this->key == self::NEEDS_VERIFICATION); + } + + public function isPartiallyAudited() { + return ($this->key == self::PARTIALLY_AUDITED); + } + + public function isAudited() { + return ($this->key == self::AUDITED); + } + + public function getIsClosed() { + return idx($this->spec, 'closed'); + } + + public static function getOpenStatusConstants() { + $constants = array(); + foreach (self::getMap() as $key => $map) { + if (!$map['closed']) { + $constants[] = $key; + } + } + return $constants; + } + + public static function newOptions() { + $map = self::getMap(); + return ipull($map, 'name'); + } + + public static function newDeprecatedOptions() { + $map = self::getMap(); + + $results = array(); + foreach ($map as $key => $spec) { + if (isset($spec['legacy'])) { + $results[$spec['legacy']] = $key; + } + } + + return $results; + } + + private static function getMap() { + return array( + self::NONE => array( + 'name' => pht('No Audits'), + 'legacy' => 0, + 'icon' => 'fa-check', + 'color' => 'bluegrey', + 'closed' => true, + 'color.ansi' => null, + ), + self::NEEDS_AUDIT => array( + 'name' => pht('Audit Required'), + 'legacy' => 1, + 'icon' => 'fa-exclamation-circle', + 'color' => 'orange', + 'closed' => false, + 'color.ansi' => 'magenta', + ), + self::CONCERN_RAISED => array( + 'name' => pht('Concern Raised'), + 'legacy' => 2, + 'icon' => 'fa-times-circle', + 'color' => 'red', + 'closed' => false, + 'color.ansi' => 'red', + ), + self::PARTIALLY_AUDITED => array( + 'name' => pht('Partially Audited'), + 'legacy' => 3, + 'icon' => 'fa-check-circle-o', + 'color' => 'yellow', + 'closed' => false, + 'color.ansi' => 'yellow', + ), + self::AUDITED => array( + 'name' => pht('Audited'), + 'legacy' => 4, + 'icon' => 'fa-check-circle', + 'color' => 'green', + 'closed' => true, + 'color.ansi' => 'green', + ), + self::NEEDS_VERIFICATION => array( + 'name' => pht('Needs Verification'), + 'legacy' => 5, + 'icon' => 'fa-refresh', + 'color' => 'indigo', + 'closed' => false, + 'color.ansi' => 'magenta', + ), + ); + } +} diff --git a/src/applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php index 002805ac4e..286831f82b 100644 --- a/src/applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php @@ -9,6 +9,14 @@ final class DiffusionGetRecentCommitsByPathConduitAPIMethod return 'diffusion.getrecentcommitsbypath'; } + public function getMethodStatus() { + return self::METHOD_STATUS_DEPRECATED; + } + + public function getMethodStatusDescription() { + return pht('Obsoleted by "diffusion.historyquery".'); + } + public function getMethodDescription() { return pht( 'Get commit identifiers for recent commits affecting a given path.'); @@ -23,6 +31,12 @@ final class DiffusionGetRecentCommitsByPathConduitAPIMethod ); } + protected function defineErrorTypes() { + return array( + 'ERR_NOT_FOUND' => pht('Repository was not found.'), + ); + } + protected function defineReturnType() { return 'nonempty list'; } @@ -36,6 +50,10 @@ final class DiffusionGetRecentCommitsByPathConduitAPIMethod 'branch' => $request->getValue('branch'), )); + if ($drequest === null) { + throw new ConduitException('ERR_NOT_FOUND'); + } + $limit = nonempty( $request->getValue('limit'), self::DEFAULT_LIMIT); diff --git a/src/applications/diffusion/controller/DiffusionBlameController.php b/src/applications/diffusion/controller/DiffusionBlameController.php index 85ab393ef6..95cc039217 100644 --- a/src/applications/diffusion/controller/DiffusionBlameController.php +++ b/src/applications/diffusion/controller/DiffusionBlameController.php @@ -24,6 +24,7 @@ final class DiffusionBlameController extends DiffusionController { ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers($identifiers) + ->needIdentities(true) ->execute(); $commits = mpull($commits, null, 'getCommitIdentifier'); } else { @@ -68,10 +69,7 @@ final class DiffusionBlameController extends DiffusionController { $handle_phids = array(); foreach ($commits as $commit) { - $author_phid = $commit->getAuthorPHID(); - if ($author_phid) { - $handle_phids[] = $author_phid; - } + $handle_phids[] = $commit->getAuthorDisplayPHID(); } foreach ($revisions as $revision) { @@ -117,11 +115,7 @@ final class DiffusionBlameController extends DiffusionController { $author_phid = null; if ($commit) { - $author_phid = $commit->getAuthorPHID(); - } - - if (!$author_phid && $revision) { - $author_phid = $revision->getAuthorPHID(); + $author_phid = $commit->getAuthorDisplayPHID(); } if (!$author_phid) { @@ -139,6 +133,7 @@ final class DiffusionBlameController extends DiffusionController { $author_meta = array( 'tip' => $handles[$author_phid]->getName(), 'align' => 'E', + 'size' => 'auto', ); } diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 67ffb73357..283278fcf8 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -46,6 +46,7 @@ final class DiffusionCommitController extends DiffusionController { ->needCommitData(true) ->needAuditRequests(true) ->setLimit(100) + ->needIdentities(true) ->execute(); $multiple_results = count($commits) > 1; @@ -170,13 +171,12 @@ final class DiffusionCommitController extends DiffusionController { ->setHeaderIcon('fa-code-fork') ->addTag($commit_tag); - if ($commit->getAuditStatus()) { - $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon( - $commit->getAuditStatus()); - $color = PhabricatorAuditCommitStatusConstants::getStatusColor( - $commit->getAuditStatus()); - $status = PhabricatorAuditCommitStatusConstants::getStatusName( - $commit->getAuditStatus()); + if (!$commit->isAuditStatusNoAudit()) { + $status = $commit->getAuditStatusObject(); + + $icon = $status->getIcon(); + $color = $status->getColor(); + $status = $status->getName(); $header->setStatus($icon, $color, $status); } @@ -504,15 +504,13 @@ final class DiffusionCommitController extends DiffusionController { $phids = $edge_query->getDestinationPHIDs(array($commit_phid)); - if ($data->getCommitDetail('authorPHID')) { - $phids[] = $data->getCommitDetail('authorPHID'); - } + if ($data->getCommitDetail('reviewerPHID')) { $phids[] = $data->getCommitDetail('reviewerPHID'); } - if ($data->getCommitDetail('committerPHID')) { - $phids[] = $data->getCommitDetail('committerPHID'); - } + + $phids[] = $commit->getCommitterDisplayPHID(); + $phids[] = $commit->getAuthorDisplayPHID(); // NOTE: We should never normally have more than a single push log, but // it can occur naturally if a commit is pushed, then the branch it was @@ -573,24 +571,11 @@ final class DiffusionCommitController extends DiffusionController { } } - $author_phid = $data->getCommitDetail('authorPHID'); - $author_name = $data->getAuthorName(); $author_epoch = $data->getCommitDetail('authorEpoch'); $committed_info = id(new PHUIStatusItemView()) - ->setNote(phabricator_datetime($commit->getEpoch(), $viewer)); - - $committer_phid = $data->getCommitDetail('committerPHID'); - $committer_name = $data->getCommitDetail('committer'); - if ($committer_phid) { - $committed_info->setTarget($handles[$committer_phid]->renderLink()); - } else if (strlen($committer_name)) { - $committed_info->setTarget($committer_name); - } else if ($author_phid) { - $committed_info->setTarget($handles[$author_phid]->renderLink()); - } else if (strlen($author_name)) { - $committed_info->setTarget($author_name); - } + ->setNote(phabricator_datetime($commit->getEpoch(), $viewer)) + ->setTarget($commit->renderAnyCommitter($viewer, $handles)); $committed_list = new PHUIStatusListView(); $committed_list->addItem($committed_info); @@ -716,7 +701,7 @@ final class DiffusionCommitController extends DiffusionController { return null; } - $author_phid = $data->getCommitDetail('authorPHID'); + $author_phid = $commit->getAuthorDisplayPHID(); $author_name = $data->getAuthorName(); $author_epoch = $data->getCommitDetail('authorEpoch'); $date = null; @@ -748,10 +733,8 @@ final class DiffusionCommitController extends DiffusionController { ->setImage($image_uri) ->setImageHref($image_href) ->setContent($content); - } - private function buildComments(PhabricatorRepositoryCommit $commit) { $timeline = $this->buildTransactionTimeline( $commit, diff --git a/src/applications/diffusion/controller/DiffusionInlineCommentController.php b/src/applications/diffusion/controller/DiffusionInlineCommentController.php index 8af9a1cd65..ce69511dde 100644 --- a/src/applications/diffusion/controller/DiffusionInlineCommentController.php +++ b/src/applications/diffusion/controller/DiffusionInlineCommentController.php @@ -50,19 +50,17 @@ final class DiffusionInlineCommentController } protected function loadCommentForEdit($id) { - $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $this->getViewer(); $inline = $this->loadComment($id); - if (!$this->canEditInlineComment($user, $inline)) { + if (!$this->canEditInlineComment($viewer, $inline)) { throw new Exception(pht('That comment is not editable!')); } return $inline; } protected function loadCommentForDone($id) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $inline = $this->loadComment($id); if (!$inline) { @@ -77,20 +75,32 @@ final class DiffusionInlineCommentController throw new Exception(pht('Failed to load commit.')); } - if ((!$commit->getAuthorPHID()) || - ($commit->getAuthorPHID() != $viewer->getPHID())) { - throw new Exception(pht('You can not mark this comment as complete.')); + $owner_phid = $commit->getAuthorPHID(); + $viewer_phid = $viewer->getPHID(); + $viewer_is_owner = ($owner_phid && ($owner_phid == $viewer_phid)); + $viewer_is_author = ($viewer_phid == $inline->getAuthorPHID()); + $is_draft = $inline->isDraft(); + + if ($viewer_is_owner) { + // You can mark inlines on your own commits as "Done". + } else if ($viewer_is_author && $is_draft) { + // You can mark your own unsubmitted inlines as "Done". + } else { + throw new Exception( + pht( + 'You can not mark this comment as complete: you did not author '. + 'the commit and the comment is not a draft you wrote.')); } return $inline; } private function canEditInlineComment( - PhabricatorUser $user, + PhabricatorUser $viewer, PhabricatorAuditInlineComment $inline) { // Only the author may edit a comment. - if ($inline->getAuthorPHID() != $user->getPHID()) { + if ($inline->getAuthorPHID() != $viewer->getPHID()) { return false; } diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index 945f8a58b5..1a31d3a2ba 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -35,6 +35,7 @@ final class DiffusionLastModifiedController extends DiffusionController { ->withRepository($drequest->getRepository()) ->withIdentifiers(array_values($modified_map)) ->needCommitData(true) + ->needIdentities(true) ->execute(); $commit_map = mpull($commit_map, null, 'getCommitIdentifier'); } else { @@ -54,9 +55,8 @@ final class DiffusionLastModifiedController extends DiffusionController { $phids = array(); foreach ($commits as $commit) { - $data = $commit->getCommitData(); - $phids[] = $data->getCommitDetail('authorPHID'); - $phids[] = $data->getCommitDetail('committerPHID'); + $phids[] = $commit->getCommitterDisplayPHID(); + $phids[] = $commit->getAuthorDisplayPHID(); } $phids = array_filter($phids); $handles = $this->loadViewerHandles($phids); @@ -110,38 +110,21 @@ final class DiffusionLastModifiedController extends DiffusionController { $date = ''; } - $data = $commit->getCommitData(); - if ($data) { - $author_phid = $data->getCommitDetail('authorPHID'); - if ($author_phid && isset($handles[$author_phid])) { - $author = $handles[$author_phid]->renderLink(); - } else { - $author = DiffusionView::renderName($data->getAuthorName()); - } + $author = $commit->renderAuthor($viewer, $handles); + $committer = $commit->renderCommitter($viewer, $handles); - $committer = $data->getCommitDetail('committer'); - if ($committer) { - $committer_phid = $data->getCommitDetail('committerPHID'); - if ($committer_phid && isset($handles[$committer_phid])) { - $committer = $handles[$committer_phid]->renderLink(); - } else { - $committer = DiffusionView::renderName($committer); - } - if ($author != $committer) { - $author = hsprintf('%s/%s', $author, $committer); - } - } - - $details = DiffusionView::linkDetail( - $drequest->getRepository(), - $commit->getCommitIdentifier(), - $data->getSummary()); - $details = AphrontTableView::renderSingleDisplayLine($details); - } else { - $author = ''; - $details = ''; + if ($author != $committer) { + $author = hsprintf('%s/%s', $author, $committer); } + $data = $commit->getCommitData(); + $details = DiffusionView::linkDetail( + $drequest->getRepository(), + $commit->getCommitIdentifier(), + $data->getSummary()); + $details = AphrontTableView::renderSingleDisplayLine($details); + + $return = array( 'commit' => $modified, 'date' => $date, diff --git a/src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php b/src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php index 088f4b752e..712b7ae150 100644 --- a/src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php +++ b/src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php @@ -31,8 +31,6 @@ final class DiffusionDoorkeeperCommitFeedStoryPublisher // After ApplicationTransactions, we could annotate feed stories more // explicitly. - $fully_audited = PhabricatorAuditCommitStatusConstants::FULLY_AUDITED; - $story = $this->getFeedStory(); $xaction = $story->getPrimaryTransaction(); switch ($xaction->getTransactionType()) { @@ -41,7 +39,7 @@ final class DiffusionDoorkeeperCommitFeedStoryPublisher case PhabricatorAuditActionConstants::CLOSE: return true; case PhabricatorAuditActionConstants::ACCEPT: - if ($object->getAuditStatus() == $fully_audited) { + if ($object->isAuditStatusAudited()) { return true; } break; @@ -165,14 +163,7 @@ final class DiffusionDoorkeeperCommitFeedStoryPublisher } public function isObjectClosed($object) { - switch ($object->getAuditStatus()) { - case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT: - case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED: - case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED: - return false; - default: - return true; - } + return $object->getAuditStatusObject()->getIsClosed(); } public function getResponsibilityTitle($object) { diff --git a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php index 0711028796..3ce95cb3c3 100644 --- a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php +++ b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php @@ -41,12 +41,12 @@ final class DiffusionHovercardEngineExtension $hovercard->addField(pht('Date'), phabricator_date($commit->getEpoch(), $viewer)); - if ($commit->getAuditStatus() != - PhabricatorAuditCommitStatusConstants::NONE) { + if (!$commit->isAuditStatusNoAudit()) { + $status = $commit->getAuditStatusObject(); - $hovercard->addField(pht('Audit Status'), - PhabricatorAuditCommitStatusConstants::getStatusName( - $commit->getAuditStatus())); + $hovercard->addField( + pht('Audit Status'), + $status->getName()); } } diff --git a/src/applications/diffusion/harbormaster/DiffusionBuildableEngine.php b/src/applications/diffusion/harbormaster/DiffusionBuildableEngine.php index bb222df28e..748386f1bd 100644 --- a/src/applications/diffusion/harbormaster/DiffusionBuildableEngine.php +++ b/src/applications/diffusion/harbormaster/DiffusionBuildableEngine.php @@ -34,4 +34,10 @@ final class DiffusionBuildableEngine $this->applyTransactions(array($xaction)); } + public function getAuthorIdentity() { + return $this->getObject() + ->loadIdentities($this->getViewer()) + ->getAuthorIdentity(); + } + } diff --git a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php index 251935a6dc..8a9deb5d84 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php @@ -23,12 +23,16 @@ final class DiffusionMercurialWireProtocol extends Phobject { 'listkeys' => array('namespace'), 'lookup' => array('key'), 'pushkey' => array('namespace', 'key', 'old', 'new'), + 'protocaps' => array('caps'), 'stream_out' => array(''), 'unbundle' => array('heads'), ); if (!isset($commands[$command])) { - throw new Exception(pht("Unknown Mercurial command '%s!", $command)); + throw new Exception( + pht( + 'Unknown Mercurial command "%s"!', + $command)); } return $commands[$command]; @@ -49,6 +53,7 @@ final class DiffusionMercurialWireProtocol extends Phobject { 'known' => true, 'listkeys' => true, 'lookup' => true, + 'protocaps' => true, 'stream_out' => true, ); diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php index d5ba74a30e..2e5d4215db 100644 --- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php +++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php @@ -170,12 +170,13 @@ final class DiffusionRepositoryClusterEngine extends Phobject { pht( 'Acquired read lock immediately.')); } - } catch (Exception $ex) { + } catch (PhutilLockException $ex) { throw new PhutilProxyException( pht( 'Failed to acquire read lock after waiting %s second(s). You '. - 'may be able to retry later.', - new PhutilNumber($lock_wait)), + 'may be able to retry later. (%s)', + new PhutilNumber($lock_wait), + $ex->getHint()), $ex); } @@ -207,7 +208,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { $this->synchronizeWorkingCopyFromDevices($fetchable); } else { - $this->synchornizeWorkingCopyFromRemote(); + $this->synchronizeWorkingCopyFromRemote(); } PhabricatorRepositoryWorkingCopyVersion::updateVersion( @@ -349,12 +350,13 @@ final class DiffusionRepositoryClusterEngine extends Phobject { pht( 'Acquired write lock immediately.')); } - } catch (Exception $ex) { + } catch (PhutilLockException $ex) { throw new PhutilProxyException( pht( 'Failed to acquire write lock after waiting %s second(s). You '. - 'may be able to retry later.', - new PhutilNumber($lock_wait)), + 'may be able to retry later. (%s)', + new PhutilNumber($lock_wait), + $ex->getHint()), $ex); } @@ -607,7 +609,7 @@ final class DiffusionRepositoryClusterEngine extends Phobject { /** * @task internal */ - private function synchornizeWorkingCopyFromRemote() { + private function synchronizeWorkingCopyFromRemote() { $repository = $this->getRepository(); $device = AlmanacKeys::getLiveDevice(); @@ -686,6 +688,9 @@ final class DiffusionRepositoryClusterEngine extends Phobject { 'fetchable.')); } + // If we can synchronize from multiple sources, choose one at random. + shuffle($fetchable); + $caught = null; foreach ($fetchable as $binding) { try { diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index 0ca9cd97d4..f2d0920b3a 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -714,10 +714,13 @@ final class DiffusionCommitQuery } if ($this->statuses !== null) { + $statuses = DiffusionCommitAuditStatus::newModernKeys( + $this->statuses); + $where[] = qsprintf( $conn, - 'commit.auditStatus IN (%Ld)', - $this->statuses); + 'commit.auditStatus IN (%Ls)', + $statuses); } if ($this->packagePHIDs !== null) { diff --git a/src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php b/src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php index 40bc63025d..25984c93e1 100644 --- a/src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php +++ b/src/applications/diffusion/query/DiffusionCommitRequiredActionResultBucket.php @@ -69,14 +69,12 @@ final class DiffusionCommitRequiredActionResultBucket $results = array(); $objects = $this->objects; - $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; - foreach ($objects as $key => $object) { if (empty($phids[$object->getAuthorPHID()])) { continue; } - if ($object->getAuditStatus() != $status_concern) { + if (!$object->isAuditStatusConcernRaised()) { continue; } @@ -91,7 +89,6 @@ final class DiffusionCommitRequiredActionResultBucket $results = array(); $objects = $this->objects; - $status_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION; $has_concern = array( PhabricatorAuditStatusConstants::CONCERNED, ); @@ -102,7 +99,7 @@ final class DiffusionCommitRequiredActionResultBucket continue; } - if ($object->getAuditStatus() != $status_verify) { + if (!$object->isAuditStatusNeedsVerification()) { continue; } @@ -147,10 +144,8 @@ final class DiffusionCommitRequiredActionResultBucket $results = array(); $objects = $this->objects; - $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; - foreach ($objects as $key => $object) { - if ($object->getAuditStatus() != $status_concern) { + if (!$object->isAuditStatusConcernRaised()) { continue; } @@ -169,15 +164,13 @@ final class DiffusionCommitRequiredActionResultBucket $results = array(); $objects = $this->objects; - $status_waiting = array( - PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT, - PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION, - PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED, - ); - $status_waiting = array_fuse($status_waiting); - foreach ($objects as $key => $object) { - if (empty($status_waiting[$object->getAuditStatus()])) { + $any_waiting = + $object->isAuditStatusNeedsAudit() || + $object->isAuditStatusNeedsVerification() || + $object->isAuditStatusPartiallyAudited(); + + if (!$any_waiting) { continue; } diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index 3885bbf47c..cfd7019679 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -114,10 +114,10 @@ final class DiffusionHistoryTableView extends DiffusionHistoryView { 'type' => $history->getFileType(), )); - $status = $commit->getAuditStatus(); - $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon($status); - $color = PhabricatorAuditCommitStatusConstants::getStatusColor($status); - $name = PhabricatorAuditCommitStatusConstants::getStatusName($status); + $status = $commit->getAuditStatusObject(); + $icon = $status->getIcon(); + $color = $status->getColor(); + $name = $status->getName(); $audit_view = id(new PHUIIconView()) ->setIcon($icon, $color) diff --git a/src/applications/diffusion/view/DiffusionReadmeView.php b/src/applications/diffusion/view/DiffusionReadmeView.php index 693333249d..21e97f4718 100644 --- a/src/applications/diffusion/view/DiffusionReadmeView.php +++ b/src/applications/diffusion/view/DiffusionReadmeView.php @@ -94,7 +94,7 @@ final class DiffusionReadmeView extends DiffusionView { } $readme_content = phutil_tag_div($class, $readme_content); - $document = id(new PHUIDocumentViewPro()) + $document = id(new PHUIDocumentView()) ->setFluid(true) ->appendChild($readme_content); diff --git a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php index 30ec8d9f6d..1f6ae72621 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php @@ -33,8 +33,7 @@ final class DiffusionCommitConcernTransaction public function applyInternalEffects($object, $value) { // NOTE: We force the commit directly into "Concern Raised" so that we // override a possible "Needs Verification" state. - $object->setAuditStatus( - PhabricatorAuditCommitStatusConstants::CONCERN_RAISED); + $object->setAuditStatus(DiffusionCommitAuditStatus::CONCERN_RAISED); } public function applyExternalEffects($object, $value) { @@ -54,10 +53,8 @@ final class DiffusionCommitConcernTransaction // Even if you've already raised a concern, you can raise again as long // as the author requested you verify. - $state_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION; - if ($this->isViewerFullyRejected($object, $viewer)) { - if ($object->getAuditStatus() != $state_verify) { + if (!$object->isAuditStatusNeedsVerification()) { throw new Exception( pht( 'You can not raise a concern with this commit because you have '. diff --git a/src/applications/diffusion/xaction/DiffusionCommitStateTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitStateTransaction.php index a6288923f4..6fd2a6977d 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitStateTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitStateTransaction.php @@ -11,29 +11,32 @@ final class DiffusionCommitStateTransaction throw new PhutilMethodNotImplementedException(); } - public function getIcon() { + private function getAuditStatusObject() { $new = $this->getNewValue(); - return PhabricatorAuditCommitStatusConstants::getStatusIcon($new); + return DiffusionCommitAuditStatus::newForStatus($new); + } + + public function getIcon() { + return $this->getAuditStatusObject()->getIcon(); } public function getColor() { - $new = $this->getNewValue(); - return PhabricatorAuditCommitStatusConstants::getStatusColor($new); + return $this->getAuditStatusObject()->getColor(); } public function getTitle() { - $new = $this->getNewValue(); + $status = $this->getAuditStatusObject(); - switch ($new) { - case PhabricatorAuditCommitStatusConstants::NONE: + switch ($status->getKey()) { + case DiffusionCommitAuditStatus::NONE: return pht('This commit no longer requires audit.'); - case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT: + case DiffusionCommitAuditStatus::NEEDS_AUDIT: return pht('This commit now requires audit.'); - case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED: + case DiffusionCommitAuditStatus::CONCERN_RAISED: return pht('This commit now has outstanding concerns.'); - case PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION: + case DiffusionCommitAuditStatus::NEEDS_VERIFICATION: return pht('This commit now requires verification by auditors.'); - case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED: + case DiffusionCommitAuditStatus::AUDITED: return pht('All concerns with this commit have now been addressed.'); } @@ -41,26 +44,26 @@ final class DiffusionCommitStateTransaction } public function getTitleForFeed() { - $new = $this->getNewValue(); + $status = $this->getAuditStatusObject(); - switch ($new) { - case PhabricatorAuditCommitStatusConstants::NONE: + switch ($status->getKey()) { + case DiffusionCommitAuditStatus::NONE: return pht( '%s no longer requires audit.', $this->renderObject()); - case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT: + case DiffusionCommitAuditStatus::NEEDS_AUDIT: return pht( '%s now requires audit.', $this->renderObject()); - case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED: + case DiffusionCommitAuditStatus::CONCERN_RAISED: return pht( '%s now has outstanding concerns.', $this->renderObject()); - case PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION: + case DiffusionCommitAuditStatus::NEEDS_VERIFICATION: return pht( '%s now requires verification by auditors.', $this->renderObject()); - case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED: + case DiffusionCommitAuditStatus::AUDITED: return pht( 'All concerns with %s have now been addressed.', $this->renderObject()); diff --git a/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php index a44261f7a4..f1ec5834b1 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php @@ -36,8 +36,7 @@ final class DiffusionCommitVerifyTransaction } public function applyInternalEffects($object, $value) { - $object->setAuditStatus( - PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION); + $object->setAuditStatus(DiffusionCommitAuditStatus::NEEDS_VERIFICATION); } protected function validateAction($object, PhabricatorUser $viewer) { @@ -48,8 +47,7 @@ final class DiffusionCommitVerifyTransaction 'are not the author.')); } - $status = $object->getAuditStatus(); - if ($status != PhabricatorAuditCommitStatusConstants::CONCERN_RAISED) { + if (!$object->isAuditStatusConcernRaised()) { throw new Exception( pht( 'You can not request verification of this commit because no '. diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php index bf69497b16..0c92bd1738 100644 --- a/src/applications/diviner/controller/DivinerAtomController.php +++ b/src/applications/diviner/controller/DivinerAtomController.php @@ -72,7 +72,7 @@ final class DivinerAtomController extends DivinerController { $prop_list = new PHUIPropertyGroupView(); $prop_list->addPropertyList($properties); - $document = id(new PHUIDocumentViewPro()) + $document = id(new PHUIDocumentView()) ->setBook($book->getTitle(), $group_name) ->setHeader($header) ->addClass('diviner-view'); diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php index 1aa8790ffa..4e0ae3524b 100644 --- a/src/applications/diviner/controller/DivinerBookController.php +++ b/src/applications/diviner/controller/DivinerBookController.php @@ -45,7 +45,7 @@ final class DivinerBookController extends DivinerController { ->setName($book->getRepository()->getMonogram())); } - $document = new PHUIDocumentViewPro(); + $document = new PHUIDocumentView(); $document->setHeader($header); $document->addClass('diviner-view'); diff --git a/src/applications/diviner/controller/DivinerMainController.php b/src/applications/diviner/controller/DivinerMainController.php index e7d179c077..400fb48d0f 100644 --- a/src/applications/diviner/controller/DivinerMainController.php +++ b/src/applications/diviner/controller/DivinerMainController.php @@ -27,7 +27,7 @@ final class DivinerMainController extends DivinerController { ->setHeader(pht('Documentation Books')) ->addActionLink($query_button); - $document = new PHUIDocumentViewPro(); + $document = new PHUIDocumentView(); $document->setHeader($header); $document->addClass('diviner-view'); diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index fef2c843ff..79bc1cb0d7 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -93,6 +93,8 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { '' => 'DrydockRepositoryOperationViewController', 'status/' => 'DrydockRepositoryOperationStatusController', 'dismiss/' => 'DrydockRepositoryOperationDismissController', + 'logs/(?:query/(?P[^/]+)/)?' => + 'DrydockLogListController', ), ), ), diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index d3f73bf78c..2c31e4ae8f 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -58,8 +58,11 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $log_query = id(new DrydockLogQuery()) ->withBlueprintPHIDs(array($blueprint->getPHID())); + $log_table = $this->buildLogTable($log_query) + ->setHideBlueprints(true); + $logs = $this->buildLogBox( - $log_query, + $log_table, $this->getApplicationURI("blueprint/{$id}/logs/query/all/")); $view = id(new PHUITwoColumnView()) diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index 872a78f66f..f393f210d7 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -79,7 +79,7 @@ abstract class DrydockController extends PhabricatorController { ->addRawContent($table); } - protected function buildLogBox(DrydockLogQuery $query, $all_uri) { + protected function buildLogTable(DrydockLogQuery $query) { $viewer = $this->getViewer(); $logs = $query @@ -89,9 +89,12 @@ abstract class DrydockController extends PhabricatorController { $log_table = id(new DrydockLogListView()) ->setUser($viewer) - ->setLogs($logs) - ->render(); + ->setLogs($logs); + return $log_table; + } + + protected function buildLogBox(DrydockLogListView $log_table, $all_uri) { $log_header = id(new PHUIHeaderView()) ->setHeader(pht('Logs')) ->addActionLink( diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 91a911277f..dfffcf8402 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -43,8 +43,11 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $log_query = id(new DrydockLogQuery()) ->withLeasePHIDs(array($lease->getPHID())); + $log_table = $this->buildLogTable($log_query) + ->setHideLeases(true); + $logs = $this->buildLogBox( - $log_query, + $log_table, $this->getApplicationURI("lease/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); @@ -163,6 +166,30 @@ final class DrydockLeaseViewController extends DrydockLeaseController { } $view->addProperty(pht('Expires'), $until_display); + $acquired_epoch = $lease->getAcquiredEpoch(); + $activated_epoch = $lease->getActivatedEpoch(); + + if ($acquired_epoch) { + $acquired_display = phabricator_datetime($acquired_epoch, $viewer); + } else { + if ($activated_epoch) { + $acquired_display = phutil_tag( + 'em', + array(), + pht('Activated on Acquisition')); + } else { + $acquired_display = phutil_tag('em', array(), pht('Not Acquired')); + } + } + $view->addProperty(pht('Acquired'), $acquired_display); + + if ($activated_epoch) { + $activated_display = phabricator_datetime($activated_epoch, $viewer); + } else { + $activated_display = phutil_tag('em', array(), pht('Not Activated')); + } + $view->addProperty(pht('Activated'), $activated_display); + $attributes = $lease->getAttributes(); if ($attributes) { $view->addSectionHeader( diff --git a/src/applications/drydock/controller/DrydockLogController.php b/src/applications/drydock/controller/DrydockLogController.php index 704cbb53b9..69e0c84804 100644 --- a/src/applications/drydock/controller/DrydockLogController.php +++ b/src/applications/drydock/controller/DrydockLogController.php @@ -6,6 +6,7 @@ abstract class DrydockLogController private $blueprint; private $resource; private $lease; + private $operation; public function setBlueprint(DrydockBlueprint $blueprint) { $this->blueprint = $blueprint; @@ -34,6 +35,15 @@ abstract class DrydockLogController return $this->lease; } + public function setOperation(DrydockRepositoryOperation $operation) { + $this->operation = $operation; + return $this; + } + + public function getOperation() { + return $this->operation; + } + public function buildSideNavView() { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); @@ -56,6 +66,11 @@ abstract class DrydockLogController $engine->setLease($lease); } + $operation = $this->getOperation(); + if ($operation) { + $engine->setOperation($operation); + } + $engine->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); @@ -66,9 +81,12 @@ abstract class DrydockLogController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); + $viewer = $this->getViewer(); + $blueprint = $this->getBlueprint(); $resource = $this->getResource(); $lease = $this->getLease(); + $operation = $this->getOperation(); if ($blueprint) { $id = $blueprint->getID(); @@ -111,6 +129,20 @@ abstract class DrydockLogController $crumbs->addTextCrumb( pht('Logs'), $this->getApplicationURI("lease/{$id}/logs/")); + } else if ($operation) { + $id = $operation->getID(); + + $crumbs->addTextCrumb( + pht('Operations'), + $this->getApplicationURI('operation/')); + + $crumbs->addTextCrumb( + pht('Repository Operation %d', $id), + $this->getApplicationURI("operation/{$id}/")); + + $crumbs->addTextCrumb( + pht('Logs'), + $this->getApplicationURI("operation/{$id}/logs/")); } return $crumbs; diff --git a/src/applications/drydock/controller/DrydockLogListController.php b/src/applications/drydock/controller/DrydockLogListController.php index b5e4d4ff0c..26719db39e 100644 --- a/src/applications/drydock/controller/DrydockLogListController.php +++ b/src/applications/drydock/controller/DrydockLogListController.php @@ -46,6 +46,17 @@ final class DrydockLogListController extends DrydockLogController { $engine->setLease($lease); $this->setLease($lease); break; + case 'operation': + $operation = id(new DrydockRepositoryOperationQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$operation) { + return new Aphront404Response(); + } + $engine->setOperation($operation); + $this->setOperation($operation); + break; default: return new Aphront404Response(); } diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php b/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php index e7fbf07b35..e255c63ec3 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php @@ -47,13 +47,25 @@ final class DrydockRepositoryOperationViewController ->setUser($viewer) ->setOperation($operation); + $log_query = id(new DrydockLogQuery()) + ->withOperationPHIDs(array($operation->getPHID())); + + $log_table = $this->buildLogTable($log_query) + ->setHideOperations(true); + + $logs = $this->buildLogBox( + $log_table, + $this->getApplicationURI("operation/{$id}/logs/query/all/")); + $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->addPropertySection(pht('Properties'), $properties) - ->setMainColumn(array( - $status_view, - )); + ->setMainColumn( + array( + $status_view, + $logs, + )); return $this->newPage() ->setTitle($title) diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 8718caa9e2..ec0dce53e1 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -48,8 +48,11 @@ final class DrydockResourceViewController extends DrydockResourceController { $log_query = id(new DrydockLogQuery()) ->withResourcePHIDs(array($resource->getPHID())); - $log_box = $this->buildLogBox( - $log_query, + $log_table = $this->buildLogTable($log_query) + ->setHideResources(true); + + $logs = $this->buildLogBox( + $log_table, $this->getApplicationURI("resource/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); @@ -86,11 +89,12 @@ final class DrydockResourceViewController extends DrydockResourceController { $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) - ->setMainColumn(array( - $object_box, - $lease_box, - $log_box, - )); + ->setMainColumn( + array( + $object_box, + $lease_box, + $logs, + )); return $this->newPage() ->setTitle($title) diff --git a/src/applications/drydock/logtype/DrydockOperationWorkLogType.php b/src/applications/drydock/logtype/DrydockOperationWorkLogType.php new file mode 100644 index 0000000000..deff90c16c --- /dev/null +++ b/src/applications/drydock/logtype/DrydockOperationWorkLogType.php @@ -0,0 +1,19 @@ +viewer; } + final public function setOperation(DrydockRepositoryOperation $operation) { + $this->operation = $operation; + return $this; + } + + final public function getOperation() { + return $this->operation; + } + + final public function setInterface(DrydockInterface $interface) { + $this->interface = $interface; + return $this; + } + + final public function getInterface() { + if (!$this->interface) { + throw new PhutilInvalidStateException('setInterface'); + } + return $this->interface; + } + final public function getOperationConstant() { return $this->getPhobjectClassConstant('OPCONST', 32); } diff --git a/src/applications/drydock/phid/DrydockRepositoryOperationPHIDType.php b/src/applications/drydock/phid/DrydockRepositoryOperationPHIDType.php index 0926f92388..6222552f84 100644 --- a/src/applications/drydock/phid/DrydockRepositoryOperationPHIDType.php +++ b/src/applications/drydock/phid/DrydockRepositoryOperationPHIDType.php @@ -5,7 +5,7 @@ final class DrydockRepositoryOperationPHIDType extends PhabricatorPHIDType { const TYPECONST = 'DRYO'; public function getTypeName() { - return pht('Drydock Repository Operation'); + return pht('Repository Operation'); } public function newObject() { @@ -33,7 +33,7 @@ final class DrydockRepositoryOperationPHIDType extends PhabricatorPHIDType { $operation = $objects[$phid]; $id = $operation->getID(); - $handle->setName(pht('Drydock Repository Operation %d', $id)); + $handle->setName(pht('Repository Operation %d', $id)); $handle->setURI("/drydock/operation/{$id}/"); } } diff --git a/src/applications/drydock/query/DrydockLogQuery.php b/src/applications/drydock/query/DrydockLogQuery.php index 00980edb4d..b73ad371ae 100644 --- a/src/applications/drydock/query/DrydockLogQuery.php +++ b/src/applications/drydock/query/DrydockLogQuery.php @@ -5,6 +5,7 @@ final class DrydockLogQuery extends DrydockQuery { private $blueprintPHIDs; private $resourcePHIDs; private $leasePHIDs; + private $operationPHIDs; public function withBlueprintPHIDs(array $phids) { $this->blueprintPHIDs = $phids; @@ -21,6 +22,11 @@ final class DrydockLogQuery extends DrydockQuery { return $this; } + public function withOperationPHIDs(array $phids) { + $this->operationPHIDs = $phids; + return $this; + } + public function newResultObject() { return new DrydockLog(); } @@ -93,6 +99,27 @@ final class DrydockLogQuery extends DrydockQuery { $log->attachLease($lease); } + $operation_phids = array_filter(mpull($logs, 'getOperationPHID')); + if ($operation_phids) { + $operations = id(new DrydockRepositoryOperationQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($operation_phids) + ->execute(); + $operations = mpull($operations, null, 'getPHID'); + } else { + $operations = array(); + } + + foreach ($logs as $key => $log) { + $operation = null; + $operation_phid = $log->getOperationPHID(); + if ($operation_phid) { + $operation = idx($operations, $operation_phid); + } + $log->attachOperation($operation); + } + return $logs; } @@ -120,6 +147,13 @@ final class DrydockLogQuery extends DrydockQuery { $this->leasePHIDs); } + if ($this->operationPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'operationPHID IN (%Ls)', + $this->operationPHIDs); + } + return $where; } diff --git a/src/applications/drydock/query/DrydockLogSearchEngine.php b/src/applications/drydock/query/DrydockLogSearchEngine.php index 43b1511c01..3602714a60 100644 --- a/src/applications/drydock/query/DrydockLogSearchEngine.php +++ b/src/applications/drydock/query/DrydockLogSearchEngine.php @@ -5,6 +5,7 @@ final class DrydockLogSearchEngine extends PhabricatorApplicationSearchEngine { private $blueprint; private $resource; private $lease; + private $operation; public function setBlueprint(DrydockBlueprint $blueprint) { $this->blueprint = $blueprint; @@ -33,6 +34,15 @@ final class DrydockLogSearchEngine extends PhabricatorApplicationSearchEngine { return $this->lease; } + public function setOperation(DrydockRepositoryOperation $operation) { + $this->operation = $operation; + return $this; + } + + public function getOperation() { + return $this->operation; + } + public function canUseInPanelContext() { // Prevent use on Dashboard panels since all log queries currently need a // parent object and these don't seem particularly useful in any case. @@ -65,6 +75,11 @@ final class DrydockLogSearchEngine extends PhabricatorApplicationSearchEngine { $query->withLeasePHIDs(array($lease->getPHID())); } + $operation = $this->getOperation(); + if ($operation) { + $query->withOperationPHIDs(array($operation->getPHID())); + } + return $query; } @@ -97,9 +112,15 @@ final class DrydockLogSearchEngine extends PhabricatorApplicationSearchEngine { return "/drydock/lease/{$id}/logs/{$path}"; } + $operation = $this->getOperation(); + if ($operation) { + $id = $operation->getID(); + return "/drydock/operation/{$id}/logs/{$path}"; + } + throw new Exception( pht( - 'Search engine has no blueprint, resource, or lease.')); + 'Search engine has no blueprint, resource, lease, or operation.')); } protected function getBuiltinQueryNames() { diff --git a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php index 570e68e825..a5fbe0acc9 100644 --- a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php +++ b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php @@ -9,6 +9,7 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { private $operationStates; private $operationTypes; private $isDismissed; + private $authorPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -45,6 +46,11 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { return $this; } + public function withAuthorPHIDs(array $phids) { + $this->authorPHIDs = $phids; + return $this; + } + public function newResultObject() { return new DrydockRepositoryOperation(); } @@ -56,6 +62,8 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { protected function willFilterPage(array $operations) { $implementations = DrydockRepositoryOperationType::getAllOperationTypes(); + $viewer = $this->getViewer(); + foreach ($operations as $key => $operation) { $impl = idx($implementations, $operation->getOperationType()); if (!$impl) { @@ -63,7 +71,10 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { unset($operations[$key]); continue; } - $impl = clone $impl; + $impl = id(clone $impl) + ->setViewer($viewer) + ->setOperation($operation); + $operation->attachImplementation($impl); } @@ -165,6 +176,13 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { (int)$this->isDismissed); } + if ($this->authorPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'authorPHID IN (%Ls)', + $this->authorPHIDs); + } + return $where; } diff --git a/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php b/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php index 1de32be28f..eeb85329bf 100644 --- a/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php +++ b/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php @@ -18,11 +18,41 @@ final class DrydockRepositoryOperationSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['repositoryPHIDs']) { + $query->withRepositoryPHIDs($map['repositoryPHIDs']); + } + + if ($map['authorPHIDs']) { + $query->withAuthorPHIDs($map['authorPHIDs']); + } + + if ($map['states']) { + $query->withOperationStates($map['states']); + } + return $query; } protected function buildCustomSearchFields() { return array( + id(new PhabricatorSearchDatasourceField()) + ->setLabel(pht('Repositories')) + ->setKey('repositoryPHIDs') + ->setAliases(array('repository', 'repositories', 'repositoryPHID')) + ->setDatasource(new DiffusionRepositoryFunctionDatasource()), + + // NOTE: Repository operations aren't necessarily created by a real + // user, but for now they normally are. Just use a user typeahead until + // more use cases arise. + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Authors')) + ->setKey('authorPHIDs') + ->setAliases(array('author', 'authors', 'authorPHID')), + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('States')) + ->setKey('states') + ->setAliases(array('state')) + ->setOptions(DrydockRepositoryOperation::getOperationStateNameMap()), ); } @@ -72,6 +102,10 @@ final class DrydockRepositoryOperationSearchEngine $item->setStatusIcon($icon, $name); + + $created = phabricator_datetime($operation->getDateCreated(), $viewer); + $item->addIcon(null, $created); + $item->addByline( array( pht('Via:'), diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 4cee7a5f17..866bb21b37 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -10,6 +10,8 @@ final class DrydockLease extends DrydockDAO protected $authorizingPHID; protected $attributes = array(); protected $status = DrydockLeaseStatus::STATUS_PENDING; + protected $acquiredEpoch; + protected $activatedEpoch; private $resource = self::ATTACHABLE; private $unconsumedCommands = self::ATTACHABLE; @@ -62,6 +64,22 @@ final class DrydockLease extends DrydockDAO $this->scheduleUpdate(); } + public function setStatus($status) { + if ($status == DrydockLeaseStatus::STATUS_ACQUIRED) { + if (!$this->getAcquiredEpoch()) { + $this->setAcquiredEpoch(PhabricatorTime::getNow()); + } + } + + if ($status == DrydockLeaseStatus::STATUS_ACTIVE) { + if (!$this->getActivatedEpoch()) { + $this->setActivatedEpoch(PhabricatorTime::getNow()); + } + } + + return parent::setStatus($status); + } + public function getLeaseName() { return pht('Lease %d', $this->getID()); } @@ -78,6 +96,8 @@ final class DrydockLease extends DrydockDAO 'resourceType' => 'text128', 'ownerPHID' => 'phid?', 'resourcePHID' => 'phid?', + 'acquiredEpoch' => 'epoch?', + 'activatedEpoch' => 'epoch?', ), self::CONFIG_KEY_SCHEMA => array( 'key_resource' => array( diff --git a/src/applications/drydock/storage/DrydockLog.php b/src/applications/drydock/storage/DrydockLog.php index 5d75d82c65..81879944a0 100644 --- a/src/applications/drydock/storage/DrydockLog.php +++ b/src/applications/drydock/storage/DrydockLog.php @@ -6,6 +6,7 @@ final class DrydockLog extends DrydockDAO protected $blueprintPHID; protected $resourcePHID; protected $leasePHID; + protected $operationPHID; protected $epoch; protected $type; protected $data = array(); @@ -13,6 +14,7 @@ final class DrydockLog extends DrydockDAO private $blueprint = self::ATTACHABLE; private $resource = self::ATTACHABLE; private $lease = self::ATTACHABLE; + private $operation = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -24,6 +26,7 @@ final class DrydockLog extends DrydockDAO 'blueprintPHID' => 'phid?', 'resourcePHID' => 'phid?', 'leasePHID' => 'phid?', + 'operationPHID' => 'phid?', 'type' => 'text64', ), self::CONFIG_KEY_SCHEMA => array( @@ -36,6 +39,9 @@ final class DrydockLog extends DrydockDAO 'key_lease' => array( 'columns' => array('leasePHID', 'type'), ), + 'key_operation' => array( + 'columns' => array('operationPHID', 'type'), + ), 'epoch' => array( 'columns' => array('epoch'), ), @@ -70,6 +76,16 @@ final class DrydockLog extends DrydockDAO return $this->assertAttached($this->lease); } + public function attachOperation( + DrydockRepositoryOperation $operation = null) { + $this->operation = $operation; + return $this; + } + + public function getOperation() { + return $this->assertAttached($this->operation); + } + public function isComplete() { if ($this->getBlueprintPHID() && !$this->getBlueprint()) { return false; @@ -83,6 +99,10 @@ final class DrydockLog extends DrydockDAO return false; } + if ($this->getOperationPHID() && !$this->getOperation()) { + return false; + } + return true; } @@ -108,8 +128,8 @@ final class DrydockLog extends DrydockDAO public function describeAutomaticCapability($capability) { return pht( - 'To view log details, you must be able to view the associated '. - 'blueprint, resource and lease.'); + 'To view log details, you must be able to view all associated '. + 'blueprints, resources, leases, and repository operations.'); } } diff --git a/src/applications/drydock/storage/DrydockRepositoryOperation.php b/src/applications/drydock/storage/DrydockRepositoryOperation.php index 190b5b1310..195c5de8ec 100644 --- a/src/applications/drydock/storage/DrydockRepositoryOperation.php +++ b/src/applications/drydock/storage/DrydockRepositoryOperation.php @@ -25,6 +25,7 @@ final class DrydockRepositoryOperation extends DrydockDAO private $repository = self::ATTACHABLE; private $object = self::ATTACHABLE; private $implementation = self::ATTACHABLE; + private $workingCopyLease = self::ATTACHABLE; public static function initializeNewOperation( DrydockRepositoryOperationType $op) { @@ -90,6 +91,19 @@ final class DrydockRepositoryOperation extends DrydockDAO return $this->implementation; } + public function getWorkingCopyLease() { + return $this->assertAttached($this->workingCopyLease); + } + + public function attachWorkingCopyLease(DrydockLease $lease) { + $this->workingCopyLease = $lease; + return $this; + } + + public function hasWorkingCopyLease() { + return ($this->workingCopyLease !== self::ATTACHABLE); + } + public function getProperty($key, $default = null) { return idx($this->properties, $key, $default); } @@ -99,6 +113,15 @@ final class DrydockRepositoryOperation extends DrydockDAO return $this; } + public static function getOperationStateNameMap() { + return array( + self::STATE_WAIT => pht('Waiting'), + self::STATE_WORK => pht('Working'), + self::STATE_DONE => pht('Done'), + self::STATE_FAIL => pht('Failed'), + ); + } + public static function getOperationStateIcon($state) { $map = array( self::STATE_WAIT => 'fa-clock-o', @@ -111,13 +134,7 @@ final class DrydockRepositoryOperation extends DrydockDAO } public static function getOperationStateName($state) { - $map = array( - self::STATE_WAIT => pht('Waiting'), - self::STATE_WORK => pht('Working'), - self::STATE_DONE => pht('Done'), - self::STATE_FAIL => pht('Failed'), - ); - + $map = self::getOperationStateNameMap(); return idx($map, $state, pht('', $state)); } @@ -134,9 +151,9 @@ final class DrydockRepositoryOperation extends DrydockDAO } public function applyOperation(DrydockInterface $interface) { - return $this->getImplementation()->applyOperation( - $this, - $interface); + $impl = $this->getImplementation(); + $impl->setInterface($interface); + return $impl->applyOperation($this, $interface); } public function getOperationDescription(PhabricatorUser $viewer) { @@ -186,6 +203,37 @@ final class DrydockRepositoryOperation extends DrydockDAO return $this->getProperty('exec.workingcopy.error'); } + public function logText($text) { + return $this->logEvent( + DrydockTextLogType::LOGCONST, + array( + 'text' => $text, + )); + } + + public function logEvent($type, array $data = array()) { + $log = id(new DrydockLog()) + ->setEpoch(PhabricatorTime::getNow()) + ->setType($type) + ->setData($data); + + $log->setOperationPHID($this->getPHID()); + + if ($this->hasWorkingCopyLease()) { + $lease = $this->getWorkingCopyLease(); + $log->setLeasePHID($lease->getPHID()); + + $resource_phid = $lease->getResourcePHID(); + if ($resource_phid) { + $resource = $lease->getResource(); + + $log->setResourcePHID($resource->getPHID()); + $log->setBlueprintPHID($resource->getBlueprintPHID()); + } + } + + return $log->save(); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/view/DrydockLogListView.php b/src/applications/drydock/view/DrydockLogListView.php index 61fde7cee7..32f5d0d7ed 100644 --- a/src/applications/drydock/view/DrydockLogListView.php +++ b/src/applications/drydock/view/DrydockLogListView.php @@ -3,6 +3,46 @@ final class DrydockLogListView extends AphrontView { private $logs; + private $hideBlueprints; + private $hideResources; + private $hideLeases; + private $hideOperations; + + public function setHideBlueprints($hide_blueprints) { + $this->hideBlueprints = $hide_blueprints; + return $this; + } + + public function getHideBlueprints() { + return $this->hideBlueprints; + } + + public function setHideResources($hide_resources) { + $this->hideResources = $hide_resources; + return $this; + } + + public function getHideResources() { + return $this->hideResources; + } + + public function setHideLeases($hide_leases) { + $this->hideLeases = $hide_leases; + return $this; + } + + public function getHideLeases() { + return $this->hideLeases; + } + + public function setHideOperations($hide_operations) { + $this->hideOperations = $hide_operations; + return $this; + } + + public function getHideOperations() { + return $this->hideOperations; + } public function setLogs(array $logs) { assert_instances_of($logs, 'DrydockLog'); @@ -41,6 +81,13 @@ final class DrydockLogListView extends AphrontView { $lease = null; } + $operation_phid = $log->getOperationPHID(); + if ($operation_phid) { + $operation = $viewer->renderHandle($operation_phid); + } else { + $operation = null; + } + if ($log->isComplete()) { $type_key = $log->getType(); if (isset($types[$type_key])) { @@ -72,6 +119,7 @@ final class DrydockLogListView extends AphrontView { $blueprint, $resource, $lease, + $operation, id(new PHUIIconView())->setIcon($icon), $type, $data, @@ -86,16 +134,25 @@ final class DrydockLogListView extends AphrontView { pht('Blueprint'), pht('Resource'), pht('Lease'), + pht('Operation'), null, pht('Type'), pht('Data'), pht('Date'), )) + ->setColumnVisibility( + array( + !$this->getHideBlueprints(), + !$this->getHideResources(), + !$this->getHideLeases(), + !$this->getHideOperations(), + )) ->setColumnClasses( array( '', '', '', + '', 'icon', '', 'wide', diff --git a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php index dfd603fd12..c6e6927f22 100644 --- a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php @@ -25,8 +25,6 @@ final class DrydockRepositoryOperationUpdateWorker private function handleUpdate(DrydockRepositoryOperation $operation) { - $viewer = $this->getViewer(); - $operation_state = $operation->getOperationState(); switch ($operation_state) { @@ -53,9 +51,6 @@ final class DrydockRepositoryOperationUpdateWorker // waiting for a lease we're holding. try { - $operation->getImplementation() - ->setViewer($viewer); - $lease = $this->loadWorkingCopyLease($operation); $interface = $lease->getInterface( @@ -64,6 +59,10 @@ final class DrydockRepositoryOperationUpdateWorker // No matter what happens here, destroy the lease away once we're done. $lease->setReleaseOnDestruction(true); + $operation->attachWorkingCopyLease($lease); + + $operation->logEvent(DrydockOperationWorkLogType::LOGCONST); + $operation->applyOperation($interface); } catch (PhabricatorWorkerYieldException $ex) { diff --git a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php index 9afb836b94..7b5d6d0720 100644 --- a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php +++ b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php @@ -30,7 +30,8 @@ final class PhabricatorFavoritesMainMenuBarExtension ->addClass('phabricator-core-user-menu') ->setNoCSS(true) ->setDropdown(true) - ->setDropdownMenu($dropdown); + ->setDropdownMenu($dropdown) + ->setAuralLabel(pht('Favorites Menu')); return array( $favorites_menu, diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 01824d5fcb..408524edc5 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -159,10 +159,22 @@ final class PhabricatorFile extends PhabricatorFileDAO public function saveAndIndex() { $this->save(); - PhabricatorSearchWorker::queueDocumentForIndexing($this->getPHID()); + + if ($this->isIndexableFile()) { + PhabricatorSearchWorker::queueDocumentForIndexing($this->getPHID()); + } + return $this; } + private function isIndexableFile() { + if ($this->getIsChunk()) { + return false; + } + + return true; + } + public function getMonogram() { return 'F'.$this->getID(); } diff --git a/src/applications/guides/module/PhabricatorGuideInstallModule.php b/src/applications/guides/module/PhabricatorGuideInstallModule.php index 7a46b20027..172ff7a17b 100644 --- a/src/applications/guides/module/PhabricatorGuideInstallModule.php +++ b/src/applications/guides/module/PhabricatorGuideInstallModule.php @@ -169,7 +169,7 @@ final class PhabricatorGuideInstallModule extends PhabricatorGuideModule { $intro = new PHUIRemarkupView($viewer, $intro); - $intro = id(new PHUIDocumentViewPro()) + $intro = id(new PHUIDocumentView()) ->appendChild($intro); return array($intro, $guide_items); diff --git a/src/applications/guides/module/PhabricatorGuideQuickStartModule.php b/src/applications/guides/module/PhabricatorGuideQuickStartModule.php index c06e320df7..8c4b4c2606 100644 --- a/src/applications/guides/module/PhabricatorGuideQuickStartModule.php +++ b/src/applications/guides/module/PhabricatorGuideQuickStartModule.php @@ -206,7 +206,7 @@ final class PhabricatorGuideQuickStartModule extends PhabricatorGuideModule { 'these features at your own pace.'); $intro = new PHUIRemarkupView($viewer, $intro); - $intro = id(new PHUIDocumentViewPro()) + $intro = id(new PHUIDocumentView()) ->appendChild($intro); return array($intro, $guide_items); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 2ebc934605..1e79ad2b46 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -318,7 +318,7 @@ final class HarbormasterBuildableViewController if ($lint_data) { $lint_table = id(new HarbormasterLintPropertyView()) - ->setUser($viewer) + ->setViewer($viewer) ->setLimit(10) ->setLintMessages($lint_data); @@ -343,6 +343,7 @@ final class HarbormasterBuildableViewController if ($unit_data) { $unit = id(new HarbormasterUnitSummaryView()) + ->setViewer($viewer) ->setBuildable($buildable) ->setUnitMessages($unit_data) ->setShowViewAll(true) diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php index 5f65bca86d..d548ceac98 100644 --- a/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php +++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php @@ -39,6 +39,7 @@ final class HarbormasterUnitMessageListController } $unit = id(new HarbormasterUnitSummaryView()) + ->setViewer($viewer) ->setBuildable($buildable) ->setUnitMessages($unit_data); diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php index 9881da06c6..5cb33c0c9a 100644 --- a/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php @@ -88,23 +88,7 @@ final class HarbormasterUnitMessageViewController pht('Run At'), phabricator_datetime($message->getDateCreated(), $viewer)); - $details = $message->getUnitMessageDetails(); - if (strlen($details)) { - // TODO: Use the log view here, once it gets cleaned up. - // Shenanigans below. - $details = phutil_tag( - 'div', - array( - 'class' => 'PhabricatorMonospaced', - 'style' => - 'white-space: pre-wrap; '. - 'color: #666666; '. - 'overflow-x: auto;', - ), - $details); - } else { - $details = phutil_tag('em', array(), pht('No details provided.')); - } + $details = $message->newUnitMessageDetailsView($viewer); $view->addSectionHeader( pht('Details'), diff --git a/src/applications/harbormaster/engine/HarbormasterBuildableEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildableEngine.php index 41d31673d1..25222975b7 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildableEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildableEngine.php @@ -101,5 +101,8 @@ abstract class HarbormasterBuildableEngine $xactions); } + public function getAuthorIdentity() { + return null; + } } diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index ccf97451b7..e0dfe05cc0 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -193,7 +193,7 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { * @return string String with variables replaced safely into it. */ protected function mergeVariables($function, $pattern, array $variables) { - $regexp = '@\\$\\{(?P[a-z\\./-]+)\\}@'; + $regexp = '@\\$\\{(?P[a-z\\./_-]+)\\}@'; $matches = null; preg_match_all($regexp, $pattern, $matches); diff --git a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php index 8a522c4926..ef4cc0daf1 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php @@ -117,6 +117,20 @@ EOTEXT ), ); + $engine = HarbormasterBuildableEngine::newForObject( + $object, + $viewer); + + $author_identity = $engine->getAuthorIdentity(); + if ($author_identity) { + $data_structure += array( + 'author' => array( + 'name' => $author_identity->getIdentityDisplayName(), + 'email' => $author_identity->getIdentityEmailAddress(), + ), + ); + } + $json_data = phutil_json_encode($data_structure); $credential_phid = $this->getSetting('token'); diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php index 956850bd9f..b2f566c3eb 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php @@ -13,6 +13,9 @@ final class HarbormasterBuildUnitMessage private $buildTarget = self::ATTACHABLE; + const FORMAT_TEXT = 'text'; + const FORMAT_REMARKUP = 'remarkup'; + public static function initializeNewUnitMessage( HarbormasterBuildTarget $build_target) { return id(new HarbormasterBuildUnitMessage()) @@ -66,6 +69,13 @@ final class HarbormasterBuildUnitMessage 'description' => pht( 'Additional human-readable information about the failure.'), ), + 'format' => array( + 'type' => 'optional string', + 'description' => pht( + 'Format for the text provided in "details". Valid values are '. + '"text" (default) or "remarkup". This controls how test details '. + 'are rendered when shown to users.'), + ), ); } @@ -104,6 +114,11 @@ final class HarbormasterBuildUnitMessage $obj->setProperty('details', $details); } + $format = idx($dict, 'format'); + if ($format) { + $obj->setProperty('format', $format); + } + return $obj; } @@ -149,6 +164,66 @@ final class HarbormasterBuildUnitMessage return $this->getProperty('details', ''); } + public function getUnitMessageDetailsFormat() { + return $this->getProperty('format', self::FORMAT_TEXT); + } + + public function newUnitMessageDetailsView( + PhabricatorUser $viewer, + $summarize = false) { + + $format = $this->getUnitMessageDetailsFormat(); + + $is_text = ($format !== self::FORMAT_REMARKUP); + $is_remarkup = ($format === self::FORMAT_REMARKUP); + + $full_details = $this->getUnitMessageDetails(); + + if (!strlen($full_details)) { + if ($summarize) { + return null; + } + $details = phutil_tag('em', array(), pht('No details provided.')); + } else if ($summarize) { + if ($is_text) { + $details = id(new PhutilUTF8StringTruncator()) + ->setMaximumBytes(2048) + ->truncateString($full_details); + $details = phutil_split_lines($details); + + $limit = 3; + if (count($details) > $limit) { + $details = array_slice($details, 0, $limit); + } + + $details = implode('', $details); + } else { + $details = $full_details; + } + } else { + $details = $full_details; + } + + require_celerity_resource('harbormaster-css'); + + $classes = array(); + $classes[] = 'harbormaster-unit-details'; + + if ($is_remarkup) { + $details = new PHUIRemarkupView($viewer, $details); + } else { + $classes[] = 'harbormaster-unit-details-text'; + $classes[] = 'PhabricatorMonospaced'; + } + + return phutil_tag( + 'div', + array( + 'class' => implode(' ', $classes), + ), + $details); + } + public function getUnitMessageDisplayName() { $name = $this->getName(); diff --git a/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php b/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php index 0ce1688027..ee8b80a7a7 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php @@ -34,9 +34,8 @@ final class HarbormasterUnitPropertyView extends AphrontView { return $this; } - public function render() { - require_celerity_resource('harbormaster-css'); + $viewer = $this->getViewer(); $messages = $this->unitMessages; $messages = msort($messages, 'getSortKey'); @@ -84,13 +83,10 @@ final class HarbormasterUnitPropertyView extends AphrontView { $name); } - $details = $message->getUnitMessageDetails(); - if (strlen($details)) { - $name = array( - $name, - $this->renderUnitTestDetails($details), - ); - } + $name = array( + $name, + $message->newUnitMessageDetailsView($viewer, true), + ); $rows[] = array( $result_icon, @@ -158,25 +154,4 @@ final class HarbormasterUnitPropertyView extends AphrontView { return $table; } - private function renderUnitTestDetails($full_details) { - $details = id(new PhutilUTF8StringTruncator()) - ->setMaximumBytes(2048) - ->truncateString($full_details); - $details = phutil_split_lines($details); - - $limit = 3; - if (count($details) > $limit) { - $details = array_slice($details, 0, $limit); - } - - $details = implode('', $details); - - return phutil_tag( - 'div', - array( - 'class' => 'PhabricatorMonospaced harbormaster-unit-details', - ), - $details); - } - } diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php index e5a66a3eb8..66ba2307b6 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -77,6 +77,7 @@ final class HarbormasterUnitSummaryView extends AphrontView { ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $table = id(new HarbormasterUnitPropertyView()) + ->setViewer($this->getViewer()) ->setUnitMessages($messages); if ($this->showViewAll) { diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php index f19cf43d0e..1748dba452 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php @@ -272,7 +272,7 @@ final class LegalpadDocumentSignController extends LegalpadController { $preamble_box->addPropertyList($preamble); } - $content = id(new PHUIDocumentViewPro()) + $content = id(new PHUIDocumentView()) ->addClass('legalpad') ->setHeader($header) ->appendChild( diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index ecceb5a630..a4377ad3a7 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -86,11 +86,6 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication { 'template' => ManiphestTaskPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), - ManiphestEditStatusCapability::CAPABILITY => array(), - ManiphestEditAssignCapability::CAPABILITY => array(), - ManiphestEditPoliciesCapability::CAPABILITY => array(), - ManiphestEditPriorityCapability::CAPABILITY => array(), - ManiphestEditProjectsCapability::CAPABILITY => array(), ManiphestBulkEditCapability::CAPABILITY => array(), ); } diff --git a/src/applications/maniphest/capability/ManiphestEditAssignCapability.php b/src/applications/maniphest/capability/ManiphestEditAssignCapability.php deleted file mode 100644 index 5da3adf55e..0000000000 --- a/src/applications/maniphest/capability/ManiphestEditAssignCapability.php +++ /dev/null @@ -1,15 +0,0 @@ -setTask($object); } - protected function requireCapabilities( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - parent::requireCapabilities($object, $xaction); - - $app_capability_map = array( - ManiphestTaskPriorityTransaction::TRANSACTIONTYPE => - ManiphestEditPriorityCapability::CAPABILITY, - ManiphestTaskStatusTransaction::TRANSACTIONTYPE => - ManiphestEditStatusCapability::CAPABILITY, - ManiphestTaskOwnerTransaction::TRANSACTIONTYPE => - ManiphestEditAssignCapability::CAPABILITY, - PhabricatorTransactions::TYPE_EDIT_POLICY => - ManiphestEditPoliciesCapability::CAPABILITY, - PhabricatorTransactions::TYPE_VIEW_POLICY => - ManiphestEditPoliciesCapability::CAPABILITY, - ); - - - $transaction_type = $xaction->getTransactionType(); - - $app_capability = null; - if ($transaction_type == PhabricatorTransactions::TYPE_EDGE) { - switch ($xaction->getMetadataValue('edge:type')) { - case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST: - $app_capability = ManiphestEditProjectsCapability::CAPABILITY; - break; - } - } else { - $app_capability = idx($app_capability_map, $transaction_type); - } - - if ($app_capability) { - // Special exception for Blender: - // We don't want users to modify someone's else task policy and we don't - // really want user himself to modifu policy. Additionally, we can't grant - // policy modification to just task author which makes it's tricky to create - // new objects. - // So we ignore edit policy for new objects. - if ($app_capability == ManiphestEditPoliciesCapability::CAPABILITY) { - if ($object->getPHID() == "") { - return; - } - } - $app = id(new PhabricatorApplicationQuery()) - ->setViewer($this->getActor()) - ->withClasses(array('PhabricatorManiphestApplication')) - ->executeOne(); - PhabricatorPolicyFilter::requireCapability( - $this->getActor(), - $app, - $app_capability); - } - } - protected function adjustObjectForPolicyChecks( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 4480757d71..33787a9cf5 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -422,11 +422,7 @@ final class ManiphestTaskSearchEngine $can_edit_priority = false; $can_bulk_edit = false; } else { - $can_edit_priority = PhabricatorPolicyFilter::hasCapability( - $viewer, - $this->getApplication(), - ManiphestEditPriorityCapability::CAPABILITY); - + $can_edit_priority = true; $can_bulk_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $this->getApplication(), diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php index 83f38fc659..398808e006 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php @@ -172,4 +172,33 @@ final class ManiphestTaskPriorityTransaction return $errors; } + public function getTransactionTypeForConduit($xaction) { + return 'priority'; + } + + public function getFieldValuesForConduit($xaction, $data) { + $old = $xaction->getOldValue(); + if ($old !== null) { + $old = (int)$old; + $old_name = ManiphestTaskPriority::getTaskPriorityName($old); + } else { + $old_name = null; + } + + $new = $xaction->getNewValue(); + $new = (int)$new; + $new_name = ManiphestTaskPriority::getTaskPriorityName($new); + + return array( + 'old' => array( + 'value' => $old, + 'name' => $old_name, + ), + 'new' => array( + 'value' => $new, + 'name' => $new_name, + ), + ); + } + } diff --git a/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php b/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php index 2979e92229..43ed2e957e 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php @@ -132,7 +132,7 @@ final class PhabricatorApplicationEmailCommandsController $header = id(new PHUIHeaderView()) ->setHeader($title); - $document = id(new PHUIDocumentViewPro()) + $document = id(new PHUIDocumentView()) ->setHeader($header) ->appendChild($info_view) ->appendChild($content_box); diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php index 059a0b8ae6..22a404e285 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -621,7 +621,7 @@ final class PhabricatorMetaMTAMail public function sendWithMailers(array $mailers) { if (!$mailers) { - $any_mailers = self::newMailers(); + $any_mailers = self::newMailers(array()); // NOTE: We can end up here with some custom list of "$mailers", like // from a unit test. In that case, this message could be misleading. We diff --git a/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php b/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php index 4b231b959e..b5d82c9749 100644 --- a/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php +++ b/src/applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php @@ -23,6 +23,7 @@ final class PhabricatorMetaMTAMailableFunctionDatasource new PhabricatorProjectMembersDatasource(), new PhabricatorProjectDatasource(), new PhabricatorOwnersPackageDatasource(), + new PhabricatorOwnersPackageOwnerDatasource(), ); } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 1caf12a82d..f71009cf19 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -71,7 +71,7 @@ final class PhabricatorOwnersDetailController 'package' => $package->getPHID(), )); - $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; + $status_concern = DiffusionCommitAuditStatus::CONCERN_RAISED; $attention_commits = id(new DiffusionCommitQuery()) ->setViewer($request->getUser()) diff --git a/src/applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php b/src/applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php index 99e770d266..2a0b16e60f 100644 --- a/src/applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php +++ b/src/applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php @@ -27,7 +27,7 @@ final class PhabricatorOwnersPackageOwnerDatasource 'packages' => array( 'name' => pht('Packages: ...'), 'arguments' => pht('owner'), - 'summary' => pht("Find results in any of an owner's projects."), + 'summary' => pht("Find results in any of an owner's packages."), 'description' => pht( "This function allows you to find results associated with any ". "of the packages a specified user or project is an owner of. ". @@ -61,18 +61,21 @@ final class PhabricatorOwnersPackageOwnerDatasource $phids = $this->resolvePHIDs($phids); - $user_phids = array(); + $owner_phids = array(); foreach ($phids as $key => $phid) { - if (phid_get_type($phid) == PhabricatorPeopleUserPHIDType::TYPECONST) { - $user_phids[] = $phid; - unset($phids[$key]); + switch (phid_get_type($phid)) { + case PhabricatorPeopleUserPHIDType::TYPECONST: + case PhabricatorProjectProjectPHIDType::TYPECONST: + $owner_phids[] = $phid; + unset($phids[$key]); + break; } } - if ($user_phids) { + if ($owner_phids) { $packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($this->getViewer()) - ->withOwnerPHIDs($user_phids) + ->withOwnerPHIDs($owner_phids) ->execute(); foreach ($packages as $package) { $phids[] = $package->getPHID(); @@ -116,8 +119,13 @@ final class PhabricatorOwnersPackageOwnerDatasource $usernames = array(); foreach ($phids as $key => $phid) { - if (phid_get_type($phid) != PhabricatorPeopleUserPHIDType::TYPECONST) { - $usernames[$key] = $phid; + switch (phid_get_type($phid)) { + case PhabricatorPeopleUserPHIDType::TYPECONST: + case PhabricatorProjectProjectPHIDType::TYPECONST: + break; + default: + $usernames[$key] = $phid; + break; } } diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index 6322b29b24..9238d8da3b 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -97,6 +97,9 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication { PeopleCreateUsersCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), + PeopleDisableUsersCapability::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), PeopleBrowseUserDirectoryCapability::CAPABILITY => array(), ); } diff --git a/src/applications/people/capability/PeopleDisableUsersCapability.php b/src/applications/people/capability/PeopleDisableUsersCapability.php new file mode 100644 index 0000000000..bb58ed2e76 --- /dev/null +++ b/src/applications/people/capability/PeopleDisableUsersCapability.php @@ -0,0 +1,16 @@ + 'required list', @@ -43,11 +51,23 @@ final class UserDisableConduitAPIMethod extends UserConduitAPIMethod { throw new ConduitException('ERR-BAD-PHID'); } - foreach ($users as $user) { - id(new PhabricatorUserEditor()) - ->setActor($actor) - ->disableUser($user, true); + foreach ($phids as $phid) { + $params = array( + 'transactions' => array( + array( + 'type' => 'disabled', + 'value' => true, + ), + ), + 'objectIdentifier' => $phid, + ); + + id(new ConduitCall('user.edit', $params)) + ->setUser($actor) + ->execute(); } + + return null; } } diff --git a/src/applications/people/conduit/UserEditConduitAPIMethod.php b/src/applications/people/conduit/UserEditConduitAPIMethod.php new file mode 100644 index 0000000000..edc4204c3b --- /dev/null +++ b/src/applications/people/conduit/UserEditConduitAPIMethod.php @@ -0,0 +1,20 @@ + 'required list', @@ -43,11 +51,23 @@ final class UserEnableConduitAPIMethod extends UserConduitAPIMethod { throw new ConduitException('ERR-BAD-PHID'); } - foreach ($users as $user) { - id(new PhabricatorUserEditor()) - ->setActor($actor) - ->disableUser($user, false); + foreach ($phids as $phid) { + $params = array( + 'transactions' => array( + array( + 'type' => 'disabled', + 'value' => false, + ), + ), + 'objectIdentifier' => $phid, + ); + + id(new ConduitCall('user.edit', $params)) + ->setUser($actor) + ->execute(); } + + return null; } } diff --git a/src/applications/people/controller/PhabricatorPeopleApproveController.php b/src/applications/people/controller/PhabricatorPeopleApproveController.php index 58cd2e2119..0e97ad6ee6 100644 --- a/src/applications/people/controller/PhabricatorPeopleApproveController.php +++ b/src/applications/people/controller/PhabricatorPeopleApproveController.php @@ -16,6 +16,13 @@ final class PhabricatorPeopleApproveController $done_uri = $this->getApplicationURI('query/approval/'); + if ($user->getIsApproved()) { + return $this->newDialog() + ->setTitle(pht('Already Approved')) + ->appendChild(pht('This user has already been approved.')) + ->addCancelButton($done_uri); + } + if ($request->isFormPost()) { id(new PhabricatorUserEditor()) ->setActor($viewer) diff --git a/src/applications/people/controller/PhabricatorPeopleDisableController.php b/src/applications/people/controller/PhabricatorPeopleDisableController.php index a556f8e519..9f2718086b 100644 --- a/src/applications/people/controller/PhabricatorPeopleDisableController.php +++ b/src/applications/people/controller/PhabricatorPeopleDisableController.php @@ -3,10 +3,14 @@ final class PhabricatorPeopleDisableController extends PhabricatorPeopleController { + public function shouldRequireAdmin() { + return false; + } + public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $id = $request->getURIData('id'); - $via = $request->getURIData('id'); + $via = $request->getURIData('via'); $user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) @@ -20,11 +24,36 @@ final class PhabricatorPeopleDisableController // on profiles and also via the "X" action on the approval queue. We do // things slightly differently depending on the context the actor is in. + // In particular, disabling via "Disapprove" requires you be an + // administrator (and bypasses the "Can Disable Users" permission). + // Disabling via "Disable" requires the permission only. + $is_disapprove = ($via == 'disapprove'); if ($is_disapprove) { $done_uri = $this->getApplicationURI('query/approval/'); + + if (!$viewer->getIsAdmin()) { + return $this->newDialog() + ->setTitle(pht('No Permission')) + ->appendParagraph(pht('Only administrators can disapprove users.')) + ->addCancelButton($done_uri); + } + + if ($user->getIsApproved()) { + return $this->newDialog() + ->setTitle(pht('Already Approved')) + ->appendParagraph(pht('This user has already been approved.')) + ->addCancelButton($done_uri); + } + + // On the "Disapprove" flow, bypass the "Can Disable Users" permission. + $actor = PhabricatorUser::getOmnipotentUser(); $should_disable = true; } else { + $this->requireApplicationCapability( + PeopleDisableUsersCapability::CAPABILITY); + + $actor = $viewer; $done_uri = $this->getApplicationURI("manage/{$id}/"); $should_disable = !$user->getIsDisabled(); } @@ -39,9 +68,19 @@ final class PhabricatorPeopleDisableController } if ($request->isFormPost()) { - id(new PhabricatorUserEditor()) - ->setActor($viewer) - ->disableUser($user, $should_disable); + $xactions = array(); + + $xactions[] = id(new PhabricatorUserTransaction()) + ->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE) + ->setNewValue($should_disable); + + id(new PhabricatorUserTransactionEditor()) + ->setActor($actor) + ->setActingAsPHID($viewer->getPHID()) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->applyTransactions($user, $xactions); return id(new AphrontRedirectResponse())->setURI($done_uri); } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php index 3eda8a968b..4876f4495d 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php @@ -38,10 +38,9 @@ final class PhabricatorPeopleProfileEditController new PhabricatorUserTransaction(), $request); - $editor = id(new PhabricatorUserProfileEditor()) + $editor = id(new PhabricatorUserTransactionEditor()) ->setActor($viewer) - ->setContentSource( - PhabricatorContentSource::newFromRequest($request)) + ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { diff --git a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php index 2ac3e6de89..9759a375c7 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php @@ -75,11 +75,22 @@ final class PhabricatorPeopleProfileManageController private function buildCurtain(PhabricatorUser $user) { $viewer = $this->getViewer(); + $is_self = ($user->getPHID() === $viewer->getPHID()); + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $user, PhabricatorPolicyCapability::CAN_EDIT); + $is_admin = $viewer->getIsAdmin(); + $can_admin = ($is_admin && !$is_self); + + $has_disable = $this->hasApplicationCapability( + PeopleDisableUsersCapability::CAPABILITY); + $can_disable = ($has_disable && !$is_self); + + $can_welcome = ($is_admin && $user->canEstablishWebSessions()); + $curtain = $this->newCurtainView($user); $curtain->addAction( @@ -114,10 +125,6 @@ final class PhabricatorPeopleProfileManageController $empower_name = pht('Make Administrator'); } - $is_admin = $viewer->getIsAdmin(); - $is_self = ($user->getPHID() === $viewer->getPHID()); - $can_admin = ($is_admin && !$is_self); - $curtain->addAction( id(new PhabricatorActionView()) ->setIcon($empower_icon) @@ -146,7 +153,7 @@ final class PhabricatorPeopleProfileManageController id(new PhabricatorActionView()) ->setIcon($disable_icon) ->setName($disable_name) - ->setDisabled(!$can_admin) + ->setDisabled(!$can_disable) ->setWorkflow(true) ->setHref($this->getApplicationURI('disable/'.$user->getID().'/'))); @@ -158,8 +165,6 @@ final class PhabricatorPeopleProfileManageController ->setWorkflow(true) ->setHref($this->getApplicationURI('delete/'.$user->getID().'/'))); - $can_welcome = ($is_admin && $user->canEstablishWebSessions()); - $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-envelope') diff --git a/src/applications/people/customfield/PhabricatorUserBlurbField.php b/src/applications/people/customfield/PhabricatorUserBlurbField.php index 04fb419918..222061ad04 100644 --- a/src/applications/people/customfield/PhabricatorUserBlurbField.php +++ b/src/applications/people/customfield/PhabricatorUserBlurbField.php @@ -9,6 +9,14 @@ final class PhabricatorUserBlurbField return 'user:blurb'; } + public function getModernFieldKey() { + return 'blurb'; + } + + public function getFieldKeyForConduit() { + return $this->getModernFieldKey(); + } + public function getFieldName() { return pht('Blurb'); } @@ -50,6 +58,11 @@ final class PhabricatorUserBlurbField $this->value = $request->getStr($this->getFieldKey()); } + public function setValueFromStorage($value) { + $this->value = $value; + return $this; + } + public function renderEditControl(array $handles) { return id(new PhabricatorRemarkupControl()) ->setUser($this->getViewer()) @@ -85,4 +98,12 @@ final class PhabricatorUserBlurbField return 'block'; } + public function shouldAppearInConduitTransactions() { + return true; + } + + protected function newConduitEditParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/applications/people/customfield/PhabricatorUserIconField.php b/src/applications/people/customfield/PhabricatorUserIconField.php index a3f43cb7bf..0b25a9dd81 100644 --- a/src/applications/people/customfield/PhabricatorUserIconField.php +++ b/src/applications/people/customfield/PhabricatorUserIconField.php @@ -9,6 +9,14 @@ final class PhabricatorUserIconField return 'user:icon'; } + public function getModernFieldKey() { + return 'icon'; + } + + public function getFieldKeyForConduit() { + return $this->getModernFieldKey(); + } + public function getFieldName() { return pht('Icon'); } @@ -50,6 +58,11 @@ final class PhabricatorUserIconField $this->value = $request->getStr($this->getFieldKey()); } + public function setValueFromStorage($value) { + $this->value = $value; + return $this; + } + public function renderEditControl(array $handles) { return id(new PHUIFormIconSetControl()) ->setName($this->getFieldKey()) @@ -58,4 +71,12 @@ final class PhabricatorUserIconField ->setIconSet(new PhabricatorPeopleIconSet()); } + public function shouldAppearInConduitTransactions() { + return true; + } + + protected function newConduitEditParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/applications/people/customfield/PhabricatorUserRealNameField.php b/src/applications/people/customfield/PhabricatorUserRealNameField.php index 90e490fc3b..e7e610fb07 100644 --- a/src/applications/people/customfield/PhabricatorUserRealNameField.php +++ b/src/applications/people/customfield/PhabricatorUserRealNameField.php @@ -9,6 +9,14 @@ final class PhabricatorUserRealNameField return 'user:realname'; } + public function getModernFieldKey() { + return 'realName'; + } + + public function getFieldKeyForConduit() { + return $this->getModernFieldKey(); + } + public function getFieldName() { return pht('Real Name'); } @@ -53,6 +61,11 @@ final class PhabricatorUserRealNameField $this->value = $request->getStr($this->getFieldKey()); } + public function setValueFromStorage($value) { + $this->value = $value; + return $this; + } + public function renderEditControl(array $handles) { return id(new AphrontFormTextControl()) ->setName($this->getFieldKey()) @@ -65,4 +78,12 @@ final class PhabricatorUserRealNameField return PhabricatorEnv::getEnvConfig('account.editable'); } + public function shouldAppearInConduitTransactions() { + return true; + } + + protected function newConduitEditParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/applications/people/customfield/PhabricatorUserTitleField.php b/src/applications/people/customfield/PhabricatorUserTitleField.php index af6793e76e..5ceb774fa4 100644 --- a/src/applications/people/customfield/PhabricatorUserTitleField.php +++ b/src/applications/people/customfield/PhabricatorUserTitleField.php @@ -9,6 +9,14 @@ final class PhabricatorUserTitleField return 'user:title'; } + public function getModernFieldKey() { + return 'title'; + } + + public function getFieldKeyForConduit() { + return $this->getModernFieldKey(); + } + public function getFieldName() { return pht('Title'); } @@ -50,6 +58,11 @@ final class PhabricatorUserTitleField $this->value = $request->getStr($this->getFieldKey()); } + public function setValueFromStorage($value) { + $this->value = $value; + return $this; + } + public function renderEditControl(array $handles) { return id(new AphrontFormTextControl()) ->setName($this->getFieldKey()) @@ -57,4 +70,12 @@ final class PhabricatorUserTitleField ->setLabel($this->getFieldName()); } + public function shouldAppearInConduitTransactions() { + return true; + } + + protected function newConduitEditParameterType() { + return new ConduitStringParameterType(); + } + } diff --git a/src/applications/people/editor/PhabricatorUserEditEngine.php b/src/applications/people/editor/PhabricatorUserEditEngine.php new file mode 100644 index 0000000000..c547426b12 --- /dev/null +++ b/src/applications/people/editor/PhabricatorUserEditEngine.php @@ -0,0 +1,81 @@ +getUsername()); + } + + protected function getObjectEditShortText($object) { + return $object->getMonogram(); + } + + protected function getObjectCreateShortText() { + return pht('Create User'); + } + + protected function getObjectName() { + return pht('User'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getCreateNewObjectPolicy() { + // At least for now, forbid creating new users via EditEngine. This is + // primarily enforcing that "user.edit" can not create users via the API. + return PhabricatorPolicies::POLICY_NOONE; + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorBoolEditField()) + ->setKey('disabled') + ->setOptions(pht('Active'), pht('Disabled')) + ->setLabel(pht('Disabled')) + ->setDescription(pht('Disable the user.')) + ->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE) + ->setIsConduitOnly(true) + ->setConduitDescription(pht('Disable or enable the user.')) + ->setConduitTypeDescription(pht('True to disable the user.')) + ->setValue($object->getIsDisabled()), + ); + } + +} diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index 786ead79c3..58c2ba1ff1 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -293,45 +293,6 @@ final class PhabricatorUserEditor extends PhabricatorEditor { return $this; } - /** - * @task role - */ - public function disableUser(PhabricatorUser $user, $disable) { - $actor = $this->requireActor(); - - if (!$user->getID()) { - throw new Exception(pht('User has not been created yet!')); - } - - $user->openTransaction(); - $user->beginWriteLocking(); - - $user->reload(); - if ($user->getIsDisabled() == $disable) { - $user->endWriteLocking(); - $user->killTransaction(); - return $this; - } - - $log = PhabricatorUserLog::initializeNewLog( - $actor, - $user->getPHID(), - PhabricatorUserLog::ACTION_DISABLE); - $log->setOldValue($user->getIsDisabled()); - $log->setNewValue($disable); - - $user->setIsDisabled((int)$disable); - $user->save(); - - $log->save(); - - $user->endWriteLocking(); - $user->saveTransaction(); - - return $this; - } - - /** * @task role */ diff --git a/src/applications/people/editor/PhabricatorUserProfileEditor.php b/src/applications/people/editor/PhabricatorUserTransactionEditor.php similarity index 73% rename from src/applications/people/editor/PhabricatorUserProfileEditor.php rename to src/applications/people/editor/PhabricatorUserTransactionEditor.php index 9ec02ba2af..c0dfa941af 100644 --- a/src/applications/people/editor/PhabricatorUserProfileEditor.php +++ b/src/applications/people/editor/PhabricatorUserTransactionEditor.php @@ -1,6 +1,6 @@ setIcon($profile_image) ->addClass('phabricator-core-user-menu') ->setHasCaret(true) - ->setNoCSS(true); + ->setNoCSS(true) + ->setAuralLabel(pht('Account Menu')); return array( $user_menu, diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index c35e6127d0..78fe4566a0 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -376,6 +376,14 @@ final class PhabricatorUser return ($this->session !== self::ATTACHABLE); } + public function hasHighSecuritySession() { + if (!$this->hasSession()) { + return false; + } + + return $this->getSession()->isHighSecuritySession(); + } + private function generateConduitCertificate() { return Filesystem::readRandomCharacters(255); } @@ -1432,7 +1440,7 @@ final class PhabricatorUser public function getApplicationTransactionEditor() { - return new PhabricatorUserProfileEditor(); + return new PhabricatorUserTransactionEditor(); } public function getApplicationTransactionObject() { diff --git a/src/applications/people/storage/PhabricatorUserTransaction.php b/src/applications/people/storage/PhabricatorUserTransaction.php index b4bd1c9865..24edb2f5b5 100644 --- a/src/applications/people/storage/PhabricatorUserTransaction.php +++ b/src/applications/people/storage/PhabricatorUserTransaction.php @@ -1,7 +1,7 @@ getIsDisabled(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsDisabled((int)$value); + + $this->newUserLog(PhabricatorUserLog::ACTION_DISABLE) + ->setOldValue((bool)$object->getIsDisabled()) + ->setNewValue((bool)$value) + ->save(); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s disabled this user.', + $this->renderAuthor()); + } else { + return pht( + '%s enabled this user.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s disabled %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s enabled %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + foreach ($xactions as $xaction) { + $is_disabled = (bool)$object->getIsDisabled(); + + if ((bool)$xaction->getNewValue() === $is_disabled) { + continue; + } + + // You must have the "Can Disable Users" permission to disable a user. + $this->requireApplicationCapability( + PeopleDisableUsersCapability::CAPABILITY); + + if ($this->getActingAsPHID() === $object->getPHID()) { + $errors[] = $this->newInvalidError( + pht('You can not enable or disable your own account.')); + } + } + + return $errors; + } + + public function getRequiredCapabilities( + $object, + PhabricatorApplicationTransaction $xaction) { + + // You do not need to be able to edit users to disable them. Instead, this + // requirement is replaced with a requirement that you have the "Can + // Disable Users" permission. + + return null; + } +} diff --git a/src/applications/people/xaction/PhabricatorUserTransactionType.php b/src/applications/people/xaction/PhabricatorUserTransactionType.php new file mode 100644 index 0000000000..dcd45d480e --- /dev/null +++ b/src/applications/people/xaction/PhabricatorUserTransactionType.php @@ -0,0 +1,13 @@ +getActor(), + $this->getObject()->getPHID(), + $action); + } + +} diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php index 037026fa80..bf263e97a8 100644 --- a/src/applications/phame/controller/PhameHomeController.php +++ b/src/applications/phame/controller/PhameHomeController.php @@ -84,7 +84,7 @@ final class PhameHomeController extends PhamePostController { pht('Recent Posts'), $this->getApplicationURI('post/')); - $page = id(new PHUIDocumentViewPro()) + $page = id(new PHUIDocumentView()) ->setHeader($header) ->appendChild($post_list); diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php index 08b23e5a95..e9e422aeb1 100644 --- a/src/applications/phame/controller/blog/PhameBlogViewController.php +++ b/src/applications/phame/controller/blog/PhameBlogViewController.php @@ -85,7 +85,7 @@ final class PhameBlogViewController extends PhameLiveController { } } - $page = id(new PHUIDocumentViewPro()) + $page = id(new PHUIDocumentView()) ->setHeader($header) ->appendChild($post_list); diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 63adedb7ae..11d94d2f94 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -30,7 +30,7 @@ final class PhamePostViewController $header->setActionList($actions); } - $document = id(new PHUIDocumentViewPro()) + $document = id(new PHUIDocumentView()) ->setHeader($header); if ($moved) { diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 6bd49d2e7b..d1f7daf3a5 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -128,51 +128,30 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { PhabricatorLiskDAO $object, array $xactions) { - $body = new PhabricatorMetaMTAMailBody(); - $headers = array(); - $comments = array(); - $inline_comments = array(); + $viewer = $this->requireActor(); + $body = id(new PhabricatorMetaMTAMailBody()) + ->setViewer($viewer); + + $mock_uri = $object->getURI(); + $mock_uri = PhabricatorEnv::getProductionURI($mock_uri); + + $this->addHeadersAndCommentsToMailBody( + $body, + $xactions, + pht('View Mock'), + $mock_uri); + + $type_inline = PholioMockInlineTransaction::TRANSACTIONTYPE; + + $inlines = array(); foreach ($xactions as $xaction) { - if ($xaction->shouldHide()) { - continue; - } - $comment = $xaction->getComment(); - switch ($xaction->getTransactionType()) { - case PholioMockInlineTransaction::TRANSACTIONTYPE: - if ($comment && strlen($comment->getContent())) { - $inline_comments[] = $comment; - } - break; - case PhabricatorTransactions::TYPE_COMMENT: - if ($comment && strlen($comment->getContent())) { - $comments[] = $comment->getContent(); - } - // fallthrough - default: - $headers[] = id(clone $xaction) - ->setRenderingTarget('text') - ->getTitle(); - break; + if ($xaction->getTransactionType() == $type_inline) { + $inlines[] = $xaction; } } - $body->addRawSection(implode("\n", $headers)); - - foreach ($comments as $comment) { - $body->addRawSection($comment); - } - - if ($inline_comments) { - $body->addRawSection(pht('INLINE COMMENTS')); - foreach ($inline_comments as $comment) { - $text = pht( - 'Image %d: %s', - $comment->getImageID(), - $comment->getContent()); - $body->addRawSection($text); - } - } + $this->appendInlineCommentsForMail($object, $inlines, $body); $body->addLinkSection( pht('MOCK DETAIL'), @@ -181,6 +160,51 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return $body; } + private function appendInlineCommentsForMail( + $object, + array $inlines, + PhabricatorMetaMTAMailBody $body) { + + if (!$inlines) { + return; + } + + $viewer = $this->requireActor(); + + $header = pht('INLINE COMMENTS'); + $body->addRawPlaintextSection($header); + $body->addRawHTMLSection(phutil_tag('strong', array(), $header)); + + $image_ids = array(); + foreach ($inlines as $inline) { + $comment = $inline->getComment(); + $image_id = $comment->getImageID(); + $image_ids[$image_id] = $image_id; + } + + $images = id(new PholioImageQuery()) + ->setViewer($viewer) + ->withIDs($image_ids) + ->execute(); + $images = mpull($images, null, 'getID'); + + foreach ($inlines as $inline) { + $comment = $inline->getComment(); + $content = $comment->getContent(); + $image_id = $comment->getImageID(); + $image = idx($images, $image_id); + if ($image) { + $image_name = $image->getName(); + } else { + $image_name = pht('Unknown (ID %d)', $image_id); + } + + $body->addRemarkupSection( + pht('Image "%s":', $image_name), + $content); + } + } + protected function getMailSubjectPrefix() { return PhabricatorEnv::getEnvConfig('metamta.pholio.subject-prefix'); } diff --git a/src/applications/pholio/storage/PholioMock.php b/src/applications/pholio/storage/PholioMock.php index 523733b3df..569513cb46 100644 --- a/src/applications/pholio/storage/PholioMock.php +++ b/src/applications/pholio/storage/PholioMock.php @@ -58,6 +58,10 @@ final class PholioMock extends PholioDAO return 'M'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index a932cf2a60..240b9d93e6 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -53,4 +53,13 @@ final class PholioTransaction extends PhabricatorModularTransaction { return $tags; } + public function isInlineCommentTransaction() { + switch ($this->getTransactionType()) { + case PholioMockInlineTransaction::TRANSACTIONTYPE: + return true; + } + + return parent::isInlineCommentTransaction(); + } + } diff --git a/src/applications/phriction/application/PhabricatorPhrictionApplication.php b/src/applications/phriction/application/PhabricatorPhrictionApplication.php index ea91727c76..02f285aef3 100644 --- a/src/applications/phriction/application/PhabricatorPhrictionApplication.php +++ b/src/applications/phriction/application/PhabricatorPhrictionApplication.php @@ -56,11 +56,16 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication { 'edit/(?:(?P[1-9]\d*)/)?' => 'PhrictionEditController', 'delete/(?P[1-9]\d*)/' => 'PhrictionDeleteController', + 'publish/(?P[1-9]\d*)/(?P[1-9]\d*)/' + => 'PhrictionPublishController', 'new/' => 'PhrictionNewController', 'move/(?P[1-9]\d*)/' => 'PhrictionMoveController', 'preview/(?P.*/)' => 'PhrictionMarkupPreviewController', 'diff/(?P[1-9]\d*)/' => 'PhrictionDiffController', + + $this->getEditRoutePattern('document/edit/') + => 'PhrictionEditEngineController', ), ); } diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php index f443ad657b..537f743eba 100644 --- a/src/applications/phriction/controller/PhrictionDiffController.php +++ b/src/applications/phriction/controller/PhrictionDiffController.php @@ -65,8 +65,8 @@ final class PhrictionDiffController extends PhrictionController { $slug = $document->getSlug(); - $revert_l = $this->renderRevertButton($content_l, $current); - $revert_r = $this->renderRevertButton($content_r, $current); + $revert_l = $this->renderRevertButton($document, $content_l, $current); + $revert_r = $this->renderRevertButton($document, $content_r, $current); $crumbs = $this->buildApplicationCrumbs(); $crumb_views = $this->renderBreadcrumbs($slug); @@ -95,7 +95,7 @@ final class PhrictionDiffController extends PhrictionController { $navigation_table = null; if ($l + 1 == $r) { $nav_l = ($l > 1); - $nav_r = ($r != $current->getVersion()); + $nav_r = ($r != $document->getMaxVersion()); $uri = $request->getRequestURI(); @@ -179,10 +179,11 @@ final class PhrictionDiffController extends PhrictionController { } private function renderRevertButton( + PhrictionDocument $document, PhrictionContent $content, PhrictionContent $current) { - $document_id = $content->getDocumentID(); + $document_id = $document->getID(); $version = $content->getVersion(); $hidden_statuses = array( @@ -190,30 +191,28 @@ final class PhrictionDiffController extends PhrictionController { PhrictionChangeType::CHANGE_MOVE_AWAY => true, // Plain silly PhrictionChangeType::CHANGE_STUB => true, // Utterly silly ); + if (isset($hidden_statuses[$content->getChangeType()])) { // Don't show an edit/revert button for changes which deleted, moved or // stubbed the content since it's silly. return null; } - if ($content->getID() == $current->getID()) { - return phutil_tag( - 'a', - array( - 'href' => '/phriction/edit/'.$document_id.'/', - 'class' => 'button button-grey', - ), - pht('Edit Current Version')); + if ($version == $current->getVersion()) { + $label = pht('Edit Current Version %s...', new PhutilNumber($version)); + } else if ($version < $current->getVersion()) { + $label = pht('Edit Older Version %s...', new PhutilNumber($version)); + } else { + $label = pht('Edit Draft Version %s...', new PhutilNumber($version)); } - return phutil_tag( 'a', array( 'href' => '/phriction/edit/'.$document_id.'/?revert='.$version, 'class' => 'button button-grey', ), - pht('Revert to Version %s...', $version)); + $label); } private function renderComparisonTable(array $content) { diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index 8377cf7d29..8da9807064 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -20,8 +20,6 @@ final class PhrictionDocumentController return id(new AphrontRedirectResponse())->setURI($uri); } - require_celerity_resource('phriction-document-css'); - $version_note = null; $core_content = ''; $move_notice = ''; @@ -29,6 +27,8 @@ final class PhrictionDocumentController $content = null; $toc = null; + $is_draft = false; + $document = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withSlugs(array($slug)) @@ -66,8 +66,9 @@ final class PhrictionDocumentController ->addAction($create_button); } else { - $version = $request->getInt('v'); + $max_version = (int)$document->getMaxVersion(); + $version = $request->getInt('v'); if ($version) { $content = id(new PhrictionContentQuery()) ->setViewer($viewer) @@ -78,17 +79,142 @@ final class PhrictionDocumentController return new Aphront404Response(); } - if ($content->getID() != $document->getContentID()) { - $version_note = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild( - pht( - 'You are viewing an older version of this document, as it '. - 'appeared on %s.', - phabricator_datetime($content->getDateCreated(), $viewer))); + // When the "v" parameter exists, the user is in history mode so we + // show this header even if they're looking at the current version + // of the document. This keeps the next/previous links working. + + $view_version = (int)$content->getVersion(); + $published_version = (int)$document->getContent()->getVersion(); + + if ($view_version < $published_version) { + $version_note = pht( + 'You are viewing an older version of this document, as it '. + 'appeared on %s.', + phabricator_datetime($content->getDateCreated(), $viewer)); + } else if ($view_version > $published_version) { + $is_draft = true; + $version_note = pht( + 'You are viewing an unpublished draft of this document.'); + } else { + $version_note = pht( + 'You are viewing the current published version of this document.'); } + + $version_note = array( + phutil_tag( + 'strong', + array(), + pht('Version %d of %d: ', $view_version, $max_version)), + ' ', + $version_note, + ); + + $version_note = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild($version_note); + + $document_uri = new PhutilURI($document->getURI()); + + if ($view_version > 1) { + $previous_uri = $document_uri->alter('v', ($view_version - 1)); + } else { + $previous_uri = null; + } + + if ($view_version !== $published_version) { + $current_uri = $document_uri->alter('v', $published_version); + } else { + $current_uri = null; + } + + if ($view_version < $max_version) { + $next_uri = $document_uri->alter('v', ($view_version + 1)); + } else { + $next_uri = null; + } + + if ($view_version !== $max_version) { + $draft_uri = $document_uri->alter('v', $max_version); + } else { + $draft_uri = null; + } + + $button_bar = id(new PHUIButtonBarView()) + ->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setColor('grey') + ->setIcon('fa-backward') + ->setDisabled(!$previous_uri) + ->setHref($previous_uri) + ->setText(pht('Previous'))) + ->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setColor('grey') + ->setIcon('fa-file-o') + ->setDisabled(!$current_uri) + ->setHref($current_uri) + ->setText(pht('Published'))) + ->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setColor('grey') + ->setIcon('fa-forward', false) + ->setDisabled(!$next_uri) + ->setHref($next_uri) + ->setText(pht('Next'))) + ->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setColor('grey') + ->setIcon('fa-fast-forward', false) + ->setDisabled(!$draft_uri) + ->setHref($draft_uri) + ->setText(pht('Draft'))); + + require_celerity_resource('phui-document-view-css'); + + $version_note = array( + $version_note, + phutil_tag( + 'div', + array( + 'class' => 'phui-document-version-navigation', + ), + $button_bar), + ); } else { $content = $document->getContent(); + + if ($content->getVersion() < $document->getMaxVersion()) { + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $document, + PhabricatorPolicyCapability::CAN_EDIT); + if ($can_edit) { + $document_uri = new PhutilURI($document->getURI()); + $draft_uri = $document_uri->alter('v', $document->getMaxVersion()); + + $draft_link = phutil_tag( + 'a', + array( + 'href' => $draft_uri, + ), + pht('View Draft Version')); + + $draft_link = phutil_tag('strong', array(), $draft_link); + + $version_note = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild( + array( + pht('This document has unpublished draft changes.'), + ' ', + $draft_link, + )); + } + } } $page_title = $content->getTitle(); @@ -201,7 +327,10 @@ final class PhrictionDocumentController $children = $this->renderDocumentChildren($slug); - $actions = $this->buildActionView($viewer, $document); + $curtain = null; + if ($document->getID()) { + $curtain = $this->buildCurtain($document, $content); + } $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); @@ -213,10 +342,17 @@ final class PhrictionDocumentController $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($document) - ->setHeader($page_title) - ->setActionList($actions); + ->setHeader($page_title); - if ($content) { + if ($is_draft) { + $draft_tag = id(new PHUITagView()) + ->setName(pht('Draft')) + ->setIcon('fa-spinner') + ->setColor('pink') + ->setType(PHUITagView::TYPE_SHADE); + + $header->addTag($draft_tag); + } else if ($content) { $header->setEpoch($content->getDateCreated()); } @@ -227,25 +363,55 @@ final class PhrictionDocumentController } $prop_list = phutil_tag_div('phui-document-view-pro-box', $prop_list); - $page_content = id(new PHUIDocumentViewPro()) + $page_content = id(new PHUIDocumentView()) + ->setBanner($version_note) ->setHeader($header) ->setToc($toc) ->appendChild( array( - $version_note, $move_notice, $core_content, )); + if ($curtain) { + $page_content->setCurtain($curtain); + } + + if ($document->getPHID()) { + $timeline = $this->buildTransactionTimeline( + $document, + new PhrictionTransactionQuery()); + + $edit_engine = id(new PhrictionDocumentEditEngine()) + ->setViewer($viewer) + ->setTargetObject($document); + + $comment_view = $edit_engine + ->buildEditEngineCommentView($document); + } else { + $timeline = null; + $comment_view = null; + } + return $this->newPage() ->setTitle($page_title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($document->getPHID())) - ->appendChild(array( - $page_content, - $prop_list, - $children, - )); + ->appendChild( + array( + $page_content, + $prop_list, + phutil_tag( + 'div', + array( + 'class' => 'phui-document-view-pro-box', + ), + array( + $children, + $timeline, + $comment_view, + )), + )); } @@ -254,49 +420,95 @@ final class PhrictionDocumentController PhrictionContent $content, $slug) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); + $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($document); + ->setUser($viewer); $view->addProperty( pht('Last Author'), $viewer->renderHandle($content->getAuthorPHID())); + $view->addProperty( + pht('Last Edited'), + phabricator_datetime($content->getDateCreated(), $viewer)); + return $view; } - private function buildActionView( - PhabricatorUser $viewer, - PhrictionDocument $document) { + private function buildCurtain( + PhrictionDocument $document, + PhrictionContent $content) { + $viewer = $this->getViewer(); + $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $document, PhabricatorPolicyCapability::CAN_EDIT); $slug = PhabricatorSlug::normalize($this->slug); + $id = $document->getID(); - $action_view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($document); + $curtain = $this->newCurtainView($document); - if (!$document->getID()) { - return $action_view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Create This Document')) - ->setIcon('fa-plus-square') - ->setHref('/phriction/edit/?slug='.$slug)); - } - - $action_view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Document')) ->setDisabled(!$can_edit) ->setIcon('fa-pencil') ->setHref('/phriction/edit/'.$document->getID().'/')); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View History')) + ->setIcon('fa-history') + ->setHref(PhrictionDocument::getSlugURI($slug, 'history'))); + + $is_current = false; + $content_id = null; + $is_draft = false; + if ($content) { + if ($content->getPHID() == $document->getContentPHID()) { + $is_current = true; + } + $content_id = $content->getID(); + + $current_version = $document->getContent()->getVersion(); + $is_draft = ($content->getVersion() >= $current_version); + } + $can_publish = ($can_edit && $content && !$is_current); + + if ($is_draft) { + $publish_name = pht('Publish Draft'); + } else { + $publish_name = pht('Publish Older Version'); + } + + // If you're looking at the current version; and it's an unpublished + // draft; and you can publish it, add a UI hint that this might be an + // interesting action to take. + $hint_publish = false; + if ($is_draft) { + if ($can_publish) { + if ($document->getMaxVersion() == $content->getVersion()) { + $hint_publish = true; + } + } + } + + $publish_uri = "/phriction/publish/{$id}/{$content_id}/"; + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName($publish_name) + ->setIcon('fa-upload') + ->setSelected($hint_publish) + ->setDisabled(!$can_publish) + ->setWorkflow(true) + ->setHref($publish_uri)); + if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) { - $action_view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Move Document')) ->setDisabled(!$can_edit) @@ -304,7 +516,7 @@ final class PhrictionDocumentController ->setHref('/phriction/move/'.$document->getID().'/') ->setWorkflow(true)); - $action_view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Document')) ->setDisabled(!$can_edit) @@ -313,23 +525,16 @@ final class PhrictionDocumentController ->setWorkflow(true)); } - $action_view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View History')) - ->setIcon('fa-list') - ->setHref(PhrictionDocument::getSlugURI($slug, 'history'))); - $print_uri = PhrictionDocument::getSlugURI($slug).'?__print__=1'; - $action_view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Printable Page')) ->setIcon('fa-print') ->setOpenInNewWindow(true) ->setHref($print_uri)); - return $action_view; - + return $curtain; } private function renderDocumentChildren($slug) { @@ -458,7 +663,7 @@ final class PhrictionDocumentController ), $list))); - return phutil_tag_div('phui-document-view-pro-box', $box); + return $box; } private function renderChildDocumentLink(array $info) { diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index ccd473931c..9f59b63121 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -7,7 +7,7 @@ final class PhrictionEditController $viewer = $request->getViewer(); $id = $request->getURIData('id'); - $current_version = null; + $max_version = null; if ($id) { $is_new = false; $document = id(new PhrictionDocumentQuery()) @@ -24,7 +24,7 @@ final class PhrictionEditController return new Aphront404Response(); } - $current_version = $document->getContent()->getVersion(); + $max_version = $document->getMaxVersion(); $revert = $request->getInt('revert'); if ($revert) { @@ -37,9 +37,12 @@ final class PhrictionEditController return new Aphront404Response(); } } else { - $content = $document->getContent(); + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->setLimit(1) + ->executeOne(); } - } else { $slug = $request->getStr('slug'); $slug = PhabricatorSlug::normalize($slug); @@ -54,8 +57,13 @@ final class PhrictionEditController ->executeOne(); if ($document) { - $content = $document->getContent(); - $current_version = $content->getVersion(); + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->setLimit(1) + ->executeOne(); + + $max_version = $document->getMaxVersion(); $is_new = false; } else { $document = PhrictionDocument::initializeNewDocument($viewer, $slug); @@ -64,43 +72,6 @@ final class PhrictionEditController } } - if ($request->getBool('nodraft')) { - $draft = null; - $draft_key = null; - } else { - if ($document->getPHID()) { - $draft_key = $document->getPHID().':'.$content->getVersion(); - } else { - $draft_key = 'phriction:'.$content->getSlug(); - } - $draft = id(new PhabricatorDraft())->loadOneWhere( - 'authorPHID = %s AND draftKey = %s', - $viewer->getPHID(), - $draft_key); - } - - if ($draft && - strlen($draft->getDraft()) && - ($draft->getDraft() != $content->getContent())) { - $content_text = $draft->getDraft(); - - $discard = phutil_tag( - 'a', - array( - 'href' => $request->getRequestURI()->alter('nodraft', true), - ), - pht('discard this draft')); - - $draft_note = new PHUIInfoView(); - $draft_note->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - $draft_note->setTitle(pht('Recovered Draft')); - $draft_note->appendChild( - pht('Showing a saved draft of your edits, you can %s.', $discard)); - } else { - $content_text = $content->getContent(); - $draft_note = null; - } - require_celerity_resource('phriction-document-css'); $e_title = true; @@ -123,25 +94,39 @@ final class PhrictionEditController $v_space = $document->getSpacePHID(); + $content_text = $content->getContent(); + $is_draft_mode = ($document->getContent()->getVersion() != $max_version); + if ($request->isFormPost()) { + if ($is_new) { + $save_as_draft = false; + } else { + $save_as_draft = ($is_draft_mode || $request->getExists('draft')); + } $title = $request->getStr('title'); $content_text = $request->getStr('content'); $notes = $request->getStr('description'); - $current_version = $request->getInt('contentVersion'); + $max_version = $request->getInt('contentVersion'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); $v_cc = $request->getArr('cc'); $v_projects = $request->getArr('projects'); $v_space = $request->getStr('spacePHID'); + if ($save_as_draft) { + $edit_type = PhrictionDocumentDraftTransaction::TRANSACTIONTYPE; + } else { + $edit_type = PhrictionDocumentContentTransaction::TRANSACTIONTYPE; + } + $xactions = array(); + $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhrictionDocumentTitleTransaction::TRANSACTIONTYPE) ->setNewValue($title); $xactions[] = id(new PhrictionTransaction()) - ->setTransactionType( - PhrictionDocumentContentTransaction::TRANSACTIONTYPE) + ->setTransactionType($edit_type) ->setNewValue($content_text); $xactions[] = id(new PhrictionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) @@ -168,16 +153,20 @@ final class PhrictionEditController ->setContinueOnNoEffect(true) ->setDescription($notes) ->setProcessContentVersionError(!$request->getBool('overwrite')) - ->setContentVersion($current_version); + ->setContentVersion($max_version); try { $editor->applyTransactions($document, $xactions); - if ($draft) { - $draft->delete(); + $uri = PhrictionDocument::getSlugURI($document->getSlug()); + $uri = new PhutilURI($uri); + + // If the user clicked "Save as Draft", take them to the draft, not + // to the current published page. + if ($save_as_draft) { + $uri = $uri->alter('v', $document->getMaxVersion()); } - $uri = PhrictionDocument::getSlugURI($document->getSlug()); return id(new AphrontRedirectResponse())->setURI($uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; @@ -207,7 +196,7 @@ final class PhrictionEditController if ($overwrite) { $submit_button = pht('Overwrite Changes'); } else { - $submit_button = pht('Save Changes'); + $submit_button = pht('Save and Publish'); } } else { $submit_button = pht('Create Document'); @@ -226,19 +215,11 @@ final class PhrictionEditController ->execute(); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; $edit_capability = PhabricatorPolicyCapability::CAN_EDIT; - $codex = id(PhabricatorPolicyCodex::newFromObject($document, $viewer)) - ->setCapability($view_capability); - - $view_capability_description = $codex->getPolicySpecialRuleForCapability( - PhabricatorPolicyCapability::CAN_VIEW)->getDescription(); - $edit_capability_description = $codex->getPolicySpecialRuleForCapability( - PhabricatorPolicyCapability::CAN_EDIT)->getDescription(); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('slug', $document->getSlug()) - ->addHiddenInput('nodraft', $request->getBool('nodraft')) - ->addHiddenInput('contentVersion', $current_version) + ->addHiddenInput('contentVersion', $max_version) ->addHiddenInput('overwrite', $overwrite) ->appendChild( id(new AphrontFormTextControl()) @@ -279,25 +260,43 @@ final class PhrictionEditController ->setSpacePHID($v_space) ->setPolicyObject($document) ->setCapability($view_capability) - ->setPolicies($policies) - ->setCaption($view_capability_description)) + ->setPolicies($policies)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($document) ->setCapability($edit_capability) - ->setPolicies($policies) - ->setCaption($edit_capability_description)) + ->setPolicies($policies)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Edit Notes')) ->setValue($notes) ->setError(null) - ->setName('description')) - ->appendChild( + ->setName('description')); + + if ($is_draft_mode) { + $form->appendControl( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) - ->setValue($submit_button)); + ->setValue(pht('Save Draft'))); + } else { + $submit = id(new AphrontFormSubmitControl()); + + if (!$is_new) { + $draft_button = id(new PHUIButtonView()) + ->setTag('input') + ->setName('draft') + ->setText(pht('Save as Draft')) + ->setColor(PHUIButtonView::GREEN); + $submit->addButton($draft_button); + } + + $submit + ->addCancelButton($cancel_uri) + ->setValue($submit_button); + + $form->appendControl($submit); + } $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) @@ -323,17 +322,16 @@ final class PhrictionEditController $crumbs->setBorder(true); $view = id(new PHUITwoColumnView()) - ->setFooter(array( - $draft_note, - $form_box, - $preview, - )); + ->setFooter( + array( + $form_box, + $preview, + )); return $this->newPage() ->setTitle($page_title) ->setCrumbs($crumbs) ->appendChild($view); - } } diff --git a/src/applications/phriction/controller/PhrictionEditEngineController.php b/src/applications/phriction/controller/PhrictionEditEngineController.php new file mode 100644 index 0000000000..f2b7722eab --- /dev/null +++ b/src/applications/phriction/controller/PhrictionEditEngineController.php @@ -0,0 +1,14 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/phriction/controller/PhrictionHistoryController.php b/src/applications/phriction/controller/PhrictionHistoryController.php index 432b1dfdef..3023d7dcce 100644 --- a/src/applications/phriction/controller/PhrictionHistoryController.php +++ b/src/applications/phriction/controller/PhrictionHistoryController.php @@ -31,32 +31,18 @@ final class PhrictionHistoryController ->executeWithCursorPager($pager); $author_phids = mpull($history, 'getAuthorPHID'); - $handles = $this->loadViewerHandles($author_phids); + $handles = $viewer->loadHandles($author_phids); + + $max_version = (int)$document->getMaxVersion(); + $current_version = $document->getContent()->getVersion(); $list = new PHUIObjectItemListView(); $list->setFlush(true); - foreach ($history as $content) { - - $author = $handles[$content->getAuthorPHID()]->renderLink(); $slug_uri = PhrictionDocument::getSlugURI($document->getSlug()); $version = $content->getVersion(); - $diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/'); - - $vs_previous = null; - if ($content->getVersion() != 1) { - $vs_previous = $diff_uri - ->alter('l', $content->getVersion() - 1) - ->alter('r', $content->getVersion()); - } - - $vs_head = null; - if ($content->getID() != $document->getContentID()) { - $vs_head = $diff_uri - ->alter('l', $content->getVersion()) - ->alter('r', $current->getVersion()); - } + $base_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/'); $change_type = PhrictionChangeType::getChangeTypeLabel( $content->getChangeType()); @@ -68,63 +54,90 @@ final class PhrictionHistoryController $color = 'lightbluetext'; break; case PhrictionChangeType::CHANGE_MOVE_HERE: - $color = 'yellow'; + $color = 'yellow'; break; case PhrictionChangeType::CHANGE_MOVE_AWAY: - $color = 'orange'; + $color = 'orange'; break; case PhrictionChangeType::CHANGE_STUB: $color = 'green'; break; default: - throw new Exception(pht('Unknown change type!')); + $color = 'indigo'; break; } + $version_uri = $slug_uri.'?v='.$version; + $item = id(new PHUIObjectItemView()) - ->setHeader(pht('%s by %s', $change_type, $author)) - ->setStatusIcon('fa-file '.$color) - ->addAttribute( - phutil_tag( - 'a', - array( - 'href' => $slug_uri.'?v='.$version, - ), - pht('Version %s', $version))) - ->addAttribute(pht('%s %s', - phabricator_date($content->getDateCreated(), $viewer), - phabricator_time($content->getDateCreated(), $viewer))); + ->setHref($version_uri); - if ($content->getDescription()) { - $item->addAttribute($content->getDescription()); - } - - if ($vs_previous) { - $item->addIcon( - 'fa-reply', - pht('Show Change'), - array( - 'href' => $vs_previous, - )); + if ($version > $current_version) { + $icon = 'fa-spinner'; + $color = 'pink'; + $header = pht('Draft %d', $version); } else { - $item->addIcon( - 'fa-reply grey', - phutil_tag('em', array(), pht('No previous change'))); + $icon = 'fa-file-o'; + $header = pht('Version %d', $version); } - if ($vs_head) { - $item->addIcon( - 'fa-reply-all', - pht('Show Later Changes'), - array( - 'href' => $vs_head, - )); - } else { - $item->addIcon( - 'fa-reply-all grey', - phutil_tag('em', array(), pht('No later changes'))); + if ($version == $current_version) { + $item->setEffect('selected'); } + $item + ->setHeader($header) + ->setStatusIcon($icon.' '.$color); + + $description = $content->getDescription(); + if (strlen($description)) { + $item->addAttribute($description); + } + + $item->addIcon( + null, + phabricator_datetime($content->getDateCreated(), $viewer)); + + $author_phid = $content->getAuthorPHID(); + $item->addByline($viewer->renderHandle($author_phid)); + + $diff_uri = null; + if ($version > 1) { + $diff_uri = $base_uri + ->alter('l', $version - 1) + ->alter('r', $version); + } else { + $diff_uri = null; + } + + if ($content->getVersion() != $max_version) { + $compare_uri = $base_uri + ->alter('l', $version) + ->alter('r', $max_version); + } else { + $compare_uri = null; + } + + $button_bar = id(new PHUIButtonBarView()) + ->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setColor('grey') + ->setIcon('fa-chevron-down') + ->setDisabled(!$diff_uri) + ->setHref($diff_uri) + ->setText(pht('Diff'))) + ->addButton( + id(new PHUIButtonView()) + ->setTag('a') + ->setColor('grey') + ->setIcon('fa-chevron-circle-up') + ->setDisabled(!$compare_uri) + ->setHref($compare_uri) + ->setText(pht('Compare'))); + + $item->setSideColumn($button_bar); + $list->addItem($item); } diff --git a/src/applications/phriction/controller/PhrictionPublishController.php b/src/applications/phriction/controller/PhrictionPublishController.php new file mode 100644 index 0000000000..e24b5f9be0 --- /dev/null +++ b/src/applications/phriction/controller/PhrictionPublishController.php @@ -0,0 +1,86 @@ +getViewer(); + $id = $request->getURIData('documentID'); + $content_id = $request->getURIData('contentID'); + + $document = id(new PhrictionDocumentQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needContent(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_EDIT, + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->executeOne(); + if (!$document) { + return new Aphront404Response(); + } + + $document_uri = $document->getURI(); + + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withIDs(array($content_id)) + ->executeOne(); + if (!$content) { + return new Aphront404Response(); + } + + if ($content->getPHID() == $document->getContentPHID()) { + return $this->newDialog() + ->setTitle(pht('Already Published')) + ->appendChild( + pht( + 'This version of the document is already the published '. + 'version.')) + ->addCancelButton($document_uri); + } + + $content_uri = $document_uri.'?v='.$content->getVersion(); + + if ($request->isFormPost()) { + $xactions = array(); + + $xactions[] = id(new PhrictionTransaction()) + ->setTransactionType( + PhrictionDocumentPublishTransaction::TRANSACTIONTYPE) + ->setNewValue($content->getPHID()); + + id(new PhrictionTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($document, $xactions); + + return id(new AphrontRedirectResponse())->setURI($document_uri); + } + + if ($content->getVersion() < $document->getContent()->getVersion()) { + $title = pht('Publish Older Version?'); + $body = pht( + 'Revert the published version of this document to an older '. + 'version?'); + $button = pht('Revert'); + } else { + $title = pht('Publish Draft?'); + $body = pht( + 'Update the published version of this document to this newer '. + 'version?'); + $button = pht('Publish'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendChild($body) + ->addSubmitButton($button) + ->addCancelButton($content_uri); + } + +} diff --git a/src/applications/phriction/editor/PhrictionDocumentEditEngine.php b/src/applications/phriction/editor/PhrictionDocumentEditEngine.php new file mode 100644 index 0000000000..22f56f2f57 --- /dev/null +++ b/src/applications/phriction/editor/PhrictionDocumentEditEngine.php @@ -0,0 +1,85 @@ +getViewer(); + return PhrictionDocument::initializeNewDocument( + $viewer, + '/'); + } + + protected function newObjectQuery() { + return id(new PhrictionDocumentQuery()) + ->needContent(true); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Document'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Document'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Document: %s', $object->getContent()->getTitle()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Document'); + } + + protected function getObjectCreateShortText() { + return pht('Create Document'); + } + + protected function getObjectName() { + return pht('Document'); + } + + protected function getEditorURI() { + return '/phriction/document/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/phriction/document/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getCreateNewObjectPolicy() { + // NOTE: For now, this engine is only to support commenting. + return PhabricatorPolicies::POLICY_NOONE; + } + + protected function buildCustomEditFields($object) { + return array(); + } + +} diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index e6bf46c150..6d72fc5b8e 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -74,6 +74,21 @@ final class PhrictionTransactionEditor return $this; } + public function setShouldPublishContent( + PhrictionDocument $object, + $publish) { + + if ($publish) { + $content_phid = $this->getNewContent()->getPHID(); + } else { + $content_phid = $this->getOldContent()->getPHID(); + } + + $object->setContentPHID($content_phid); + + return $this; + } + public function getEditorApplicationClass() { return 'PhabricatorPhrictionApplication'; } @@ -93,29 +108,13 @@ final class PhrictionTransactionEditor return $types; } - protected function shouldApplyInitialEffects( - PhabricatorLiskDAO $object, - array $xactions) { - - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: - case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: - case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: - case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: - return true; - } - } - return parent::shouldApplyInitialEffects($object, $xactions); - } - - protected function applyInitialEffects( + protected function expandTransactions( PhabricatorLiskDAO $object, array $xactions) { $this->setOldContent($object->getContent()); - $this->setNewContent($this->buildNewContentTemplate($object)); + + return parent::expandTransactions($object, $xactions); } protected function expandTransaction( @@ -148,7 +147,6 @@ final class PhrictionTransactionEditor break; default: break; - } return $xactions; @@ -158,29 +156,12 @@ final class PhrictionTransactionEditor PhabricatorLiskDAO $object, array $xactions) { - $save_content = false; - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhrictionDocumentTitleTransaction::TRANSACTIONTYPE: - case PhrictionDocumentMoveToTransaction::TRANSACTIONTYPE: - case PhrictionDocumentMoveAwayTransaction::TRANSACTIONTYPE: - case PhrictionDocumentDeleteTransaction::TRANSACTIONTYPE: - case PhrictionDocumentContentTransaction::TRANSACTIONTYPE: - $save_content = true; - break; - default: - break; - } - } + if ($this->hasNewDocumentContent()) { + $content = $this->getNewDocumentContent($object); - if ($save_content) { - $content = $this->getNewContent(); - $content->setDocumentID($object->getID()); - $content->save(); - - $object->setContentID($content->getID()); - $object->save(); - $object->attachContent($content); + $content + ->setDocumentPHID($object->getPHID()) + ->save(); } if ($this->getIsNewObject() && !$this->getSkipAncestorCheck()) { @@ -502,7 +483,7 @@ final class PhrictionTransactionEditor $error = null; if ($this->getContentVersion() && - ($object->getContent()->getVersion() != $this->getContentVersion())) { + ($object->getMaxVersion() != $this->getContentVersion())) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Edit Conflict'), @@ -516,58 +497,6 @@ final class PhrictionTransactionEditor } return $error; } - protected function requireCapabilities( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - /* - * New objects have a special case. If a user can't see - * x/y - * then definitely don't let them make some - * x/y/z - * We need to load the direct parent to handle this case. - */ - if ($this->getIsNewObject()) { - $actor = $this->requireActor(); - $parent_doc = null; - $ancestral_slugs = PhabricatorSlug::getAncestry($object->getSlug()); - // No ancestral slugs is "/"; the first person gets to play with "/". - if ($ancestral_slugs) { - $parent = end($ancestral_slugs); - $parent_doc = id(new PhrictionDocumentQuery()) - ->setViewer($actor) - ->withSlugs(array($parent)) - ->executeOne(); - // If the $actor can't see the $parent_doc then they can't create - // the child $object; throw a policy exception. - if (!$parent_doc) { - id(new PhabricatorPolicyFilter()) - ->setViewer($actor) - ->raisePolicyExceptions(true) - ->rejectObject( - $object, - $object->getEditPolicy(), - PhabricatorPolicyCapability::CAN_EDIT); - } - - // If the $actor can't edit the $parent_doc then they can't create - // the child $object; throw a policy exception. - if (!PhabricatorPolicyFilter::hasCapability( - $actor, - $parent_doc, - PhabricatorPolicyCapability::CAN_EDIT)) { - id(new PhabricatorPolicyFilter()) - ->setViewer($actor) - ->raisePolicyExceptions(true) - ->rejectObject( - $object, - $object->getEditPolicy(), - PhabricatorPolicyCapability::CAN_EDIT); - } - } - } - return parent::requireCapabilities($object, $xaction); - } protected function supportsSearch() { return true; @@ -587,24 +516,48 @@ final class PhrictionTransactionEditor ->setDocument($object); } - private function buildNewContentTemplate( - PhrictionDocument $document) { + private function hasNewDocumentContent() { + return (bool)$this->newContent; + } - $new_content = id(new PhrictionContent()) + public function getNewDocumentContent(PhrictionDocument $document) { + if (!$this->hasNewDocumentContent()) { + $content = $this->newDocumentContent($document); + + // Generate a PHID now so we can populate "contentPHID" before saving + // the document to the database: the column is not nullable so we need + // a value. + $content_phid = $content->generatePHID(); + + $content->setPHID($content_phid); + + $document->setContentPHID($content_phid); + $document->attachContent($content); + $document->setEditedEpoch(PhabricatorTime::getNow()); + $document->setMaxVersion($content->getVersion()); + + $this->newContent = $content; + } + + return $this->newContent; + } + + private function newDocumentContent(PhrictionDocument $document) { + $content = id(new PhrictionContent()) ->setSlug($document->getSlug()) - ->setAuthorPHID($this->getActor()->getPHID()) + ->setAuthorPHID($this->getActingAsPHID()) ->setChangeType(PhrictionChangeType::CHANGE_EDIT) ->setTitle($this->getOldContent()->getTitle()) ->setContent($this->getOldContent()->getContent()) ->setDescription(''); if (strlen($this->getDescription())) { - $new_content->setDescription($this->getDescription()); + $content->setDescription($this->getDescription()); } - $new_content->setVersion($this->getOldContent()->getVersion() + 1); + $content->setVersion($document->getMaxVersion() + 1); - return $new_content; + return $content; } protected function getCustomWorkerState() { diff --git a/src/applications/phriction/query/PhrictionContentQuery.php b/src/applications/phriction/query/PhrictionContentQuery.php index 053f40cf9c..6efab5e1c6 100644 --- a/src/applications/phriction/query/PhrictionContentQuery.php +++ b/src/applications/phriction/query/PhrictionContentQuery.php @@ -76,7 +76,7 @@ final class PhrictionContentQuery if ($this->shouldJoinDocumentTable()) { $joins[] = qsprintf( $conn, - 'JOIN %T d ON d.id = c.documentID', + 'JOIN %T d ON d.phid = c.documentPHID', id(new PhrictionDocument())->getTableName()); } @@ -84,19 +84,19 @@ final class PhrictionContentQuery } protected function willFilterPage(array $contents) { - $document_ids = mpull($contents, 'getDocumentID'); + $document_phids = mpull($contents, 'getDocumentPHID'); $documents = id(new PhrictionDocumentQuery()) ->setViewer($this->getViewer()) ->setParentQuery($this) - ->withIDs($document_ids) + ->withPHIDs($document_phids) ->execute(); - $documents = mpull($documents, null, 'getID'); + $documents = mpull($documents, null, 'getPHID'); foreach ($contents as $key => $content) { - $document_id = $content->getDocumentID(); + $document_phid = $content->getDocumentPHID(); - $document = idx($documents, $document_id); + $document = idx($documents, $document_phid); if (!$document) { unset($contents[$key]); $this->didRejectResult($content); diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index 0c6a2c23bc..8a0fa325ae 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -151,17 +151,17 @@ final class PhrictionDocumentQuery $contents = id(new PhrictionContentQuery()) ->setViewer($this->getViewer()) ->setParentQuery($this) - ->withIDs(mpull($documents, 'getContentID')) + ->withPHIDs(mpull($documents, 'getContentPHID')) ->execute(); - $contents = mpull($contents, null, 'getID'); + $contents = mpull($contents, null, 'getPHID'); foreach ($documents as $key => $document) { - $content_id = $document->getContentID(); - if (empty($contents[$content_id])) { + $content_phid = $document->getContentPHID(); + if (empty($contents[$content_phid])) { unset($documents[$key]); continue; } - $document->attachContent($contents[$content_id]); + $document->attachContent($contents[$content_phid]); } } @@ -175,7 +175,7 @@ final class PhrictionDocumentQuery $content_dao = new PhrictionContent(); $joins[] = qsprintf( $conn, - 'JOIN %T c ON d.contentID = c.id', + 'JOIN %T c ON d.contentPHID = c.phid', $content_dao->getTableName()); } @@ -321,7 +321,7 @@ final class PhrictionDocumentQuery public function getBuiltinOrders() { return parent::getBuiltinOrders() + array( self::ORDER_HIERARCHY => array( - 'vector' => array('depth', 'title', 'updated'), + 'vector' => array('depth', 'title', 'updated', 'id'), 'name' => pht('Hierarchy'), ), ); @@ -343,9 +343,9 @@ final class PhrictionDocumentQuery ), 'updated' => array( 'table' => 'd', - 'column' => 'contentID', + 'column' => 'editedEpoch', 'type' => 'int', - 'unique' => true, + 'unique' => false, ), ); } @@ -356,7 +356,7 @@ final class PhrictionDocumentQuery $map = array( 'id' => $document->getID(), 'depth' => $document->getDepth(), - 'updated' => $document->getContentID(), + 'updated' => $document->getEditedEpoch(), ); foreach ($keys as $key) { diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index 515fc6b7d5..5c597ab885 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -7,7 +7,7 @@ final class PhrictionContent PhabricatorDestructibleInterface, PhabricatorConduitResultInterface { - protected $documentID; + protected $documentPHID; protected $version; protected $authorPHID; @@ -34,8 +34,8 @@ final class PhrictionContent 'description' => 'text', ), self::CONFIG_KEY_SCHEMA => array( - 'documentID' => array( - 'columns' => array('documentID', 'version'), + 'key_version' => array( + 'columns' => array('documentPHID', 'version'), 'unique' => true, ), 'authorPHID' => array( diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 6edb516e6e..b6dcd6d56d 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -17,12 +17,13 @@ final class PhrictionDocument extends PhrictionDAO protected $slug; protected $depth; - protected $contentID; + protected $contentPHID; protected $status; - protected $mailKey; protected $viewPolicy; protected $editPolicy; protected $spacePHID; + protected $editedEpoch; + protected $maxVersion; private $contentObject = self::ATTACHABLE; private $ancestors = array(); @@ -34,16 +35,11 @@ final class PhrictionDocument extends PhrictionDAO self::CONFIG_COLUMN_SCHEMA => array( 'slug' => 'sort128', 'depth' => 'uint32', - 'contentID' => 'id?', 'status' => 'text32', - 'mailKey' => 'bytes20', + 'editedEpoch' => 'epoch', + 'maxVersion' => 'uint32', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, - ), 'slug' => array( 'columns' => array('slug'), 'unique' => true, @@ -56,17 +52,16 @@ final class PhrictionDocument extends PhrictionDAO ) + parent::getConfiguration(); } - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - PhrictionDocumentPHIDType::TYPECONST); + public function getPHIDType() { + return PhrictionDocumentPHIDType::TYPECONST; } public static function initializeNewDocument(PhabricatorUser $actor, $slug) { - $document = new PhrictionDocument(); - $document->setSlug($slug); + $document = id(new self()) + ->setSlug($slug); - $content = new PhrictionContent(); - $content->setSlug($slug); + $content = id(new PhrictionContent()) + ->setSlug($slug); $default_title = PhabricatorSlug::getDefaultTitle($slug); $content->setTitle($default_title); @@ -95,14 +90,10 @@ final class PhrictionDocument extends PhrictionDAO ->setSpacePHID($actor->getDefaultSpacePHID()); } - return $document; - } + $document->setEditedEpoch(PhabricatorTime::getNow()); + $document->setMaxVersion(0); - public function save() { - if (!$this->getMailKey()) { - $this->setMailKey(Filesystem::readRandomCharacters(20)); - } - return parent::save(); + return $document; } public static function getSlugURI($slug, $type = 'document') { @@ -332,9 +323,9 @@ final class PhrictionDocument extends PhrictionDAO /* -( PhabricatorPolicyCodexInterface )------------------------------------ */ - public function newPolicyCodex() { - return new PhrictionDocumentPolicyCodex(); - } + public function newPolicyCodex() { + return new PhrictionDocumentPolicyCodex(); + } } diff --git a/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php index a051546824..f5f1305279 100644 --- a/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php +++ b/src/applications/phriction/xaction/PhrictionDocumentContentTransaction.php @@ -1,88 +1,25 @@ getEditor()->getIsNewObject()) { - return null; - } - return $object->getContent()->getContent(); - } - - public function generateNewValue($object, $value) { - return $value; - } - public function applyInternalEffects($object, $value) { + parent::applyInternalEffects($object, $value); + $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); - } - public function applyExternalEffects($object, $value) { - $this->getEditor()->getNewContent()->setContent($value); - } - - public function shouldHide() { - if ($this->getOldValue() === null) { - return true; - } else { - return false; - } - } - - public function getActionStrength() { - return 1.3; - } - - public function getActionName() { - return pht('Edited'); - } - - public function getTitle() { - return pht( - '%s edited the content of this document.', - $this->renderAuthor()); - } - - public function getTitleForFeed() { - return pht( - '%s edited the content of %s.', - $this->renderAuthor(), - $this->renderObject()); - } - - public function hasChangeDetailView() { - return true; - } - - public function getMailDiffSectionHeader() { - return pht('CHANGES TO DOCUMENT CONTENT'); - } - - public function newChangeDetailView() { - $viewer = $this->getViewer(); - - return id(new PhabricatorApplicationTransactionTextDiffDetailView()) - ->setViewer($viewer) - ->setOldText($this->getOldValue()) - ->setNewText($this->getNewValue()); - } - - public function newRemarkupChanges() { - $changes = array(); - - $changes[] = $this->newRemarkupChange() - ->setOldValue($this->getOldValue()) - ->setNewValue($this->getNewValue()); - - return $changes; + $this->getEditor()->setShouldPublishContent($object, true); } public function validateTransactions($object, array $xactions) { $errors = array(); + // NOTE: This is slightly different from the draft validation. Here, + // we're validating that: you can't edit away a document; and you can't + // create an empty document. + $content = $object->getContent()->getContent(); if ($this->isEmptyTextTransaction($content, $xactions)) { $errors[] = $this->newRequiredError( diff --git a/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php index 25cc7c2b28..b1d894b96c 100644 --- a/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php +++ b/src/applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php @@ -1,7 +1,7 @@ setStatus(PhrictionDocumentStatus::STATUS_DELETED); - } - public function applyExternalEffects($object, $value) { - $this->getEditor()->getNewContent()->setContent(''); - $this->getEditor()->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_DELETE); + $content = $this->getNewDocumentContent($object); + + $content->setContent(''); + $content->setChangeType(PhrictionChangeType::CHANGE_DELETE); } public function getActionStrength() { diff --git a/src/applications/phriction/xaction/PhrictionDocumentDraftTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentDraftTransaction.php new file mode 100644 index 0000000000..3fc9d63ca8 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentDraftTransaction.php @@ -0,0 +1,39 @@ +getEditor()->setShouldPublishContent($object, false); + } + + public function shouldHideForFeed() { + return true; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + // NOTE: We're only validating that you can't edit a document down to + // nothing in a draft transaction. Alone, this doesn't prevent you from + // creating a document with no content. The content transaction has + // validation for that. + + if (!$xactions) { + return $errors; + } + + $content = $object->getContent()->getContent(); + if ($this->isEmptyTextTransaction($content, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Documents must have content.')); + } + + return $errors; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentEditTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentEditTransaction.php new file mode 100644 index 0000000000..3130658311 --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentEditTransaction.php @@ -0,0 +1,82 @@ +getEditor()->getIsNewObject()) { + return null; + } + + // NOTE: We want to get the newest version of the content here, regardless + // of whether it's published or not. + + $actor = $this->getActor(); + + return id(new PhrictionContentQuery()) + ->setViewer($actor) + ->withDocumentPHIDs(array($object->getPHID())) + ->setOrder('newest') + ->setLimit(1) + ->executeOne() + ->getContent(); + } + + public function generateNewValue($object, $value) { + return $value; + } + + public function applyInternalEffects($object, $value) { + $content = $this->getNewDocumentContent($object); + $content->setContent($value); + } + + public function getActionStrength() { + return 1.3; + } + + public function getActionName() { + return pht('Edited'); + } + + public function getTitle() { + return pht( + '%s edited the content of this document.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s edited the content of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO DOCUMENT CONTENT'); + } + + public function hasChangeDetailView() { + return true; + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php index 3cd45f73b7..af77a5adea 100644 --- a/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php +++ b/src/applications/phriction/xaction/PhrictionDocumentMoveAwayTransaction.php @@ -1,7 +1,7 @@ setStatus(PhrictionDocumentStatus::STATUS_MOVED); - } - public function applyExternalEffects($object, $value) { - $dict = $value; - $this->getEditor()->getNewContent()->setContent(''); - $this->getEditor()->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_MOVE_AWAY); - $this->getEditor()->getNewContent()->setChangeRef($dict['id']); + $content = $this->getNewDocumentContent($object); + + $content->setContent(''); + $content->setChangeType(PhrictionChangeType::CHANGE_MOVE_AWAY); + $content->setChangeRef($value['id']); } public function getActionName() { @@ -40,19 +38,19 @@ final class PhrictionDocumentMoveAwayTransaction $new = $this->getNewValue(); return pht( - '%s moved this document to %s', + '%s moved this document to %s.', $this->renderAuthor(), - $this->renderHandleLink($new['phid'])); + $this->renderObject($new['phid'])); } public function getTitleForFeed() { $new = $this->getNewValue(); return pht( - '%s moved %s to %s', + '%s moved %s to %s.', $this->renderAuthor(), $this->renderObject(), - $this->renderHandleLink($new['phid'])); + $this->renderObject($new['phid'])); } public function getIcon() { diff --git a/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php index bdf2c5d8fc..180c263942 100644 --- a/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php +++ b/src/applications/phriction/xaction/PhrictionDocumentMoveToTransaction.php @@ -1,7 +1,7 @@ $document->getID(), 'phid' => $document->getPHID(), @@ -26,15 +27,13 @@ final class PhrictionDocumentMoveToTransaction public function applyInternalEffects($object, $value) { $object->setStatus(PhrictionDocumentStatus::STATUS_EXISTS); - } - public function applyExternalEffects($object, $value) { - $dict = $value; - $this->getEditor()->getNewContent()->setContent($dict['content']); - $this->getEditor()->getNewContent()->setTitle($dict['title']); - $this->getEditor()->getNewContent()->setChangeType( - PhrictionChangeType::CHANGE_MOVE_HERE); - $this->getEditor()->getNewContent()->setChangeRef($dict['id']); + $content = $this->getNewDocumentContent($object); + + $content->setContent($value['content']); + $content->setTitle($value['title']); + $content->setChangeType(PhrictionChangeType::CHANGE_MOVE_HERE); + $content->setChangeRef($value['id']); } public function getActionStrength() { diff --git a/src/applications/phriction/xaction/PhrictionDocumentPublishTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentPublishTransaction.php new file mode 100644 index 0000000000..324e5c2d4d --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentPublishTransaction.php @@ -0,0 +1,71 @@ +getContentPHID(); + } + + public function applyInternalEffects($object, $value) { + $object->setContentPHID($value); + } + + public function getActionName() { + return pht('Published'); + } + + public function getTitle() { + return pht( + '%s published a new version of this document.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s published a new version of %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function validateTransactions($object, array $xactions) { + $actor = $this->getActor(); + $errors = array(); + + foreach ($xactions as $xaction) { + $content_phid = $xaction->getNewValue(); + + // If this isn't changing anything, skip it. + if ($content_phid === $object->getContentPHID()) { + continue; + } + + $content = id(new PhrictionContentQuery()) + ->setViewer($actor) + ->withPHIDs(array($content_phid)) + ->executeOne(); + if (!$content) { + $errors[] = $this->newInvalidError( + pht( + 'Unable to load Content object with PHID "%s".', + $content_phid), + $xaction); + continue; + } + + if ($content->getDocumentPHID() !== $object->getPHID()) { + $errors[] = $this->newInvalidError( + pht( + 'Content object "%s" can not be published because it belongs '. + 'to a different document.', + $content_phid)); + continue; + } + } + + return $errors; + } + +} diff --git a/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php index 4f1ba850a7..25a77efffe 100644 --- a/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php +++ b/src/applications/phriction/xaction/PhrictionDocumentTitleTransaction.php @@ -1,7 +1,7 @@ setStatus(PhrictionDocumentStatus::STATUS_EXISTS); - } - public function applyExternalEffects($object, $value) { - $this->getEditor()->getNewContent()->setTitle($value); + $content = $this->getNewDocumentContent($object); + + $content->setTitle($value); } public function getActionStrength() { @@ -91,6 +91,29 @@ final class PhrictionDocumentTitleTransaction pht('Documents must have a title.')); } + if ($this->isNewObject()) { + // No ancestral slugs is "/". No ancestry checks apply when creating the + // root document. + $ancestral_slugs = PhabricatorSlug::getAncestry($object->getSlug()); + if ($ancestral_slugs) { + // You must be able to view and edit the parent document to create a new + // child. + $parent_document = id(new PhrictionDocumentQuery()) + ->setViewer($this->getActor()) + ->withSlugs(array(last($ancestral_slugs))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$parent_document) { + $errors[] = $this->newInvalidError( + pht('You can not create a document which does not have a parent.')); + } + } + } + return $errors; } diff --git a/src/applications/phriction/xaction/PhrictionDocumentVersionTransaction.php b/src/applications/phriction/xaction/PhrictionDocumentVersionTransaction.php new file mode 100644 index 0000000000..005e617e3b --- /dev/null +++ b/src/applications/phriction/xaction/PhrictionDocumentVersionTransaction.php @@ -0,0 +1,10 @@ +getEditor()->getNewDocumentContent($object); + } + +} diff --git a/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php index 19f059b8b5..d494767085 100644 --- a/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectColumnTransactionEditor.php @@ -137,22 +137,4 @@ final class PhabricatorProjectColumnTransactionEditor return $errors; } - - protected function requireCapabilities( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectColumnTransaction::TYPE_NAME: - case PhabricatorProjectColumnTransaction::TYPE_STATUS: - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - return; - } - - return parent::requireCapabilities($object, $xaction); - } - } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 2a90d6ce29..581ec23153 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -115,64 +115,6 @@ final class PhabricatorProjectTransactionEditor return $errors; } - protected function requireCapabilities( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorProjectLockTransaction::TRANSACTIONTYPE: - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - newv($this->getEditorApplicationClass(), array()), - ProjectCanLockProjectsCapability::CAPABILITY); - return; - case PhabricatorTransactions::TYPE_EDGE: - switch ($xaction->getMetadataValue('edge:type')) { - case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST: - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - - $add = array_keys(array_diff_key($new, $old)); - $rem = array_keys(array_diff_key($old, $new)); - - $actor_phid = $this->requireActor()->getPHID(); - - $is_join = (($add === array($actor_phid)) && !$rem); - $is_leave = (($rem === array($actor_phid)) && !$add); - - if ($is_join) { - // You need CAN_JOIN to join a project. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_JOIN); - } else if ($is_leave) { - // You usually don't need any capabilities to leave a project. - if ($object->getIsMembershipLocked()) { - // you must be able to edit though to leave locked projects - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - } - } else { - if (!$this->getIsNewObject()) { - // You need CAN_EDIT to change members other than yourself. - // (PHI193) Just skip this check if we're creating a project. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - } - } - return; - } - break; - } - - return parent::requireCapabilities($object, $xaction); - } - protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { // NOTE: We're using the omnipotent user here because the original actor // may no longer have permission to view the object. diff --git a/src/applications/project/xaction/PhabricatorProjectLockTransaction.php b/src/applications/project/xaction/PhabricatorProjectLockTransaction.php index 42551dfb2c..c54c9284ce 100644 --- a/src/applications/project/xaction/PhabricatorProjectLockTransaction.php +++ b/src/applications/project/xaction/PhabricatorProjectLockTransaction.php @@ -53,4 +53,12 @@ final class PhabricatorProjectLockTransaction } } + public function validateTransactions($object, array $xactions) { + if ($xactions) { + $this->requireApplicationCapability( + ProjectCanLockProjectsCapability::CAPABILITY); + } + return array(); + } + } diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 768121de5a..882c1a11fa 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -236,40 +236,6 @@ final class PhabricatorRepositoryEditor } - protected function requireCapabilities( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: - case PhabricatorRepositoryTransaction::TYPE_NAME: - case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: - case PhabricatorRepositoryTransaction::TYPE_ENCODING: - case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: - case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: - case PhabricatorRepositoryTransaction::TYPE_UUID: - case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: - case PhabricatorRepositoryTransaction::TYPE_VCS: - case PhabricatorRepositoryTransaction::TYPE_NOTIFY: - case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: - case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: - case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: - case PhabricatorRepositoryTransaction::TYPE_ENORMOUS: - case PhabricatorRepositoryTransaction::TYPE_SLUG: - case PhabricatorRepositoryTransaction::TYPE_SERVICE: - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: - case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: - case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: - case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - break; - } - } - protected function validateTransaction( PhabricatorLiskDAO $object, $type, diff --git a/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php index b5abd40032..c37bdc04f9 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php +++ b/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php @@ -82,10 +82,10 @@ final class PhabricatorRepositoryCommitPHIDType extends PhabricatorPHIDType { $handle->setURI($commit->getURI()); $handle->setTimestamp($commit->getEpoch()); - $status = $commit->getAuditStatus(); - $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon($status); - $color = PhabricatorAuditCommitStatusConstants::getStatusColor($status); - $name = PhabricatorAuditCommitStatusConstants::getStatusName($status); + $status = $commit->getAuditStatusObject(); + $icon = $status->getIcon(); + $color = $status->getColor(); + $name = $status->getName(); $handle ->setStateIcon($icon) diff --git a/src/applications/repository/phid/PhabricatorRepositoryIdentityPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryIdentityPHIDType.php index 5bb4d5b907..873cfbbae6 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryIdentityPHIDType.php +++ b/src/applications/repository/phid/PhabricatorRepositoryIdentityPHIDType.php @@ -30,6 +30,7 @@ final class PhabricatorRepositoryIdentityPHIDType array $handles, array $objects) { + $avatar_uri = celerity_get_resource_uri('/rsrc/image/avatar.png'); foreach ($handles as $phid => $handle) { $identity = $objects[$phid]; @@ -39,6 +40,8 @@ final class PhabricatorRepositoryIdentityPHIDType $handle->setObjectName(pht('Identity %d', $id)); $handle->setName($name); $handle->setURI($identity->getURI()); + $handle->setIcon('fa-user'); + $handle->setImageURI($avatar_uri); } } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 8889040ddf..52824c2aa4 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -2010,12 +2010,72 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } } - shuffle($results); + if ($writable) { + $results = $this->sortWritableAlmanacServiceURIs($results); + } else { + shuffle($results); + } $result = head($results); return $result['uri']; } + private function sortWritableAlmanacServiceURIs(array $results) { + // See T13109 for discussion of how this method routes requests. + + // In the absence of other rules, we'll send traffic to devices randomly. + // We also want to select randomly among nodes which are equally good + // candidates to receive the write, and accomplish that by shuffling the + // list up front. + shuffle($results); + + $order = array(); + + // If some device is currently holding the write lock, send all requests + // to that device. We're trying to queue writes on a single device so they + // do not need to wait for read synchronization after earlier writes + // complete. + $writer = PhabricatorRepositoryWorkingCopyVersion::loadWriter( + $this->getPHID()); + if ($writer) { + $device_phid = $writer->getWriteProperty('devicePHID'); + foreach ($results as $key => $result) { + if ($result['devicePHID'] === $device_phid) { + $order[] = $key; + } + } + } + + // If no device is currently holding the write lock, try to send requests + // to a device which is already up to date and will not need to synchronize + // before it can accept the write. + $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions( + $this->getPHID()); + if ($versions) { + $max_version = (int)max(mpull($versions, 'getRepositoryVersion')); + + $max_devices = array(); + foreach ($versions as $version) { + if ($version->getRepositoryVersion() == $max_version) { + $max_devices[] = $version->getDevicePHID(); + } + } + $max_devices = array_fuse($max_devices); + + foreach ($results as $key => $result) { + if (isset($max_devices[$result['devicePHID']])) { + $order[] = $key; + } + } + } + + // Reorder the results, putting any we've selected as preferred targets for + // the write at the head of the list. + $results = array_select_keys($results, $order) + $results; + + return $results; + } + public function supportsSynchronization() { // TODO: For now, this is only supported for Git. if (!$this->isGit()) { @@ -2036,7 +2096,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $parts = array( "repo({$repository_phid})", "serv({$service_phid})", - 'v2', + 'v3', ); return implode('.', $parts); @@ -2063,12 +2123,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $uri = $this->getClusterRepositoryURIFromBinding($binding); $protocol = $uri->getProtocol(); $device_name = $iface->getDevice()->getName(); + $device_phid = $iface->getDevice()->getPHID(); $uris[] = array( 'protocol' => $protocol, 'uri' => (string)$uri, 'device' => $device_name, 'writable' => (bool)$binding->getAlmanacPropertyValue('writable'), + 'devicePHID' => $device_phid, ); } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index abcf862b40..5615b5231f 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -25,9 +25,8 @@ final class PhabricatorRepositoryCommit protected $committerIdentityPHID; protected $commitIdentifier; protected $epoch; - protected $mailKey; protected $authorPHID; - protected $auditStatus = PhabricatorAuditCommitStatusConstants::NONE; + protected $auditStatus = DiffusionCommitAuditStatus::NONE; protected $summary = ''; protected $importStatus = 0; @@ -116,11 +115,10 @@ final class PhabricatorRepositoryCommit self::CONFIG_TIMESTAMPS => false, self::CONFIG_COLUMN_SCHEMA => array( 'commitIdentifier' => 'text40', - 'mailKey' => 'bytes20', 'authorPHID' => 'phid?', 'authorIdentityPHID' => 'phid?', 'committerIdentityPHID' => 'phid?', - 'auditStatus' => 'uint32', + 'auditStatus' => 'text32', 'summary' => 'text255', 'importStatus' => 'uint32', ), @@ -200,13 +198,15 @@ final class PhabricatorRepositoryCommit $this->authorIdentity = $author; $this->committerIdentity = $committer; + + return $this; } public function getAuthorIdentity() { return $this->assertAttached($this->authorIdentity); } - public function getCommiterIdentity() { + public function getCommitterIdentity() { return $this->assertAttached($this->committerIdentity); } @@ -319,13 +319,6 @@ final class PhabricatorRepositoryCommit return mpull($audits, 'getAuditorPHID'); } - public function save() { - if (!$this->mailKey) { - $this->mailKey = Filesystem::readRandomCharacters(20); - } - return parent::save(); - } - public function delete() { $data = $this->loadCommitData(); $audits = id(new PhabricatorRepositoryAuditRequest()) @@ -379,27 +372,24 @@ final class PhabricatorRepositoryCommit } } - $current_status = $this->getAuditStatus(); - $status_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION; - if ($any_concern) { - if ($current_status == $status_verify) { + if ($this->isAuditStatusNeedsVerification()) { // If the change is in "Needs Verification", we keep it there as // long as any auditors still have concerns. - $status = $status_verify; + $status = DiffusionCommitAuditStatus::NEEDS_VERIFICATION; } else { - $status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; + $status = DiffusionCommitAuditStatus::CONCERN_RAISED; } } else if ($any_accept) { if ($any_need) { - $status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED; + $status = DiffusionCommitAuditStatus::PARTIALLY_AUDITED; } else { - $status = PhabricatorAuditCommitStatusConstants::FULLY_AUDITED; + $status = DiffusionCommitAuditStatus::AUDITED; } } else if ($any_need) { - $status = PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT; + $status = DiffusionCommitAuditStatus::NEEDS_AUDIT; } else { - $status = PhabricatorAuditCommitStatusConstants::NONE; + $status = DiffusionCommitAuditStatus::NONE; } return $this->setAuditStatus($status); @@ -439,6 +429,123 @@ final class PhabricatorRepositoryCommit return $repository->formatCommitName($identifier, $local = true); } + /** + * Make a strong effort to find a way to render this commit's committer. + * This currently attempts to use @{PhabricatorRepositoryIdentity}, and + * falls back to examining the commit detail information. After we force + * the migration to using identities, update this method to remove the + * fallback. See T12164 for details. + */ + public function renderAnyCommitter(PhabricatorUser $viewer, $handles) { + $committer = $this->renderCommitter($viewer, $handles); + if ($committer) { + return $committer; + } + + return $this->renderAuthor($viewer, $handles); + } + + public function renderCommitter(PhabricatorUser $viewer, $handles) { + $committer_phid = $this->getCommitterDisplayPHID(); + if ($committer_phid) { + return $handles[$committer_phid]->renderLink(); + } + + $data = $this->getCommitData(); + $committer_name = $data->getCommitDetail('committer'); + if (strlen($committer_name)) { + return DiffusionView::renderName($committer_name); + } + + return null; + } + + public function renderAuthor(PhabricatorUser $viewer, $handles) { + $author_phid = $this->getAuthorDisplayPHID(); + if ($author_phid) { + return $handles[$author_phid]->renderLink(); + } + + $data = $this->getCommitData(); + $author_name = $data->getAuthorName(); + if (strlen($author_name)) { + return DiffusionView::renderName($author_name); + } + + return null; + } + + public function loadIdentities(PhabricatorUser $viewer) { + if ($this->authorIdentity !== self::ATTACHABLE) { + return $this; + } + + $commit = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withIDs(array($this->getID())) + ->needIdentities(true) + ->executeOne(); + + $author_identity = $commit->getAuthorIdentity(); + $committer_identity = $commit->getCommitterIdentity(); + + return $this->attachIdentities($author_identity, $committer_identity); + } + + public function hasCommitterIdentity() { + return ($this->getCommitterIdentity() !== null); + } + + public function hasAuthorIdentity() { + return ($this->getAuthorIdentity() !== null); + } + + public function getCommitterDisplayPHID() { + if ($this->hasCommitterIdentity()) { + return $this->getCommitterIdentity()->getIdentityDisplayPHID(); + } + + $data = $this->getCommitData(); + return $data->getCommitDetail('committerPHID'); + } + + public function getAuthorDisplayPHID() { + if ($this->hasAuthorIdentity()) { + return $this->getAuthorIdentity()->getIdentityDisplayPHID(); + } + + $data = $this->getCommitData(); + return $data->getCommitDetail('authorPHID'); + } + + public function getAuditStatusObject() { + $status = $this->getAuditStatus(); + return DiffusionCommitAuditStatus::newForStatus($status); + } + + public function isAuditStatusNoAudit() { + return $this->getAuditStatusObject()->isNoAudit(); + } + + public function isAuditStatusNeedsAudit() { + return $this->getAuditStatusObject()->isNeedsAudit(); + } + + public function isAuditStatusConcernRaised() { + return $this->getAuditStatusObject()->isConcernRaised(); + } + + public function isAuditStatusNeedsVerification() { + return $this->getAuditStatusObject()->isNeedsVerification(); + } + + public function isAuditStatusPartiallyAudited() { + return $this->getAuditStatusObject()->isPartiallyAudited(); + } + + public function isAuditStatusAudited() { + return $this->getAuditStatusObject()->isAudited(); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -489,7 +596,6 @@ final class PhabricatorRepositoryCommit 'phid' => $this->getPHID(), 'commitIdentifier' => $this->getCommitIdentifier(), 'epoch' => $this->getEpoch(), - 'mailKey' => $this->getMailKey(), 'authorPHID' => $this->getAuthorPHID(), 'auditStatus' => $this->getAuditStatus(), 'summary' => $this->getSummary(), @@ -735,16 +841,110 @@ final class PhabricatorRepositoryCommit ->setKey('identifier') ->setType('string') ->setDescription(pht('The commit identifier.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('repositoryPHID') + ->setType('phid') + ->setDescription(pht('The repository this commit belongs to.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('author') + ->setType('map') + ->setDescription(pht('Information about the commit author.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('committer') + ->setType('map') + ->setDescription(pht('Information about the committer.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('isImported') + ->setType('bool') + ->setDescription(pht('True if the commit is fully imported.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('isUnreachable') + ->setType('bool') + ->setDescription( + pht( + 'True if the commit is not the ancestor of any tag, branch, or '. + 'ref.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('auditStatus') + ->setType('map') + ->setDescription(pht('Information about the current audit status.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('message') + ->setType('string') + ->setDescription(pht('The commit message.')), ); } public function getFieldValuesForConduit() { + $data = $this->getCommitData(); - // NOTE: This data should be similar to the information returned about - // commmits by "differential.diff.search" with the "commits" attachment. + $author_identity = $this->getAuthorIdentity(); + if ($author_identity) { + $author_name = $author_identity->getIdentityDisplayName(); + $author_email = $author_identity->getIdentityEmailAddress(); + $author_raw = $author_identity->getIdentityName(); + $author_identity_phid = $author_identity->getPHID(); + $author_user_phid = $author_identity->getCurrentEffectiveUserPHID(); + } else { + $author_name = null; + $author_email = null; + $author_raw = null; + $author_identity_phid = null; + $author_user_phid = null; + } + + $committer_identity = $this->getCommitterIdentity(); + if ($committer_identity) { + $committer_name = $committer_identity->getIdentityDisplayName(); + $committer_email = $committer_identity->getIdentityEmailAddress(); + $committer_raw = $committer_identity->getIdentityName(); + $committer_identity_phid = $committer_identity->getPHID(); + $committer_user_phid = $committer_identity->getCurrentEffectiveUserPHID(); + } else { + $committer_name = null; + $committer_email = null; + $committer_raw = null; + $committer_identity_phid = null; + $committer_user_phid = null; + } + + $author_epoch = $data->getCommitDetail('authorEpoch'); + if ($author_epoch) { + $author_epoch = (int)$author_epoch; + } else { + $author_epoch = null; + } + + $audit_status = $this->getAuditStatusObject(); return array( 'identifier' => $this->getCommitIdentifier(), + 'repositoryPHID' => $this->getRepository()->getPHID(), + 'author' => array( + 'name' => $author_name, + 'email' => $author_email, + 'raw' => $author_raw, + 'epoch' => $author_epoch, + 'identityPHID' => $author_identity_phid, + 'userPHID' => $author_user_phid, + ), + 'committer' => array( + 'name' => $committer_name, + 'email' => $committer_email, + 'raw' => $committer_raw, + 'epoch' => (int)$this->getEpoch(), + 'identityPHID' => $committer_identity_phid, + 'userPHID' => $committer_user_phid, + ), + 'isUnreachable' => (bool)$this->isUnreachable(), + 'isImported' => (bool)$this->isImported(), + 'auditStatus' => array( + 'value' => $audit_status->getKey(), + 'name' => $audit_status->getName(), + 'closed' => (bool)$audit_status->getIsClosed(), + 'color.ansi' => $audit_status->getAnsiColor(), + ), + 'message' => $data->getCommitMessage(), ); } diff --git a/src/applications/repository/storage/PhabricatorRepositoryIdentity.php b/src/applications/repository/storage/PhabricatorRepositoryIdentity.php index bf421692a0..416d1a3cd2 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryIdentity.php +++ b/src/applications/repository/storage/PhabricatorRepositoryIdentity.php @@ -65,6 +65,16 @@ final class PhabricatorRepositoryIdentity $this->getIdentityNameEncoding()); } + public function getIdentityEmailAddress() { + $address = new PhutilEmailAddress($this->getIdentityName()); + return $address->getAddress(); + } + + public function getIdentityDisplayName() { + $address = new PhutilEmailAddress($this->getIdentityName()); + return $address->getDisplayName(); + } + public function getIdentityShortName() { // TODO return $this->getIdentityName(); @@ -78,6 +88,14 @@ final class PhabricatorRepositoryIdentity return ($this->currentEffectiveUserPHID != null); } + public function getIdentityDisplayPHID() { + if ($this->hasEffectiveUser()) { + return $this->getCurrentEffectiveUserPHID(); + } else { + return $this->getPHID(); + } + } + public function save() { if ($this->manuallySetUserPHID) { $this->currentEffectiveUserPHID = $this->manuallySetUserPHID; diff --git a/src/applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php b/src/applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php index da5d54b57d..e297dfbf0e 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php +++ b/src/applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php @@ -76,18 +76,20 @@ final class PhabricatorRepositoryWorkingCopyVersion public static function getReadLock($repository_phid, $device_phid) { - $repository_hash = PhabricatorHash::digestForIndex($repository_phid); - $device_hash = PhabricatorHash::digestForIndex($device_phid); - $lock_key = "repo.read({$repository_hash}, {$device_hash})"; + $parameters = array( + 'repositoryPHID' => $repository_phid, + 'devicePHID' => $device_phid, + ); - return PhabricatorGlobalLock::newLock($lock_key); + return PhabricatorGlobalLock::newLock('repo.read', $parameters); } public static function getWriteLock($repository_phid) { - $repository_hash = PhabricatorHash::digestForIndex($repository_phid); - $lock_key = "repo.write({$repository_hash})"; + $parameters = array( + 'repositoryPHID' => $repository_phid, + ); - return PhabricatorGlobalLock::newLock($lock_key); + return PhabricatorGlobalLock::newLock('repo.write', $parameters); } diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index 3d13f2c3d9..235d74d6f3 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -161,7 +161,9 @@ If you specify both a `queryKey` and `constraints`, the builtin or saved query will be applied first as a starting point, then any additional values in `constraints` will be applied, overwriting the defaults from the original query. -Specify constraints like this: +Different endpoints support different constraints. The constraints this method +supports are detailed below. As an example, you might specify constraints like +this: ```lang=json, name="Example Custom Constraints" { @@ -188,15 +190,26 @@ EOTEXT $fields, array('ids', 'phids')) + $fields; + $constant_lists = array(); + $rows = array(); foreach ($fields as $field) { $key = $field->getConduitKey(); $label = $field->getLabel(); + $constants = $field->newConduitConstants(); + $type_object = $field->getConduitParameterType(); if ($type_object) { $type = $type_object->getTypeName(); $description = $field->getDescription(); + if ($constants) { + $description = array( + $description, + ' ', + phutil_tag('em', array(), pht('(See table below.)')), + ); + } } else { $type = null; $description = phutil_tag('em', array(), pht('Not supported.')); @@ -208,6 +221,46 @@ EOTEXT $type, $description, ); + + if ($constants) { + $constant_lists[] = $this->buildRemarkup( + pht( + 'Constants supported by the `%s` constraint:', + 'statuses')); + + $constants_rows = array(); + foreach ($constants as $constant) { + if ($constant->getIsDeprecated()) { + $icon = id(new PHUIIconView()) + ->setIcon('fa-exclamation-triangle', 'red'); + } else { + $icon = null; + } + + $constants_rows[] = array( + $constant->getKey(), + array( + $icon, + ' ', + $constant->getValue(), + ), + ); + } + + $constants_table = id(new AphrontTableView($constants_rows)) + ->setHeaders( + array( + pht('Key'), + pht('Value'), + )) + ->setColumnClasses( + array( + 'mono', + 'wide', + )); + + $constant_lists[] = $constants_table; + } } $table = id(new AphrontTableView($rows)) @@ -231,7 +284,8 @@ EOTEXT ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) - ->appendChild($table); + ->appendChild($table) + ->appendChild($constant_lists); } private function buildOrderBox( diff --git a/src/applications/search/field/PhabricatorSearchCheckboxesField.php b/src/applications/search/field/PhabricatorSearchCheckboxesField.php index e1a72f4576..ce41f85db3 100644 --- a/src/applications/search/field/PhabricatorSearchCheckboxesField.php +++ b/src/applications/search/field/PhabricatorSearchCheckboxesField.php @@ -4,6 +4,7 @@ final class PhabricatorSearchCheckboxesField extends PhabricatorSearchField { private $options; + private $deprecatedOptions = array(); public function setOptions(array $options) { $this->options = $options; @@ -14,6 +15,15 @@ final class PhabricatorSearchCheckboxesField return $this->options; } + public function setDeprecatedOptions(array $deprecated_options) { + $this->deprecatedOptions = $deprecated_options; + return $this; + } + + public function getDeprecatedOptions() { + return $this->deprecatedOptions; + } + protected function getDefaultValue() { return array(); } @@ -23,11 +33,12 @@ final class PhabricatorSearchCheckboxesField return array(); } - return $value; + return $this->getCanonicalValue($value); } protected function getValueFromRequest(AphrontRequest $request, $key) { - return $this->getListFromRequest($request, $key); + $value = $this->getListFromRequest($request, $key); + return $this->getCanonicalValue($value); } protected function newControl() { @@ -49,4 +60,38 @@ final class PhabricatorSearchCheckboxesField return new ConduitStringListParameterType(); } + public function newConduitConstants() { + $list = array(); + + foreach ($this->getOptions() as $key => $option) { + $list[] = id(new ConduitConstantDescription()) + ->setKey($key) + ->setValue($option); + } + + foreach ($this->getDeprecatedOptions() as $key => $value) { + $list[] = id(new ConduitConstantDescription()) + ->setKey($key) + ->setIsDeprecated(true) + ->setValue(pht('Deprecated alias for "%s".', $value)); + } + + return $list; + } + + private function getCanonicalValue(array $values) { + // Always map the current normal options to themselves. + $normal_options = array_fuse(array_keys($this->getOptions())); + + // Map deprecated values to their new values. + $deprecated_options = $this->getDeprecatedOptions(); + + $map = $normal_options + $deprecated_options; + foreach ($values as $key => $value) { + $values[$key] = idx($map, $value, $value); + } + + return $values; + } + } diff --git a/src/applications/search/field/PhabricatorSearchField.php b/src/applications/search/field/PhabricatorSearchField.php index 1a038ec665..36db0523b7 100644 --- a/src/applications/search/field/PhabricatorSearchField.php +++ b/src/applications/search/field/PhabricatorSearchField.php @@ -382,6 +382,10 @@ abstract class PhabricatorSearchField extends Phobject { return $this->enableForConduit; } + public function newConduitConstants() { + return array(); + } + /* -( Utility Methods )----------------------------------------------------- */ diff --git a/src/applications/subscriptions/controller/PhabricatorSubscriptionsMuteController.php b/src/applications/subscriptions/controller/PhabricatorSubscriptionsMuteController.php index 1369643ffc..e29d5e3802 100644 --- a/src/applications/subscriptions/controller/PhabricatorSubscriptionsMuteController.php +++ b/src/applications/subscriptions/controller/PhabricatorSubscriptionsMuteController.php @@ -44,8 +44,6 @@ final class PhabricatorSubscriptionsMuteController ); } - $muted_type = PhabricatorMutedByEdgeType::EDGECONST; - $xaction = id($object->getApplicationTransactionTemplate()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $muted_type) diff --git a/src/applications/subscriptions/view/SubscriptionListDialogBuilder.php b/src/applications/subscriptions/view/SubscriptionListDialogBuilder.php index 7d1b8d799c..da157848ea 100644 --- a/src/applications/subscriptions/view/SubscriptionListDialogBuilder.php +++ b/src/applications/subscriptions/view/SubscriptionListDialogBuilder.php @@ -58,7 +58,7 @@ final class SubscriptionListDialogBuilder extends Phobject { ->addCancelButton($object_handle->getURI(), pht('Close')); } - private function buildBody(PhabricatorUser $viewer, array $handles) { + private function buildBody(PhabricatorUser $viewer, $handles) { $list = id(new PHUIObjectItemListView()) ->setUser($viewer); diff --git a/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php b/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php index cd0ed346fc..7f10a28d4b 100644 --- a/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php +++ b/src/applications/transactions/bulk/management/PhabricatorBulkManagementExportWorkflow.php @@ -26,7 +26,8 @@ final class PhabricatorBulkManagementExportWorkflow 'name' => 'query', 'param' => 'key', 'help' => pht( - 'Export the data selected by this query.'), + 'Export the data selected by one or more queries.'), + 'repeat' => true, ), array( 'name' => 'output', @@ -47,56 +48,7 @@ final class PhabricatorBulkManagementExportWorkflow public function execute(PhutilArgumentParser $args) { $viewer = $this->getViewer(); - $class = $args->getArg('class'); - - if (!strlen($class)) { - throw new PhutilArgumentUsageException( - pht( - 'Specify a search engine class to export data from with '. - '"--class".')); - } - - if (!is_subclass_of($class, 'PhabricatorApplicationSearchEngine')) { - throw new PhutilArgumentUsageException( - pht( - 'SearchEngine class ("%s") is unknown.', - $class)); - } - - $engine = newv($class, array()) - ->setViewer($viewer); - - if (!$engine->canExport()) { - throw new PhutilArgumentUsageException( - pht( - 'SearchEngine class ("%s") does not support data export.', - $class)); - } - - $query_key = $args->getArg('query'); - if (!strlen($query_key)) { - throw new PhutilArgumentUsageException( - pht( - 'Specify a query to export with "--query".')); - } - - if ($engine->isBuiltinQuery($query_key)) { - $saved_query = $engine->buildSavedQueryFromBuiltin($query_key); - } else if ($query_key) { - $saved_query = id(new PhabricatorSavedQueryQuery()) - ->setViewer($viewer) - ->withQueryKeys(array($query_key)) - ->executeOne(); - } else { - $saved_query = null; - } - - if (!$saved_query) { - throw new PhutilArgumentUsageException( - pht( - 'Failed to load saved query ("%s").', - $query_key)); - } + list($engine, $queries) = $this->newQueries($args); $format_key = $args->getArg('format'); if (!strlen($format_key)) { @@ -125,14 +77,27 @@ final class PhabricatorBulkManagementExportWorkflow $is_overwrite = $args->getArg('overwrite'); $output_path = $args->getArg('output'); - if (!strlen($output_path) && $is_overwrite) { + if (!strlen($output_path)) { throw new PhutilArgumentUsageException( pht( - 'Flag "--overwrite" has no effect without "--output".')); + 'Use "--output " to specify an output file, or "--output -" '. + 'to print to stdout.')); + } + + if ($output_path === '-') { + $is_stdout = true; + } else { + $is_stdout = false; + } + + if ($is_stdout && $is_overwrite) { + throw new PhutilArgumentUsageException( + pht( + 'Flag "--overwrite" has no effect when outputting to stdout.')); } if (!$is_overwrite) { - if (Filesystem::pathExists($output_path)) { + if (!$is_stdout && Filesystem::pathExists($output_path)) { throw new PhutilArgumentUsageException( pht( 'Output path already exists. Use "--overwrite" to overwrite '. @@ -140,6 +105,15 @@ final class PhabricatorBulkManagementExportWorkflow } } + // If we have more than one query, execute the queries to figure out which + // results they hit, then build a synthetic query for all those results + // using the IDs. + if (count($queries) > 1) { + $saved_query = $this->newUnionQuery($engine, $queries); + } else { + $saved_query = head($queries); + } + $export_engine = id(new PhabricatorExportEngine()) ->setViewer($viewer) ->setTitle(pht('Export')) @@ -152,10 +126,20 @@ final class PhabricatorBulkManagementExportWorkflow $iterator = $file->getFileDataIterator(); - if (strlen($output_path)) { + if (!$is_stdout) { + // Empty the file before we start writing to it. Otherwise, "--overwrite" + // will really mean "--append". + Filesystem::writeFile($output_path, ''); + foreach ($iterator as $chunk) { Filesystem::appendFile($output_path, $chunk); } + + echo tsprintf( + "%s\n", + pht( + 'Exported data to "%s".', + Filesystem::readablePath($output_path))); } else { foreach ($iterator as $chunk) { echo $chunk; @@ -165,4 +149,179 @@ final class PhabricatorBulkManagementExportWorkflow return 0; } + private function newQueries(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $query_keys = $args->getArg('query'); + if (!$query_keys) { + throw new PhutilArgumentUsageException( + pht( + 'Specify one or more queries to export with "--query".')); + } + + $engine_classes = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorApplicationSearchEngine') + ->execute(); + + $class = $args->getArg('class'); + if (strlen($class)) { + + $class_list = array(); + foreach ($engine_classes as $class_name => $engine_object) { + $can_export = id(clone $engine_object) + ->setViewer($viewer) + ->canExport(); + if ($can_export) { + $class_list[] = $class_name; + } + } + + sort($class_list); + $class_list = implode(', ', $class_list); + + $matches = array(); + foreach ($engine_classes as $class_name => $engine_object) { + if (stripos($class_name, $class) !== false) { + if (strtolower($class_name) == strtolower($class)) { + $matches = array($class_name); + break; + } else { + $matches[] = $class_name; + } + } + } + + if (!$matches) { + throw new PhutilArgumentUsageException( + pht( + 'No search engines match "%s". Available engines which support '. + 'data export are: %s.', + $class, + $class_list)); + } else if (count($matches) > 1) { + throw new PhutilArgumentUsageException( + pht( + 'Multiple search engines match "%s": %s.', + $class, + implode(', ', $matches))); + } else { + $class = head($matches); + } + + $engine = newv($class, array()) + ->setViewer($viewer); + } else { + $engine = null; + } + + $queries = array(); + foreach ($query_keys as $query_key) { + if ($engine) { + if ($engine->isBuiltinQuery($query_key)) { + $queries[$query_key] = $engine->buildSavedQueryFromBuiltin( + $query_key); + continue; + } + } + + $saved_query = id(new PhabricatorSavedQueryQuery()) + ->setViewer($viewer) + ->withQueryKeys(array($query_key)) + ->executeOne(); + if (!$saved_query) { + if (!$engine) { + throw new PhutilArgumentUsageException( + pht( + 'Query "%s" is unknown. To run a builtin query like "all" or '. + '"active", also specify the search engine with "--class".', + $query_key)); + } else { + throw new PhutilArgumentUsageException( + pht( + 'Query "%s" is not a recognized query for class "%s".', + $query_key, + get_class($engine))); + } + } + + $queries[$query_key] = $saved_query; + } + + // If we don't have an engine from "--class", fill it in by looking at the + // class of the first query. + if (!$engine) { + foreach ($queries as $query) { + $engine = newv($query->getEngineClassName(), array()) + ->setViewer($viewer); + break; + } + } + + $engine_class = get_class($engine); + + foreach ($queries as $query) { + $query_class = $query->getEngineClassName(); + if ($query_class !== $engine_class) { + throw new PhutilArgumentUsageException( + pht( + 'Specified queries use different engines: query "%s" uses '. + 'engine "%s", not "%s". All queries must run on the same '. + 'engine.', + $query->getQueryKey(), + $query_class, + $engine_class)); + } + } + + if (!$engine->canExport()) { + throw new PhutilArgumentUsageException( + pht( + 'SearchEngine class ("%s") does not support data export.', + $engine_class)); + } + + return array($engine, $queries); + } + + private function newUnionQuery( + PhabricatorApplicationSearchEngine $engine, + array $queries) { + + assert_instances_of($queries, 'PhabricatorSavedQuery'); + + $engine = clone $engine; + + $ids = array(); + foreach ($queries as $saved_query) { + $page_size = 1000; + $page_cursor = null; + do { + $query = $engine->buildQueryFromSavedQuery($saved_query); + $pager = $engine->newPagerForSavedQuery($saved_query); + $pager->setPageSize($page_size); + + if ($page_cursor !== null) { + $pager->setAfterID($page_cursor); + } + + $objects = $engine->executeQuery($query, $pager); + $page_cursor = $pager->getNextPageID(); + + foreach ($objects as $object) { + $ids[] = $object->getID(); + } + } while ($pager->getHasMoreResults()); + } + + // When we're merging multiple different queries, override any query order + // and just put the combined result list in ID order. At time of writing, + // we can't merge the result sets together while retaining the overall sort + // order even if they all used the same order, and it's meaningless to try + // to retain orders if the queries had different orders in the first place. + rsort($ids); + + return id($engine->newSavedQuery()) + ->setParameter('ids', $ids); + } + } diff --git a/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php index 3b040ea2c3..0394432ff3 100644 --- a/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php +++ b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php @@ -79,6 +79,14 @@ final class TransactionSearchConduitAPIMethod )); $with_phids = idx($constraints, 'phids'); + + if ($with_phids === array()) { + throw new Exception( + pht( + 'Constraint "phids" to "transaction.search" requires nonempty list, '. + 'empty list provided.')); + } + if ($with_phids) { $xaction_query->withPHIDs($with_phids); } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index f8f6f371bc..579b53a989 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1679,7 +1679,7 @@ abstract class PhabricatorEditEngine ->setUser($viewer) ->setFields($fields); - $document = id(new PHUIDocumentViewPro()) + $document = id(new PHUIDocumentView()) ->setUser($viewer) ->setHeader($header) ->appendChild($help_view); @@ -2003,7 +2003,19 @@ abstract class PhabricatorEditEngine $identifier = $request->getValue('objectIdentifier'); if ($identifier) { $this->setIsCreate(false); - $object = $this->newObjectFromIdentifier($identifier); + + // After T13186, each transaction can individually weaken or replace the + // capabilities required to apply it, so we no longer need CAN_EDIT to + // attempt to apply transactions to objects. In practice, almost all + // transactions require CAN_EDIT so we won't get very far if we don't + // have it. + $capabilities = array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + + $object = $this->newObjectFromIdentifier( + $identifier, + $capabilities); } else { $this->requireCreateCapability(); diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 2499b14e15..6202a7df5a 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -850,6 +850,10 @@ abstract class PhabricatorApplicationTransactionEditor $xaction->setIsSilentTransaction(true); } + if ($actor->hasHighSecuritySession()) { + $xaction->setIsMFATransaction(true); + } + return $xaction; } @@ -976,6 +980,27 @@ abstract class PhabricatorApplicationTransactionEditor $this->adjustTransactionValues($object, $xaction); } + // Now that we've merged and combined transactions, check for required + // capabilities. Note that we're doing this before filtering + // transactions: if you try to apply an edit which you do not have + // permission to apply, we want to give you a permissions error even + // if the edit would have no effect. + $this->applyCapabilityChecks($object, $xactions); + + // See T13186. Fatal hard if this object has an older + // "requireCapabilities()" method. The code may rely on this method being + // called to apply policy checks, so err on the side of safety and fatal. + // TODO: Remove this check after some time has passed. + if (method_exists($this, 'requireCapabilities')) { + throw new Exception( + pht( + 'Editor (of class "%s") implements obsolete policy method '. + 'requireCapabilities(). The implementation for this Editor '. + 'MUST be updated. See <%s> for discussion.', + get_class($this), + 'https://secure.phabricator.com/T13186')); + } + $xactions = $this->filterTransactions($object, $xactions); // TODO: Once everything is on EditEngine, just use getIsNewObject() to @@ -994,12 +1019,6 @@ abstract class PhabricatorApplicationTransactionEditor } } - // Now that we've merged, filtered, and combined transactions, check for - // required capabilities. - foreach ($xactions as $xaction) { - $this->requireCapabilities($object, $xaction); - } - $xactions = $this->sortTransactions($xactions); $file_phids = $this->extractFilePHIDs($object, $xactions); @@ -1459,34 +1478,151 @@ abstract class PhabricatorApplicationTransactionEditor } } - protected function requireCapabilities( + private function applyCapabilityChecks( PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { + array $xactions) { + assert_instances_of($xactions, 'PhabricatorApplicationTransaction'); + + $can_edit = PhabricatorPolicyCapability::CAN_EDIT; if ($this->getIsNewObject()) { - return; + // If we're creating a new object, we don't need any special capabilities + // on the object. The actor has already made it through creation checks, + // and objects which haven't been created yet often can not be + // meaningfully tested for capabilities anyway. + $required_capabilities = array(); + } else { + if (!$xactions && !$this->xactions) { + // If we aren't doing anything, require CAN_EDIT to improve consistency. + $required_capabilities = array($can_edit); + } else { + $required_capabilities = array(); + + foreach ($xactions as $xaction) { + $type = $xaction->getTransactionType(); + + $xtype = $this->getModularTransactionType($type); + if (!$xtype) { + $capabilities = $this->getLegacyRequiredCapabilities($xaction); + } else { + $capabilities = $xtype->getRequiredCapabilities($object, $xaction); + } + + // For convenience, we allow flexibility in the return types because + // it's very unusual that a transaction actually requires multiple + // capability checks. + if ($capabilities === null) { + $capabilities = array(); + } else { + $capabilities = (array)$capabilities; + } + + foreach ($capabilities as $capability) { + $required_capabilities[$capability] = $capability; + } + } + } } - $actor = $this->requireActor(); - switch ($xaction->getTransactionType()) { - case PhabricatorTransactions::TYPE_COMMENT: - PhabricatorPolicyFilter::requireCapability( - $actor, - $object, - PhabricatorPolicyCapability::CAN_VIEW); - break; - case PhabricatorTransactions::TYPE_VIEW_POLICY: - case PhabricatorTransactions::TYPE_EDIT_POLICY: - case PhabricatorTransactions::TYPE_JOIN_POLICY: - case PhabricatorTransactions::TYPE_SPACE: - PhabricatorPolicyFilter::requireCapability( - $actor, - $object, - PhabricatorPolicyCapability::CAN_EDIT); - break; + $required_capabilities = array_fuse($required_capabilities); + $actor = $this->getActor(); + + if ($required_capabilities) { + id(new PhabricatorPolicyFilter()) + ->setViewer($actor) + ->requireCapabilities($required_capabilities) + ->raisePolicyExceptions(true) + ->apply(array($object)); } } + private function getLegacyRequiredCapabilities( + PhabricatorApplicationTransaction $xaction) { + + $type = $xaction->getTransactionType(); + switch ($type) { + case PhabricatorTransactions::TYPE_COMMENT: + // TODO: Comments technically require CAN_INTERACT, but this is + // currently somewhat special and handled through EditEngine. For now, + // don't enforce it here. + return null; + case PhabricatorTransactions::TYPE_SUBSCRIBERS: + // TODO: Removing subscribers other than yourself should probably + // require CAN_EDIT permission. You can do this via the API but + // generally can not via the web interface. + return null; + case PhabricatorTransactions::TYPE_TOKEN: + // TODO: This technically requires CAN_INTERACT, like comments. + return null; + case PhabricatorTransactions::TYPE_HISTORY: + // This is a special magic transaction which sends you history via + // email and is only partially supported in the upstream. You don't + // need any capabilities to apply it. + return null; + case PhabricatorTransactions::TYPE_EDGE: + return $this->getLegacyRequiredEdgeCapabilities($xaction); + default: + // For other older (non-modular) transactions, always require exactly + // CAN_EDIT. Transactions which do not need CAN_EDIT or need additional + // capabilities must move to ModularTransactions. + return PhabricatorPolicyCapability::CAN_EDIT; + } + } + + private function getLegacyRequiredEdgeCapabilities( + PhabricatorApplicationTransaction $xaction) { + + // You don't need to have edit permission on an object to mention it or + // otherwise add a relationship pointing toward it. + if ($this->getIsInverseEdgeEditor()) { + return null; + } + + $edge_type = $xaction->getMetadataValue('edge:type'); + switch ($edge_type) { + case PhabricatorMutedByEdgeType::EDGECONST: + // At time of writing, you can only write this edge for yourself, so + // you don't need permissions. If you can eventually mute an object + // for other users, this would need to be revisited. + return null; + case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: + return null; + case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + $add = array_keys(array_diff_key($new, $old)); + $rem = array_keys(array_diff_key($old, $new)); + + $actor_phid = $this->requireActor()->getPHID(); + + $is_join = (($add === array($actor_phid)) && !$rem); + $is_leave = (($rem === array($actor_phid)) && !$add); + + if ($is_join) { + // You need CAN_JOIN to join a project. + return PhabricatorPolicyCapability::CAN_JOIN; + } + + if ($is_leave) { + $object = $this->object; + // You usually don't need any capabilities to leave a project... + if ($object->getIsMembershipLocked()) { + // ...you must be able to edit to leave locked projects, though. + return PhabricatorPolicyCapability::CAN_EDIT; + } else { + return null; + } + } + + // You need CAN_EDIT to change members other than yourself. + return PhabricatorPolicyCapability::CAN_EDIT; + default: + return PhabricatorPolicyCapability::CAN_EDIT; + } + } + + private function buildSubscribeTransaction( PhabricatorLiskDAO $object, array $xactions, diff --git a/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php b/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php index 08e6ec464b..43a2fbc1b5 100644 --- a/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php +++ b/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php @@ -83,7 +83,15 @@ final class PhabricatorApplicationTransactionResponse $xactions[$key] = hsprintf('%s', $xaction); } + $aural = phutil_tag( + 'h3', + array( + 'class' => 'aural-only', + ), + pht('Comment Preview')); + $content = array( + 'header' => hsprintf('%s', $aural), 'xactions' => $xactions, 'spacer' => PHUITimelineView::renderSpacer(), 'previewContent' => hsprintf('%s', $this->getPreviewContent()), diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index ed88ac98db..c65adcac6b 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -165,6 +165,14 @@ abstract class PhabricatorApplicationTransaction return (bool)$this->getMetadataValue('core.silent', false); } + public function setIsMFATransaction($mfa) { + return $this->setMetadataValue('core.mfa', $mfa); + } + + public function getIsMFATransaction() { + return (bool)$this->getMetadataValue('core.mfa', false); + } + public function attachComment( PhabricatorApplicationTransactionComment $comment) { $this->comment = $comment; @@ -1461,6 +1469,12 @@ abstract class PhabricatorApplicationTransaction if ($is_silent != $xaction->getIsSilentTransaction()) { return false; } + + // Don't group MFA and non-MFA transactions together. + $is_mfa = $this->getIsMFATransaction(); + if ($is_mfa != $xaction->getIsMFATransaction()) { + return false; + } } return true; diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index eaf7d029ce..3d81e02c38 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -356,4 +356,40 @@ abstract class PhabricatorModularTransactionType return array(); } + protected function requireApplicationCapability($capability) { + $application_class = $this->getEditor()->getEditorApplicationClass(); + $application = newv($application_class, array()); + + PhabricatorPolicyFilter::requireCapability( + $this->getActor(), + $application, + $capability); + } + + /** + * Get a list of capabilities the actor must have on the object to apply + * a transaction to it. + * + * Usually, you should use this to reduce capability requirements when a + * transaction (like leaving a Conpherence thread) can be applied without + * having edit permission on the object. You can override this method to + * remove the CAN_EDIT requirement, or to replace it with a different + * requirement. + * + * If you are increasing capability requirements and need to add an + * additional capability or policy requirement above and beyond CAN_EDIT, it + * is usually better implemented as a validation check. + * + * @param object Object being edited. + * @param PhabricatorApplicationTransaction Transaction being applied. + * @return null|const|list A capability constant (or list of + * capability constants) which the actor must have on the object. You can + * return `null` as a shorthand for "no capabilities are required". + */ + public function getRequiredCapabilities( + $object, + PhabricatorApplicationTransaction $xaction) { + return PhabricatorPolicyCapability::CAN_EDIT; + } + } diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php index a5dcfcb4ae..c0f514c904 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php @@ -23,6 +23,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { private $fullWidth; private $infoView; private $editEngineLock; + private $noBorder; private $currentVersion; private $versionedDraft; @@ -230,7 +231,7 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { 'div', array( 'style' => 'background-image: url('.$image_uri.')', - 'class' => 'phui-comment-image', + 'class' => 'phui-comment-image visual-only', )); $wedge = phutil_tag( 'div', @@ -245,6 +246,13 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { ->setFlush(true) ->addClass('phui-comment-form-view') ->addSigil('phui-comment-form') + ->appendChild( + phutil_tag( + 'h3', + array( + 'class' => 'aural-only', + ), + pht('Add Comment'))) ->appendChild($image) ->appendChild($badge_view) ->appendChild($wedge) @@ -319,14 +327,18 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView { foreach ($comment_actions as $key => $comment_action) { $key = $comment_action->getKey(); + $label = $comment_action->getLabel(); + + $action_map[$key] = array( 'key' => $key, - 'label' => $comment_action->getLabel(), + 'label' => $label, 'type' => $comment_action->getPHUIXControlType(), 'spec' => $comment_action->getPHUIXControlSpecification(), 'initialValue' => $comment_action->getInitialValue(), 'groupKey' => $comment_action->getGroupKey(), 'conflictKey' => $comment_action->getConflictKey(), + 'auralLabel' => pht('Remove Action: %s', $label), ); $type_map[$key] = $comment_action; diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php index 6691450cfb..04952c6398 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php @@ -432,7 +432,8 @@ class PhabricatorApplicationTransactionView extends AphrontView { ->setIcon($xaction->getIcon()) ->setColor($xaction->getColor()) ->setHideCommentOptions($this->getHideCommentOptions()) - ->setIsSilent($xaction->getIsSilentTransaction()); + ->setIsSilent($xaction->getIsSilentTransaction()) + ->setIsMFA($xaction->getIsMFATransaction()); list($token, $token_removed) = $xaction->getToken(); if ($token) { diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php index 27b30a6278..a7a1f55caa 100644 --- a/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php +++ b/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php @@ -142,7 +142,7 @@ final class PhabricatorTypeaheadFunctionHelpController $header = id(new PHUIHeaderView()) ->setHeader($title); - $document = id(new PHUIDocumentViewPro()) + $document = id(new PHUIDocumentView()) ->setHeader($header) ->appendChild($content_box); diff --git a/src/docs/user/userguide/phriction.diviner b/src/docs/user/userguide/phriction.diviner index 60ad6357fe..1a56dce46b 100644 --- a/src/docs/user/userguide/phriction.diviner +++ b/src/docs/user/userguide/phriction.diviner @@ -6,5 +6,78 @@ Construct a detailed written history of your civilization. Overview ======== -Phriction is a simple wiki. You can edit pages, and the text you write will stay +Phriction is a wiki. You can edit pages, and the text you write will stay there. Other people can see it later. + +Phriction documents are arranged in a hierarchy, like a filesystem. This can +make it easier to keep things organized and to apply policy controls to +groups of documents. + + +Policies +======== + +Documents and policies in Phriction are hierarchical, similar to a filesystem. +For example, a document called "Zebra Information" may be located +at `/zoo/animals/zebra/`. + +To view a document in Phrction, you must first be able to view all of its +ancestors. So a user can only see {nav Zoo > Animals > Zebra Information} if +they can see the pages {nav Zoo} and {nav Zoo > Animals}. + +This allows sections of the wiki to be restricted by applying a restrictive +policy to the parent (or grandparent) document. For example, if you apply a +restrictive view policy to the {nav Zoo} page, it will implicitly apply to +all sub-pages, including {nav Zoo > Animals > Zebra Information}. + + +Versions and Drafts +=================== + +Document content is tracked with linear version numbers: version 1, version 2, +version 3, and so on. Each time a page is edited, a new version of the page is +created. + +You can {nav View History} to review older versions of a page and see how it +has changed over time (and who has changed it). + +When you visit a particular document, you are normally shown the most recent +version of that document. For example, if there are 17 versions, you'll see +version 17. + +Likewise, when you edit a document using {nav Edit Document > Save and Publish}, +your changes are published immediately. If there were previously 17 versions, +your new changes will become version 18 and visitors to the document will begin +seeing version 18. + +If you want to edit a document without publishing the changes right away, you +can use {nav Edit Document > Save as Draft} instead. This will still create a +new version 18, but it won't change which version users see when they visit the +document: they'll still see version 17 (the last published version). + +You (and other users) can continue editing the draft by using +{nav Edit Document}. (Once a document has an unpublished draft, editing will +stay in draft mode.) + +Once you're satisfied with your changes, use {nav Publish Draft} to make your +changes the current visible version of the document that users see by default +when they visit it. + +If you made a mistake and published something you didn't intend to, you can +navigate back to an older version of the document and use +{nav Publish Older Version} to change the current visible version of the +document to some older version. + +Note that draft versions are still normal versions of the document: they are +not private, they can not be deleted, other users can see them if they can see +the document, and they will eventually become a standard part of the document +history. The only private parts of drafts are: editing a draft does not +generate a feed story; and users won't see draft content by default when +viewing a document. + +Drafts may be a good fit if you are: + + - working on changes over time; or + - starting with a rough change and refining it in several iterations; or + - collaborating with others on a change; or + - sharing changes before they're published to get feedback. diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index d7f1a9dd3a..e6572672ce 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -88,7 +88,7 @@ abstract class PhabricatorInlineCommentController public function processRequest() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $this->getViewer(); $this->readRequestParameters(); @@ -221,7 +221,7 @@ abstract class PhabricatorInlineCommentController $inline = $this->createComment() ->setChangesetID($this->getChangesetID()) - ->setAuthorPHID($user->getPHID()) + ->setAuthorPHID($viewer->getPHID()) ->setLineNumber($this->getLineNumber()) ->setLineLength($this->getLineLength()) ->setIsNewFile($this->getIsNewFile()) @@ -231,6 +231,15 @@ abstract class PhabricatorInlineCommentController $inline->setReplyToCommentPHID($this->getReplyToCommentPHID()); } + // If you own this object, mark your own inlines as "Done" by default. + $owner_phid = $this->loadObjectOwnerPHID($inline); + if ($owner_phid) { + if ($viewer->getPHID() == $owner_phid) { + $fixed_state = PhabricatorInlineCommentInterface::STATE_DRAFT; + $inline->setFixedState($fixed_state); + } + } + $this->saveComment($inline); return $this->buildRenderedCommentResponse( @@ -313,10 +322,10 @@ abstract class PhabricatorInlineCommentController private function buildEditDialog() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $this->getViewer(); $edit_dialog = id(new PHUIDiffInlineCommentEditView()) - ->setUser($user) + ->setUser($viewer) ->setSubmitURI($request->getRequestURI()) ->setIsOnRight($this->getIsOnRight()) ->setIsNewFile($this->getIsNewFile()) @@ -342,22 +351,22 @@ abstract class PhabricatorInlineCommentController $on_right) { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $this->getViewer(); $engine = new PhabricatorMarkupEngine(); - $engine->setViewer($user); + $engine->setViewer($viewer); $engine->addObject( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); $engine->process(); - $phids = array($user->getPHID()); + $phids = array($viewer->getPHID()); $handles = $this->loadViewerHandles($phids); $object_owner_phid = $this->loadObjectOwnerPHID($inline); $view = id(new PHUIDiffInlineCommentDetailView()) - ->setUser($user) + ->setUser($viewer) ->setInlineComment($inline) ->setIsOnRight($on_right) ->setMarkupEngine($engine) @@ -378,7 +387,7 @@ abstract class PhabricatorInlineCommentController private function renderTextArea($text) { return id(new PhabricatorRemarkupControl()) - ->setUser($this->getRequest()->getUser()) + ->setViewer($this->getViewer()) ->setSigil('differential-inline-comment-edit-textarea') ->setName('text') ->setValue($text) diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 27a489a705..4da3bbd8fa 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -233,7 +233,8 @@ final class PHUIDiffInlineCommentDetailView ->setIcon('fa-reply') ->setTooltip(pht('Reply')) ->addSigil('differential-inline-reply') - ->setMustCapture(true); + ->setMustCapture(true) + ->setAuralLabel(pht('Reply')); } if ($this->editable && !$this->preview) { @@ -242,14 +243,16 @@ final class PHUIDiffInlineCommentDetailView ->setIcon('fa-pencil') ->setTooltip(pht('Edit')) ->addSigil('differential-inline-edit') - ->setMustCapture(true); + ->setMustCapture(true) + ->setAuralLabel(pht('Edit')); $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-trash-o') ->setTooltip(pht('Delete')) ->addSigil('differential-inline-delete') - ->setMustCapture(true); + ->setMustCapture(true) + ->setAuralLabel(pht('Delete')); } else if ($this->preview) { $links[] = javelin_tag( @@ -268,7 +271,8 @@ final class PHUIDiffInlineCommentDetailView ->setTooltip(pht('Delete')) ->setIcon('fa-trash-o') ->addSigil('differential-inline-delete') - ->setMustCapture(true); + ->setMustCapture(true) + ->setAuralLabel(pht('Delete')); } if (!$this->preview && $this->canHide()) { @@ -277,20 +281,30 @@ final class PHUIDiffInlineCommentDetailView ->setTooltip(pht('Collapse')) ->setIcon('fa-times') ->addSigil('hide-inline') - ->setMustCapture(true); + ->setMustCapture(true) + ->setAuralLabel(pht('Collapse')); } $done_button = null; + $mark_done = $this->getCanMarkDone(); + + // Allow users to mark their own draft inlines as "Done". + if ($viewer_phid == $inline->getAuthorPHID()) { + if ($inline->isDraft()) { + $mark_done = true; + } + } + if (!$is_synthetic) { $draft_state = false; switch ($inline->getFixedState()) { case PhabricatorInlineCommentInterface::STATE_DRAFT: - $is_done = ($this->getCanMarkDone()); + $is_done = $mark_done; $draft_state = true; break; case PhabricatorInlineCommentInterface::STATE_UNDRAFT: - $is_done = !($this->getCanMarkDone()); + $is_done = !$mark_done; $draft_state = true; break; case PhabricatorInlineCommentInterface::STATE_DONE: @@ -304,7 +318,7 @@ final class PHUIDiffInlineCommentDetailView // If you don't have permission to mark the comment as "Done", you also // can not see the draft state. - if (!$this->getCanMarkDone()) { + if (!$mark_done) { $draft_state = false; } @@ -316,21 +330,19 @@ final class PHUIDiffInlineCommentDetailView $classes[] = 'inline-state-is-draft'; } - if ($this->getCanMarkDone()) { + if ($mark_done && !$this->preview) { $done_input = javelin_tag( 'input', array( 'type' => 'checkbox', 'checked' => ($is_done ? 'checked' : null), - 'disabled' => ($this->getCanMarkDone() ? null : 'disabled'), 'class' => 'differential-inline-done', 'sigil' => 'differential-inline-done', )); $done_button = phutil_tag( 'label', array( - 'class' => 'differential-inline-done-label '. - ($this->getCanMarkDone() ? null : 'done-is-disabled'), + 'class' => 'differential-inline-done-label ', ), array( $done_input, diff --git a/src/infrastructure/export/engine/PhabricatorExportEngine.php b/src/infrastructure/export/engine/PhabricatorExportEngine.php index f2c6a1270b..f29eaf0f60 100644 --- a/src/infrastructure/export/engine/PhabricatorExportEngine.php +++ b/src/infrastructure/export/engine/PhabricatorExportEngine.php @@ -125,7 +125,7 @@ final class PhabricatorExportEngine $field_list = mpull($field_list, null, 'getKey'); $format->addHeaders($field_list); - // Iterate over the query results in large page so we don't have to hold + // Iterate over the query results in large pages so we don't have to hold // too much stuff in memory. $page_size = 1000; $page_cursor = null; diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php index a75afe69e8..5c55dc779e 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php @@ -56,8 +56,18 @@ final class PhabricatorStorageManagementProbeWorkflow ->setShowHeader(false) ->setPadding(2) ->addColumn('name', array('title' => pht('Database / Table'))) - ->addColumn('size', array('title' => pht('Size'))) - ->addColumn('percentage', array('title' => pht('Percentage'))); + ->addColumn( + 'size', + array( + 'title' => pht('Size'), + 'align' => PhutilConsoleTable::ALIGN_RIGHT, + )) + ->addColumn( + 'percentage', + array( + 'title' => pht('Percentage'), + 'align' => PhutilConsoleTable::ALIGN_RIGHT, + )); foreach ($totals as $db => $size) { list($database_size, $database_percentage) = $this->formatSize( @@ -98,7 +108,7 @@ final class PhabricatorStorageManagementProbeWorkflow private function formatSize($n, $o) { return array( - sprintf('%8.8s MB', number_format($n / (1024 * 1024), 1)), + pht('%s MB', new PhutilNumber($n / (1024 * 1024), 1)), sprintf('%3.1f%%', 100 * ($n / $o)), ); } diff --git a/src/infrastructure/util/PhabricatorGlobalLock.php b/src/infrastructure/util/PhabricatorGlobalLock.php index 827d7e0e3d..221a75b37c 100644 --- a/src/infrastructure/util/PhabricatorGlobalLock.php +++ b/src/infrastructure/util/PhabricatorGlobalLock.php @@ -144,7 +144,8 @@ final class PhabricatorGlobalLock extends PhutilLock { $ok = head($result); if (!$ok) { - throw new PhutilLockException($lock_name); + throw id(new PhutilLockException($lock_name)) + ->setHint($this->newHint($lock_name, $wait)); } $conn->rememberLock($lock_name); @@ -152,13 +153,7 @@ final class PhabricatorGlobalLock extends PhutilLock { $this->conn = $conn; if ($this->shouldLogLock()) { - global $argv; - - $lock_context = array( - 'pid' => getmypid(), - 'host' => php_uname('n'), - 'argv' => $argv, - ); + $lock_context = $this->newLockContext(); $log = id(new PhabricatorDaemonLockLog()) ->setLockName($lock_name) @@ -228,4 +223,153 @@ final class PhabricatorGlobalLock extends PhutilLock { return true; } + private function newLockContext() { + $context = array( + 'pid' => getmypid(), + 'host' => php_uname('n'), + 'sapi' => php_sapi_name(), + ); + + global $argv; + if ($argv) { + $context['argv'] = $argv; + } + + $access_log = null; + + // TODO: There's currently no cohesive way to get the parameterized access + // log for the current request across different request types. Web requests + // have an "AccessLog", SSH requests have an "SSHLog", and other processes + // (like scripts) have no log. But there's no method to say "give me any + // log you've got". For now, just test if we have a web request and use the + // "AccessLog" if we do, since that's the only one we actually read any + // parameters from. + + // NOTE: "PhabricatorStartup" is only available from web requests, not + // from CLI scripts. + if (class_exists('PhabricatorStartup', false)) { + $access_log = PhabricatorAccessLog::getLog(); + } + + if ($access_log) { + $controller = $access_log->getData('C'); + if ($controller) { + $context['controller'] = $controller; + } + + $method = $access_log->getData('m'); + if ($method) { + $context['method'] = $method; + } + } + + return $context; + } + + private function newHint($lock_name, $wait) { + if (!$this->shouldLogLock()) { + return pht( + 'Enable the lock log for more detailed information about '. + 'which process is holding this lock.'); + } + + $now = PhabricatorTime::getNow(); + + // First, look for recent logs. If other processes have been acquiring and + // releasing this lock while we've been waiting, this is more likely to be + // a contention/throughput issue than an issue with something hung while + // holding the lock. + $limit = 100; + $logs = id(new PhabricatorDaemonLockLog())->loadAllWhere( + 'lockName = %s AND dateCreated >= %d ORDER BY id ASC LIMIT %d', + $lock_name, + ($now - $wait), + $limit); + + if ($logs) { + if (count($logs) === $limit) { + return pht( + 'During the last %s second(s) spent waiting for the lock, more '. + 'than %s other process(es) acquired it, so this is likely a '. + 'bottleneck. Use "bin/lock log --name %s" to review log activity.', + new PhutilNumber($wait), + new PhutilNumber($limit), + $lock_name); + } else { + return pht( + 'During the last %s second(s) spent waiting for the lock, %s '. + 'other process(es) acquired it, so this is likely a '. + 'bottleneck. Use "bin/lock log --name %s" to review log activity.', + new PhutilNumber($wait), + phutil_count($logs), + $lock_name); + } + } + + $last_log = id(new PhabricatorDaemonLockLog())->loadOneWhere( + 'lockName = %s ORDER BY id DESC LIMIT 1', + $lock_name); + + if ($last_log) { + $info = array(); + + $acquired = $last_log->getDateCreated(); + $context = $last_log->getLockContext(); + + $process_info = array(); + + $pid = idx($context, 'pid'); + if ($pid) { + $process_info[] = 'pid='.$pid; + } + + $host = idx($context, 'host'); + if ($host) { + $process_info[] = 'host='.$host; + } + + $sapi = idx($context, 'sapi'); + if ($sapi) { + $process_info[] = 'sapi='.$sapi; + } + + $argv = idx($context, 'argv'); + if ($argv) { + $process_info[] = 'argv='.(string)csprintf('%LR', $argv); + } + + $controller = idx($context, 'controller'); + if ($controller) { + $process_info[] = 'controller='.$controller; + } + + $method = idx($context, 'method'); + if ($method) { + $process_info[] = 'method='.$method; + } + + $process_info = implode(', ', $process_info); + + $info[] = pht( + 'This lock was most recently acquired by a process (%s) '. + '%s second(s) ago.', + $process_info, + new PhutilNumber($now - $acquired)); + + $released = $last_log->getLockReleased(); + if ($released) { + $info[] = pht( + 'This lock was released %s second(s) ago.', + new PhutilNumber($now - $released)); + } else { + $info[] = pht('There is no record of this lock being released.'); + } + + return implode(' ', $info); + } + + return pht( + 'Found no records of processes acquiring or releasing this lock.'); + } + } diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php index 0b0e608048..53e6f940c9 100644 --- a/src/view/form/control/AphrontFormPolicyControl.php +++ b/src/view/form/control/AphrontFormPolicyControl.php @@ -279,7 +279,6 @@ final class AphrontFormPolicyControl extends AphrontFormControl { ->setIcon($icon); } - if ($this->templatePHIDType) { $context_path = 'template/'.$this->templatePHIDType.'/'; } else { @@ -346,15 +345,6 @@ final class AphrontFormPolicyControl extends AphrontFormControl { )), $input, )); - - return AphrontFormSelectControl::renderSelectTag( - $this->getValue(), - $this->getOptions(), - array( - 'name' => $this->getName(), - 'disabled' => $this->getDisabled() ? 'disabled' : null, - 'id' => $this->getID(), - )); } public static function getSelectCustomKey() { diff --git a/src/view/form/control/PHUIFormNumberControl.php b/src/view/form/control/PHUIFormNumberControl.php index d65e590746..26e7e03955 100644 --- a/src/view/form/control/PHUIFormNumberControl.php +++ b/src/view/form/control/PHUIFormNumberControl.php @@ -2,11 +2,28 @@ final class PHUIFormNumberControl extends AphrontFormControl { + private $disableAutocomplete; + + public function setDisableAutocomplete($disable_autocomplete) { + $this->disableAutocomplete = $disable_autocomplete; + return $this; + } + + public function getDisableAutocomplete() { + return $this->disableAutocomplete; + } + protected function getCustomControlClass() { return 'phui-form-number'; } protected function renderInput() { + if ($this->getDisableAutocomplete()) { + $autocomplete = 'off'; + } else { + $autocomplete = null; + } + return javelin_tag( 'input', array( @@ -15,6 +32,7 @@ final class PHUIFormNumberControl extends AphrontFormControl { 'name' => $this->getName(), 'value' => $this->getValue(), 'disabled' => $this->getDisabled() ? 'disabled' : null, + 'autocomplete' => $autocomplete, 'id' => $this->getID(), )); } diff --git a/src/view/page/menu/PhabricatorMainMenuSearchView.php b/src/view/page/menu/PhabricatorMainMenuSearchView.php index 0b2ca8ab10..15319a357e 100644 --- a/src/view/page/menu/PhabricatorMainMenuSearchView.php +++ b/src/view/page/menu/PhabricatorMainMenuSearchView.php @@ -94,21 +94,24 @@ final class PhabricatorMainMenuSearchView extends AphrontView { 'action' => '/search/', 'method' => 'POST', ), - phutil_tag_div('phabricator-main-menu-search-container', array( - $input, - phutil_tag( - 'button', - array( - 'id' => $button_id, - 'class' => 'phui-icon-view phui-font-fa fa-search', + phutil_tag( + 'div', + array( + 'class' => 'phabricator-main-menu-search-container', + ), + array( + $input, + phutil_tag( + 'button', + array( + 'id' => $button_id, + 'class' => 'phui-icon-view phui-font-fa fa-search', ), - array( - $selector, - $search_text, - )), - $primary_input, - $target, - ))); + $search_text), + $selector, + $primary_input, + $target, + ))); return $form; } @@ -207,6 +210,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView { id(new PHUIIconView()) ->addSigil('global-search-dropdown-icon') ->setIcon($current_icon)) + ->setAuralLabel(pht('Configure Global Search')) ->setDropdown(true); $input = javelin_tag( diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 583952b0a9..67badce911 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -232,7 +232,8 @@ final class PhabricatorMainMenuView extends AphrontView { ->addClass('phabricator-core-user-menu') ->addClass('phabricator-core-user-mobile-menu') ->setNoCSS(true) - ->setDropdownMenu($dropdown); + ->setDropdownMenu($dropdown) + ->setAuralLabel(pht('Page Menu')); } private function renderApplicationMenu() { diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php index 91c336eaaf..2231649f12 100644 --- a/src/view/phui/PHUIButtonView.php +++ b/src/view/phui/PHUIButtonView.php @@ -31,6 +31,7 @@ final class PHUIButtonView extends AphrontTagView { private $noCSS; private $hasCaret; private $buttonType = self::BUTTONTYPE_DEFAULT; + private $auralLabel; public function setName($name) { $this->name = $name; @@ -123,6 +124,15 @@ final class PHUIButtonView extends AphrontTagView { return $this->buttonType; } + public function setAuralLabel($aural_label) { + $this->auralLabel = $aural_label; + return $this; + } + + public function getAuralLabel() { + return $this->auralLabel; + } + public function setIcon($icon, $first = true) { if (!($icon instanceof PHUIIconView)) { $icon = id(new PHUIIconView()) @@ -223,17 +233,36 @@ final class PHUIButtonView extends AphrontTagView { $classes = array(); } - return array( + // See PHI823. If we aren't rendering a "