diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2eab0ac0c3..4ca4c327f9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,9 +7,9 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '32f2c040', + 'conpherence.pkg.css' => '82aca405', 'conpherence.pkg.js' => '6249a1cf', - 'core.pkg.css' => '491d7018', + 'core.pkg.css' => 'dc689e29', 'core.pkg.js' => '1fa7c0c5', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', @@ -46,7 +46,7 @@ return array( 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '292c71f0', - 'rsrc/css/application/conpherence/header-pane.css' => 'db93ebc6', + 'rsrc/css/application/conpherence/header-pane.css' => '4082233d', 'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c', 'rsrc/css/application/conpherence/message-pane.css' => 'd1fc13e1', 'rsrc/css/application/conpherence/notification.css' => '965db05b', @@ -83,7 +83,7 @@ return array( 'rsrc/css/application/paste/paste.css' => '1898e534', 'rsrc/css/application/people/people-picture-menu-item.css' => 'a06f7f34', 'rsrc/css/application/people/people-profile.css' => '4df76faf', - 'rsrc/css/application/phame/phame.css' => '53fa6236', + 'rsrc/css/application/phame/phame.css' => 'b3a0b3a3', 'rsrc/css/application/pholio/pholio-edit.css' => '07676f51', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => 'ca89d380', @@ -96,7 +96,7 @@ return array( 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96', - 'rsrc/css/application/project/project-card-view.css' => '1be8c87b', + 'rsrc/css/application/project/project-card-view.css' => '3d3c1f91', 'rsrc/css/application/project/project-view.css' => '792c9057', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', @@ -112,7 +112,6 @@ return array( 'rsrc/css/core/syntax.css' => '769d3498', 'rsrc/css/core/z-index.css' => '5e72c4e0', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', - 'rsrc/css/font/font-aleo.css' => '8bdb2835', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', @@ -145,11 +144,11 @@ return array( 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', - 'rsrc/css/phui/phui-fontkit.css' => 'b78a0059', - 'rsrc/css/phui/phui-form-view.css' => 'cf198e10', + 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', + 'rsrc/css/phui/phui-form-view.css' => '6175808d', 'rsrc/css/phui/phui-form.css' => 'b62c01d8', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => 'fef6a54e', + 'rsrc/css/phui/phui-header-view.css' => '9cf828ce', 'rsrc/css/phui/phui-hovercard.css' => 'ae091fc5', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '12b387a1', @@ -169,7 +168,7 @@ return array( 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '84d65f26', 'rsrc/css/phui/phui-timeline-view.css' => 'bf45789e', - 'rsrc/css/phui/phui-two-column-view.css' => '8a1074c7', + 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', 'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92', @@ -178,16 +177,6 @@ return array( 'rsrc/css/sprite-tokens.css' => '9cdfd599', 'rsrc/css/syntax/syntax-default.css' => '9923583c', 'rsrc/externals/d3/d3.min.js' => 'a11a5ff2', - 'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7', - 'rsrc/externals/font/aleo/aleo-bold.svg' => '45899c8e', - 'rsrc/externals/font/aleo/aleo-bold.ttf' => '4b08bef0', - 'rsrc/externals/font/aleo/aleo-bold.woff' => '93b513a1', - 'rsrc/externals/font/aleo/aleo-bold.woff2' => '75fbf322', - 'rsrc/externals/font/aleo/aleo-regular.eot' => 'a4e29e2f', - 'rsrc/externals/font/aleo/aleo-regular.svg' => '42a86f7a', - 'rsrc/externals/font/aleo/aleo-regular.ttf' => '751e7479', - 'rsrc/externals/font/aleo/aleo-regular.woff' => 'c3744be9', - 'rsrc/externals/font/aleo/aleo-regular.woff2' => '851aa0ee', 'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '24a7064f', 'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '0039fe26', 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => 'de978a43', @@ -541,7 +530,7 @@ return array( 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', 'rsrc/js/phuix/PHUIXAutocomplete.js' => '7c492cd2', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', - 'rsrc/js/phuix/PHUIXFormControl.js' => 'bbece68d', + 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), 'symbols' => array( @@ -564,7 +553,7 @@ return array( 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', 'conpherence-durable-column-view' => '292c71f0', - 'conpherence-header-pane-css' => 'db93ebc6', + 'conpherence-header-pane-css' => '4082233d', 'conpherence-menu-css' => '3d8e5c9c', 'conpherence-message-pane-css' => 'd1fc13e1', 'conpherence-notification-css' => '965db05b', @@ -584,7 +573,6 @@ return array( 'diffusion-readme-css' => '297373eb', 'diffusion-source-css' => '750add59', 'diviner-shared-css' => '896f1d43', - 'font-aleo' => '8bdb2835', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => '5c1b47c2', @@ -826,7 +814,7 @@ return array( 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-zindex-css' => '5e72c4e0', - 'phame-css' => '53fa6236', + 'phame-css' => 'b3a0b3a3', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', 'pholio-inline-comments-css' => '8e545e49', @@ -857,11 +845,11 @@ return array( 'phui-document-view-pro-css' => 'f56738ed', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', - 'phui-fontkit-css' => 'b78a0059', + 'phui-fontkit-css' => '1320ed01', 'phui-form-css' => 'b62c01d8', - 'phui-form-view-css' => 'cf198e10', + 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => 'fef6a54e', + 'phui-header-view-css' => '9cf828ce', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'ae091fc5', 'phui-icon-set-selector-css' => '87db8fee', @@ -890,7 +878,7 @@ return array( 'phui-tag-view-css' => '84d65f26', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => 'bf45789e', - 'phui-two-column-view-css' => '8a1074c7', + 'phui-two-column-view-css' => 'ce9fa0b7', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', 'phui-workcard-view-css' => 'cca5fa92', @@ -899,13 +887,13 @@ return array( 'phuix-action-view' => 'b3465b9b', 'phuix-autocomplete' => '7c492cd2', 'phuix-dropdown-menu' => '8018ee50', - 'phuix-form-control-view' => 'bbece68d', + 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => 'fbd45f96', - 'project-card-view-css' => '1be8c87b', + 'project-card-view-css' => '3d3c1f91', 'project-view-css' => '792c9057', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', @@ -1530,6 +1518,10 @@ return array( 'javelin-behavior', 'javelin-scrollbar', ), + '83e03671' => array( + 'javelin-install', + 'javelin-dom', + ), '8499b6ab' => array( 'javelin-behavior', 'javelin-dom', @@ -1585,9 +1577,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '8bdb2835' => array( - 'phui-fontkit-css', - ), '8ce821c5' => array( 'phabricator-notification', 'javelin-stratcom', @@ -1917,10 +1906,6 @@ return array( 'javelin-vector', 'javelin-install', ), - 'bbece68d' => array( - 'javelin-install', - 'javelin-dom', - ), 'bcaccd64' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2322,7 +2307,6 @@ return array( 'phui-list-view-css', 'font-fontawesome', 'font-lato', - 'font-aleo', 'phui-font-icon-base-css', 'phui-fontkit-css', 'phui-box-css', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 4822633426..5f935c8620 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -138,7 +138,6 @@ return array( 'font-fontawesome', 'font-lato', - 'font-aleo', 'phui-font-icon-base-css', 'phui-fontkit-css', 'phui-box-css', diff --git a/resources/sql/autopatches/20170320.reviewers.01.lastaction.sql b/resources/sql/autopatches/20170320.reviewers.01.lastaction.sql new file mode 100644 index 0000000000..41b8051275 --- /dev/null +++ b/resources/sql/autopatches/20170320.reviewers.01.lastaction.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_differential.differential_reviewer + ADD lastActionDiffPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20170320.reviewers.02.lastcomment.sql b/resources/sql/autopatches/20170320.reviewers.02.lastcomment.sql new file mode 100644 index 0000000000..c430d86064 --- /dev/null +++ b/resources/sql/autopatches/20170320.reviewers.02.lastcomment.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_differential.differential_reviewer + ADD lastCommentDiffPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20170320.reviewers.03.migrate.php b/resources/sql/autopatches/20170320.reviewers.03.migrate.php new file mode 100644 index 0000000000..04abb5c141 --- /dev/null +++ b/resources/sql/autopatches/20170320.reviewers.03.migrate.php @@ -0,0 +1,125 @@ +establishConnection('w'); + +// Previously "DifferentialRevisionHasReviewerEdgeType::EDGECONST". +$edge_type = 35; + +// NOTE: We can't use normal migration iterators for edges because they don't +// have an "id" column. For now, try just loading the whole result set: the +// actual size of the rows is small. If we run into issues, we could write an +// EdgeIterator. +$every_edge = queryfx_all( + $conn, + 'SELECT * FROM %T edge LEFT JOIN %T data ON edge.dataID = data.id + WHERE edge.type = %d', + $table_name, + $data_name, + $edge_type); + +foreach ($every_edge as $edge) { + if ($edge['type'] != $edge_type) { + // Ignore edges which aren't "reviewers", like subscribers. + continue; + } + + try { + $data = phutil_json_decode($edge['data']); + $data = idx($data, 'data'); + } catch (Exception $ex) { + // Just ignore any kind of issue with the edge data, we'll use a default + // below. + $data = null; + } + + if (!$data) { + $data = array( + 'status' => 'added', + ); + } + + $status = idx($data, 'status'); + + $diff_phid = null; + + // NOTE: At one point, the code to populate "diffID" worked correctly, but + // it seems to have later been broken. Salvage it if we can, and look up + // the corresponding diff PHID. + $diff_id = idx($data, 'diffID'); + if ($diff_id) { + $row = queryfx_one( + $conn, + 'SELECT phid FROM %T WHERE id = %d', + $diff_table->getTableName(), + $diff_id); + if ($row) { + $diff_phid = $row['phid']; + } + } + + if (!$diff_phid) { + // If the status is "accepted" or "rejected", look up the current diff + // PHID so we can distinguish between "accepted" and "accepted older". + switch ($status) { + case 'accepted': + case 'rejected': + case 'commented': + $row = queryfx_one( + $conn, + 'SELECT diff.phid FROM %T diff JOIN %T revision + ON diff.revisionID = revision.id + WHERE revision.phid = %s + ORDER BY diff.id DESC LIMIT 1', + $diff_table->getTableName(), + $table->getTableName(), + $edge['src']); + if ($row) { + $diff_phid = $row['phid']; + } + break; + } + } + + // We now represent some states (like "Commented" and "Accepted Older") as + // a primary state plus an extra flag, instead of making "Commented" a + // primary state. Map old states to new states and flags. + + if ($status == 'commented') { + $status = 'added'; + $comment_phid = $diff_phid; + $action_phid = null; + } else { + $comment_phid = null; + $action_phid = $diff_phid; + } + + if ($status == 'accepted-older') { + $status = 'accepted'; + } + + if ($status == 'rejected-older') { + $status = 'rejected'; + } + + queryfx( + $conn, + 'INSERT INTO %T (revisionPHID, reviewerPHID, reviewerStatus, + lastActionDiffPHID, lastCommentDiffPHID, dateCreated, dateModified) + VALUES (%s, %s, %s, %ns, %ns, %d, %d) + ON DUPLICATE KEY UPDATE dateCreated = VALUES(dateCreated)', + $reviewer_table->getTableName(), + $edge['src'], + $edge['dst'], + $status, + $action_phid, + $comment_phid, + $edge['dateCreated'], + $edge['dateCreated']); +} diff --git a/resources/sql/autopatches/20170322.reviewers.04.actor.sql b/resources/sql/autopatches/20170322.reviewers.04.actor.sql new file mode 100644 index 0000000000..27b46848a7 --- /dev/null +++ b/resources/sql/autopatches/20170322.reviewers.04.actor.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_differential.differential_reviewer + ADD lastActorPHID VARBINARY(64); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 29bf2e418f..270fb84194 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -494,7 +494,6 @@ phutil_register_library_map(array( 'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php', 'DifferentialReviewerDatasource' => 'applications/differential/typeahead/DifferentialReviewerDatasource.php', 'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php', - 'DifferentialReviewerProxy' => 'applications/differential/storage/DifferentialReviewerProxy.php', 'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php', 'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php', 'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php', @@ -2013,7 +2012,6 @@ phutil_register_library_map(array( 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', 'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php', 'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php', - 'PhabricatorBadgeHasRecipientEdgeType' => 'applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php', 'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.php', 'PhabricatorBadgesArchiveController' => 'applications/badges/controller/PhabricatorBadgesArchiveController.php', 'PhabricatorBadgesAward' => 'applications/badges/storage/PhabricatorBadgesAward.php', @@ -2530,6 +2528,7 @@ phutil_register_library_map(array( 'PhabricatorDashboardProfileController' => 'applications/dashboard/controller/PhabricatorDashboardProfileController.php', 'PhabricatorDashboardProfileMenuItem' => 'applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php', 'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php', + 'PhabricatorDashboardQueryPanelInstallController' => 'applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php', 'PhabricatorDashboardQueryPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php', 'PhabricatorDashboardRemarkupRule' => 'applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php', 'PhabricatorDashboardRemovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php', @@ -2602,6 +2601,7 @@ phutil_register_library_map(array( 'PhabricatorEdgesDestructionEngineExtension' => 'infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php', 'PhabricatorEditEngine' => 'applications/transactions/editengine/PhabricatorEditEngine.php', 'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php', + 'PhabricatorEditEngineCheckboxesCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCheckboxesCommentAction.php', 'PhabricatorEditEngineColumnsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php', 'PhabricatorEditEngineCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php', 'PhabricatorEditEngineCommentActionGroup' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentActionGroup.php', @@ -3622,7 +3622,6 @@ phutil_register_library_map(array( 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', - 'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', 'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php', @@ -3691,6 +3690,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryPullEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPullEventPHIDType.php', 'PhabricatorRepositoryPullEventQuery' => 'applications/repository/query/PhabricatorRepositoryPullEventQuery.php', 'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php', + 'PhabricatorRepositoryPullLocalDaemonModule' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemonModule.php', 'PhabricatorRepositoryPushEvent' => 'applications/repository/storage/PhabricatorRepositoryPushEvent.php', 'PhabricatorRepositoryPushEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPushEventPHIDType.php', 'PhabricatorRepositoryPushEventQuery' => 'applications/repository/query/PhabricatorRepositoryPushEventQuery.php', @@ -3987,6 +3987,7 @@ phutil_register_library_map(array( 'PhabricatorTOTPAuthFactor' => 'applications/auth/factor/PhabricatorTOTPAuthFactor.php', 'PhabricatorTOTPAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorTOTPAuthFactorTestCase.php', 'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php', + 'PhabricatorTaskmasterDaemonModule' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemonModule.php', 'PhabricatorTestApplication' => 'applications/base/controller/__tests__/PhabricatorTestApplication.php', 'PhabricatorTestCase' => 'infrastructure/testing/PhabricatorTestCase.php', 'PhabricatorTestController' => 'applications/base/controller/__tests__/PhabricatorTestController.php', @@ -5254,7 +5255,6 @@ phutil_register_library_map(array( 'DifferentialReviewer' => 'DifferentialDAO', 'DifferentialReviewerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType', - 'DifferentialReviewerProxy' => 'Phobject', 'DifferentialReviewerStatus' => 'Phobject', 'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction', 'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction', @@ -6996,7 +6996,6 @@ phutil_register_library_map(array( 'PhabricatorAuthValidateController' => 'PhabricatorAuthController', 'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAutoEventListener' => 'PhabricatorEventListener', - 'PhabricatorBadgeHasRecipientEdgeType' => 'PhabricatorEdgeType', 'PhabricatorBadgesApplication' => 'PhabricatorApplication', 'PhabricatorBadgesArchiveController' => 'PhabricatorBadgesController', 'PhabricatorBadgesAward' => array( @@ -7610,6 +7609,7 @@ phutil_register_library_map(array( 'PhabricatorDashboardProfileController' => 'PhabricatorController', 'PhabricatorDashboardProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorDashboardQueryPanelInstallController' => 'PhabricatorDashboardController', 'PhabricatorDashboardQueryPanelType' => 'PhabricatorDashboardPanelType', 'PhabricatorDashboardRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorDashboardRemovePanelController' => 'PhabricatorDashboardController', @@ -7686,6 +7686,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhabricatorEditEngineAPIMethod' => 'ConduitAPIMethod', + 'PhabricatorEditEngineCheckboxesCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineColumnsCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineCommentAction' => 'Phobject', 'PhabricatorEditEngineCommentActionGroup' => 'Phobject', @@ -8874,7 +8875,6 @@ phutil_register_library_map(array( ), 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', - 'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', 'PhabricatorRegistrationProfile' => 'Phobject', @@ -8985,6 +8985,7 @@ phutil_register_library_map(array( 'PhabricatorRepositoryPullEventPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryPullEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorDaemon', + 'PhabricatorRepositoryPullLocalDaemonModule' => 'PhutilDaemonOverseerModule', 'PhabricatorRepositoryPushEvent' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', @@ -9314,6 +9315,7 @@ phutil_register_library_map(array( 'PhabricatorTOTPAuthFactor' => 'PhabricatorAuthFactor', 'PhabricatorTOTPAuthFactorTestCase' => 'PhabricatorTestCase', 'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon', + 'PhabricatorTaskmasterDaemonModule' => 'PhutilDaemonOverseerModule', 'PhabricatorTestApplication' => 'PhabricatorApplication', 'PhabricatorTestCase' => 'PhutilTestCase', 'PhabricatorTestController' => 'PhabricatorController', diff --git a/src/applications/audit/query/PhabricatorCommitSearchEngine.php b/src/applications/audit/query/PhabricatorCommitSearchEngine.php index 52cfe1f94f..6811427c93 100644 --- a/src/applications/audit/query/PhabricatorCommitSearchEngine.php +++ b/src/applications/audit/query/PhabricatorCommitSearchEngine.php @@ -178,10 +178,13 @@ final class PhabricatorCommitSearchEngine $groups = $bucket->newResultGroups($query, $commits); foreach ($groups as $group) { - $views[] = id(clone $template) - ->setHeader($group->getName()) - ->setNoDataString($group->getNoDataString()) - ->setCommits($group->getObjects()); + // Don't show groups in Dashboard Panels + if ($group->getObjects() || !$this->isPanelContext()) { + $views[] = id(clone $template) + ->setHeader($group->getName()) + ->setNoDataString($group->getNoDataString()) + ->setCommits($group->getObjects()); + } } } catch (Exception $ex) { $this->addError($ex->getMessage()); @@ -189,7 +192,13 @@ final class PhabricatorCommitSearchEngine } else { $views[] = id(clone $template) ->setCommits($commits) - ->setNoDataString(pht('No matching commits.')); + ->setNoDataString(pht('No commits found.')); + } + + if (!$views) { + $views[] = id(new PhabricatorAuditListView()) + ->setViewer($viewer) + ->setNoDataString(pht('No commits found.')); } if (count($views) == 1) { diff --git a/src/applications/badges/application/PhabricatorBadgesApplication.php b/src/applications/badges/application/PhabricatorBadgesApplication.php index 6eab0c55f8..4df412a6fc 100644 --- a/src/applications/badges/application/PhabricatorBadgesApplication.php +++ b/src/applications/badges/application/PhabricatorBadgesApplication.php @@ -26,10 +26,6 @@ final class PhabricatorBadgesApplication extends PhabricatorApplication { return self::GROUP_UTILITIES; } - public function isPrototype() { - return true; - } - public function getRoutes() { return array( '/badges/' => array( diff --git a/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php b/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php deleted file mode 100644 index 9b6db5a63b..0000000000 --- a/src/applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php +++ /dev/null @@ -1,103 +0,0 @@ -openTransaction(); - $this->delete(); + $invitees = id(new PhabricatorCalendarEventInvitee())->loadAllWhere( + 'eventPHID = %s', + $this->getPHID()); + foreach ($invitees as $invitee) { + $invitee->delete(); + } + + $notifications = id(new PhabricatorCalendarNotification())->loadAllWhere( + 'eventPHID = %s', + $this->getPHID()); + foreach ($notifications as $notification) { + $notification->delete(); + } + + $this->delete(); $this->saveTransaction(); } diff --git a/src/applications/conduit/controller/PhabricatorConduitController.php b/src/applications/conduit/controller/PhabricatorConduitController.php index 8588bc07f9..97e1ad0294 100644 --- a/src/applications/conduit/controller/PhabricatorConduitController.php +++ b/src/applications/conduit/controller/PhabricatorConduitController.php @@ -56,6 +56,12 @@ abstract class PhabricatorConduitController extends PhabricatorController { $panel_link), ); + if ($params === null) { + $messages[] = pht( + 'If you submit parameters, these examples will update to show '. + 'exactly how to encode the parameters you submit.'); + } + $info_view = id(new PHUIInfoView()) ->setErrors($messages) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); diff --git a/src/applications/daemon/controller/PhabricatorDaemonController.php b/src/applications/daemon/controller/PhabricatorDaemonController.php index a2734907fd..05c850d399 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonController.php @@ -7,6 +7,10 @@ abstract class PhabricatorDaemonController return true; } + public function buildApplicationMenu() { + return $this->buildSideNavView(true)->getMenu(); + } + protected function buildSideNavView() { $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php index 004ce3a84e..f4c7ba60c9 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php @@ -125,12 +125,10 @@ final class PhabricatorDaemonLogViewController case PhabricatorDaemonLog::STATUS_WAIT: $details = pht( 'This daemon is running normally and reported a status update '. - 'recently (within %s). However, it encountered an error while '. - 'doing work and is waiting a little while (%s) to resume '. - 'processing. After encountering an error, daemons wait before '. - 'resuming work to avoid overloading services.', - phutil_format_relative_time($unknown_time), - phutil_format_relative_time($wait_time)); + 'recently (within %s). The process is currently waiting to '. + 'restart, either because it is hibernating or because it '. + 'encountered an error.', + phutil_format_relative_time($unknown_time)); break; case PhabricatorDaemonLog::STATUS_EXITING: $details = pht('This daemon is shutting down gracefully.'); diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php index c0ce2ff09e..d8e2701727 100644 --- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php +++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php @@ -18,6 +18,14 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { return 'fa-dashboard'; } + public function isPinnedByDefault(PhabricatorUser $viewer) { + return true; + } + + public function getApplicationOrder() { + return 0.160; + } + public function getRoutes() { return array( '/W(?P\d+)' => 'PhabricatorDashboardPanelViewController', @@ -36,6 +44,8 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { 'removepanel/(?P\d+)/' => 'PhabricatorDashboardRemovePanelController', 'panel/' => array( + 'install/(?P[^/]+)/(?:(?P[^/]+)/)?' => + 'PhabricatorDashboardQueryPanelInstallController', '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardPanelListController', 'create/' => 'PhabricatorDashboardPanelEditController', diff --git a/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php b/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php new file mode 100644 index 0000000000..77068531a9 --- /dev/null +++ b/src/applications/dashboard/controller/PhabricatorDashboardQueryPanelInstallController.php @@ -0,0 +1,194 @@ +getViewer(); + + $v_dashboard = null; + $v_name = null; + $v_column = 0; + $v_engine = $request->getURIData('engineKey'); + $v_query = $request->getURIData('queryKey'); + + $e_name = true; + + // Validate Engines + $engines = PhabricatorApplicationSearchEngine::getAllEngines(); + foreach ($engines as $name => $engine) { + if (!$engine->canUseInPanelContext()) { + unset($engines[$name]); + } + } + if (!in_array($v_engine, array_keys($engines))) { + return new Aphront404Response(); + } + + // Validate Queries + $engine = $engines[$v_engine]; + $engine->setViewer($viewer); + $good_query = false; + if ($engine->isBuiltinQuery($v_query)) { + $good_query = true; + } else { + $saved_query = id(new PhabricatorSavedQueryQuery()) + ->setViewer($viewer) + ->withEngineClassNames(array($v_engine)) + ->withQueryKeys(array($v_query)) + ->executeOne(); + if ($saved_query) { + $good_query = true; + } + } + if (!$good_query) { + return new Aphront404Response(); + } + + $named_query = idx($engine->loadEnabledNamedQueries(), $v_query); + if ($named_query) { + $v_name = $named_query->getQueryName(); + } + + $errors = array(); + + if ($request->isFormPost()) { + $v_dashboard = $request->getInt('dashboardID'); + $v_name = $request->getStr('name'); + if (!$v_name) { + $errors[] = pht('You must provide a name for this panel.'); + $e_name = pht('Required'); + } + + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withIDs(array($v_dashboard)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if (!$dashboard) { + $errors[] = pht('Please select a valid dashboard.'); + } + + if (!$errors) { + $redirect_uri = "/dashboard/arrange/{$v_dashboard}/"; + + $panel_type = id(new PhabricatorDashboardQueryPanelType()) + ->getPanelTypeKey(); + $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); + $panel->setPanelType($panel_type); + + $field_list = PhabricatorCustomField::getObjectFields( + $panel, + PhabricatorCustomField::ROLE_EDIT); + + $field_list + ->setViewer($viewer) + ->readFieldsFromStorage($panel); + + $panel->requireImplementation()->initializeFieldsFromRequest( + $panel, + $field_list, + $request); + + $xactions = array(); + + $xactions[] = id(new PhabricatorDashboardPanelTransaction()) + ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME) + ->setNewValue($v_name); + + $xactions[] = id(new PhabricatorDashboardPanelTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) + ->setMetadataValue('customfield:key', 'std:dashboard:core:class') + ->setOldValue(null) + ->setNewValue($v_engine); + + $xactions[] = id(new PhabricatorDashboardPanelTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) + ->setMetadataValue('customfield:key', 'std:dashboard:core:key') + ->setOldValue(null) + ->setNewValue($v_query); + + $editor = id(new PhabricatorDashboardPanelTransactionEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->applyTransactions($panel, $xactions); + + PhabricatorDashboardTransactionEditor::addPanelToDashboard( + $viewer, + PhabricatorContentSource::newFromRequest($request), + $panel, + $dashboard, + $request->getInt('column', 0)); + + return id(new AphrontRedirectResponse())->setURI($redirect_uri); + } + } + + // Make this a select for now, as we don't expect someone to have + // edit access to a vast number of dashboards. + // Can add optiongroup if needed down the road. + $dashboards = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withStatuses(array( + PhabricatorDashboard::STATUS_ACTIVE, + )) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + $options = mpull($dashboards, 'getName', 'getID'); + asort($options); + + $redirect_uri = $engine->getQueryResultsPageURI($v_query); + + if (!$options) { + $notice = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild(pht('You do not have access to any dashboards. To '. + 'continue, please create a dashboard first.')); + + return $this->newDialog() + ->setTitle(pht('No Dashboards')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendChild($notice) + ->addCancelButton($redirect_uri); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->addHiddenInput('engine', $v_engine) + ->addHiddenInput('query', $v_query) + ->addHiddenInput('column', $v_column) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($v_name) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setUser($this->getViewer()) + ->setValue($v_dashboard) + ->setName('dashboardID') + ->setOptions($options) + ->setLabel(pht('Dashboard'))); + + return $this->newDialog() + ->setTitle(pht('Add Panel to Dashboard')) + ->setErrors($errors) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendChild($form->buildLayoutView()) + ->addCancelButton($redirect_uri) + ->addSubmitButton(pht('Add Panel')); + + } + +} diff --git a/src/applications/dashboard/icon/PhabricatorDashboardIconSet.php b/src/applications/dashboard/icon/PhabricatorDashboardIconSet.php index 1bd12609b7..a93c361bb5 100644 --- a/src/applications/dashboard/icon/PhabricatorDashboardIconSet.php +++ b/src/applications/dashboard/icon/PhabricatorDashboardIconSet.php @@ -12,6 +12,7 @@ final class PhabricatorDashboardIconSet protected function newIcons() { $map = array( 'fa-home' => pht('Home'), + 'fa-dashboard' => pht('Dashboard'), 'fa-th-large' => pht('Blocks'), 'fa-columns' => pht('Columns'), 'fa-bookmark' => pht('Page Saver'), @@ -20,16 +21,26 @@ final class PhabricatorDashboardIconSet 'fa-bomb' => pht('Kaboom'), 'fa-pie-chart' => pht('Apple Blueberry'), 'fa-bar-chart' => pht('Serious Business'), + 'fa-briefcase' => pht('Project'), 'fa-bell' => pht('Ding Ding'), 'fa-credit-card' => pht('Plastic Debt'), 'fa-code' => pht('PHP is Life'), 'fa-sticky-note' => pht('To Self'), - 'fa-newspaper-o' => pht('Stay Woke'), + 'fa-server' => pht('Metallica'), 'fa-hashtag' => pht('Corned Beef'), - 'fa-group' => pht('Triplets'), + 'fa-anchor' => pht('Tasks'), + 'fa-calendar' => pht('Calendar'), + 'fa-compass' => pht('Wayfinding'), + + 'fa-futbol-o' => pht('Sports'), + 'fa-flag' => pht('Flag'), + 'fa-ship' => pht('Water Vessel'), + 'fa-feed' => pht('Wireless'), + 'fa-bullhorn' => pht('Announcement'), + ); $icons = array(); diff --git a/src/applications/dashboard/query/PhabricatorDashboardQuery.php b/src/applications/dashboard/query/PhabricatorDashboardQuery.php index c005197df2..9f5e256391 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardQuery.php +++ b/src/applications/dashboard/query/PhabricatorDashboardQuery.php @@ -7,6 +7,7 @@ final class PhabricatorDashboardQuery private $phids; private $statuses; private $authorPHIDs; + private $canEdit; private $needPanels; private $needProjects; @@ -41,6 +42,11 @@ final class PhabricatorDashboardQuery return $this; } + public function withCanEdit($can_edit) { + $this->canEdit = $can_edit; + return $this; + } + public function withNameNgrams($ngrams) { return $this->withNgramsConstraint( id(new PhabricatorDashboardNgrams()), @@ -59,6 +65,15 @@ final class PhabricatorDashboardQuery $phids = mpull($dashboards, 'getPHID'); + if ($this->canEdit) { + $dashboards = id(new PhabricatorPolicyFilter()) + ->setViewer($this->getViewer()) + ->requireCapabilities(array( + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->apply($dashboards); + } + if ($this->needPanels) { $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($phids) diff --git a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php index a854b066f8..a05d1c4121 100644 --- a/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php +++ b/src/applications/dashboard/query/PhabricatorDashboardSearchEngine.php @@ -34,6 +34,10 @@ final class PhabricatorDashboardSearchEngine ->setKey('statuses') ->setLabel(pht('Status')) ->setOptions(PhabricatorDashboard::getStatusNameMap()), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('editable') + ->setLabel(pht('Editable')) + ->setOptions(array('editable' => null)), ); } @@ -94,6 +98,10 @@ final class PhabricatorDashboardSearchEngine $query->withNameNgrams($map['name']); } + if ($map['editable'] !== null) { + $query->withCanEdit($map['editable']); + } + return $query; } @@ -126,8 +134,10 @@ final class PhabricatorDashboardSearchEngine ->setHref($this->getApplicationURI("view/{$id}/")) ->setObject($dashboard); + $bg_color = 'bg-dark'; if ($dashboard->isArchived()) { $item->setDisabled(true); + $bg_color = 'bg-grey'; } $panels = $dashboard->getPanels(); @@ -142,7 +152,7 @@ final class PhabricatorDashboardSearchEngine $icon = id(new PHUIIconView()) ->setIcon($dashboard->getIcon()) - ->setBackground('bg-dark'); + ->setBackground($bg_color); $item->setImageIcon($icon); $item->setEpoch($dashboard->getDateModified()); diff --git a/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php index d71876961e..30f8a5e5b7 100644 --- a/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php @@ -44,7 +44,7 @@ final class DifferentialCloseConduitAPIMethod $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($id)) ->setViewer($viewer) - ->needReviewerStatus(true) + ->needReviewers(true) ->executeOne(); if (!$revision) { throw new ConduitException('ERR_NOT_FOUND'); diff --git a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php index 459298c54f..ee70644537 100644 --- a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php @@ -47,7 +47,7 @@ final class DifferentialCreateCommentConduitAPIMethod $revision = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withIDs(array($request->getValue('revision_id'))) - ->needReviewerStatus(true) + ->needReviewers(true) ->needReviewerAuthority(true) ->executeOne(); if (!$revision) { @@ -56,11 +56,28 @@ final class DifferentialCreateCommentConduitAPIMethod $xactions = array(); + $modular_map = array( + 'accept' => DifferentialRevisionAcceptTransaction::TRANSACTIONTYPE, + 'reject' => DifferentialRevisionRejectTransaction::TRANSACTIONTYPE, + 'resign' => DifferentialRevisionResignTransaction::TRANSACTIONTYPE, + ); + $action = $request->getValue('action'); - if ($action && ($action != 'comment') && ($action != 'none')) { + if (isset($modular_map[$action])) { $xactions[] = id(new DifferentialTransaction()) - ->setTransactionType(DifferentialTransaction::TYPE_ACTION) - ->setNewValue($action); + ->setTransactionType($modular_map[$action]) + ->setNewValue(true); + } else if ($action) { + switch ($action) { + case 'comment': + case 'none': + break; + default: + $xactions[] = id(new DifferentialTransaction()) + ->setTransactionType(DifferentialTransaction::TYPE_ACTION) + ->setNewValue($action); + break; + } } $content = $request->getValue('message'); diff --git a/src/applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php index 532d63680b..7039089b06 100644 --- a/src/applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php @@ -53,7 +53,7 @@ final class DifferentialCreateRevisionConduitAPIMethod } $revision = DifferentialRevision::initializeNewRevision($viewer); - $revision->attachReviewerStatus(array()); + $revision->attachReviewers(array()); $result = $this->applyFieldEdit( $request, diff --git a/src/applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php index d45fc22fd3..51225023ff 100644 --- a/src/applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php @@ -39,7 +39,7 @@ final class DifferentialGetCommitMessageConduitAPIMethod $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($id)) ->setViewer($viewer) - ->needReviewerStatus(true) + ->needReviewers(true) ->needActiveDiffs(true) ->executeOne(); if (!$revision) { diff --git a/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php index a158d86d9f..b05efb5c15 100644 --- a/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php @@ -42,15 +42,14 @@ final class DifferentialGetRevisionConduitAPIMethod $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($revision_id)) ->setViewer($request->getUser()) - ->needRelationships(true) - ->needReviewerStatus(true) + ->needReviewers(true) ->executeOne(); if (!$revision) { throw new ConduitException('ERR_BAD_REVISION'); } - $reviewer_phids = array_values($revision->getReviewers()); + $reviewer_phids = $revision->getReviewerPHIDs(); $diffs = id(new DifferentialDiffQuery()) ->setViewer($request->getUser()) diff --git a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php index 720361367a..3b88087a67 100644 --- a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php @@ -182,7 +182,7 @@ final class DifferentialQueryConduitAPIMethod $query->withBranches($branches); } - $query->needRelationships(true); + $query->needReviewers(true); $query->needCommitPHIDs(true); $query->needDiffIDs(true); $query->needActiveDiffs(true); @@ -194,6 +194,14 @@ final class DifferentialQueryConduitAPIMethod $request->getUser(), $revisions); + if ($revisions) { + $ccs = id(new PhabricatorSubscribersQuery()) + ->withObjectPHIDs(mpull($revisions, 'getPHID')) + ->execute(); + } else { + $ccs = array(); + } + $results = array(); foreach ($revisions as $revision) { $diff = $revision->getActiveDiff(); @@ -224,8 +232,8 @@ final class DifferentialQueryConduitAPIMethod 'activeDiffPHID' => $diff->getPHID(), 'diffs' => $revision->getDiffIDs(), 'commits' => $revision->getCommitPHIDs(), - 'reviewers' => array_values($revision->getReviewers()), - 'ccs' => array_values($revision->getCCPHIDs()), + 'reviewers' => $revision->getReviewerPHIDs(), + 'ccs' => idx($ccs, $phid, array()), 'hashes' => $revision->getHashes(), 'auxiliary' => idx($field_data, $phid, array()), 'repositoryPHID' => $diff->getRepositoryPHID(), diff --git a/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php index d45dc9749e..3ad5b07564 100644 --- a/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php @@ -57,7 +57,7 @@ final class DifferentialUpdateRevisionConduitAPIMethod $revision = id(new DifferentialRevisionQuery()) ->setViewer($request->getUser()) ->withIDs(array($request->getValue('id'))) - ->needReviewerStatus(true) + ->needReviewers(true) ->needActiveDiffs(true) ->requireCapabilities( array( diff --git a/src/applications/differential/constants/DifferentialAction.php b/src/applications/differential/constants/DifferentialAction.php index 6227d61dd0..a955ad9dd2 100644 --- a/src/applications/differential/constants/DifferentialAction.php +++ b/src/applications/differential/constants/DifferentialAction.php @@ -119,37 +119,4 @@ final class DifferentialAction extends Phobject { return $title; } - public static function getActionVerb($action) { - $verbs = array( - self::ACTION_COMMENT => pht('Comment'), - self::ACTION_ACCEPT => pht("Accept Revision \xE2\x9C\x94"), - self::ACTION_REJECT => pht("Request Changes \xE2\x9C\x98"), - self::ACTION_RETHINK => pht("Plan Changes \xE2\x9C\x98"), - self::ACTION_ABANDON => pht('Abandon Revision'), - self::ACTION_REQUEST => pht('Request Review'), - self::ACTION_RECLAIM => pht('Reclaim Revision'), - self::ACTION_RESIGN => pht('Resign as Reviewer'), - self::ACTION_ADDREVIEWERS => pht('Add Reviewers'), - self::ACTION_ADDCCS => pht('Add Subscribers'), - self::ACTION_CLOSE => pht('Close Revision'), - self::ACTION_CLAIM => pht('Commandeer Revision'), - self::ACTION_REOPEN => pht('Reopen'), - ); - - if (!empty($verbs[$action])) { - return $verbs[$action]; - } else { - return pht('brazenly %s', $action); - } - } - - public static function allowReviewers($action) { - if ($action == self::ACTION_ADDREVIEWERS || - $action == self::ACTION_REQUEST || - $action == self::ACTION_RESIGN) { - return true; - } - return false; - } - } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index d31da8fefc..e7d0a1cc87 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -17,8 +17,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($this->revisionID)) ->setViewer($viewer) - ->needRelationships(true) - ->needReviewerStatus(true) + ->needReviewers(true) ->needReviewerAuthority(true) ->executeOne(); if (!$revision) { @@ -103,9 +102,12 @@ final class DifferentialRevisionViewController extends DifferentialController { $this->loadDiffProperties($diffs); $props = $target_manual->getDiffProperties(); + $subscriber_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( + $revision->getPHID()); + $object_phids = array_merge( - $revision->getReviewers(), - $revision->getCCPHIDs(), + $revision->getReviewerPHIDs(), + $subscriber_phids, $revision->loadCommitPHIDs(), array( $revision->getAuthorPHID(), @@ -782,7 +784,7 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setLimit(10) ->needFlags(true) ->needDrafts(true) - ->needRelationships(true); + ->needReviewers(true); foreach ($path_map as $path => $path_id) { $query->withPath($repository->getID(), $path_id); diff --git a/src/applications/differential/customfield/DifferentialCustomField.php b/src/applications/differential/customfield/DifferentialCustomField.php index 0382034a0e..0b2d330775 100644 --- a/src/applications/differential/customfield/DifferentialCustomField.php +++ b/src/applications/differential/customfield/DifferentialCustomField.php @@ -70,6 +70,15 @@ abstract class DifferentialCustomField return array(); } + protected function getActiveDiff() { + $object = $this->getObject(); + try { + return $object->getActiveDiff(); + } catch (Exception $ex) { + return null; + } + } + public function getRequiredHandlePHIDsForRevisionHeaderWarnings() { return array(); } diff --git a/src/applications/differential/customfield/DifferentialProjectReviewersField.php b/src/applications/differential/customfield/DifferentialProjectReviewersField.php index 87ea0c34a3..4e0bb13986 100644 --- a/src/applications/differential/customfield/DifferentialProjectReviewersField.php +++ b/src/applications/differential/customfield/DifferentialProjectReviewersField.php @@ -42,14 +42,17 @@ final class DifferentialProjectReviewersField ->setReviewers($reviewers) ->setHandles($handles); - // TODO: Active diff stuff. + $diff = $this->getActiveDiff(); + if ($diff) { + $view->setActiveDiff($diff); + } return $view; } private function getProjectReviewers() { $reviewers = array(); - foreach ($this->getObject()->getReviewerStatus() as $reviewer) { + foreach ($this->getObject()->getReviewers() as $reviewer) { if (!$reviewer->isUser()) { $reviewers[] = $reviewer; } diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index ad96a22b88..7633bfb492 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -17,7 +17,7 @@ final class DifferentialReviewersField protected function readValueFromRevision( DifferentialRevision $revision) { - return $revision->getReviewerStatus(); + return $revision->getReviewers(); } public function shouldAppearInPropertyView() { @@ -43,14 +43,17 @@ final class DifferentialReviewersField ->setReviewers($reviewers) ->setHandles($handles); - // TODO: Active diff stuff. + $diff = $this->getActiveDiff(); + if ($diff) { + $view->setActiveDiff($diff); + } return $view; } private function getUserReviewers() { $reviewers = array(); - foreach ($this->getObject()->getReviewerStatus() as $reviewer) { + foreach ($this->getObject()->getReviewers() as $reviewer) { if ($reviewer->isUser()) { $reviewers[] = $reviewer; } diff --git a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php index 23ad165ca9..9583486c98 100644 --- a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php +++ b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php @@ -26,7 +26,7 @@ final class DifferentialDoorkeeperRevisionFeedStoryPublisher return id(new DifferentialRevisionQuery()) ->setViewer($this->getViewer()) ->withIDs(array($object->getID())) - ->needRelationships(true) + ->needReviewers(true) ->executeOne(); } @@ -37,7 +37,7 @@ final class DifferentialDoorkeeperRevisionFeedStoryPublisher public function getActiveUserPHIDs($object) { $status = $object->getStatus(); if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { - return $object->getReviewers(); + return $object->getReviewerPHIDs(); } else { return array(); } @@ -48,12 +48,13 @@ final class DifferentialDoorkeeperRevisionFeedStoryPublisher if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) { return array(); } else { - return $object->getReviewers(); + return $object->getReviewerPHIDs(); } } public function getCCUserPHIDs($object) { - return $object->getCCPHIDs(); + return PhabricatorSubscribersQuery::loadSubscribersForPHID( + $object->getPHID()); } public function getObjectTitle($object) { diff --git a/src/applications/differential/editor/DifferentialRevisionEditEngine.php b/src/applications/differential/editor/DifferentialRevisionEditEngine.php index 5c2fef275d..9aad031a7c 100644 --- a/src/applications/differential/editor/DifferentialRevisionEditEngine.php +++ b/src/applications/differential/editor/DifferentialRevisionEditEngine.php @@ -41,7 +41,7 @@ final class DifferentialRevisionEditEngine protected function newObjectQuery() { return id(new DifferentialRevisionQuery()) ->needActiveDiffs(true) - ->needReviewerStatus(true) + ->needReviewers(true) ->needReviewerAuthority(true); } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 85a44669c8..3f00f4b837 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -130,33 +130,6 @@ final class DifferentialTransactionEditor $action_type = $xaction->getNewValue(); switch ($action_type) { - case DifferentialAction::ACTION_ACCEPT: - case DifferentialAction::ACTION_REJECT: - if ($action_type == DifferentialAction::ACTION_ACCEPT) { - $new_status = DifferentialReviewerStatus::STATUS_ACCEPTED; - } else { - $new_status = DifferentialReviewerStatus::STATUS_REJECTED; - } - - $actor = $this->getActor(); - - // These transactions can cause effects in two ways: by altering the - // status of an existing reviewer; or by adding the actor as a new - // reviewer. - - $will_add_reviewer = true; - foreach ($object->getReviewerStatus() as $reviewer) { - if ($reviewer->hasAuthority($actor)) { - if ($reviewer->getStatus() != $new_status) { - return true; - } - } - if ($reviewer->getReviewerPHID() == $actor_phid) { - $will_add_reviwer = false; - } - } - - return $will_add_reviewer; case DifferentialAction::ACTION_CLOSE: return ($object->getStatus() != $status_closed); case DifferentialAction::ACTION_ABANDON: @@ -169,13 +142,6 @@ final class DifferentialTransactionEditor return ($object->getStatus() != $status_plan); case DifferentialAction::ACTION_REQUEST: return ($object->getStatus() != $status_review); - case DifferentialAction::ACTION_RESIGN: - foreach ($object->getReviewerStatus() as $reviewer) { - if ($reviewer->getReviewerPHID() == $actor_phid) { - return true; - } - } - return false; case DifferentialAction::ACTION_CLAIM: return ($actor_phid != $object->getAuthorPHID()); } @@ -222,12 +188,6 @@ final class DifferentialTransactionEditor return; case DifferentialTransaction::TYPE_ACTION: switch ($xaction->getNewValue()) { - case DifferentialAction::ACTION_RESIGN: - case DifferentialAction::ACTION_ACCEPT: - case DifferentialAction::ACTION_REJECT: - // These have no direct effects, and affect review status only - // indirectly by altering reviewers with TYPE_EDGE transactions. - return; case DifferentialAction::ACTION_ABANDON: $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); return; @@ -366,9 +326,9 @@ final class DifferentialTransactionEditor // actually change the diff text. $edits = array(); - foreach ($object->getReviewerStatus() as $reviewer) { + foreach ($object->getReviewers() as $reviewer) { if ($downgrade_rejects) { - if ($reviewer->getStatus() == $new_reject) { + if ($reviewer->getReviewerStatus() == $new_reject) { $edits[$reviewer->getReviewerPHID()] = array( 'data' => array( 'status' => $old_reject, @@ -378,7 +338,7 @@ final class DifferentialTransactionEditor } if ($downgrade_accepts) { - if ($reviewer->getStatus() == $new_accept) { + if ($reviewer->getReviewerStatus() == $new_accept) { $edits[$reviewer->getReviewerPHID()] = array( 'data' => array( 'status' => $old_accept, @@ -455,9 +415,9 @@ final class DifferentialTransactionEditor ); $edits = array(); - foreach ($object->getReviewerStatus() as $reviewer) { + foreach ($object->getReviewers() as $reviewer) { if ($reviewer->getReviewerPHID() == $actor_phid) { - if ($reviewer->getStatus() == $status_added) { + if ($reviewer->getReviewerStatus() == $status_added) { $edits[$actor_phid] = array( 'data' => $data, ); @@ -482,59 +442,9 @@ final class DifferentialTransactionEditor $action_type = $xaction->getNewValue(); switch ($action_type) { - case DifferentialAction::ACTION_ACCEPT: - case DifferentialAction::ACTION_REJECT: - if ($action_type == DifferentialAction::ACTION_ACCEPT) { - $data = array( - 'status' => DifferentialReviewerStatus::STATUS_ACCEPTED, - ); - } else { - $data = array( - 'status' => DifferentialReviewerStatus::STATUS_REJECTED, - ); - } - - $edits = array(); - - foreach ($object->getReviewerStatus() as $reviewer) { - if ($reviewer->hasAuthority($actor)) { - $edits[$reviewer->getReviewerPHID()] = array( - 'data' => $data, - ); - } - } - - // Also either update or add the actor themselves as a reviewer. - $edits[$actor_phid] = array( - 'data' => $data, - ); - - $results[] = id(new DifferentialTransaction()) - ->setTransactionType($type_edge) - ->setMetadataValue('edge:type', $edge_reviewer) - ->setIgnoreOnNoEffect(true) - ->setNewValue(array('+' => $edits)); - break; - case DifferentialAction::ACTION_CLAIM: $is_commandeer = true; break; - case DifferentialAction::ACTION_RESIGN: - // If the user is resigning, add a separate reviewer edit - // transaction which removes them as a reviewer. - - $results[] = id(new DifferentialTransaction()) - ->setTransactionType($type_edge) - ->setMetadataValue('edge:type', $edge_reviewer) - ->setIgnoreOnNoEffect(true) - ->setNewValue( - array( - '-' => array( - $actor_phid => $actor_phid, - ), - )); - - break; } break; } @@ -704,7 +614,7 @@ final class DifferentialTransactionEditor $new_revision = id(new DifferentialRevisionQuery()) ->setViewer($this->getActor()) - ->needReviewerStatus(true) + ->needReviewers(true) ->needActiveDiffs(true) ->withIDs(array($object->getID())) ->executeOne(); @@ -713,7 +623,7 @@ final class DifferentialTransactionEditor pht('Failed to load revision from transaction finalization.')); } - $object->attachReviewerStatus($new_revision->getReviewerStatus()); + $object->attachReviewers($new_revision->getReviewers()); $object->attachActiveDiff($new_revision->getActiveDiff()); $object->attachRepository($new_revision->getRepository()); @@ -735,7 +645,11 @@ final class DifferentialTransactionEditor $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; + $is_sticky_accept = PhabricatorEnv::getEnvConfig( + 'differential.sticky-accept'); + $old_status = $object->getStatus(); + $active_diff = $object->getActiveDiff(); switch ($old_status) { case $status_accepted: case $status_revision: @@ -751,11 +665,17 @@ final class DifferentialTransactionEditor $has_rejecting_reviewer = false; $has_rejecting_older_reviewer = false; $has_blocking_reviewer = false; - foreach ($object->getReviewerStatus() as $reviewer) { - $reviewer_status = $reviewer->getStatus(); + foreach ($object->getReviewers() as $reviewer) { + $reviewer_status = $reviewer->getReviewerStatus(); switch ($reviewer_status) { case DifferentialReviewerStatus::STATUS_REJECTED: - $has_rejecting_reviewer = true; + $action_phid = $reviewer->getLastActionDiffPHID(); + $active_phid = $active_diff->getPHID(); + $is_current = ($action_phid == $active_phid); + + if ($is_current) { + $has_rejecting_reviewer = true; + } break; case DifferentialReviewerStatus::STATUS_REJECTED_OLDER: $has_rejecting_older_reviewer = true; @@ -765,7 +685,13 @@ final class DifferentialTransactionEditor break; case DifferentialReviewerStatus::STATUS_ACCEPTED: if ($reviewer->isUser()) { - $has_accepting_user = true; + $action_phid = $reviewer->getLastActionDiffPHID(); + $active_phid = $active_diff->getPHID(); + $is_current = ($action_phid == $active_phid); + + if ($is_sticky_accept || $is_current) { + $has_accepting_user = true; + } } break; } @@ -808,6 +734,9 @@ final class DifferentialTransactionEditor break; } + + $this->markReviewerComments($object, $xactions); + return $xactions; } @@ -925,60 +854,6 @@ final class DifferentialTransactionEditor $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; switch ($action) { - case DifferentialAction::ACTION_ACCEPT: - if ($actor_is_author && !$allow_self_accept) { - return pht( - 'You can not accept this revision because you are the owner.'); - } - - if ($revision_status == $status_abandoned) { - return pht( - 'You can not accept this revision because it has been '. - 'abandoned.'); - } - - if ($revision_status == $status_closed) { - return pht( - 'You can not accept this revision because it has already been '. - 'closed.'); - } - - // TODO: It would be nice to make this generic at some point. - $signatures = DifferentialRequiredSignaturesField::loadForRevision( - $revision); - foreach ($signatures as $phid => $signed) { - if (!$signed) { - return pht( - 'You can not accept this revision because the author has '. - 'not signed all of the required legal documents.'); - } - } - - break; - - case DifferentialAction::ACTION_REJECT: - if ($actor_is_author) { - return pht('You can not request changes to your own revision.'); - } - - if ($revision_status == $status_abandoned) { - return pht( - 'You can not request changes to this revision because it has been '. - 'abandoned.'); - } - - if ($revision_status == $status_closed) { - return pht( - 'You can not request changes to this revision because it has '. - 'already been closed.'); - } - break; - - case DifferentialAction::ACTION_RESIGN: - // You can always resign from a revision if you're a reviewer. If you - // aren't, this is a no-op rather than invalid. - break; - case DifferentialAction::ACTION_CLAIM: // You can claim a revision if you're not the owner. If you are, this // is a no-op rather than invalid. @@ -1173,7 +1048,7 @@ final class DifferentialTransactionEditor protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); $phids[] = $object->getAuthorPHID(); - foreach ($object->getReviewerStatus() as $reviewer) { + foreach ($object->getReviewers() as $reviewer) { $phids[] = $reviewer->getReviewerPHID(); } return $phids; @@ -1648,7 +1523,7 @@ final class DifferentialTransactionEditor // and both are needlessly complex. This logic should live in the normal // transaction application pipeline. See T10967. - $reviewers = $object->getReviewerStatus(); + $reviewers = $object->getReviewers(); $reviewers = mpull($reviewers, null, 'getReviewerPHID'); if ($is_blocking) { @@ -1669,7 +1544,7 @@ final class DifferentialTransactionEditor // If we're applying a stronger status (usually, upgrading a reviewer // into a blocking reviewer), skip this check so we apply the change. $old_strength = DifferentialReviewerStatus::getStatusStrength( - $reviewers[$phid]->getStatus()); + $reviewers[$phid]->getReviewerStatus()); if ($old_strength <= $new_strength) { continue; } @@ -1687,22 +1562,21 @@ final class DifferentialTransactionEditor $value = array(); foreach ($phids as $phid) { - $value[$phid] = array( - 'data' => array( - 'status' => $new_status, - ), - ); + if ($is_blocking) { + $value[] = 'blocking('.$phid.')'; + } else { + $value[] = $phid; + } } - $edgetype_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; - $owners_phid = id(new PhabricatorOwnersApplication()) ->getPHID(); + $reviewers_type = DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE; + return $object->getApplicationTransactionTemplate() ->setAuthorPHID($owners_phid) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $edgetype_reviewer) + ->setTransactionType($reviewers_type) ->setNewValue( array( '+' => $value, @@ -1717,7 +1591,7 @@ final class DifferentialTransactionEditor ->setViewer($this->getActor()) ->withPHIDs(array($object->getPHID())) ->needActiveDiffs(true) - ->needReviewerStatus(true) + ->needReviewers(true) ->executeOne(); if (!$revision) { throw new Exception( @@ -1933,7 +1807,7 @@ final class DifferentialTransactionEditor // Reload to pick up the active diff and reviewer status. return id(new DifferentialRevisionQuery()) ->setViewer($this->getActor()) - ->needReviewerStatus(true) + ->needReviewers(true) ->needActiveDiffs(true) ->withIDs(array($object->getID())) ->executeOne(); @@ -1981,4 +1855,59 @@ final class DifferentialTransactionEditor ->setNewValue($edits); } + public function getActiveDiff($object) { + if ($this->getIsNewObject()) { + return null; + } else { + return $object->getActiveDiff(); + } + } + + /** + * When a reviewer makes a comment, mark the last revision they commented + * on. + * + * This allows us to show a hint to help authors and other reviewers quickly + * distinguish between reviewers who have participated in the discussion and + * reviewers who haven't been part of it. + */ + private function markReviewerComments($object, array $xactions) { + $acting_phid = $this->getActingAsPHID(); + if (!$acting_phid) { + return; + } + + $diff = $this->getActiveDiff($object); + if (!$diff) { + return; + } + + $has_comment = false; + foreach ($xactions as $xaction) { + if ($xaction->hasComment()) { + $has_comment = true; + break; + } + } + + if (!$has_comment) { + return; + } + + $reviewer_table = new DifferentialReviewer(); + $conn = $reviewer_table->establishConnection('w'); + + queryfx( + $conn, + 'UPDATE %T SET lastCommentDiffPHID = %s + WHERE revisionPHID = %s + AND reviewerPHID = %s', + $reviewer_table->getTableName(), + $diff->getPHID(), + $object->getPHID(), + $acting_phid); + } + + + } diff --git a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php index 6c178f44e0..3b92c2b4dd 100644 --- a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php +++ b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php @@ -25,7 +25,7 @@ final class DifferentialHovercardEngineExtension $revisions = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withPHIDs($phids) - ->needReviewerStatus(true) + ->needReviewers(true) ->execute(); $revisions = mpull($revisions, null, 'getPHID'); @@ -54,8 +54,7 @@ final class DifferentialHovercardEngineExtension pht('Author'), $viewer->renderHandle($revision->getAuthorPHID())); - $reviewer_phids = $revision->getReviewerStatus(); - $reviewer_phids = mpull($reviewer_phids, 'getReviewerPHID'); + $reviewer_phids = $revision->getReviewerPHIDs(); $hovercard->addField( pht('Reviewers'), diff --git a/src/applications/differential/field/DifferentialReviewedByCommitMessageField.php b/src/applications/differential/field/DifferentialReviewedByCommitMessageField.php index 523697b1e7..5ce8c722e8 100644 --- a/src/applications/differential/field/DifferentialReviewedByCommitMessageField.php +++ b/src/applications/differential/field/DifferentialReviewedByCommitMessageField.php @@ -37,10 +37,9 @@ final class DifferentialReviewedByCommitMessageField } $phids = array(); - foreach ($revision->getReviewerStatus() as $reviewer) { - switch ($reviewer->getStatus()) { + foreach ($revision->getReviewers() as $reviewer) { + switch ($reviewer->getReviewerStatus()) { case DifferentialReviewerStatus::STATUS_ACCEPTED: - case DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER: $phids[] = $reviewer->getReviewerPHID(); break; } diff --git a/src/applications/differential/field/DifferentialReviewersCommitMessageField.php b/src/applications/differential/field/DifferentialReviewersCommitMessageField.php index 0f110b6e3a..100897c28b 100644 --- a/src/applications/differential/field/DifferentialReviewersCommitMessageField.php +++ b/src/applications/differential/field/DifferentialReviewersCommitMessageField.php @@ -45,8 +45,8 @@ final class DifferentialReviewersCommitMessageField $status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING; $results = array(); - foreach ($revision->getReviewerStatus() as $reviewer) { - if ($reviewer->getStatus() == $status_blocking) { + foreach ($revision->getReviewers() as $reviewer) { + if ($reviewer->getReviewerStatus() == $status_blocking) { $suffixes = array('!' => '!'); } else { $suffixes = array(); diff --git a/src/applications/differential/herald/DifferentialReviewersHeraldAction.php b/src/applications/differential/herald/DifferentialReviewersHeraldAction.php index 3e98fdd92f..9537ad13d3 100644 --- a/src/applications/differential/herald/DifferentialReviewersHeraldAction.php +++ b/src/applications/differential/herald/DifferentialReviewersHeraldAction.php @@ -37,8 +37,7 @@ abstract class DifferentialReviewersHeraldAction } } - $reviewers = $object->getReviewerStatus(); - $reviewers = mpull($reviewers, null, 'getReviewerPHID'); + $reviewers = $object->getReviewers(); if ($is_blocking) { $new_status = DifferentialReviewerStatus::STATUS_BLOCKING; @@ -58,7 +57,7 @@ abstract class DifferentialReviewersHeraldAction // If we're applying a stronger status (usually, upgrading a reviewer // into a blocking reviewer), skip this check so we apply the change. $old_strength = DifferentialReviewerStatus::getStatusStrength( - $reviewers[$phid]->getStatus()); + $reviewers[$phid]->getReviewerStatus()); if ($old_strength <= $new_strength) { continue; } @@ -81,18 +80,17 @@ abstract class DifferentialReviewersHeraldAction $value = array(); foreach ($phids as $phid) { - $value[$phid] = array( - 'data' => array( - 'status' => $new_status, - ), - ); + if ($is_blocking) { + $value[] = 'blocking('.$phid.')'; + } else { + $value[] = $phid; + } } - $edgetype_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; + $reviewers_type = DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE; $xaction = $adapter->newTransaction() - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $edgetype_reviewer) + ->setTransactionType($reviewers_type) ->setNewValue( array( '+' => $value, diff --git a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php index 53fd62fe23..e20a4fd37d 100644 --- a/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialRevisionAdapter.php @@ -85,8 +85,7 @@ final class HeraldDifferentialRevisionAdapter $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($revision->getID())) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->needRelationships(true) - ->needReviewerStatus(true) + ->needReviewers(true) ->executeOne(); $object->revision = $revision; @@ -138,8 +137,7 @@ final class HeraldDifferentialRevisionAdapter } public function loadReviewers() { - $reviewers = $this->getObject()->getReviewerStatus(); - return mpull($reviewers, 'getReviewerPHID'); + return $this->getObject()->getReviewerPHIDs(); } diff --git a/src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php b/src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php index 2fe7c141f9..c37974fa2e 100644 --- a/src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php +++ b/src/applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php @@ -13,7 +13,7 @@ final class PhabricatorDifferentialRevisionTestDataGenerator $author = $this->loadPhabricatorUser(); $revision = DifferentialRevision::initializeNewRevision($author); - $revision->attachReviewerStatus(array()); + $revision->attachReviewers(array()); $revision->attachActiveDiff(null); // This could be a bit richer and more formal than it is. diff --git a/src/applications/differential/mail/DifferentialRevisionMailReceiver.php b/src/applications/differential/mail/DifferentialRevisionMailReceiver.php index 6b27c5a371..929ee72647 100644 --- a/src/applications/differential/mail/DifferentialRevisionMailReceiver.php +++ b/src/applications/differential/mail/DifferentialRevisionMailReceiver.php @@ -18,7 +18,7 @@ final class DifferentialRevisionMailReceiver return id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withIDs(array($id)) - ->needReviewerStatus(true) + ->needReviewers(true) ->needReviewerAuthority(true) ->needActiveDiffs(true) ->executeOne(); diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 5dff433e9c..701463e6a7 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -43,12 +43,11 @@ final class DifferentialRevisionQuery const ORDER_MODIFIED = 'order-modified'; const ORDER_CREATED = 'order-created'; - private $needRelationships = false; private $needActiveDiffs = false; private $needDiffIDs = false; private $needCommitPHIDs = false; private $needHashes = false; - private $needReviewerStatus = false; + private $needReviewers = false; private $needReviewerAuthority; private $needDrafts; private $needFlags; @@ -227,20 +226,6 @@ final class DifferentialRevisionQuery } - - /** - * Set whether or not the query will load and attach relationships. - * - * @param bool True to load and attach relationships. - * @return this - * @task config - */ - public function needRelationships($need_relationships) { - $this->needRelationships = $need_relationships; - return $this; - } - - /** * Set whether or not the query should load the active diff for each * revision. @@ -298,14 +283,14 @@ final class DifferentialRevisionQuery /** - * Set whether or not the query should load associated reviewer status. + * Set whether or not the query should load associated reviewers. * * @param bool True to load and attach reviewers. * @return this * @task config */ - public function needReviewerStatus($need_reviewer_status) { - $this->needReviewerStatus = $need_reviewer_status; + public function needReviewers($need_reviewers) { + $this->needReviewers = $need_reviewers; return $this; } @@ -425,10 +410,6 @@ final class DifferentialRevisionQuery $table = new DifferentialRevision(); $conn_r = $table->establishConnection('r'); - if ($this->needRelationships) { - $this->loadRelationships($conn_r, $revisions); - } - if ($this->needCommitPHIDs) { $this->loadCommitPHIDs($conn_r, $revisions); } @@ -448,7 +429,7 @@ final class DifferentialRevisionQuery $this->loadHashes($conn_r, $revisions); } - if ($this->needReviewerStatus || $this->needReviewerAuthority) { + if ($this->needReviewers || $this->needReviewerAuthority) { $this->loadReviewers($conn_r, $revisions); } @@ -605,11 +586,11 @@ final class DifferentialRevisionQuery if ($this->reviewers) { $joins[] = qsprintf( $conn_r, - 'JOIN %T e_reviewers ON e_reviewers.src = r.phid '. - 'AND e_reviewers.type = %s '. - 'AND e_reviewers.dst in (%Ls)', - PhabricatorEdgeConfig::TABLE_NAME_EDGE, - DifferentialRevisionHasReviewerEdgeType::EDGECONST, + 'JOIN %T reviewer ON reviewer.revisionPHID = r.phid + AND reviewer.reviewerStatus != %s + AND reviewer.reviewerPHID in (%Ls)', + id(new DifferentialReviewer())->getTableName(), + DifferentialReviewerStatus::STATUS_RESIGNED, $this->reviewers); } @@ -854,40 +835,6 @@ final class DifferentialRevisionQuery ); } - private function loadRelationships($conn_r, array $revisions) { - assert_instances_of($revisions, 'DifferentialRevision'); - - $type_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST; - $type_subscriber = PhabricatorObjectHasSubscriberEdgeType::EDGECONST; - - $edges = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs(mpull($revisions, 'getPHID')) - ->withEdgeTypes(array($type_reviewer, $type_subscriber)) - ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST) - ->execute(); - - $type_map = array( - DifferentialRevision::RELATION_REVIEWER => $type_reviewer, - DifferentialRevision::RELATION_SUBSCRIBED => $type_subscriber, - ); - - foreach ($revisions as $revision) { - $data = array(); - foreach ($type_map as $rel_type => $edge_type) { - $revision_edges = $edges[$revision->getPHID()][$edge_type]; - foreach ($revision_edges as $dst_phid => $edge_data) { - $data[] = array( - 'relation' => $rel_type, - 'objectPHID' => $dst_phid, - 'reasonPHID' => null, - ); - } - } - - $revision->attachRelationships($data); - } - } - private function loadCommitPHIDs($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $commit_phids = queryfx_all( @@ -972,21 +919,28 @@ final class DifferentialRevisionQuery } private function loadReviewers( - AphrontDatabaseConnection $conn_r, + AphrontDatabaseConnection $conn, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); - $edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST; - $edges = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs(mpull($revisions, 'getPHID')) - ->withEdgeTypes(array($edge_type)) - ->needEdgeData(true) - ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST) - ->execute(); + $reviewer_table = new DifferentialReviewer(); + $reviewer_rows = queryfx_all( + $conn, + 'SELECT * FROM %T WHERE revisionPHID IN (%Ls) + ORDER BY id ASC', + $reviewer_table->getTableName(), + mpull($revisions, 'getPHID')); + $reviewer_list = $reviewer_table->loadAllFromArray($reviewer_rows); + $reviewer_map = mgroup($reviewer_list, 'getRevisionPHID'); + + foreach ($reviewer_map as $key => $reviewers) { + $reviewer_map[$key] = mpull($reviewers, null, 'getReviewerPHID'); + } $viewer = $this->getViewer(); $viewer_phid = $viewer->getPHID(); + $allow_key = 'differential.allow-self-accept'; $allow_self = PhabricatorEnv::getEnvConfig($allow_key); @@ -994,18 +948,13 @@ final class DifferentialRevisionQuery if ($this->needReviewerAuthority && $viewer_phid) { $authority = $this->loadReviewerAuthority( $revisions, - $edges, + $reviewer_map, $allow_self); } foreach ($revisions as $revision) { - $revision_edges = $edges[$revision->getPHID()][$edge_type]; - $reviewers = array(); - foreach ($revision_edges as $reviewer_phid => $edge) { - $reviewer = new DifferentialReviewerProxy( - $reviewer_phid, - $edge['data']); - + $reviewers = idx($reviewer_map, $revision->getPHID(), array()); + foreach ($reviewers as $reviewer_phid => $reviewer) { if ($this->needReviewerAuthority) { if (!$viewer_phid) { // Logged-out users never have authority. @@ -1025,13 +974,13 @@ final class DifferentialRevisionQuery $reviewers[$reviewer_phid] = $reviewer; } - $revision->attachReviewerStatus($reviewers); + $revision->attachReviewers($reviewers); } } private function loadReviewerAuthority( array $revisions, - array $edges, + array $reviewers, $allow_self) { $revision_map = mpull($revisions, null, 'getPHID'); @@ -1044,10 +993,9 @@ final class DifferentialRevisionQuery $project_type = PhabricatorProjectProjectPHIDType::TYPECONST; $package_type = PhabricatorOwnersPackagePHIDType::TYPECONST; - $edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST; - foreach ($edges as $src => $types) { + foreach ($reviewers as $revision_phid => $reviewer_list) { if (!$allow_self) { - if ($revision_map[$src]->getAuthorPHID() == $viewer_phid) { + if ($revision_map[$revision_phid]->getAuthorPHID() == $viewer_phid) { // If self-review isn't permitted, the user will never have // authority over projects on revisions they authored because you // can't accept your own revisions, so we don't need to load any @@ -1055,14 +1003,14 @@ final class DifferentialRevisionQuery continue; } } - $edge_data = idx($types, $edge_type, array()); - foreach ($edge_data as $dst => $data) { - $phid_type = phid_get_type($dst); + + foreach ($reviewer_list as $reviewer_phid => $reviewer) { + $phid_type = phid_get_type($reviewer_phid); if ($phid_type == $project_type) { - $project_phids[] = $dst; + $project_phids[] = $reviewer_phid; } if ($phid_type == $package_type) { - $package_phids[] = $dst; + $package_phids[] = $reviewer_phid; } } } diff --git a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php index d37754227e..6d83d97a47 100644 --- a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php @@ -29,6 +29,14 @@ final class DifferentialRevisionRequiredActionResultBucket } $phids = array_fuse($phids); + // Before continuing, throw away any revisions which responsible users + // have explicitly resigned from. + + // The goal is to allow users to resign from revisions they don't want to + // review to get these revisions off their dashboard, even if there are + // other project or package reviewers which they have authority over. + $this->filterResigned($phids); + $groups = array(); $groups[] = $this->newGroup() @@ -229,4 +237,25 @@ final class DifferentialRevisionRequiredActionResultBucket return $results; } + private function filterResigned(array $phids) { + $resigned = array( + DifferentialReviewerStatus::STATUS_RESIGNED, + ); + $resigned = array_fuse($resigned); + + $objects = $this->getRevisionsNotAuthored($this->objects, $phids); + + $results = array(); + foreach ($objects as $key => $object) { + if (!$this->hasReviewersWithStatus($object, $phids, $resigned)) { + continue; + } + + $results[$key] = $object; + unset($this->objects[$key]); + } + + return $results; + } + } diff --git a/src/applications/differential/query/DifferentialRevisionResultBucket.php b/src/applications/differential/query/DifferentialRevisionResultBucket.php index 3cf7d1b7ff..762e2d97f4 100644 --- a/src/applications/differential/query/DifferentialRevisionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionResultBucket.php @@ -56,13 +56,13 @@ abstract class DifferentialRevisionResultBucket array $phids, array $statuses) { - foreach ($revision->getReviewerStatus() as $reviewer) { + foreach ($revision->getReviewers() as $reviewer) { $reviewer_phid = $reviewer->getReviewerPHID(); if (empty($phids[$reviewer_phid])) { continue; } - $status = $reviewer->getStatus(); + $status = $reviewer->getReviewerStatus(); if (empty($statuses[$status])) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index 9212860292..4248119cb9 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -19,8 +19,7 @@ final class DifferentialRevisionSearchEngine return id(new DifferentialRevisionQuery()) ->needFlags(true) ->needDrafts(true) - ->needRelationships(true) - ->needReviewerStatus(true); + ->needReviewers(true); } protected function buildQueryFromParameters(array $map) { @@ -163,10 +162,13 @@ final class DifferentialRevisionSearchEngine $groups = $bucket->newResultGroups($query, $revisions); foreach ($groups as $group) { - $views[] = id(clone $template) - ->setHeader($group->getName()) - ->setNoDataString($group->getNoDataString()) - ->setRevisions($group->getObjects()); + // Don't show groups in Dashboard Panels + if ($group->getObjects() || !$this->isPanelContext()) { + $views[] = id(clone $template) + ->setHeader($group->getName()) + ->setNoDataString($group->getNoDataString()) + ->setRevisions($group->getObjects()); + } } } catch (Exception $ex) { $this->addError($ex->getMessage()); @@ -177,6 +179,12 @@ final class DifferentialRevisionSearchEngine ->setHandles(array()); } + if (!$views) { + $views[] = id(new DifferentialRevisionListView()) + ->setUser($viewer) + ->setNoDataString(pht('No revisions found.')); + } + $phids = array_mergev(mpull($views, 'getRequiredHandlePHIDs')); if ($phids) { $handles = id(new PhabricatorHandleQuery()) diff --git a/src/applications/differential/search/DifferentialRevisionFulltextEngine.php b/src/applications/differential/search/DifferentialRevisionFulltextEngine.php index 60d267fe71..45639edbbe 100644 --- a/src/applications/differential/search/DifferentialRevisionFulltextEngine.php +++ b/src/applications/differential/search/DifferentialRevisionFulltextEngine.php @@ -10,11 +10,11 @@ final class DifferentialRevisionFulltextEngine $revision = id(new DifferentialRevisionQuery()) ->setViewer($this->getViewer()) ->withPHIDs(array($object->getPHID())) - ->needReviewerStatus(true) + ->needReviewers(true) ->executeOne(); // TODO: This isn't very clean, but custom fields currently rely on it. - $object->attachReviewerStatus($revision->getReviewerStatus()); + $object->attachReviewers($revision->getReviewers()); $document->setDocumentTitle($revision->getTitle()); @@ -36,8 +36,9 @@ final class DifferentialRevisionFulltextEngine // owner is the author (e.g., accepted, rejected, closed). $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; if ($revision->getStatus() == $status_review) { - $reviewers = $revision->getReviewerStatus(); - $reviewers = mpull($reviewers, 'getReviewerPHID', 'getReviewerPHID'); + $reviewers = $revision->getReviewerPHIDs(); + $reviewers = array_fuse($reviewers); + if ($reviewers) { foreach ($reviewers as $phid) { $document->addRelationship( diff --git a/src/applications/differential/storage/DifferentialReviewer.php b/src/applications/differential/storage/DifferentialReviewer.php index 72317a0a4c..d43f533b5c 100644 --- a/src/applications/differential/storage/DifferentialReviewer.php +++ b/src/applications/differential/storage/DifferentialReviewer.php @@ -6,19 +6,81 @@ final class DifferentialReviewer protected $revisionPHID; protected $reviewerPHID; protected $reviewerStatus; + protected $lastActionDiffPHID; + protected $lastCommentDiffPHID; + protected $lastActorPHID; + + private $authority = array(); protected function getConfiguration() { return array( self::CONFIG_COLUMN_SCHEMA => array( 'reviewerStatus' => 'text64', + 'lastActionDiffPHID' => 'phid?', + 'lastCommentDiffPHID' => 'phid?', + 'lastActorPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_revision' => array( 'columns' => array('revisionPHID', 'reviewerPHID'), 'unique' => true, ), + 'key_reviewer' => array( + 'columns' => array('reviewerPHID', 'revisionPHID'), + ), ), ) + parent::getConfiguration(); } + public function isUser() { + $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; + return (phid_get_type($this->getReviewerPHID()) == $user_type); + } + + public function attachAuthority(PhabricatorUser $user, $has_authority) { + $this->authority[$user->getCacheFragment()] = $has_authority; + return $this; + } + + public function hasAuthority(PhabricatorUser $viewer) { + $cache_fragment = $viewer->getCacheFragment(); + return $this->assertAttachedKey($this->authority, $cache_fragment); + } + + public function isResigned() { + $status_resigned = DifferentialReviewerStatus::STATUS_RESIGNED; + return ($this->getReviewerStatus() == $status_resigned); + } + + public function isAccepted($diff_phid) { + $status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED; + + if ($this->getReviewerStatus() != $status_accepted) { + return false; + } + + if (!$diff_phid) { + return true; + } + + $action_phid = $this->getLastActionDiffPHID(); + + if (!$action_phid) { + return true; + } + + if ($action_phid == $diff_phid) { + return true; + } + + $sticky_key = 'differential.sticky-accept'; + $is_sticky = PhabricatorEnv::getEnvConfig($sticky_key); + + if ($is_sticky) { + return true; + } + + return false; + } + } diff --git a/src/applications/differential/storage/DifferentialReviewerProxy.php b/src/applications/differential/storage/DifferentialReviewerProxy.php deleted file mode 100644 index bf38ee27e3..0000000000 --- a/src/applications/differential/storage/DifferentialReviewerProxy.php +++ /dev/null @@ -1,56 +0,0 @@ -reviewerPHID = $reviewer_phid; - $this->status = idx($edge_data, 'status'); - $this->diffID = idx($edge_data, 'diff'); - } - - public function getReviewerPHID() { - return $this->reviewerPHID; - } - - public function getStatus() { - return $this->status; - } - - public function getDiffID() { - return $this->diffID; - } - - public function isUser() { - $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; - return (phid_get_type($this->getReviewerPHID()) == $user_type); - } - - public function attachAuthority(PhabricatorUser $user, $has_authority) { - $this->authority[$user->getPHID()] = $has_authority; - return $this; - } - - public function hasAuthority(PhabricatorUser $viewer) { - // It would be nice to use assertAttachedKey() here, but we don't extend - // PhabricatorLiskDAO, and faking that seems sketchy. - - $viewer_phid = $viewer->getPHID(); - if (!array_key_exists($viewer_phid, $this->authority)) { - throw new Exception(pht('You must %s first!', 'attachAuthority()')); - } - return $this->authority[$viewer_phid]; - } - - public function getEdgeData() { - return array( - 'status' => $this->status, - 'diffID' => $this->diffID, - ); - } - -} diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index df43043d6d..7189c5f5b4 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -38,7 +38,6 @@ final class DifferentialRevision extends DifferentialDAO protected $editPolicy = PhabricatorPolicies::POLICY_USER; protected $properties = array(); - private $relationships = self::ATTACHABLE; private $commits = self::ATTACHABLE; private $activeDiff = self::ATTACHABLE; private $diffIDs = self::ATTACHABLE; @@ -69,10 +68,9 @@ final class DifferentialRevision extends DifferentialDAO return id(new DifferentialRevision()) ->setViewPolicy($view_policy) ->setAuthorPHID($actor->getPHID()) - ->attachRelationships(array()) ->attachRepository(null) ->attachActiveDiff(null) - ->attachReviewerStatus(array()) + ->attachReviewers(array()) ->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); } @@ -238,73 +236,6 @@ final class DifferentialRevision extends DifferentialDAO return parent::save(); } - public function loadRelationships() { - if (!$this->getID()) { - $this->relationships = array(); - return; - } - - $data = array(); - - $subscriber_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $this->getPHID(), - PhabricatorObjectHasSubscriberEdgeType::EDGECONST); - $subscriber_phids = array_reverse($subscriber_phids); - foreach ($subscriber_phids as $phid) { - $data[] = array( - 'relation' => self::RELATION_SUBSCRIBED, - 'objectPHID' => $phid, - 'reasonPHID' => null, - ); - } - - $reviewer_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $this->getPHID(), - DifferentialRevisionHasReviewerEdgeType::EDGECONST); - $reviewer_phids = array_reverse($reviewer_phids); - foreach ($reviewer_phids as $phid) { - $data[] = array( - 'relation' => self::RELATION_REVIEWER, - 'objectPHID' => $phid, - 'reasonPHID' => null, - ); - } - - return $this->attachRelationships($data); - } - - public function attachRelationships(array $relationships) { - $this->relationships = igroup($relationships, 'relation'); - return $this; - } - - public function getReviewers() { - return $this->getRelatedPHIDs(self::RELATION_REVIEWER); - } - - public function getCCPHIDs() { - return $this->getRelatedPHIDs(self::RELATION_SUBSCRIBED); - } - - private function getRelatedPHIDs($relation) { - $this->assertAttached($this->relationships); - - return ipull($this->getRawRelations($relation), 'objectPHID'); - } - - public function getRawRelations($relation) { - return idx($this->relationships, $relation, array()); - } - - public function getPrimaryReviewer() { - $reviewers = $this->getReviewers(); - $last = $this->lastReviewerPHID; - if (!$last || !in_array($last, $reviewers)) { - return head($this->getReviewers()); - } - return $last; - } - public function getHashes() { return $this->assertAttached($this->hashes); } @@ -401,26 +332,31 @@ final class DifferentialRevision extends DifferentialDAO ); } - public function getReviewerStatus() { + public function getReviewers() { return $this->assertAttached($this->reviewerStatus); } - public function attachReviewerStatus(array $reviewers) { - assert_instances_of($reviewers, 'DifferentialReviewerProxy'); - + public function attachReviewers(array $reviewers) { + assert_instances_of($reviewers, 'DifferentialReviewer'); + $reviewers = mpull($reviewers, null, 'getReviewerPHID'); $this->reviewerStatus = $reviewers; return $this; } + public function getReviewerPHIDs() { + $reviewers = $this->getReviewers(); + return mpull($reviewers, 'getReviewerPHID'); + } + public function getReviewerPHIDsForEdit() { - $reviewers = $this->getReviewerStatus(); + $reviewers = $this->getReviewers(); $status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING; $value = array(); foreach ($reviewers as $reviewer) { $phid = $reviewer->getReviewerPHID(); - if ($reviewer->getStatus() == $status_blocking) { + if ($reviewer->getReviewerStatus() == $status_blocking) { $value[] = 'blocking('.$phid.')'; } else { $value[] = $phid; @@ -543,11 +479,11 @@ final class DifferentialRevision extends DifferentialDAO $reviewers = id(new DifferentialRevisionQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($this->getPHID())) - ->needReviewerStatus(true) + ->needReviewers(true) ->executeOne() - ->getReviewerStatus(); + ->getReviewers(); } else { - $reviewers = $this->getReviewerStatus(); + $reviewers = $this->getReviewers(); } foreach ($reviewers as $reviewer) { diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php index 7342f35498..868a24ebb0 100644 --- a/src/applications/differential/storage/DifferentialTransaction.php +++ b/src/applications/differential/storage/DifferentialTransaction.php @@ -212,13 +212,6 @@ final class DifferentialTransaction $tags[] = self::MAILTAG_UPDATED; } break; - case PhabricatorTransactions::TYPE_EDGE: - switch ($this->getMetadataValue('edge:type')) { - case DifferentialRevisionHasReviewerEdgeType::EDGECONST: - $tags[] = self::MAILTAG_REVIEWERS; - break; - } - break; case PhabricatorTransactions::TYPE_COMMENT: case self::TYPE_INLINE: $tags[] = self::MAILTAG_COMMENT; @@ -598,14 +591,6 @@ final class DifferentialTransaction public function getNoEffectDescription() { switch ($this->getTransactionType()) { - case PhabricatorTransactions::TYPE_EDGE: - switch ($this->getMetadataValue('edge:type')) { - case DifferentialRevisionHasReviewerEdgeType::EDGECONST: - return pht( - 'The reviewers you are trying to add are already reviewing '. - 'this revision.'); - } - break; case self::TYPE_ACTION: switch ($this->getNewValue()) { case DifferentialAction::ACTION_CLOSE: @@ -624,18 +609,10 @@ final class DifferentialTransaction return pht('This revision already requires changes.'); case DifferentialAction::ACTION_REQUEST: return pht('Review is already requested for this revision.'); - case DifferentialAction::ACTION_RESIGN: - return pht( - 'You can not resign from this revision because you are not '. - 'a reviewer.'); case DifferentialAction::ACTION_CLAIM: return pht( 'You can not commandeer this revision because you already own '. 'it.'); - case DifferentialAction::ACTION_ACCEPT: - return pht('You have already accepted this revision.'); - case DifferentialAction::ACTION_REJECT: - return pht('You have already requested changes to this revision.'); } break; } diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index fd558d85f1..291f859d08 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -7,7 +7,7 @@ final class DifferentialReviewersView extends AphrontView { private $diff; public function setReviewers(array $reviewers) { - assert_instances_of($reviewers, 'DifferentialReviewerProxy'); + assert_instances_of($reviewers, 'DifferentialReviewer'); $this->reviewers = $reviewers; return $this; } @@ -25,53 +25,74 @@ final class DifferentialReviewersView extends AphrontView { public function render() { $viewer = $this->getUser(); + $reviewers = $this->reviewers; $view = new PHUIStatusListView(); - foreach ($this->reviewers as $reviewer) { + + // Move resigned reviewers to the bottom. + $head = array(); + $tail = array(); + foreach ($reviewers as $key => $reviewer) { + if ($reviewer->isResigned()) { + $tail[$key] = $reviewer; + } else { + $head[$key] = $reviewer; + } + } + + $reviewers = $head + $tail; + foreach ($reviewers as $reviewer) { $phid = $reviewer->getReviewerPHID(); $handle = $this->handles[$phid]; - // If we're missing either the diff or action information for the - // reviewer, render information as current. - $is_current = (!$this->diff) || - (!$reviewer->getDiffID()) || - ($this->diff->getID() == $reviewer->getDiffID()); + $action_phid = $reviewer->getLastActionDiffPHID(); + $is_current_action = $this->isCurrent($action_phid); + + $comment_phid = $reviewer->getLastCommentDiffPHID(); + $is_current_comment = $this->isCurrent($comment_phid); $item = new PHUIStatusItemView(); $item->setHighlighted($reviewer->hasAuthority($viewer)); - switch ($reviewer->getStatus()) { + switch ($reviewer->getReviewerStatus()) { case DifferentialReviewerStatus::STATUS_ADDED: - $item->setIcon( - PHUIStatusItemView::ICON_OPEN, - 'bluegrey', - pht('Review Requested')); + if ($comment_phid) { + if ($is_current_comment) { + $item->setIcon( + 'fa-comment', + 'blue', + pht('Commented')); + } else { + $item->setIcon( + 'fa-comment-o', + 'bluegrey', + pht('Commented Previously')); + } + } else { + $item->setIcon( + PHUIStatusItemView::ICON_OPEN, + 'bluegrey', + pht('Review Requested')); + } break; case DifferentialReviewerStatus::STATUS_ACCEPTED: - if ($is_current) { + if ($is_current_action) { $item->setIcon( PHUIStatusItemView::ICON_ACCEPT, 'green', pht('Accepted')); } else { $item->setIcon( - PHUIStatusItemView::ICON_ACCEPT, + 'fa-check-circle-o', 'bluegrey', pht('Accepted Prior Diff')); } break; - case DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER: - $item->setIcon( - 'fa-check-circle-o', - 'bluegrey', - pht('Accepted Prior Diff')); - break; - case DifferentialReviewerStatus::STATUS_REJECTED: - if ($is_current) { + if ($is_current_action) { $item->setIcon( PHUIStatusItemView::ICON_REJECT, 'red', @@ -84,27 +105,6 @@ final class DifferentialReviewersView extends AphrontView { } break; - case DifferentialReviewerStatus::STATUS_REJECTED_OLDER: - $item->setIcon( - 'fa-times-circle-o', - 'bluegrey', - pht('Rejected Prior Diff')); - break; - - case DifferentialReviewerStatus::STATUS_COMMENTED: - if ($is_current) { - $item->setIcon( - 'fa-question-circle', - 'blue', - pht('Commented')); - } else { - $item->setIcon( - 'fa-question-circle-o', - 'bluegrey', - pht('Commented Previously')); - } - break; - case DifferentialReviewerStatus::STATUS_BLOCKING: $item->setIcon( PHUIStatusItemView::ICON_MINUS, @@ -112,11 +112,18 @@ final class DifferentialReviewersView extends AphrontView { pht('Blocking Review')); break; + case DifferentialReviewerStatus::STATUS_RESIGNED: + $item->setIcon( + 'fa-times', + 'grey', + pht('Resigned')); + break; + default: $item->setIcon( PHUIStatusItemView::ICON_QUESTION, 'bluegrey', - pht('%s?', $reviewer->getStatus())); + pht('%s?', $reviewer->getReviewerStatus())); break; } @@ -128,4 +135,26 @@ final class DifferentialReviewersView extends AphrontView { return $view; } + private function isCurrent($action_phid) { + if (!$this->diff) { + echo "A\n"; + return true; + } + + if (!$action_phid) { + return true; + } + + $diff_phid = $this->diff->getPHID(); + if (!$diff_phid) { + return true; + } + + if ($diff_phid == $action_phid) { + return true; + } + + return false; + } + } diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 811e74fd65..5aacafbb69 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -52,10 +52,7 @@ final class DifferentialRevisionListView extends AphrontView { $phids = array(); foreach ($this->revisions as $revision) { $phids[] = array($revision->getAuthorPHID()); - - // TODO: Switch to getReviewerStatus(), but not all callers pass us - // revisions with this data loaded. - $phids[] = $revision->getReviewers(); + $phids[] = $revision->getReviewerPHIDs(); } return array_mergev($phids); } @@ -132,8 +129,7 @@ final class DifferentialRevisionListView extends AphrontView { } $reviewers = array(); - // TODO: As above, this should be based on `getReviewerStatus()`. - foreach ($revision->getReviewers() as $reviewer) { + foreach ($revision->getReviewerPHIDs() as $reviewer) { $reviewers[] = $this->handles[$reviewer]->renderLink(); } if (!$reviewers) { diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index 3a64a44767..4f6bedd698 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -48,6 +48,72 @@ final class DifferentialRevisionAcceptTransaction return pht('Accept a revision.'); } + protected function getActionOptions( + PhabricatorUser $viewer, + DifferentialRevision $revision) { + + $reviewers = $revision->getReviewers(); + + $options = array(); + $value = array(); + + // Put the viewer's user reviewer first, if it exists, so that "Accept as + // yourself" is always at the top. + $head = array(); + $tail = array(); + foreach ($reviewers as $key => $reviewer) { + if ($reviewer->isUser()) { + $head[$key] = $reviewer; + } else { + $tail[$key] = $reviewer; + } + } + $reviewers = $head + $tail; + + $diff_phid = $this->getActiveDiffPHID($revision); + $reviewer_phids = array(); + + // If the viewer isn't a reviewer, add them to the list of options first. + // This happens when you navigate to some revision you aren't involved in: + // you can accept and become a reviewer. + + $viewer_phid = $viewer->getPHID(); + if ($viewer_phid) { + if (!isset($reviewers[$viewer_phid])) { + $reviewer_phids[$viewer_phid] = $viewer_phid; + } + } + + foreach ($reviewers as $reviewer) { + if (!$reviewer->hasAuthority($viewer)) { + // If the viewer doesn't have authority to act on behalf of a reviewer, + // don't include that reviewer as an option. + continue; + } + + if ($reviewer->isAccepted($diff_phid)) { + // If a reviewer is already in a full "accepted" state, don't + // include that reviewer as an option. + continue; + } + + $reviewer_phid = $reviewer->getReviewerPHID(); + $reviewer_phids[$reviewer_phid] = $reviewer_phid; + } + + $handles = $viewer->loadHandles($reviewer_phids); + + foreach ($reviewer_phids as $reviewer_phid) { + $options[$reviewer_phid] = pht( + 'Accept as %s', + $viewer->renderHandle($reviewer_phid)); + + $value[] = $reviewer_phid; + } + + return array($options, $value); + } + public function generateOldValue($object) { $actor = $this->getActor(); return $this->isViewerFullyAccepted($object, $actor); @@ -87,10 +153,39 @@ final class DifferentialRevisionAcceptTransaction } } + protected function validateOptionValue($object, $actor, array $value) { + if (!$value) { + throw new Exception( + pht( + 'When accepting a revision, you must accept on behalf of at '. + 'least one reviewer.')); + } + + list($options) = $this->getActionOptions($actor, $object); + foreach ($value as $phid) { + if (!isset($options[$phid])) { + throw new Exception( + pht( + 'Reviewer "%s" is not a valid reviewer which you have authority '. + 'to accept on behalf of.', + $phid)); + } + } + } + public function getTitle() { - return pht( - '%s accepted this revision.', - $this->renderAuthor()); + $new = $this->getNewValue(); + if (is_array($new) && $new) { + return pht( + '%s accepted this revision as %s reviewer(s): %s.', + $this->renderAuthor(), + phutil_count($new), + $this->renderHandleList($new)); + } else { + return pht( + '%s accepted this revision.', + $this->renderAuthor()); + } } public function getTitleForFeed() { diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php index 82198083f8..f232398533 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -19,6 +19,10 @@ abstract class DifferentialRevisionActionTransaction abstract protected function validateAction($object, PhabricatorUser $viewer); abstract protected function getRevisionActionLabel(); + protected function validateOptionValue($object, $actor, array $value) { + return null; + } + public function getCommandKeyword() { return null; } @@ -70,6 +74,15 @@ abstract class DifferentialRevisionActionTransaction return ($viewer->getPHID() === $revision->getAuthorPHID()); } + protected function getActionOptions( + PhabricatorUser $viewer, + DifferentialRevision $revision) { + return array( + array(), + null, + ); + } + public function newEditField( DifferentialRevision $revision, PhabricatorUser $viewer) { @@ -107,6 +120,12 @@ abstract class DifferentialRevisionActionTransaction // It's not clear that these combinations are actually useful, so just // keep things simple for now. $field->setActionConflictKey('revision.action'); + + list($options, $value) = $this->getActionOptions($viewer, $revision); + if (count($options) > 1) { + $field->setOptions($options); + $field->setValue($value); + } } } @@ -129,6 +148,20 @@ abstract class DifferentialRevisionActionTransaction $errors[] = $this->newInvalidError( $action_exception->getMessage(), $xaction); + continue; + } + + $new = $xaction->getNewValue(); + if (!is_array($new)) { + continue; + } + + try { + $this->validateOptionValue($object, $actor, $new); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + $ex->getMessage(), + $xaction); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index 5b75d7753f..c7805ad35f 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -44,7 +44,9 @@ final class DifferentialRevisionResignTransaction public function generateOldValue($object) { $actor = $this->getActor(); - return !$this->isViewerAnyReviewer($object, $actor); + $resigned = DifferentialReviewerStatus::STATUS_RESIGNED; + + return ($this->getViewerReviewerStatus($object, $actor) == $resigned); } public function applyExternalEffects($object, $value) { @@ -61,12 +63,19 @@ final class DifferentialRevisionResignTransaction 'been closed. You can only resign from open revisions.')); } - if (!$this->isViewerAnyReviewer($object, $viewer)) { + $resigned = DifferentialReviewerStatus::STATUS_RESIGNED; + if ($this->getViewerReviewerStatus($object, $viewer) == $resigned) { + throw new Exception( + pht( + 'You can not resign from this revision because you have already '. + 'resigned.')); + } + + if (!$this->isViewerAnyAuthority($object, $viewer)) { throw new Exception( pht( 'You can not resign from this revision because you are not a '. - 'reviewer. You can only resign from revisions where you are a '. - 'reviewer.')); + 'reviewer, and do not have authority over any reviewer.')); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php index 9ac731fea9..3db289470a 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php @@ -7,12 +7,46 @@ abstract class DifferentialRevisionReviewTransaction return DifferentialRevisionEditEngine::ACTIONGROUP_REVIEW; } + public function generateNewValue($object, $value) { + if (!is_array($value)) { + return true; + } + + // If the list of options is the same as the default list, just treat this + // as a "take the default action" transaction. + $viewer = $this->getActor(); + list($options, $default) = $this->getActionOptions($viewer, $object); + + sort($default); + sort($value); + + if ($default === $value) { + return true; + } + + return $value; + } + protected function isViewerAnyReviewer( DifferentialRevision $revision, PhabricatorUser $viewer) { return ($this->getViewerReviewerStatus($revision, $viewer) !== null); } + protected function isViewerAnyAuthority( + DifferentialRevision $revision, + PhabricatorUser $viewer) { + + $reviewers = $revision->getReviewers(); + foreach ($revision->getReviewers() as $reviewer) { + if ($reviewer->hasAuthority($viewer)) { + return true; + } + } + + return false; + } + protected function isViewerFullyAccepted( DifferentialRevision $revision, PhabricatorUser $viewer) { @@ -21,7 +55,8 @@ abstract class DifferentialRevisionReviewTransaction $viewer, array( DifferentialReviewerStatus::STATUS_ACCEPTED, - )); + ), + true); } protected function isViewerFullyRejected( @@ -32,7 +67,8 @@ abstract class DifferentialRevisionReviewTransaction $viewer, array( DifferentialReviewerStatus::STATUS_REJECTED, - )); + ), + true); } protected function getViewerReviewerStatus( @@ -43,12 +79,12 @@ abstract class DifferentialRevisionReviewTransaction return null; } - foreach ($revision->getReviewerStatus() as $reviewer) { + foreach ($revision->getReviewers() as $reviewer) { if ($reviewer->getReviewerPHID() != $viewer->getPHID()) { continue; } - return $reviewer->getStatus(); + return $reviewer->getReviewerStatus(); } return null; @@ -57,7 +93,8 @@ abstract class DifferentialRevisionReviewTransaction protected function isViewerReviewerStatusFullyAmong( DifferentialRevision $revision, PhabricatorUser $viewer, - array $status_list) { + array $status_list, + $require_current) { // If the user themselves is not a reviewer, the reviews they have // authority over can not all be in any set of states since their own @@ -67,18 +104,26 @@ abstract class DifferentialRevisionReviewTransaction return false; } + $active_phid = $this->getActiveDiffPHID($revision); + // Otherwise, check that all reviews they have authority over are in // the desired set of states. $status_map = array_fuse($status_list); - foreach ($revision->getReviewerStatus() as $reviewer) { + foreach ($revision->getReviewers() as $reviewer) { if (!$reviewer->hasAuthority($viewer)) { continue; } - $status = $reviewer->getStatus(); + $status = $reviewer->getReviewerStatus(); if (!isset($status_map[$status])) { return false; } + + if ($require_current) { + if ($reviewer->getLastActionDiffPHID() != $active_phid) { + return false; + } + } } return true; @@ -97,7 +142,7 @@ abstract class DifferentialRevisionReviewTransaction // yourself. $with_authority = ($status != DifferentialReviewerStatus::STATUS_RESIGNED); if ($with_authority) { - foreach ($revision->getReviewerStatus() as $reviewer) { + foreach ($revision->getReviewers() as $reviewer) { if ($reviewer->hasAuthority($viewer)) { $map[$reviewer->getReviewerPHID()] = $status; } @@ -107,6 +152,12 @@ abstract class DifferentialRevisionReviewTransaction // In all cases, you affect yourself. $map[$viewer->getPHID()] = $status; + // If the user has submitted a specific list of reviewers to act as (by + // unchecking some checkboxes under "Accept"), only affect those reviewers. + if (is_array($value)) { + $map = array_select_keys($map, $value); + } + // Convert reviewer statuses into edge data. foreach ($map as $reviewer_phid => $reviewer_status) { $map[$reviewer_phid] = array( @@ -138,6 +189,13 @@ abstract class DifferentialRevisionReviewTransaction // Now, do the new write. if ($map) { + $diff = $this->getEditor()->getActiveDiff($revision); + if ($diff) { + $diff_phid = $diff->getPHID(); + } else { + $diff_phid = null; + } + $table = new DifferentialReviewer(); $reviewers = $table->loadAllWhere( @@ -154,18 +212,21 @@ abstract class DifferentialRevisionReviewTransaction ->setReviewerPHID($dst_phid); } + $old_status = $reviewer->getReviewerStatus(); $reviewer->setReviewerStatus($status); - if ($status == DifferentialReviewerStatus::STATUS_RESIGNED) { - if ($reviewer->getID()) { - $reviewer->delete(); - } - } else { - try { - $reviewer->save(); - } catch (AphrontDuplicateKeyQueryException $ex) { - // At least for now, just ignore it if we lost a race. - } + if ($diff_phid) { + $reviewer->setLastActionDiffPHID($diff_phid); + } + + if ($old_status !== $status) { + $reviewer->setLastActorPHID($this->getActingAsPHID()); + } + + try { + $reviewer->save(); + } catch (AphrontDuplicateKeyQueryException $ex) { + // At least for now, just ignore it if we lost a race. } } } diff --git a/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php index c4112a4516..a7122f9de4 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php @@ -7,8 +7,8 @@ final class DifferentialRevisionReviewersTransaction const EDITKEY = 'reviewers'; public function generateOldValue($object) { - $reviewers = $object->getReviewerStatus(); - $reviewers = mpull($reviewers, 'getStatus', 'getReviewerPHID'); + $reviewers = $object->getReviewers(); + $reviewers = mpull($reviewers, 'getReviewerStatus', 'getReviewerPHID'); return $reviewers; } diff --git a/src/applications/differential/xaction/DifferentialRevisionTransactionType.php b/src/applications/differential/xaction/DifferentialRevisionTransactionType.php index 59b0c7510d..79435bee78 100644 --- a/src/applications/differential/xaction/DifferentialRevisionTransactionType.php +++ b/src/applications/differential/xaction/DifferentialRevisionTransactionType.php @@ -57,4 +57,16 @@ abstract class DifferentialRevisionTransactionType $xaction); } + protected function getActiveDiffPHID(DifferentialRevision $revision) { + try { + $diff = $revision->getActiveDiff(); + if (!$diff) { + return null; + } + return $diff->getPHID(); + } catch (Exception $ex) { + return null; + } + } + } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index aa8e370e25..b29dcf3d1e 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1765,7 +1765,7 @@ final class DiffusionBrowseController extends DiffusionController { ->withUpdatedEpochBetween($recent, null) ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) ->setLimit(10) - ->needRelationships(true) + ->needReviewers(true) ->needFlags(true) ->needDrafts(true) ->execute(); diff --git a/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php index 684f5d75d6..50900ede78 100644 --- a/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php @@ -20,7 +20,7 @@ final class DiffusionCommitRevisionReviewersHeraldField return array(); } - return $revision->getReviewers(); + return $revision->getReviewerPHIDs(); } protected function getHeraldFieldStandardType() { diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php index bce6b5b517..936126ba89 100644 --- a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php @@ -20,8 +20,8 @@ final class DiffusionPreCommitContentRevisionReviewersHeraldField return array(); } - return $revision->getReviewers(); - } + return $revision->getReviewerPHIDs(); + } protected function getHeraldFieldStandardType() { return self::STANDARD_PHID_LIST; diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 9d63b9db3d..4687028418 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -190,8 +190,7 @@ final class HeraldCommitAdapter $revision = id(new DifferentialRevisionQuery()) ->withIDs(array($revision_id)) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->needRelationships(true) - ->needReviewerStatus(true) + ->needReviewers(true) ->executeOne(); if ($revision) { $this->affectedRevision = $revision; diff --git a/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php index 13c2695fed..f4d7e794a2 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php @@ -190,7 +190,7 @@ final class HeraldPreCommitContentAdapter extends HeraldPreCommitAdapter { $this->revision = id(new DifferentialRevisionQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withIDs(array($revision_id)) - ->needRelationships(true) + ->needReviewers(true) ->executeOne(); } } diff --git a/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php index 7eca716e34..4e461ec3f5 100644 --- a/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php +++ b/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php @@ -119,6 +119,7 @@ final class PhabricatorFilesComposeAvatarBuiltinFile foreach ($list as $file) { $map['alphanumeric/'.$file] = $root.$file; } + return $map; } @@ -138,11 +139,11 @@ final class PhabricatorFilesComposeAvatarBuiltinFile $border_seed = $username.'_border'; $pack_key = - PhabricatorHash::digestToRange($pack_seed, 1, $pack_count); + PhabricatorHash::digestToRange($pack_seed, 0, $pack_count - 1); $color_key = - PhabricatorHash::digestToRange($color_seed, 1, $color_count); + PhabricatorHash::digestToRange($color_seed, 0, $color_count - 1); $border_key = - PhabricatorHash::digestToRange($border_seed, 1, $border_count); + PhabricatorHash::digestToRange($border_seed, 0, $border_count - 1); $pack = $pack_map[$pack_key]; $icon = 'alphanumeric/'.$pack.'/'.$file.'.png'; @@ -188,7 +189,7 @@ final class PhabricatorFilesComposeAvatarBuiltinFile ->withFollowSymlinks(false) ->find(); - return $map; + return array_values($map); } public static function getBorderMap() { diff --git a/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php b/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php index e96ed4a89e..f3e95eeb66 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php @@ -10,13 +10,7 @@ final class PhabricatorPeopleProfileBadgesController $user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withIDs(array($id)) - ->needProfile(true) ->needProfileImage(true) - ->needAvailability(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->executeOne(); if (!$user) { return new Aphront404Response(); @@ -50,6 +44,7 @@ final class PhabricatorPeopleProfileBadgesController PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) + ->setLimit(1) ->execute(); $button = id(new PHUIButtonView()) @@ -59,7 +54,7 @@ final class PhabricatorPeopleProfileBadgesController ->setWorkflow(true) ->setHref('/badges/award/'.$user->getID().'/'); - if (count($badges)) { + if ($badges) { $header->addActionLink($button); } @@ -80,47 +75,43 @@ final class PhabricatorPeopleProfileBadgesController private function buildBadgesView(PhabricatorUser $user) { $viewer = $this->getViewer(); + $request = $this->getRequest(); - $awards = id(new PhabricatorBadgesAwardQuery()) + $pager = id(new AphrontCursorPagerView()) + ->readFromRequest($request); + + $query = id(new PhabricatorBadgesAwardQuery()) ->setViewer($viewer) ->withRecipientPHIDs(array($user->getPHID())) - ->withBadgeStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE)) - ->execute(); - $awards = mpull($awards, null, 'getBadgePHID'); + ->withBadgeStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE)); - $badges = array(); - foreach ($awards as $award) { - $badge = $award->getBadge(); - $badges[$award->getBadgePHID()] = $badge; - } + $awards = $query->executeWithCursorPager($pager); - if (count($badges)) { + if ($awards) { $flex = new PHUIBadgeBoxView(); + foreach ($awards as $award) { + $badge = $award->getBadge(); - foreach ($badges as $badge) { - if ($badge) { - $awarder_info = array(); + $awarder_info = array(); - $award = idx($awards, $badge->getPHID(), null); - $awarder_phid = $award->getAwarderPHID(); - $awarder_handle = $viewer->renderHandle($awarder_phid); - $awarded_date = phabricator_date($award->getDateCreated(), $viewer); + $awarder_phid = $award->getAwarderPHID(); + $awarder_handle = $viewer->renderHandle($awarder_phid); + $awarded_date = phabricator_date($award->getDateCreated(), $viewer); - $awarder_info = pht( - 'Awarded by %s', - $awarder_handle->render()); + $awarder_info = pht( + 'Awarded by %s', + $awarder_handle->render()); - $item = id(new PHUIBadgeView()) - ->setIcon($badge->getIcon()) - ->setHeader($badge->getName()) - ->setSubhead($badge->getFlavor()) - ->setQuality($badge->getQuality()) - ->setHref($badge->getViewURI()) - ->addByLine($awarder_info) - ->addByLine($awarded_date); + $item = id(new PHUIBadgeView()) + ->setIcon($badge->getIcon()) + ->setHeader($badge->getName()) + ->setSubhead($badge->getFlavor()) + ->setQuality($badge->getQuality()) + ->setHref($badge->getViewURI()) + ->addByLine($awarder_info) + ->addByLine($awarded_date); - $flex->addItem($item); - } + $flex->addItem($item); } } else { $flex = id(new PHUIInfoView()) @@ -128,6 +119,9 @@ final class PhabricatorPeopleProfileBadgesController ->appendChild(pht('User has not been awarded any badges.')); } - return $flex; + return array( + $flex, + $pager, + ); } } diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index 95f65253a8..0e1a9f37c7 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -138,6 +138,10 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { ); } + public function getApplicationOrder() { + return 0.150; + } + public function getHelpDocumentationArticles(PhabricatorUser $viewer) { return array( array( diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php index fe19849163..4d4b961765 100644 --- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php @@ -228,7 +228,10 @@ final class PhabricatorRepositoryPullLocalDaemon continue; } - $this->waitForUpdates($min_sleep, $retry_after); + $should_hibernate = $this->waitForUpdates($min_sleep, $retry_after); + if ($should_hibernate) { + break; + } } } @@ -492,6 +495,10 @@ final class PhabricatorRepositoryPullLocalDaemon while (($sleep_until - time()) > 0) { $sleep_duration = ($sleep_until - time()); + if ($this->shouldHibernate($sleep_duration)) { + return true; + } + $this->log( pht( 'Sleeping for %s more second(s)...', @@ -501,7 +508,7 @@ final class PhabricatorRepositoryPullLocalDaemon if ($this->shouldExit()) { $this->log(pht('Awakened from sleep by graceful shutdown!')); - return; + return false; } if ($this->loadRepositoryUpdateMessages()) { @@ -509,6 +516,8 @@ final class PhabricatorRepositoryPullLocalDaemon break; } } + + return false; } } diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemonModule.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemonModule.php new file mode 100644 index 0000000000..45dc49d9af --- /dev/null +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemonModule.php @@ -0,0 +1,38 @@ +getPoolDaemonClass(); + if ($class != 'PhabricatorRepositoryPullLocalDaemon') { + return false; + } + + if ($this->shouldThrottle($class, 1)) { + return false; + } + + $table = new PhabricatorRepositoryStatusMessage(); + $table_name = $table->getTableName(); + $conn = $table->establishConnection('r'); + + $row = queryfx_one( + $conn, + 'SELECT id FROM %T WHERE statusType = %s + AND id > %d ORDER BY id DESC LIMIT 1', + $table_name, + PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, + $this->cursor); + + if (!$row) { + return false; + } + + $this->cursor = (int)$row['id']; + return true; + } + +} diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php index 5deb975fa9..75ae0c9c14 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php @@ -67,7 +67,7 @@ final class PhabricatorRepositoryCommitOwnersWorker $revision = id(new DifferentialRevisionQuery()) ->setViewer($viewer) ->withIDs(array($revision_id)) - ->needReviewerStatus(true) + ->needReviewers(true) ->executeOne(); } else { $revision = null; @@ -165,7 +165,7 @@ final class PhabricatorRepositoryCommitOwnersWorker $accepted_statuses = array_fuse($accepted_statuses); $found_accept = false; - foreach ($revision->getReviewerStatus() as $reviewer) { + foreach ($revision->getReviewers() as $reviewer) { $reviewer_phid = $reviewer->getReviewerPHID(); // If this reviewer isn't a package owner, just ignore them. @@ -175,7 +175,7 @@ final class PhabricatorRepositoryCommitOwnersWorker // If this reviewer accepted the revision and owns the package, we're // all clear and do not need to trigger an audit. - if (isset($accepted_statuses[$reviewer->getStatus()])) { + if (isset($accepted_statuses[$reviewer->getReviewerStatus()])) { $found_accept = true; break; } diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index f340fbf91f..fec7c1f8af 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -178,7 +178,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $revision_query = id(new DifferentialRevisionQuery()) ->withIDs(array($revision_id)) ->setViewer($actor) - ->needReviewerStatus(true) + ->needReviewers(true) ->needActiveDiffs(true); $revision = $revision_query->executeOne(); diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index faca9991ea..dd3508e6bb 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -555,8 +555,9 @@ final class PhabricatorApplicationSearchController ->setTag('a') ->setHref('#') ->setText(pht('Use Results...')) - ->setIcon('fa-road') - ->setDropdownMenu($action_list); + ->setIcon('fa-bars') + ->setDropdownMenu($action_list) + ->addClass('dropdown'); } private function newOverflowingView() { @@ -600,9 +601,32 @@ final class PhabricatorApplicationSearchController private function newBuiltinUseActions() { $actions = array(); + $request = $this->getRequest(); + $viewer = $request->getUser(); $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); + $engine = $this->getSearchEngine(); + $engine_class = get_class($engine); + $query_key = $this->getQueryKey(); + if (!$query_key) { + $query_key = head_key($engine->loadEnabledNamedQueries()); + } + + $can_use = $engine->canUseInPanelContext(); + $is_installed = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorDashboardApplication', + $viewer); + + if ($can_use && $is_installed) { + $dashboard_uri = '/dashboard/install/'; + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-dashboard') + ->setName(pht('Add to Dasbhoard')) + ->setWorkflow(true) + ->setHref("/dashboard/panel/install/{$engine_class}/{$query_key}/"); + } + if ($is_dev) { $engine = $this->getSearchEngine(); $nux_uri = $engine->getQueryBaseURI(); @@ -610,8 +634,8 @@ final class PhabricatorApplicationSearchController ->setQueryParam('nux', true); $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-bug') - ->setName(pht('Developer: Show New User State')) + ->setIcon('fa-user-plus') + ->setName(pht('DEV: New User State')) ->setHref($nux_uri); } @@ -620,8 +644,8 @@ final class PhabricatorApplicationSearchController ->setQueryParam('overheated', true); $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-bug') - ->setName(pht('Developer: Show Overheated State')) + ->setIcon('fa-fire') + ->setName(pht('DEV: Overheated State')) ->setHref($overheated_uri); } diff --git a/src/applications/transactions/commentaction/PhabricatorEditEngineCheckboxesCommentAction.php b/src/applications/transactions/commentaction/PhabricatorEditEngineCheckboxesCommentAction.php new file mode 100644 index 0000000000..a149c5c7b2 --- /dev/null +++ b/src/applications/transactions/commentaction/PhabricatorEditEngineCheckboxesCommentAction.php @@ -0,0 +1,36 @@ +options = $options; + return $this; + } + + public function getOptions() { + return $this->options; + } + + public function getPHUIXControlType() { + return 'checkboxes'; + } + + public function getPHUIXControlSpecification() { + $options = $this->getOptions(); + + $labels = array(); + foreach ($options as $key => $option) { + $labels[$key] = hsprintf('%s', $option); + } + + return array( + 'value' => $this->getValue(), + 'keys' => array_keys($options), + 'labels' => $labels, + ); + } + +} diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php index c07b2d5f3d..80939dd1c8 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php @@ -163,15 +163,6 @@ final class PhabricatorEditEngineConfigurationViewController ->setDisabled(!$can_edit)); } - $curtain->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Change Default Values')) - ->setIcon('fa-paint-brush') - ->setHref($defaults_uri) - ->setWorkflow(!$can_edit) - ->setDisabled(!$can_edit)); - - $disable_uri = "{$base_uri}/disable/{$form_key}/"; if ($config->getIsDisabled()) { diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 4ad6e39be8..1b891cddee 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -996,8 +996,12 @@ abstract class PhabricatorEditEngine $config = $this->getEditEngineConfiguration() ->attachEngine($this); + // NOTE: Don't prompt users to override locks when creating objects, + // even if the default settings would create a locked object. + $can_interact = PhabricatorPolicyFilter::canInteract($viewer, $object); if (!$can_interact && + !$this->getIsCreate() && !$request->getBool('editEngine') && !$request->getBool('overrideLock')) { diff --git a/src/applications/transactions/editfield/PhabricatorApplyEditField.php b/src/applications/transactions/editfield/PhabricatorApplyEditField.php index a292a65021..d349767f94 100644 --- a/src/applications/transactions/editfield/PhabricatorApplyEditField.php +++ b/src/applications/transactions/editfield/PhabricatorApplyEditField.php @@ -5,6 +5,7 @@ final class PhabricatorApplyEditField private $actionDescription; private $actionConflictKey; + private $options; protected function newControl() { return null; @@ -28,8 +29,21 @@ final class PhabricatorApplyEditField return $this->actionConflictKey; } + public function setOptions(array $options) { + $this->options = $options; + return $this; + } + + public function getOptions() { + return $this->options; + } + protected function newHTTPParameterType() { - return new AphrontBoolHTTPParameterType(); + if ($this->getOptions()) { + return new AphrontPHIDListHTTPParameterType(); + } else { + return new AphrontBoolHTTPParameterType(); + } } protected function newConduitParameterType() { @@ -43,9 +57,16 @@ final class PhabricatorApplyEditField } protected function newCommentAction() { - return id(new PhabricatorEditEngineStaticCommentAction()) - ->setDescription($this->getActionDescription()) - ->setConflictKey($this->getActionConflictKey()); + $options = $this->getOptions(); + if ($options) { + return id(new PhabricatorEditEngineCheckboxesCommentAction()) + ->setConflictKey($this->getActionConflictKey()) + ->setOptions($options); + } else { + return id(new PhabricatorEditEngineStaticCommentAction()) + ->setConflictKey($this->getActionConflictKey()) + ->setDescription($this->getActionDescription()); + } } } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php index c50e5d091a..f9db0e238e 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php @@ -92,6 +92,7 @@ final class PhabricatorApplicationTransactionCommentEditor $editor ->setContentSource($this->getContentSource()) ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) ->applyTransactions($object, $support_xactions); } } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index d2250b2b5d..5e36e942ce 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -932,7 +932,15 @@ abstract class PhabricatorApplicationTransaction $type = $this->getMetadata('edge:type'); $type = head($type); - $type_obj = PhabricatorEdgeType::getByConstant($type); + try { + $type_obj = PhabricatorEdgeType::getByConstant($type); + } catch (Exception $ex) { + // Recover somewhat gracefully from edge transactions which + // we don't have the classes for. + return pht( + '%s edited an edge.', + $this->renderHandleLink($author_phid)); + } if ($add && $rem) { return $type_obj->getTransactionEditString( diff --git a/src/docs/user/support.diviner b/src/docs/user/support.diviner index 3402c099e7..18de79a9a1 100644 --- a/src/docs/user/support.diviner +++ b/src/docs/user/support.diviner @@ -121,5 +121,3 @@ Conpherence room on this install, and you can ask questions in are not upstream support channels and you may not receive a response to questions, but someone in the community may be able to point you in the right direction. - -There is also a community IRC channel in `#phabricator` on FreeNode. diff --git a/src/docs/user/userguide/diffusion_existing.diviner b/src/docs/user/userguide/diffusion_existing.diviner index 56226a84a1..b5b1fbdf76 100644 --- a/src/docs/user/userguide/diffusion_existing.diviner +++ b/src/docs/user/userguide/diffusion_existing.diviner @@ -48,7 +48,7 @@ Once the import completes, disable the **Observe** URI to automatically convert it into a hosted repository. **Push to Empty Repository**: Create an activate an empty repository, then push -all of your changes to empty the repository. +all of your changes to the empty repository. In Git and Mercurial, you can do this with `git push` or `hg push`. diff --git a/src/docs/user/userguide/external_editor.diviner b/src/docs/user/userguide/external_editor.diviner index 1139a28e0e..16a34d90fb 100644 --- a/src/docs/user/userguide/external_editor.diviner +++ b/src/docs/user/userguide/external_editor.diviner @@ -42,10 +42,3 @@ Then set your "Editor Link" to: lang=uri txmt://open/?url=file:///Users/alincoln/editor_links/%r/%f&line=%l - -== Configuring: Other Editors == - -General instructions for configuring some other editors and environments can be -found here: - -http://wiki.nette.org/en/howto-editor-link diff --git a/src/infrastructure/daemon/overseer/PhabricatorDaemonOverseerModule.php b/src/infrastructure/daemon/overseer/PhabricatorDaemonOverseerModule.php index aa238a4bf0..be5ebec79e 100644 --- a/src/infrastructure/daemon/overseer/PhabricatorDaemonOverseerModule.php +++ b/src/infrastructure/daemon/overseer/PhabricatorDaemonOverseerModule.php @@ -10,18 +10,9 @@ final class PhabricatorDaemonOverseerModule extends PhutilDaemonOverseerModule { private $configVersion; - private $timestamp; - - public function __construct() { - $this->timestamp = PhabricatorTime::getNow(); - } public function shouldReloadDaemons() { - $now = PhabricatorTime::getNow(); - $ago = ($now - $this->timestamp); - - // Don't check more than once every 10 seconds. - if ($ago < 10) { + if ($this->shouldThrottle('reload', 10)) { return false; } @@ -47,25 +38,23 @@ final class PhabricatorDaemonOverseerModule } /** - * Update the configuration version and timestamp. + * Check and update the configuration version. * * @return bool True if the daemons should restart, otherwise false. */ private function updateConfigVersion() { - $config_version = $this->loadConfigVersion(); - $this->timestamp = PhabricatorTime::getNow(); + $old_version = $this->configVersion; + $new_version = $this->loadConfigVersion(); - if (!$this->configVersion) { - $this->configVersion = $config_version; + $this->configVersion = $new_version; + + // Don't trigger a reload if we're loading the config for the very + // first time. + if ($old_version === null) { return false; } - if ($this->configVersion != $config_version) { - $this->configVersion = $config_version; - return true; - } - - return false; + return ($old_version != $new_version); } } diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php index 49e283c946..6cbbd8698e 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php @@ -43,6 +43,11 @@ final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon { $sleep = 0; } else { + + if ($this->shouldHibernate(60)) { + break; + } + // When there's no work, sleep for one second. The pool will // autoscale down if we're continuously idle for an extended period // of time. diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemonModule.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemonModule.php new file mode 100644 index 0000000000..ddd0e082bb --- /dev/null +++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemonModule.php @@ -0,0 +1,33 @@ +getPoolDaemonClass(); + + if ($class != 'PhabricatorTaskmasterDaemon') { + return false; + } + + if ($this->shouldThrottle($class, 1)) { + return false; + } + + $table = new PhabricatorWorkerActiveTask(); + $conn = $table->establishConnection('r'); + + $row = queryfx_one( + $conn, + 'SELECT id FROM %T WHERE leaseOwner IS NULL + OR leaseExpires <= %d LIMIT 1', + $table->getTableName(), + PhabricatorTime::getNow()); + if (!$row) { + return false; + } + + return true; + } + +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 428f3955cf..ee1b466dcb 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1608,6 +1608,8 @@ final class PhabricatorUSEnglishTranslation ), ), + '%s accepted this revision as %s reviewer(s): %s.' => + '%s accepted this revision as: %3$s.', ); } diff --git a/src/infrastructure/util/PhabricatorHash.php b/src/infrastructure/util/PhabricatorHash.php index 7b5780460b..05b5fa719d 100644 --- a/src/infrastructure/util/PhabricatorHash.php +++ b/src/infrastructure/util/PhabricatorHash.php @@ -88,9 +88,10 @@ final class PhabricatorHash extends Phobject { } $hash = sha1($string, $raw_output = true); - $value = head(unpack('L', $hash)); + // Make sure this ends up positive, even on 32-bit machines. + $value = head(unpack('L', $hash)) & 0x7FFFFFFF; - return $min + ($value % ($max - $min)); + return $min + ($value % (1 + $max - $min)); } diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 67c4dc188c..ffa82ab1b9 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -216,7 +216,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView require_celerity_resource('phabricator-standard-page-view'); require_celerity_resource('conpherence-durable-column-view'); require_celerity_resource('font-lato'); - require_celerity_resource('font-aleo'); Javelin::initBehavior('workflow', array()); diff --git a/webroot/rsrc/css/application/conpherence/header-pane.css b/webroot/rsrc/css/application/conpherence/header-pane.css index f181a1adb1..11fe34e3f1 100644 --- a/webroot/rsrc/css/application/conpherence/header-pane.css +++ b/webroot/rsrc/css/application/conpherence/header-pane.css @@ -9,7 +9,6 @@ .conpherence-header-pane .phui-header-header { font-size: 16px; - font-family: 'Aleo', {$fontfamily}; color: #000; } diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 7b20009356..080f5ca344 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -139,7 +139,6 @@ .phame-next-post-view { margin: 0 auto; padding: 12px 0; - font-family: 'Aleo', {$fontfamily}; } .phame-next { @@ -294,7 +293,6 @@ color: #000; font-size: 28px; font-weight: bold; - font-family: 'Aleo', {$fontfamily}; padding-top: 24px; } @@ -305,7 +303,6 @@ .phame-mega-header .phame-header-subtitle { color: {$greytext}; font-size: 20px; - font-family: 'Aleo', {$fontfamily}; padding-top: 8px; } diff --git a/webroot/rsrc/css/application/project/project-card-view.css b/webroot/rsrc/css/application/project/project-card-view.css index 7d1e9ce746..0d45de9485 100644 --- a/webroot/rsrc/css/application/project/project-card-view.css +++ b/webroot/rsrc/css/application/project/project-card-view.css @@ -28,7 +28,6 @@ .project-card-view .phui-header-shell .phui-header-header { font-size: 18px; - font-family: 'Aleo', {$fontfamily}; width: 290px; overflow: hidden; white-space: nowrap; @@ -71,7 +70,6 @@ .project-card-header .project-card-name { font-size: 20px; - font-family: 'Aleo', {$fontfamily}; font-weight: bold; color: #000; margin-bottom: 2px; diff --git a/webroot/rsrc/css/font/font-aleo.css b/webroot/rsrc/css/font/font-aleo.css deleted file mode 100644 index 91ef6e2dc4..0000000000 --- a/webroot/rsrc/css/font/font-aleo.css +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @provides font-aleo - * @requires phui-fontkit-css - */ - -@font-face { - font-family: 'Aleo'; - font-weight: bold; - font-style: normal; - src: url(/rsrc/externals/font/aleo/aleo-bold.eot); - src: url(/rsrc/externals/font/aleo/aleo-bold.eot?#iefix) - format('embedded-opentype'), - url(/rsrc/externals/font/aleo/aleo-bold.woff2) - format('woff2'), - url(/rsrc/externals/font/aleo/aleo-bold.woff) - format('woff'), - url(/rsrc/externals/font/aleo/aleo-bold.ttf) - format('truetype'), - url(/rsrc/externals/font/aleo/aleo-bold.svg#aleo-bold) - format('svg'); - -} - -@font-face { - font-family: 'Aleo'; - font-weight: normal; - font-style: normal; - src: url(/rsrc/externals/font/aleo/aleo-regular.eot); - src: url(/rsrc/externals/font/aleo/aleo-regular.eot?#iefix) - format('embedded-opentype'), - url(/rsrc/externals/font/aleo/aleo-regular.woff2) - format('woff2'), - url(/rsrc/externals/font/aleo/aleo-regular.woff) - format('woff'), - url(/rsrc/externals/font/aleo/aleo-regular.ttf) - format('truetype'), - url(/rsrc/externals/font/aleo/aleo-regular.svg#aleo-regular) - format('svg'); - -} diff --git a/webroot/rsrc/css/phui/phui-fontkit.css b/webroot/rsrc/css/phui/phui-fontkit.css index 7bca35d277..875ac41981 100644 --- a/webroot/rsrc/css/phui/phui-fontkit.css +++ b/webroot/rsrc/css/phui/phui-fontkit.css @@ -2,30 +2,10 @@ * @provides phui-fontkit-css */ -/* - Roboto Slab --------------------------------------------------------------- - - Used as Primary Headers in Object Boxes, Headers in Documents - -*/ - .diviner-document-section .phui-header-header { - font-family: 'Aleo', {$fontfamily}; color: #000; } -.phui-document-view .phui-header-tall .phui-header-header { - font-family: 'Aleo', {$fontfamily}; -} - -.phui-document-view .phabricator-remarkup h1.remarkup-header, -.phui-document-view .phabricator-remarkup h2.remarkup-header, -.phui-document-view .phabricator-remarkup h3.remarkup-header, -.phui-document-view .phabricator-remarkup h4.remarkup-header, -.phui-document-view .phabricator-remarkup h5.remarkup-header, -.phui-document-view .phabricator-remarkup h6.remarkup-header { - font-family: 'Aleo', {$fontfamily}; -} - .phui-document-view .phabricator-remarkup .remarkup-header { margin-bottom: 8px; } diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 9dce0a4476..da1a524abb 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -548,3 +548,16 @@ properly, and submit values. */ padding: 4px; color: {$bluetext}; } + +.phuix-form-checkbox-action { + padding: 4px; + color: {$bluetext}; +} + +.phuix-form-checkbox-action input[type=checkbox] { + margin: 4px 0; +} + +.phuix-form-checkbox-label { + margin-left: 4px; +} diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index b47c93fe33..14e0af986f 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -345,7 +345,6 @@ body .phui-header-shell.phui-bleed-header } .phui-profile-header.phui-header-shell .phui-header-header { - font-family: 'Aleo', {$fontfamily}; font-size: 24px; color: #000; } diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 5418644b28..d910cf71a0 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -18,7 +18,6 @@ .phui-two-column-header .phui-header-header { font-size: 20px; - font-family: 'Aleo', {$fontfamily}; color: #000; } diff --git a/webroot/rsrc/externals/font/aleo/LICENSE.txt b/webroot/rsrc/externals/font/aleo/LICENSE.txt deleted file mode 100644 index b851fd439a..0000000000 --- a/webroot/rsrc/externals/font/aleo/LICENSE.txt +++ /dev/null @@ -1,203 +0,0 @@ -Font data copyright Google 2013 - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/webroot/rsrc/externals/font/aleo/aleo-bold.eot b/webroot/rsrc/externals/font/aleo/aleo-bold.eot deleted file mode 100644 index 294fef7412..0000000000 Binary files a/webroot/rsrc/externals/font/aleo/aleo-bold.eot and /dev/null differ diff --git a/webroot/rsrc/externals/font/aleo/aleo-bold.svg b/webroot/rsrc/externals/font/aleo/aleo-bold.svg deleted file mode 100644 index e49c14686d..0000000000 --- a/webroot/rsrc/externals/font/aleo/aleo-bold.svg +++ /dev/null @@ -1,5078 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/webroot/rsrc/externals/font/aleo/aleo-bold.ttf b/webroot/rsrc/externals/font/aleo/aleo-bold.ttf deleted file mode 100644 index b05d3e78e2..0000000000 Binary files a/webroot/rsrc/externals/font/aleo/aleo-bold.ttf and /dev/null differ diff --git a/webroot/rsrc/externals/font/aleo/aleo-bold.woff b/webroot/rsrc/externals/font/aleo/aleo-bold.woff deleted file mode 100644 index a2c7a9d77b..0000000000 Binary files a/webroot/rsrc/externals/font/aleo/aleo-bold.woff and /dev/null differ diff --git a/webroot/rsrc/externals/font/aleo/aleo-bold.woff2 b/webroot/rsrc/externals/font/aleo/aleo-bold.woff2 deleted file mode 100644 index 9f91470113..0000000000 Binary files a/webroot/rsrc/externals/font/aleo/aleo-bold.woff2 and /dev/null differ diff --git a/webroot/rsrc/externals/font/aleo/aleo-regular.eot b/webroot/rsrc/externals/font/aleo/aleo-regular.eot deleted file mode 100644 index d73b2fa04b..0000000000 Binary files a/webroot/rsrc/externals/font/aleo/aleo-regular.eot and /dev/null differ diff --git a/webroot/rsrc/externals/font/aleo/aleo-regular.svg b/webroot/rsrc/externals/font/aleo/aleo-regular.svg deleted file mode 100644 index ec074310d0..0000000000 --- a/webroot/rsrc/externals/font/aleo/aleo-regular.svg +++ /dev/null @@ -1,4644 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/webroot/rsrc/externals/font/aleo/aleo-regular.ttf b/webroot/rsrc/externals/font/aleo/aleo-regular.ttf deleted file mode 100644 index dd20f0c10f..0000000000 Binary files a/webroot/rsrc/externals/font/aleo/aleo-regular.ttf and /dev/null differ diff --git a/webroot/rsrc/externals/font/aleo/aleo-regular.woff b/webroot/rsrc/externals/font/aleo/aleo-regular.woff deleted file mode 100644 index f484ee8aa6..0000000000 Binary files a/webroot/rsrc/externals/font/aleo/aleo-regular.woff and /dev/null differ diff --git a/webroot/rsrc/externals/font/aleo/aleo-regular.woff2 b/webroot/rsrc/externals/font/aleo/aleo-regular.woff2 deleted file mode 100644 index 31f38c898f..0000000000 Binary files a/webroot/rsrc/externals/font/aleo/aleo-regular.woff2 and /dev/null differ diff --git a/webroot/rsrc/js/phuix/PHUIXFormControl.js b/webroot/rsrc/js/phuix/PHUIXFormControl.js index ce2ba8bf7c..5640b95ae8 100644 --- a/webroot/rsrc/js/phuix/PHUIXFormControl.js +++ b/webroot/rsrc/js/phuix/PHUIXFormControl.js @@ -50,6 +50,9 @@ JX.install('PHUIXFormControl', { case 'static': input = this._newStatic(spec); break; + case 'checkboxes': + input = this._newCheckboxes(spec); + break; default: // TODO: Default or better error? JX.$E('Bad Input Type'); @@ -194,6 +197,89 @@ JX.install('PHUIXFormControl', { }; }, + _newCheckboxes: function(spec) { + var checkboxes = []; + var checkbox_list = []; + for (var ii = 0; ii < spec.keys.length; ii++) { + var key = spec.keys[ii]; + var checkbox_id = 'checkbox-' + Math.floor(Math.random() * 1000000); + + var checkbox = JX.$N( + 'input', + { + type: 'checkbox', + value: key, + id: checkbox_id + }); + + checkboxes.push(checkbox); + + var label = JX.$N( + 'label', + { + className: 'phuix-form-checkbox-label', + htmlFor: checkbox_id + }, + JX.$H(spec.labels[key] || '')); + + var display = JX.$N( + 'div', + { + className: 'phuix-form-checkbox-item' + }, + [checkbox, label]); + + checkbox_list.push(display); + } + + var node = JX.$N( + 'div', + { + className: 'phuix-form-checkbox-action' + }, + checkbox_list); + + var get_value = function() { + var list = []; + for (var ii = 0; ii < checkboxes.length; ii++) { + if (checkboxes[ii].checked) { + list.push(checkboxes[ii].value); + } + } + return list; + }; + + var set_value = function(value) { + value = value || []; + + if (!value.length) { + value = []; + } + + var map = {}; + var ii; + for (ii = 0; ii < value.length; ii++) { + map[value[ii]] = true; + } + + for (ii = 0; ii < checkboxes.length; ii++) { + if (map.hasOwnProperty(checkboxes[ii].value)) { + checkboxes[ii].checked = 'checked'; + } else { + checkboxes[ii].checked = false; + } + } + }; + + set_value(spec.value); + + return { + node: node, + get: get_value, + set: set_value + }; + }, + _newPoints: function(spec) { var attrs = { type: 'text',