diff --git a/.arclint b/.arclint index 1b5b63976a..6047e2ccb6 100644 --- a/.arclint +++ b/.arclint @@ -61,7 +61,19 @@ "type": "spelling" }, "text": { - "type": "text" + "type": "text", + "exclude": [ + "(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json))" + ] + }, + "text-without-length": { + "type": "text", + "include": [ + "(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json))" + ], + "severity": { + "3": "disabled" + } }, "xhpast": { "type": "xhpast", diff --git a/bin/nuance b/bin/nuance new file mode 120000 index 0000000000..c2cf50a211 --- /dev/null +++ b/bin/nuance @@ -0,0 +1 @@ +../scripts/setup/manage_nuance.php \ No newline at end of file diff --git a/externals/amazon-ses/ses.php b/externals/amazon-ses/ses.php index b0eebce690..9968e33ac9 100644 --- a/externals/amazon-ses/ses.php +++ b/externals/amazon-ses/ses.php @@ -80,9 +80,30 @@ class SimpleEmailService * @return void */ public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') { + if (!function_exists('simplexml_load_string')) { + throw new Exception( + pht( + 'The PHP SimpleXML extension is not available, but this '. + 'extension is required to send mail via Amazon SES, because '. + 'Amazon SES returns API responses in XML format. Install or '. + 'enable the SimpleXML extension.')); + } + + // Catch mistakes with reading the wrong column out of the SES + // documentation. See T10728. + if (preg_match('(-smtp)', $host)) { + throw new Exception( + pht( + 'Amazon SES is not configured correctly: the configured SES '. + 'endpoint ("%s") is an SMTP endpoint. Instead, use an API (HTTPS) '. + 'endpoint.', + $host)); + } + if ($accessKey !== null && $secretKey !== null) { $this->setAuth($accessKey, $secretKey); } + $this->__host = $host; } @@ -108,13 +129,6 @@ class SimpleEmailService $rest->setParameter('Action', 'ListVerifiedEmailAddresses'); $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('listVerifiedEmailAddresses', $rest->error); - return false; - } $response = array(); if(!isset($rest->body)) { @@ -148,13 +162,6 @@ class SimpleEmailService $rest->setParameter('EmailAddress', $email); $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('verifyEmailAddress', $rest->error); - return false; - } $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; return $response; @@ -172,13 +179,6 @@ class SimpleEmailService $rest->setParameter('EmailAddress', $email); $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('deleteVerifiedEmailAddress', $rest->error); - return false; - } $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; return $response; @@ -195,13 +195,6 @@ class SimpleEmailService $rest->setParameter('Action', 'GetSendQuota'); $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('getSendQuota', $rest->error); - return false; - } $response = array(); if(!isset($rest->body)) { @@ -227,13 +220,6 @@ class SimpleEmailService $rest->setParameter('Action', 'GetSendStatistics'); $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('getSendStatistics', $rest->error); - return false; - } $response = array(); if(!isset($rest->body)) { @@ -265,13 +251,6 @@ class SimpleEmailService $rest->setParameter('RawMessage.Data', base64_encode($raw)); $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('sendRawEmail', $rest->error); - return false; - } $response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId; $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; @@ -351,13 +330,6 @@ class SimpleEmailService } $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('sendEmail', $rest->error); - return false; - } $response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId; $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId; @@ -523,15 +495,22 @@ final class SimpleEmailServiceRequest curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // Execute, grab errors - if (curl_exec($curl)) { - $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - } else { - $this->response->error = array( - 'curl' => true, - 'code' => curl_errno($curl), - 'message' => curl_error($curl), - 'resource' => $this->resource - ); + if (!curl_exec($curl)) { + throw new SimpleEmailServiceException( + pht( + 'Encountered an error while making an HTTP request to Amazon SES '. + '(cURL Error #%d): %s', + curl_errno($curl), + curl_error($curl))); + } + + $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + if ($this->response->code != 200) { + throw new SimpleEmailServiceException( + pht( + 'Unexpected HTTP status while making request to Amazon SES: '. + 'expected 200, got %s.', + $this->response->code)); } @curl_close($curl); diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4b61c3e725..b11242df15 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,12 +7,12 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'ef2e7787', - 'core.pkg.js' => '7d8faf57', + 'core.pkg.css' => '007bd625', + 'core.pkg.js' => 'e5484f37', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => '2de124c9', + 'differential.pkg.css' => '7ba78475', 'differential.pkg.js' => 'd0cd0df6', - 'diffusion.pkg.css' => 'f45955ed', + 'diffusion.pkg.css' => 'dc8e0cc2', 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', @@ -25,7 +25,7 @@ return array( 'rsrc/css/aphront/notification.css' => '7f684b62', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758', - 'rsrc/css/aphront/table-view.css' => '036b6cdc', + 'rsrc/css/aphront/table-view.css' => '9258e19f', 'rsrc/css/aphront/tokenizer.css' => '056da01b', 'rsrc/css/aphront/tooltip.css' => '1a07aea8', 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', @@ -54,25 +54,25 @@ return array( 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '96696f21', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', - 'rsrc/css/application/dashboard/dashboard.css' => 'eb458607', + 'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'b6b0d1bb', - 'rsrc/css/application/differential/core.css' => '7ac3cabc', - 'rsrc/css/application/differential/phui-inline-comment.css' => '0fdb3667', + 'rsrc/css/application/differential/changeset-view.css' => '3e3b0b76', + 'rsrc/css/application/differential/core.css' => '5b7b8ff4', + 'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-icons.css' => '2941baf1', - 'rsrc/css/application/diffusion/diffusion-readme.css' => '356a4f3c', - 'rsrc/css/application/diffusion/diffusion-source.css' => '075ba788', + 'rsrc/css/application/diffusion/diffusion-icons.css' => '3311444d', + 'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb', + 'rsrc/css/application/diffusion/diffusion-source.css' => '68b30fd3', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', 'rsrc/css/application/flag/flag.css' => '5337623f', - 'rsrc/css/application/harbormaster/harbormaster.css' => '834879db', + 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', - 'rsrc/css/application/herald/herald.css' => '46596280', + 'rsrc/css/application/herald/herald.css' => 'dc31f6e9', 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 'rsrc/css/application/maniphest/report.css' => '9b9580b7', 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', @@ -94,7 +94,7 @@ return array( 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96', 'rsrc/css/application/project/project-card-view.css' => '9418c97d', - 'rsrc/css/application/project/project-view.css' => '9ce99f21', + 'rsrc/css/application/project/project-view.css' => 'cbaa10a1', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', @@ -104,7 +104,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'd0801452', - 'rsrc/css/core/remarkup.css' => 'fc228f08', + 'rsrc/css/core/remarkup.css' => '2c9ed46f', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '5b6fcf3f', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', @@ -123,39 +123,40 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => 'c9e01148', + 'rsrc/css/phui/phui-box.css' => '9c9159a7', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', - 'rsrc/css/phui/phui-document-pro.css' => '92d5b648', + 'rsrc/css/phui/phui-curtain-view.css' => '7148ae25', + 'rsrc/css/phui/phui-document-pro.css' => '73e45fd2', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => '9c71d2bf', 'rsrc/css/phui/phui-feed-story.css' => '04aec08f', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', - 'rsrc/css/phui/phui-form-view.css' => '61e78dcb', + 'rsrc/css/phui/phui-form-view.css' => 'c62b9b8c', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', - 'rsrc/css/phui/phui-head-thing.css' => '11731da0', - 'rsrc/css/phui/phui-header-view.css' => '32e71367', + 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', + 'rsrc/css/phui/phui-header-view.css' => '230254d3', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '3f33ab57', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', - 'rsrc/css/phui/phui-info-view.css' => '6d7c3509', + 'rsrc/css/phui/phui-info-view.css' => '28efab79', 'rsrc/css/phui/phui-list.css' => '9da2aa00', - 'rsrc/css/phui/phui-object-box.css' => '91628842', - 'rsrc/css/phui/phui-object-item-list-view.css' => '94abc3d5', + 'rsrc/css/phui/phui-object-box.css' => '6b487c57', + 'rsrc/css/phui/phui-object-item-list-view.css' => '2fd9e633', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-profile-menu.css' => '7e92a89a', - 'rsrc/css/phui/phui-property-list-view.css' => '27b2849e', + 'rsrc/css/phui/phui-property-list-view.css' => '1d42ee7c', 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-segment-bar-view.css' => '46342871', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', - 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => 'ecd7ec62', + 'rsrc/css/phui/phui-timeline-view.css' => '6e342216', + 'rsrc/css/phui/phui-two-column-view.css' => '691fec04', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -245,7 +246,7 @@ return array( 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', 'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4', - 'rsrc/externals/javelin/lib/Workflow.js' => '5b2e3e2b', + 'rsrc/externals/javelin/lib/Workflow.js' => '28cfbdd0', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68', @@ -274,6 +275,7 @@ return array( 'rsrc/image/checker_dark.png' => 'd8e65881', 'rsrc/image/checker_light.png' => 'a0155918', 'rsrc/image/checker_lighter.png' => 'd5da91b6', + 'rsrc/image/d5d8e1.png' => '0c2a1497', 'rsrc/image/darkload.gif' => '1ffd3ec6', 'rsrc/image/divot.png' => '94dded62', 'rsrc/image/examples/hero.png' => '979a86ae', @@ -401,7 +403,7 @@ return array( 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', - 'rsrc/js/application/herald/HeraldRuleEditor.js' => '746ca158', + 'rsrc/js/application/herald/HeraldRuleEditor.js' => 'd6a7e717', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', @@ -421,7 +423,7 @@ return array( 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 'rsrc/js/application/projects/WorkboardBoard.js' => '52291776', 'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f', - 'rsrc/js/application/projects/WorkboardColumn.js' => 'f05d6e5d', + 'rsrc/js/application/projects/WorkboardColumn.js' => 'bae58312', 'rsrc/js/application/projects/WorkboardController.js' => '55baf5ed', 'rsrc/js/application/projects/behavior-project-boards.js' => '14a1faae', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', @@ -433,7 +435,7 @@ return array( 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', - 'rsrc/js/application/transactions/behavior-comment-actions.js' => '1f2fcaf8', + 'rsrc/js/application/transactions/behavior-comment-actions.js' => '06460e71', 'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6', @@ -526,7 +528,7 @@ return array( 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '036b6cdc', + 'aphront-table-view-css' => '9258e19f', 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', @@ -545,25 +547,25 @@ return array( 'conpherence-update-css' => 'faf6be09', 'conpherence-widget-pane-css' => '775eaaba', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'b6b0d1bb', - 'differential-core-view-css' => '7ac3cabc', + 'differential-changeset-view-css' => '3e3b0b76', + 'differential-core-view-css' => '5b7b8ff4', 'differential-inline-comment-editor' => '64a5550f', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-icons-css' => '2941baf1', - 'diffusion-readme-css' => '356a4f3c', - 'diffusion-source-css' => '075ba788', + 'diffusion-icons-css' => '3311444d', + 'diffusion-readme-css' => '297373eb', + 'diffusion-source-css' => '68b30fd3', 'diviner-shared-css' => 'aa3656aa', 'font-aleo' => '8bdb2835', 'font-fontawesome' => 'c43323c5', 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => '5c1b47c2', - 'harbormaster-css' => '834879db', - 'herald-css' => '46596280', - 'herald-rule-editor' => '746ca158', + 'harbormaster-css' => 'f491c9f4', + 'herald-css' => 'dc31f6e9', + 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', 'javelin-aphlict' => '5359e785', @@ -580,7 +582,7 @@ return array( 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-bulk-job-reload' => 'edf8a145', 'javelin-behavior-choose-control' => '327a00d1', - 'javelin-behavior-comment-actions' => '1f2fcaf8', + 'javelin-behavior-comment-actions' => '06460e71', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', 'javelin-behavior-conpherence-menu' => '1d45c74d', @@ -732,9 +734,9 @@ return array( 'javelin-websocket' => 'e292eaf4', 'javelin-workboard-board' => '52291776', 'javelin-workboard-card' => 'c587b80f', - 'javelin-workboard-column' => 'f05d6e5d', + 'javelin-workboard-column' => 'bae58312', 'javelin-workboard-controller' => '55baf5ed', - 'javelin-workflow' => '5b2e3e2b', + 'javelin-workflow' => '28cfbdd0', 'lightbox-attachment-css' => '7acac05d', 'maniphest-batch-editor' => 'b0f0b6d5', 'maniphest-report-css' => '9b9580b7', @@ -753,7 +755,7 @@ return array( 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => 'd0801452', 'phabricator-countdown-css' => '96696f21', - 'phabricator-dashboard-css' => 'eb458607', + 'phabricator-dashboard-css' => 'bc6f2127', 'phabricator-drag-and-drop-file-upload' => '81f182b5', 'phabricator-draggable-list' => '5a13c79f', 'phabricator-fatal-config-template-css' => '8e6c6fcd', @@ -771,7 +773,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'e67df814', - 'phabricator-remarkup-css' => 'fc228f08', + 'phabricator-remarkup-css' => '2c9ed46f', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => '3a3d9f41', @@ -806,7 +808,7 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => 'c9e01148', + 'phui-box-css' => '9c9159a7', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', @@ -814,39 +816,40 @@ return array( 'phui-calendar-month-css' => '476be7e0', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => '79d536e5', + 'phui-curtain-view-css' => '7148ae25', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '9c71d2bf', - 'phui-document-view-pro-css' => '92d5b648', + 'phui-document-view-pro-css' => '73e45fd2', 'phui-feed-story-css' => '04aec08f', 'phui-font-icon-base-css' => '6449bce8', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', - 'phui-form-view-css' => '61e78dcb', - 'phui-head-thing-view-css' => '11731da0', - 'phui-header-view-css' => '32e71367', + 'phui-form-view-css' => 'c62b9b8c', + 'phui-head-thing-view-css' => 'fd311e5f', + 'phui-header-view-css' => '230254d3', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', 'phui-icon-view-css' => '3f33ab57', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', - 'phui-info-view-css' => '6d7c3509', - 'phui-inline-comment-view-css' => '0fdb3667', + 'phui-info-view-css' => '28efab79', + 'phui-inline-comment-view-css' => '5953c28e', 'phui-list-view-css' => '9da2aa00', - 'phui-object-box-css' => '91628842', - 'phui-object-item-list-view-css' => '94abc3d5', + 'phui-object-box-css' => '6b487c57', + 'phui-object-item-list-view-css' => '2fd9e633', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', 'phui-profile-menu-css' => '7e92a89a', - 'phui-property-list-view-css' => '27b2849e', + 'phui-property-list-view-css' => '1d42ee7c', 'phui-remarkup-preview-css' => '1a8f2591', 'phui-segment-bar-view-css' => '46342871', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '37309046', 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', - 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => 'ecd7ec62', + 'phui-timeline-view-css' => '6e342216', + 'phui-two-column-view-css' => '691fec04', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', @@ -862,7 +865,7 @@ return array( 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => 'fbd45f96', 'project-card-view-css' => '9418c97d', - 'project-view-css' => '9ce99f21', + 'project-view-css' => 'cbaa10a1', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', 'releeph-request-differential-create-dialog' => '8d8b92cd', @@ -920,6 +923,15 @@ return array( 'aphront-typeahead-control-css', 'phui-tag-view-css', ), + '06460e71' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-dom', + 'phuix-form-control-view', + 'phuix-icon-view', + 'javelin-behavior-phabricator-gesture', + ), '065227cc' => array( 'javelin-behavior', 'javelin-dom', @@ -1025,15 +1037,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - '1f2fcaf8' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-dom', - 'phuix-form-control-view', - 'phuix-icon-view', - 'javelin-behavior-phabricator-gesture', - ), '21ba5861' => array( 'javelin-behavior', 'javelin-dom', @@ -1059,6 +1062,17 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), + '28cfbdd0' => array( + 'javelin-stratcom', + 'javelin-request', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + 'javelin-util', + 'javelin-mask', + 'javelin-uri', + 'javelin-routable', + ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', @@ -1124,6 +1138,9 @@ return array( 'javelin-util', 'javelin-uri', ), + '3e3b0b76' => array( + 'phui-inline-comment-view-css', + ), '3f5d6dbf' => array( 'javelin-behavior', 'javelin-dom', @@ -1309,17 +1326,6 @@ return array( 'javelin-vector', 'javelin-magical-init', ), - '5b2e3e2b' => array( - 'javelin-stratcom', - 'javelin-request', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - 'javelin-util', - 'javelin-mask', - 'javelin-uri', - 'javelin-routable', - ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1432,15 +1438,6 @@ return array( 'javelin-vector', 'javelin-dom', ), - '746ca158' => array( - 'multirow-row-manager', - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-json', - 'phabricator-prefab', - ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1791,8 +1788,9 @@ return array( 'javelin-json', 'phabricator-draggable-list', ), - 'b6b0d1bb' => array( - 'phui-inline-comment-view-css', + 'bae58312' => array( + 'javelin-install', + 'javelin-workboard-card', ), 'bcaccd64' => array( 'javelin-behavior', @@ -1919,6 +1917,15 @@ return array( 'javelin-dom', 'javelin-stratcom', ), + 'd6a7e717' => array( + 'multirow-row-manager', + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-json', + 'phabricator-prefab', + ), 'd75709e6' => array( 'javelin-behavior', 'javelin-workflow', @@ -2062,10 +2069,6 @@ return array( 'javelin-workflow', 'javelin-json', ), - 'f05d6e5d' => array( - 'javelin-install', - 'javelin-workboard-card', - ), 'f411b6ae' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/resources/sql/autopatches/20140211.dx.2.migcommenttext.php b/resources/sql/autopatches/20140211.dx.2.migcommenttext.php index 705e858069..7c531f51f8 100644 --- a/resources/sql/autopatches/20140211.dx.2.migcommenttext.php +++ b/resources/sql/autopatches/20140211.dx.2.migcommenttext.php @@ -4,8 +4,7 @@ $conn_w = id(new DifferentialRevision())->establishConnection('w'); $rows = new LiskRawMigrationIterator($conn_w, 'differential_comment'); $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(); + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(); echo pht('Migrating Differential comment text to modern storage...')."\n"; foreach ($rows as $row) { diff --git a/resources/sql/autopatches/20140212.dx.1.armageddon.php b/resources/sql/autopatches/20140212.dx.1.armageddon.php index d2749bd5cf..021e886629 100644 --- a/resources/sql/autopatches/20140212.dx.1.armageddon.php +++ b/resources/sql/autopatches/20140212.dx.1.armageddon.php @@ -4,8 +4,7 @@ $conn_w = id(new DifferentialRevision())->establishConnection('w'); $rows = new LiskRawMigrationIterator($conn_w, 'differential_comment'); $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(); + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(); echo pht('Migrating Differential comments to modern storage...')."\n"; foreach ($rows as $row) { diff --git a/resources/sql/autopatches/20140722.audit.3.miginlines.php b/resources/sql/autopatches/20140722.audit.3.miginlines.php index c6778fa659..d816b534eb 100644 --- a/resources/sql/autopatches/20140722.audit.3.miginlines.php +++ b/resources/sql/autopatches/20140722.audit.3.miginlines.php @@ -10,8 +10,7 @@ $dst_table = 'audit_transaction_comment'; echo pht('Migrating Audit inline comments to new format...')."\n"; $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(); + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(); $rows = new LiskRawMigrationIterator($conn_w, $src_table); foreach ($rows as $row) { diff --git a/resources/sql/autopatches/20140722.audit.4.migtext.php b/resources/sql/autopatches/20140722.audit.4.migtext.php index 2da4e2683e..c2a775058b 100644 --- a/resources/sql/autopatches/20140722.audit.4.migtext.php +++ b/resources/sql/autopatches/20140722.audit.4.migtext.php @@ -4,8 +4,7 @@ $conn_w = id(new PhabricatorAuditTransaction())->establishConnection('w'); $rows = new LiskRawMigrationIterator($conn_w, 'audit_comment'); $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(); + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(); echo pht('Migrating Audit comment text to modern storage...')."\n"; foreach ($rows as $row) { diff --git a/resources/sql/autopatches/20140725.audit.1.migxactions.php b/resources/sql/autopatches/20140725.audit.1.migxactions.php index e0e14e439b..4eb0897aeb 100644 --- a/resources/sql/autopatches/20140725.audit.1.migxactions.php +++ b/resources/sql/autopatches/20140725.audit.1.migxactions.php @@ -4,8 +4,7 @@ $conn_w = id(new PhabricatorAuditTransaction())->establishConnection('w'); $rows = new LiskRawMigrationIterator($conn_w, 'audit_comment'); $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(); + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(); echo pht('Migrating Audit comments to modern storage...')."\n"; foreach ($rows as $row) { diff --git a/resources/sql/autopatches/20160102.badges.award.sql b/resources/sql/autopatches/20160102.badges.award.sql new file mode 100644 index 0000000000..d637c93650 --- /dev/null +++ b/resources/sql/autopatches/20160102.badges.award.sql @@ -0,0 +1,10 @@ +CREATE TABLE {$NAMESPACE}_badges.badges_award ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + badgePHID VARBINARY(64) NOT NULL, + recipientPHID VARBINARY(64) NOT NULL, + awarderPHID varbinary(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_badge` (badgePHID, recipientPHID), + KEY `key_recipient` (recipientPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.01.disabled.sql b/resources/sql/autopatches/20160308.nuance.01.disabled.sql new file mode 100644 index 0000000000..f9a6d11320 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.01.disabled.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_source + ADD isDisabled BOOL NOT NULL; diff --git a/resources/sql/autopatches/20160308.nuance.02.cursordata.sql b/resources/sql/autopatches/20160308.nuance.02.cursordata.sql new file mode 100644 index 0000000000..a3ac917f0c --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.02.cursordata.sql @@ -0,0 +1,12 @@ +CREATE TABLE {$NAMESPACE}_nuance.nuance_importcursordata ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + sourcePHID VARBINARY(64) NOT NULL, + cursorKey VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + cursorType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_source` (sourcePHID, cursorKey) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.03.sourcen.sql b/resources/sql/autopatches/20160308.nuance.03.sourcen.sql new file mode 100644 index 0000000000..42ec4b87eb --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.03.sourcen.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_nuance.nuance_sourcename_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.04.sourcei.php b/resources/sql/autopatches/20160308.nuance.04.sourcei.php new file mode 100644 index 0000000000..eb0d1da113 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.04.sourcei.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/resources/sql/autopatches/20160308.nuance.05.sourcename.sql b/resources/sql/autopatches/20160308.nuance.05.sourcename.sql new file mode 100644 index 0000000000..a2b70c683f --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.05.sourcename.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_source + CHANGE name name VARCHAR(255) NOT NULL COLLATE {$COLLATE_SORT}; diff --git a/resources/sql/autopatches/20160308.nuance.06.label.sql b/resources/sql/autopatches/20160308.nuance.06.label.sql new file mode 100644 index 0000000000..4ba67ed3e4 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.06.label.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + DROP sourceLabel; diff --git a/resources/sql/autopatches/20160308.nuance.07.itemtype.sql b/resources/sql/autopatches/20160308.nuance.07.itemtype.sql new file mode 100644 index 0000000000..d34b5c77e9 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.07.itemtype.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + ADD itemType VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.08.itemkey.sql b/resources/sql/autopatches/20160308.nuance.08.itemkey.sql new file mode 100644 index 0000000000..12b6a88673 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.08.itemkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + ADD itemKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.09.itemcontainer.sql b/resources/sql/autopatches/20160308.nuance.09.itemcontainer.sql new file mode 100644 index 0000000000..0b76c76827 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.09.itemcontainer.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + ADD itemContainerKey VARCHAR(64) COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.10.itemkeyu.sql b/resources/sql/autopatches/20160308.nuance.10.itemkeyu.sql new file mode 100644 index 0000000000..b455ada156 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.10.itemkeyu.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_nuance.nuance_item + SET itemKey = id WHERE itemKey = ''; diff --git a/resources/sql/autopatches/20160308.nuance.11.requestor.sql b/resources/sql/autopatches/20160308.nuance.11.requestor.sql new file mode 100644 index 0000000000..590b44197a --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.11.requestor.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + CHANGE requestorPHID requestorPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20160308.nuance.12.queue.sql b/resources/sql/autopatches/20160308.nuance.12.queue.sql new file mode 100644 index 0000000000..bb0554b57d --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.12.queue.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + CHANGE queuePHID queuePHID VARBINARY(64); diff --git a/resources/sql/autopatches/20160316.lfs.01.token.resource.sql b/resources/sql/autopatches/20160316.lfs.01.token.resource.sql new file mode 100644 index 0000000000..7be5bbda54 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.01.token.resource.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + CHANGE objectPHID tokenResource VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20160316.lfs.02.token.user.sql b/resources/sql/autopatches/20160316.lfs.02.token.user.sql new file mode 100644 index 0000000000..72174d6fe8 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.02.token.user.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + ADD userPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20160316.lfs.03.token.properties.sql b/resources/sql/autopatches/20160316.lfs.03.token.properties.sql new file mode 100644 index 0000000000..2cb4449d73 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.03.token.properties.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken + ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160316.lfs.04.token.default.sql b/resources/sql/autopatches/20160316.lfs.04.token.default.sql new file mode 100644 index 0000000000..0f0ce4abc4 --- /dev/null +++ b/resources/sql/autopatches/20160316.lfs.04.token.default.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_auth.auth_temporarytoken + SET properties = '{}' WHERE properties = ''; diff --git a/resources/sql/autopatches/20160317.lfs.01.ref.sql b/resources/sql/autopatches/20160317.lfs.01.ref.sql new file mode 100644 index 0000000000..18c45c525a --- /dev/null +++ b/resources/sql/autopatches/20160317.lfs.01.ref.sql @@ -0,0 +1,11 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_gitlfsref ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + repositoryPHID VARBINARY(64) NOT NULL, + objectHash BINARY(64) NOT NULL, + byteSize BIGINT UNSIGNED NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + filePHID VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_hash` (repositoryPHID, objectHash) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160321.nuance.01.taskbridge.sql b/resources/sql/autopatches/20160321.nuance.01.taskbridge.sql new file mode 100644 index 0000000000..53b80fe7d9 --- /dev/null +++ b/resources/sql/autopatches/20160321.nuance.01.taskbridge.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task + ADD bridgedObjectPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20160322.nuance.01.itemcommand.sql b/resources/sql/autopatches/20160322.nuance.01.itemcommand.sql new file mode 100644 index 0000000000..f356db4947 --- /dev/null +++ b/resources/sql/autopatches/20160322.nuance.01.itemcommand.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_nuance.nuance_itemcommand ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + itemPHID VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + command VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, + parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_item` (itemPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160323.badgemigrate.sql b/resources/sql/autopatches/20160323.badgemigrate.sql new file mode 100644 index 0000000000..c7341b287b --- /dev/null +++ b/resources/sql/autopatches/20160323.badgemigrate.sql @@ -0,0 +1,6 @@ +/* PhabricatorBadgeHasRecipientEdgeType::TYPECONST = 59 */ + +INSERT IGNORE INTO {$NAMESPACE}_badges.badges_award + (badgePHID, recipientPHID, awarderPHID, dateCreated, dateModified) + SELECT src, dst, 'PHID-VOID-00000000000000000000', dateCreated, dateCreated + FROM {$NAMESPACE}_badges.edge WHERE type = 59; diff --git a/resources/sql/autopatches/20160329.nuance.01.requestor.sql b/resources/sql/autopatches/20160329.nuance.01.requestor.sql new file mode 100644 index 0000000000..63b0a30210 --- /dev/null +++ b/resources/sql/autopatches/20160329.nuance.01.requestor.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_nuance.nuance_requestor; diff --git a/resources/sql/autopatches/20160329.nuance.02.requestorsource.sql b/resources/sql/autopatches/20160329.nuance.02.requestorsource.sql new file mode 100644 index 0000000000..1977022622 --- /dev/null +++ b/resources/sql/autopatches/20160329.nuance.02.requestorsource.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_nuance.nuance_requestorsource; diff --git a/resources/sql/autopatches/20160329.nuance.03.requestorxaction.sql b/resources/sql/autopatches/20160329.nuance.03.requestorxaction.sql new file mode 100644 index 0000000000..f05c589a5e --- /dev/null +++ b/resources/sql/autopatches/20160329.nuance.03.requestorxaction.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_nuance.nuance_requestortransaction; diff --git a/resources/sql/autopatches/20160329.nuance.04.requestorcomment.sql b/resources/sql/autopatches/20160329.nuance.04.requestorcomment.sql new file mode 100644 index 0000000000..f97db8514b --- /dev/null +++ b/resources/sql/autopatches/20160329.nuance.04.requestorcomment.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS {$NAMESPACE}_nuance.nuance_requestortransaction_comment; diff --git a/resources/sql/autopatches/20160330.badges.migratequality.sql b/resources/sql/autopatches/20160330.badges.migratequality.sql new file mode 100644 index 0000000000..e86b6df1b8 --- /dev/null +++ b/resources/sql/autopatches/20160330.badges.migratequality.sql @@ -0,0 +1,32 @@ +/* Change quality from color to int */ + +UPDATE {$NAMESPACE}_badges.badges_badge + SET quality = 140 + WHERE quality = 'grey'; + +UPDATE {$NAMESPACE}_badges.badges_badge + SET quality = 120 + WHERE quality = 'white'; + +UPDATE {$NAMESPACE}_badges.badges_badge + SET quality = 100 + WHERE quality = 'green'; + +UPDATE {$NAMESPACE}_badges.badges_badge + SET quality = 80 + WHERE quality = 'blue'; + +UPDATE {$NAMESPACE}_badges.badges_badge + SET quality = 60 + WHERE quality = 'indigo'; + +UPDATE {$NAMESPACE}_badges.badges_badge + SET quality = 40 + WHERE quality = 'orange'; + +UPDATE {$NAMESPACE}_badges.badges_badge + SET quality = 20 + WHERE quality = 'yellow'; + +ALTER TABLE {$NAMESPACE}_badges.badges_badge + MODIFY quality INT UNSIGNED NOT NULL; diff --git a/resources/sql/autopatches/20160330.badges.qualityxaction.mig.sql b/resources/sql/autopatches/20160330.badges.qualityxaction.mig.sql new file mode 100644 index 0000000000..3079618f85 --- /dev/null +++ b/resources/sql/autopatches/20160330.badges.qualityxaction.mig.sql @@ -0,0 +1,59 @@ +/* Migrate old badge quality transactions */ + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET oldValue = 140 + WHERE oldValue = '"grey"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET oldValue = 120 + WHERE oldValue = '"white"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET oldValue = 100 + WHERE oldValue = '"green"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET oldValue = 80 + WHERE oldValue = '"blue"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET oldValue = 60 + WHERE oldValue = '"indigo"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET oldValue = 40 + WHERE oldValue = '"orange"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET oldValue = 20 + WHERE oldValue = '"yellow"' AND transactionType = 'badges:quality'; + + + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET newValue = 140 + WHERE newValue = '"grey"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET newValue = 120 + WHERE newValue = '"white"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET newValue = 100 + WHERE newValue = '"green"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET newValue = 80 + WHERE newValue = '"blue"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET newValue = 60 + WHERE newValue = '"indigo"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET newValue = 40 + WHERE newValue = '"orange"' AND transactionType = 'badges:quality'; + +UPDATE {$NAMESPACE}_badges.badges_transaction + SET newValue = 20 + WHERE newValue = '"yellow"' AND transactionType = 'badges:quality'; diff --git a/resources/sql/autopatches/20160331.fund.comments.1.sql b/resources/sql/autopatches/20160331.fund.comments.1.sql new file mode 100644 index 0000000000..a4c1e2b5fa --- /dev/null +++ b/resources/sql/autopatches/20160331.fund.comments.1.sql @@ -0,0 +1,18 @@ +CREATE TABLE {$NAMESPACE}_fund.fund_initiativetransaction_comment ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARCHAR(64) NOT NULL, + transactionPHID VARCHAR(64), + authorPHID VARCHAR(64) NOT NULL, + viewPolicy VARCHAR(64) NOT NULL, + editPolicy VARCHAR(64) NOT NULL, + commentVersion INT UNSIGNED NOT NULL, + content LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + contentSource LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + isDeleted BOOL NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_version` (transactionPHID, commentVersion) + +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160404.oauth.1.xaction.sql b/resources/sql/autopatches/20160404.oauth.1.xaction.sql new file mode 100644 index 0000000000..70b7065ea2 --- /dev/null +++ b/resources/sql/autopatches/20160404.oauth.1.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_oauth_server.oauth_server_transaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160405.oauth.2.disable.sql b/resources/sql/autopatches/20160405.oauth.2.disable.sql new file mode 100644 index 0000000000..fd26ce8a6e --- /dev/null +++ b/resources/sql/autopatches/20160405.oauth.2.disable.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_oauth_server.oauth_server_oauthserverclient + ADD isDisabled BOOL NOT NULL; diff --git a/resources/sql/patches/20130715.votecomments.php b/resources/sql/patches/20130715.votecomments.php index 0d540596b3..1066126318 100644 --- a/resources/sql/patches/20130715.votecomments.php +++ b/resources/sql/patches/20130715.votecomments.php @@ -46,9 +46,8 @@ foreach ($comments as $comment) { PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST, PhabricatorSlowvotePollPHIDType::TYPECONST); - $source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(); + $content_source = PhabricatorContentSource::newForSource( + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(); queryfx( $conn_w, diff --git a/resources/sql/patches/20130728.ponderxcomment.php b/resources/sql/patches/20130728.ponderxcomment.php index 92e858f95e..9a7413e351 100644 --- a/resources/sql/patches/20130728.ponderxcomment.php +++ b/resources/sql/patches/20130728.ponderxcomment.php @@ -49,8 +49,7 @@ foreach ($rows as $row) { 1, $row['content'], PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(), + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(), 0, $row['dateCreated'], $row['dateModified']); @@ -73,8 +72,7 @@ foreach ($rows as $row) { 'null', 'null', PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(), + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(), '[]', $row['dateCreated'], $row['dateModified']); diff --git a/resources/sql/patches/20130801.pastexactions.php b/resources/sql/patches/20130801.pastexactions.php index 1977eb984e..75c2ece940 100644 --- a/resources/sql/patches/20130801.pastexactions.php +++ b/resources/sql/patches/20130801.pastexactions.php @@ -34,8 +34,7 @@ foreach ($rows as $row) { 'null', $row['filePHID'], PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(), + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(), '[]', $row['dateCreated'], $row['dateCreated'], diff --git a/resources/sql/patches/20130926.dinline.php b/resources/sql/patches/20130926.dinline.php index f2d9e9f205..7b67adc9b7 100644 --- a/resources/sql/patches/20130926.dinline.php +++ b/resources/sql/patches/20130926.dinline.php @@ -10,8 +10,7 @@ $dst_table = 'differential_transaction_comment'; echo pht('Migrating Differential inline comments to new format...')."\n"; $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(); + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(); $rows = new LiskRawMigrationIterator($conn_w, $src_table); foreach ($rows as $row) { diff --git a/resources/sql/patches/20131020.pxactionmig.php b/resources/sql/patches/20131020.pxactionmig.php index 7bf4416cd0..3d593d6e34 100644 --- a/resources/sql/patches/20131020.pxactionmig.php +++ b/resources/sql/patches/20131020.pxactionmig.php @@ -10,8 +10,7 @@ $dst_table = 'project_transaction'; echo pht('Migrating Project transactions to new format...')."\n"; $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array())->serialize(); + PhabricatorOldWorldContentSource::SOURCECONST)->serialize(); $rows = new LiskRawMigrationIterator($conn_w, $src_table); foreach ($rows as $row) { diff --git a/scripts/setup/manage_nuance.php b/scripts/setup/manage_nuance.php new file mode 100755 index 0000000000..ebf312305a --- /dev/null +++ b/scripts/setup/manage_nuance.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline(pht('manage Nuance')); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilClassMapQuery()) + ->setAncestorClass('NuanceManagementWorkflow') + ->execute(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6dd1b25117..a072ef15ac 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -473,7 +473,6 @@ phutil_register_library_map(array( 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', 'DifferentialPathField' => 'applications/differential/customfield/DifferentialPathField.php', - 'DifferentialPrimaryPaneView' => 'applications/differential/view/DifferentialPrimaryPaneView.php', 'DifferentialProjectReviewersField' => 'applications/differential/customfield/DifferentialProjectReviewersField.php', 'DifferentialProjectsField' => 'applications/differential/customfield/DifferentialProjectsField.php', 'DifferentialQueryConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryConduitAPIMethod.php', @@ -508,7 +507,6 @@ phutil_register_library_map(array( 'DifferentialRevisionControlSystem' => 'applications/differential/constants/DifferentialRevisionControlSystem.php', 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php', 'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php', - 'DifferentialRevisionDetailView' => 'applications/differential/view/DifferentialRevisionDetailView.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php', 'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php', @@ -635,6 +633,9 @@ phutil_register_library_map(array( 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', + 'DiffusionGitLFSAuthenticateWorkflow' => 'applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php', + 'DiffusionGitLFSResponse' => 'applications/diffusion/response/DiffusionGitLFSResponse.php', + 'DiffusionGitLFSTemporaryTokenType' => 'applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php', 'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php', 'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php', 'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php', @@ -840,10 +841,16 @@ phutil_register_library_map(array( 'DoorkeeperAsanaRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php', 'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php', 'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php', + 'DoorkeeperBridgeGitHub' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php', + 'DoorkeeperBridgeGitHubIssue' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php', + 'DoorkeeperBridgeGitHubUser' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php', 'DoorkeeperBridgeJIRA' => 'applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php', 'DoorkeeperBridgeJIRATestCase' => 'applications/doorkeeper/bridge/__tests__/DoorkeeperBridgeJIRATestCase.php', + 'DoorkeeperBridgedObjectCurtainExtension' => 'applications/doorkeeper/engineextension/DoorkeeperBridgedObjectCurtainExtension.php', + 'DoorkeeperBridgedObjectInterface' => 'applications/doorkeeper/bridge/DoorkeeperBridgedObjectInterface.php', 'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php', 'DoorkeeperExternalObject' => 'applications/doorkeeper/storage/DoorkeeperExternalObject.php', + 'DoorkeeperExternalObjectPHIDType' => 'applications/doorkeeper/phid/DoorkeeperExternalObjectPHIDType.php', 'DoorkeeperExternalObjectQuery' => 'applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php', 'DoorkeeperFeedStoryPublisher' => 'applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php', 'DoorkeeperFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperFeedWorker.php', @@ -886,7 +893,7 @@ phutil_register_library_map(array( 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', 'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php', 'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php', - 'DrydockCommandError' => 'applications/drydock/DrydockCommandError/DrydockCommandError.php', + 'DrydockCommandError' => 'applications/drydock/exception/DrydockCommandError.php', 'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php', 'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php', 'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php', @@ -1017,6 +1024,7 @@ phutil_register_library_map(array( 'FundInitiative' => 'applications/fund/storage/FundInitiative.php', 'FundInitiativeBackController' => 'applications/fund/controller/FundInitiativeBackController.php', 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', + 'FundInitiativeCommentController' => 'applications/fund/controller/FundInitiativeCommentController.php', 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', 'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php', @@ -1027,6 +1035,7 @@ phutil_register_library_map(array( 'FundInitiativeReplyHandler' => 'applications/fund/mail/FundInitiativeReplyHandler.php', 'FundInitiativeSearchEngine' => 'applications/fund/query/FundInitiativeSearchEngine.php', 'FundInitiativeTransaction' => 'applications/fund/storage/FundInitiativeTransaction.php', + 'FundInitiativeTransactionComment' => 'applications/fund/storage/FundInitiativeTransactionComment.php', 'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php', 'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php', 'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php', @@ -1103,6 +1112,9 @@ phutil_register_library_map(array( 'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php', 'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php', 'HarbormasterBuiltinBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php', + 'HarbormasterCircleCIBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php', + 'HarbormasterCircleCIBuildableInterface' => 'applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php', + 'HarbormasterCircleCIHookController' => 'applications/harbormaster/controller/HarbormasterCircleCIHookController.php', 'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php', 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', @@ -1220,6 +1232,7 @@ phutil_register_library_map(array( 'HeraldRulePHIDType' => 'applications/herald/phid/HeraldRulePHIDType.php', 'HeraldRuleQuery' => 'applications/herald/query/HeraldRuleQuery.php', 'HeraldRuleSearchEngine' => 'applications/herald/query/HeraldRuleSearchEngine.php', + 'HeraldRuleSerializer' => 'applications/herald/editor/HeraldRuleSerializer.php', 'HeraldRuleTestCase' => 'applications/herald/storage/__tests__/HeraldRuleTestCase.php', 'HeraldRuleTransaction' => 'applications/herald/storage/HeraldRuleTransaction.php', 'HeraldRuleTransactionComment' => 'applications/herald/storage/HeraldRuleTransactionComment.php', @@ -1419,23 +1432,47 @@ phutil_register_library_map(array( 'MultimeterViewer' => 'applications/multimeter/storage/MultimeterViewer.php', 'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php', 'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php', + 'NuanceContentSource' => 'applications/nuance/contentsource/NuanceContentSource.php', 'NuanceController' => 'applications/nuance/controller/NuanceController.php', - 'NuanceCreateItemConduitAPIMethod' => 'applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', + 'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php', + 'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php', + 'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php', + 'NuanceGitHubRawEvent' => 'applications/nuance/github/NuanceGitHubRawEvent.php', + 'NuanceGitHubRawEventTestCase' => 'applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php', + 'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php', + 'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php', + 'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php', + 'NuanceImportCursorData' => 'applications/nuance/storage/NuanceImportCursorData.php', + 'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php', + 'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php', 'NuanceItem' => 'applications/nuance/storage/NuanceItem.php', - 'NuanceItemEditController' => 'applications/nuance/controller/NuanceItemEditController.php', + 'NuanceItemActionController' => 'applications/nuance/controller/NuanceItemActionController.php', + 'NuanceItemCommand' => 'applications/nuance/storage/NuanceItemCommand.php', + 'NuanceItemCommandQuery' => 'applications/nuance/query/NuanceItemCommandQuery.php', + 'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php', 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', + 'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php', + 'NuanceItemManageController' => 'applications/nuance/controller/NuanceItemManageController.php', 'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php', 'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php', + 'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php', 'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php', 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', + 'NuanceItemType' => 'applications/nuance/item/NuanceItemType.php', + 'NuanceItemUpdateWorker' => 'applications/nuance/worker/NuanceItemUpdateWorker.php', 'NuanceItemViewController' => 'applications/nuance/controller/NuanceItemViewController.php', + 'NuanceManagementImportWorkflow' => 'applications/nuance/management/NuanceManagementImportWorkflow.php', + 'NuanceManagementUpdateWorkflow' => 'applications/nuance/management/NuanceManagementUpdateWorkflow.php', + 'NuanceManagementWorkflow' => 'applications/nuance/management/NuanceManagementWorkflow.php', 'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php', 'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php', 'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php', + 'NuanceQueueController' => 'applications/nuance/controller/NuanceQueueController.php', 'NuanceQueueDatasource' => 'applications/nuance/typeahead/NuanceQueueDatasource.php', 'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php', + 'NuanceQueueEditEngine' => 'applications/nuance/editor/NuanceQueueEditEngine.php', 'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php', 'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php', 'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php', @@ -1445,28 +1482,20 @@ phutil_register_library_map(array( 'NuanceQueueTransactionComment' => 'applications/nuance/storage/NuanceQueueTransactionComment.php', 'NuanceQueueTransactionQuery' => 'applications/nuance/query/NuanceQueueTransactionQuery.php', 'NuanceQueueViewController' => 'applications/nuance/controller/NuanceQueueViewController.php', - 'NuanceRequestor' => 'applications/nuance/storage/NuanceRequestor.php', - 'NuanceRequestorEditController' => 'applications/nuance/controller/NuanceRequestorEditController.php', - 'NuanceRequestorEditor' => 'applications/nuance/editor/NuanceRequestorEditor.php', - 'NuanceRequestorPHIDType' => 'applications/nuance/phid/NuanceRequestorPHIDType.php', - 'NuanceRequestorQuery' => 'applications/nuance/query/NuanceRequestorQuery.php', - 'NuanceRequestorSource' => 'applications/nuance/storage/NuanceRequestorSource.php', - 'NuanceRequestorTransaction' => 'applications/nuance/storage/NuanceRequestorTransaction.php', - 'NuanceRequestorTransactionComment' => 'applications/nuance/storage/NuanceRequestorTransactionComment.php', - 'NuanceRequestorTransactionQuery' => 'applications/nuance/query/NuanceRequestorTransactionQuery.php', - 'NuanceRequestorViewController' => 'applications/nuance/controller/NuanceRequestorViewController.php', 'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php', 'NuanceSource' => 'applications/nuance/storage/NuanceSource.php', 'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php', - 'NuanceSourceCreateController' => 'applications/nuance/controller/NuanceSourceCreateController.php', + 'NuanceSourceController' => 'applications/nuance/controller/NuanceSourceController.php', 'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php', 'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php', 'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php', 'NuanceSourceDefinitionTestCase' => 'applications/nuance/source/__tests__/NuanceSourceDefinitionTestCase.php', 'NuanceSourceEditController' => 'applications/nuance/controller/NuanceSourceEditController.php', + 'NuanceSourceEditEngine' => 'applications/nuance/editor/NuanceSourceEditEngine.php', 'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php', 'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php', 'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php', + 'NuanceSourceNameNgrams' => 'applications/nuance/storage/NuanceSourceNameNgrams.php', 'NuanceSourcePHIDType' => 'applications/nuance/phid/NuanceSourcePHIDType.php', 'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php', 'NuanceSourceSearchEngine' => 'applications/nuance/query/NuanceSourceSearchEngine.php', @@ -1475,6 +1504,7 @@ phutil_register_library_map(array( 'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php', 'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php', 'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php', + 'NuanceWorker' => 'applications/nuance/worker/NuanceWorker.php', 'OwnersConduitAPIMethod' => 'applications/owners/conduit/OwnersConduitAPIMethod.php', 'OwnersEditConduitAPIMethod' => 'applications/owners/conduit/OwnersEditConduitAPIMethod.php', 'OwnersPackageReplyHandler' => 'applications/owners/mail/OwnersPackageReplyHandler.php', @@ -1506,6 +1536,9 @@ phutil_register_library_map(array( 'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php', 'PHUICrumbView' => 'view/phui/PHUICrumbView.php', 'PHUICrumbsView' => 'view/phui/PHUICrumbsView.php', + 'PHUICurtainExtension' => 'view/extension/PHUICurtainExtension.php', + 'PHUICurtainPanelView' => 'view/layout/PHUICurtainPanelView.php', + 'PHUICurtainView' => 'view/layout/PHUICurtainView.php', 'PHUIDiffInlineCommentDetailView' => 'infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php', 'PHUIDiffInlineCommentEditView' => 'infrastructure/diff/view/PHUIDiffInlineCommentEditView.php', 'PHUIDiffInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php', @@ -1617,6 +1650,7 @@ phutil_register_library_map(array( 'PassphraseSSHPrivateKeyTextCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php', 'PassphraseSchemaSpec' => 'applications/passphrase/storage/PassphraseSchemaSpec.php', 'PassphraseSecret' => 'applications/passphrase/storage/PassphraseSecret.php', + 'PassphraseTokenCredentialType' => 'applications/passphrase/credentialtype/PassphraseTokenCredentialType.php', 'PasteConduitAPIMethod' => 'applications/paste/conduit/PasteConduitAPIMethod.php', 'PasteCreateConduitAPIMethod' => 'applications/paste/conduit/PasteCreateConduitAPIMethod.php', 'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php', @@ -1776,6 +1810,7 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php', 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', 'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php', + 'PhabricatorAuthLogoutConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php', 'PhabricatorAuthMainMenuBarExtension' => 'applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php', 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', @@ -1793,6 +1828,8 @@ phutil_register_library_map(array( 'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php', 'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php', 'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php', + 'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php', + 'PhabricatorAuthPasswordResetTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthPasswordResetTemporaryTokenType.php', 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', 'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php', 'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php', @@ -1814,13 +1851,18 @@ phutil_register_library_map(array( 'PhabricatorAuthSSHPublicKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php', 'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php', 'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php', + 'PhabricatorAuthSessionEngineExtension' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtension.php', + 'PhabricatorAuthSessionEngineExtensionModule' => 'applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php', 'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php', 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', + 'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php', 'PhabricatorAuthTemporaryToken' => 'applications/auth/storage/PhabricatorAuthTemporaryToken.php', 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php', 'PhabricatorAuthTemporaryTokenQuery' => 'applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php', + 'PhabricatorAuthTemporaryTokenType' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenType.php', + 'PhabricatorAuthTemporaryTokenTypeModule' => 'applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php', 'PhabricatorAuthTerminateSessionController' => 'applications/auth/controller/PhabricatorAuthTerminateSessionController.php', 'PhabricatorAuthTryFactorAction' => 'applications/auth/action/PhabricatorAuthTryFactorAction.php', 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', @@ -1830,12 +1872,16 @@ phutil_register_library_map(array( 'PhabricatorBadgeHasRecipientEdgeType' => 'applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php', 'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.php', 'PhabricatorBadgesArchiveController' => 'applications/badges/controller/PhabricatorBadgesArchiveController.php', + 'PhabricatorBadgesAward' => 'applications/badges/storage/PhabricatorBadgesAward.php', + 'PhabricatorBadgesAwardController' => 'applications/badges/controller/PhabricatorBadgesAwardController.php', + 'PhabricatorBadgesAwardQuery' => 'applications/badges/query/PhabricatorBadgesAwardQuery.php', 'PhabricatorBadgesBadge' => 'applications/badges/storage/PhabricatorBadgesBadge.php', 'PhabricatorBadgesCommentController' => 'applications/badges/controller/PhabricatorBadgesCommentController.php', 'PhabricatorBadgesController' => 'applications/badges/controller/PhabricatorBadgesController.php', 'PhabricatorBadgesCreateCapability' => 'applications/badges/capability/PhabricatorBadgesCreateCapability.php', 'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php', 'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php', + 'PhabricatorBadgesEditConduitAPIMethod' => 'applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php', 'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php', 'PhabricatorBadgesEditEngine' => 'applications/badges/editor/PhabricatorBadgesEditEngine.php', 'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php', @@ -1844,11 +1890,13 @@ phutil_register_library_map(array( 'PhabricatorBadgesListController' => 'applications/badges/controller/PhabricatorBadgesListController.php', 'PhabricatorBadgesMailReceiver' => 'applications/badges/mail/PhabricatorBadgesMailReceiver.php', 'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php', + 'PhabricatorBadgesQuality' => 'applications/badges/constants/PhabricatorBadgesQuality.php', 'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.php', 'PhabricatorBadgesRecipientsListView' => 'applications/badges/view/PhabricatorBadgesRecipientsListView.php', 'PhabricatorBadgesRemoveRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php', 'PhabricatorBadgesReplyHandler' => 'applications/badges/mail/PhabricatorBadgesReplyHandler.php', 'PhabricatorBadgesSchemaSpec' => 'applications/badges/storage/PhabricatorBadgesSchemaSpec.php', + 'PhabricatorBadgesSearchConduitAPIMethod' => 'applications/badges/conduit/PhabricatorBadgesSearchConduitAPIMethod.php', 'PhabricatorBadgesSearchEngine' => 'applications/badges/query/PhabricatorBadgesSearchEngine.php', 'PhabricatorBadgesTransaction' => 'applications/badges/storage/PhabricatorBadgesTransaction.php', 'PhabricatorBadgesTransactionComment' => 'applications/badges/storage/PhabricatorBadgesTransactionComment.php', @@ -1880,6 +1928,7 @@ phutil_register_library_map(array( 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuildbotController' => 'extensions/buildbot/controller/PhabricatorBuildbotController.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', + 'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php', 'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php', 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', 'PhabricatorCacheGeneralGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php', @@ -1954,6 +2003,7 @@ phutil_register_library_map(array( 'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/PhabricatorConduitCertificateToken.php', 'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/PhabricatorConduitConnectionLog.php', 'PhabricatorConduitConsoleController' => 'applications/conduit/controller/PhabricatorConduitConsoleController.php', + 'PhabricatorConduitContentSource' => 'infrastructure/contentsource/PhabricatorConduitContentSource.php', 'PhabricatorConduitController' => 'applications/conduit/controller/PhabricatorConduitController.php', 'PhabricatorConduitDAO' => 'applications/conduit/storage/PhabricatorConduitDAO.php', 'PhabricatorConduitEditField' => 'applications/transactions/editfield/PhabricatorConduitEditField.php', @@ -2041,8 +2091,10 @@ phutil_register_library_map(array( 'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php', 'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php', 'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php', - 'PhabricatorContentSource' => 'applications/metamta/contentsource/PhabricatorContentSource.php', - 'PhabricatorContentSourceView' => 'applications/metamta/contentsource/PhabricatorContentSourceView.php', + 'PhabricatorConsoleContentSource' => 'infrastructure/contentsource/PhabricatorConsoleContentSource.php', + 'PhabricatorContentSource' => 'infrastructure/contentsource/PhabricatorContentSource.php', + 'PhabricatorContentSourceModule' => 'infrastructure/contentsource/PhabricatorContentSourceModule.php', + 'PhabricatorContentSourceView' => 'infrastructure/contentsource/PhabricatorContentSourceView.php', 'PhabricatorContributedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorContributedToObjectEdgeType.php', 'PhabricatorController' => 'applications/base/controller/PhabricatorController.php', 'PhabricatorCookies' => 'applications/auth/constants/PhabricatorCookies.php', @@ -2099,6 +2151,7 @@ phutil_register_library_map(array( 'PhabricatorDaemonBulkJobMonitorController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobMonitorController.php', 'PhabricatorDaemonBulkJobViewController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php', 'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/PhabricatorDaemonConsoleController.php', + 'PhabricatorDaemonContentSource' => 'infrastructure/daemon/contentsource/PhabricatorDaemonContentSource.php', 'PhabricatorDaemonController' => 'applications/daemon/controller/PhabricatorDaemonController.php', 'PhabricatorDaemonDAO' => 'applications/daemon/storage/PhabricatorDaemonDAO.php', 'PhabricatorDaemonEventListener' => 'applications/daemon/event/PhabricatorDaemonEventListener.php', @@ -2139,7 +2192,6 @@ phutil_register_library_map(array( 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php', 'PhabricatorDashboardDashboardPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardDashboardPHIDType.php', 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php', - 'PhabricatorDashboardHistoryController' => 'applications/dashboard/controller/PhabricatorDashboardHistoryController.php', 'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php', 'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/PhabricatorDashboardInstallController.php', 'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php', @@ -2263,6 +2315,7 @@ phutil_register_library_map(array( 'PhabricatorElasticFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php', 'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php', 'PhabricatorEmailAddressesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php', + 'PhabricatorEmailContentSource' => 'applications/metamta/contentsource/PhabricatorEmailContentSource.php', 'PhabricatorEmailFormatSettingsPanel' => 'applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php', 'PhabricatorEmailLoginController' => 'applications/auth/controller/PhabricatorEmailLoginController.php', 'PhabricatorEmailPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php', @@ -2308,6 +2361,7 @@ phutil_register_library_map(array( 'PhabricatorFactSimpleSpec' => 'applications/fact/spec/PhabricatorFactSimpleSpec.php', 'PhabricatorFactSpec' => 'applications/fact/spec/PhabricatorFactSpec.php', 'PhabricatorFactUpdateIterator' => 'applications/fact/extract/PhabricatorFactUpdateIterator.php', + 'PhabricatorFaxContentSource' => 'infrastructure/contentsource/PhabricatorFaxContentSource.php', 'PhabricatorFeedApplication' => 'applications/feed/application/PhabricatorFeedApplication.php', 'PhabricatorFeedBuilder' => 'applications/feed/builder/PhabricatorFeedBuilder.php', 'PhabricatorFeedConfigOptions' => 'applications/feed/config/PhabricatorFeedConfigOptions.php', @@ -2325,6 +2379,7 @@ phutil_register_library_map(array( 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php', 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', + 'PhabricatorFileAccessTemporaryTokenType' => 'applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php', 'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php', 'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php', 'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php', @@ -2423,6 +2478,7 @@ phutil_register_library_map(array( 'PhabricatorHandlePool' => 'applications/phid/handle/pool/PhabricatorHandlePool.php', 'PhabricatorHandlePoolTestCase' => 'applications/phid/handle/pool/__tests__/PhabricatorHandlePoolTestCase.php', 'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php', + 'PhabricatorHandleRemarkupRule' => 'applications/phid/remarkup/PhabricatorHandleRemarkupRule.php', 'PhabricatorHandlesEditField' => 'applications/transactions/editfield/PhabricatorHandlesEditField.php', 'PhabricatorHarbormasterApplication' => 'applications/harbormaster/application/PhabricatorHarbormasterApplication.php', 'PhabricatorHarbormasterConfigOptions' => 'applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php', @@ -2435,6 +2491,7 @@ phutil_register_library_map(array( 'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php', 'PhabricatorHelpMainMenuBarExtension' => 'applications/help/extension/PhabricatorHelpMainMenuBarExtension.php', 'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php', + 'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php', 'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php', 'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php', 'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php', @@ -2469,6 +2526,7 @@ phutil_register_library_map(array( 'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php', 'PhabricatorIteratedMD5PasswordHasher' => 'infrastructure/util/password/PhabricatorIteratedMD5PasswordHasher.php', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php', + 'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php', 'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', @@ -2482,6 +2540,7 @@ phutil_register_library_map(array( 'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php', 'PhabricatorLinkProfilePanel' => 'applications/search/profilepanel/PhabricatorLinkProfilePanel.php', 'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php', + 'PhabricatorLipsumContentSource' => 'infrastructure/contentsource/PhabricatorLipsumContentSource.php', 'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php', 'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php', 'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php', @@ -2645,15 +2704,17 @@ phutil_register_library_map(array( 'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php', 'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php', 'PhabricatorOAuth1AuthProvider' => 'applications/auth/provider/PhabricatorOAuth1AuthProvider.php', + 'PhabricatorOAuth1SecretTemporaryTokenType' => 'applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php', 'PhabricatorOAuth2AuthProvider' => 'applications/auth/provider/PhabricatorOAuth2AuthProvider.php', 'PhabricatorOAuthAuthProvider' => 'applications/auth/provider/PhabricatorOAuthAuthProvider.php', 'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/PhabricatorOAuthClientAuthorization.php', 'PhabricatorOAuthClientAuthorizationQuery' => 'applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php', 'PhabricatorOAuthClientController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientController.php', - 'PhabricatorOAuthClientDeleteController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php', + 'PhabricatorOAuthClientDisableController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php', 'PhabricatorOAuthClientEditController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php', 'PhabricatorOAuthClientListController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php', 'PhabricatorOAuthClientSecretController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php', + 'PhabricatorOAuthClientTestController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php', 'PhabricatorOAuthClientViewController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php', 'PhabricatorOAuthResponse' => 'applications/oauthserver/PhabricatorOAuthResponse.php', 'PhabricatorOAuthServer' => 'applications/oauthserver/PhabricatorOAuthServer.php', @@ -2670,10 +2731,13 @@ phutil_register_library_map(array( 'PhabricatorOAuthServerController' => 'applications/oauthserver/controller/PhabricatorOAuthServerController.php', 'PhabricatorOAuthServerCreateClientsCapability' => 'applications/oauthserver/capability/PhabricatorOAuthServerCreateClientsCapability.php', 'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/PhabricatorOAuthServerDAO.php', + 'PhabricatorOAuthServerEditEngine' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php', + 'PhabricatorOAuthServerEditor' => 'applications/oauthserver/editor/PhabricatorOAuthServerEditor.php', 'PhabricatorOAuthServerScope' => 'applications/oauthserver/PhabricatorOAuthServerScope.php', 'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php', - 'PhabricatorOAuthServerTestController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTestController.php', 'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php', + 'PhabricatorOAuthServerTransaction' => 'applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php', + 'PhabricatorOAuthServerTransactionQuery' => 'applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php', 'PhabricatorObjectHandle' => 'applications/phid/PhabricatorObjectHandle.php', 'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaSubtaskEdgeType.php', 'PhabricatorObjectHasAsanaTaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaTaskEdgeType.php', @@ -2694,6 +2758,7 @@ phutil_register_library_map(array( 'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php', 'PhabricatorObjectUsesCredentialsEdgeType' => 'applications/transactions/edges/PhabricatorObjectUsesCredentialsEdgeType.php', 'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php', + 'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php', 'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php', 'PhabricatorOpcodeCacheSpec' => 'applications/cache/spec/PhabricatorOpcodeCacheSpec.php', 'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php', @@ -2828,6 +2893,7 @@ phutil_register_library_map(array( 'PhabricatorPholioConfigOptions' => 'applications/pholio/config/PhabricatorPholioConfigOptions.php', 'PhabricatorPholioMockTestDataGenerator' => 'applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php', 'PhabricatorPhortuneApplication' => 'applications/phortune/application/PhabricatorPhortuneApplication.php', + 'PhabricatorPhortuneContentSource' => 'applications/phortune/contentsource/PhabricatorPhortuneContentSource.php', 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php', 'PhabricatorPhortuneManagementWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php', 'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php', @@ -3015,6 +3081,7 @@ phutil_register_library_map(array( 'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php', 'PhabricatorProjectWorkboardBackgroundColor' => 'applications/project/constants/PhabricatorProjectWorkboardBackgroundColor.php', 'PhabricatorProjectWorkboardProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php', + 'PhabricatorProjectsCurtainExtension' => 'applications/project/engineextension/PhabricatorProjectsCurtainExtension.php', 'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php', 'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php', 'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php', @@ -3066,6 +3133,8 @@ phutil_register_library_map(array( 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php', + 'PhabricatorRepositoryGitLFSRef' => 'applications/repository/storage/PhabricatorRepositoryGitLFSRef.php', + 'PhabricatorRepositoryGitLFSRefQuery' => 'applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php', 'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php', 'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php', 'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php', @@ -3325,6 +3394,7 @@ phutil_register_library_map(array( 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php', 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php', 'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php', + 'PhabricatorSubscriptionsCurtainExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php', 'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php', 'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php', 'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php', @@ -3393,6 +3463,7 @@ phutil_register_library_map(array( 'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php', 'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php', 'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php', + 'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php', 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', @@ -3424,7 +3495,9 @@ phutil_register_library_map(array( 'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/PhabricatorUIExampleRenderController.php', 'PhabricatorUIExamplesApplication' => 'applications/uiexample/application/PhabricatorUIExamplesApplication.php', 'PhabricatorUSEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php', + 'PhabricatorUnitTestContentSource' => 'infrastructure/contentsource/PhabricatorUnitTestContentSource.php', 'PhabricatorUnitsTestCase' => 'view/__tests__/PhabricatorUnitsTestCase.php', + 'PhabricatorUnknownContentSource' => 'infrastructure/contentsource/PhabricatorUnknownContentSource.php', 'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php', 'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php', 'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php', @@ -3464,6 +3537,7 @@ phutil_register_library_map(array( 'PhabricatorVeryWowEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php', 'PhabricatorViewerDatasource' => 'applications/people/typeahead/PhabricatorViewerDatasource.php', 'PhabricatorWatcherHasObjectEdgeType' => 'applications/transactions/edges/PhabricatorWatcherHasObjectEdgeType.php', + 'PhabricatorWebContentSource' => 'infrastructure/contentsource/PhabricatorWebContentSource.php', 'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php', 'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php', 'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php', @@ -3768,6 +3842,7 @@ phutil_register_library_map(array( 'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php', 'PhrequentConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentConduitAPIMethod.php', 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', + 'PhrequentCurtainExtension' => 'applications/phrequent/engineextension/PhrequentCurtainExtension.php', 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', 'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php', 'PhrequentPopConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPopConduitAPIMethod.php', @@ -4477,6 +4552,7 @@ phutil_register_library_map(array( 'DifferentialDAO', 'PhabricatorPolicyInterface', 'HarbormasterBuildableInterface', + 'HarbormasterCircleCIBuildableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), @@ -4553,7 +4629,6 @@ phutil_register_library_map(array( 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', 'DifferentialPathField' => 'DifferentialCustomField', - 'DifferentialPrimaryPaneView' => 'AphrontView', 'DifferentialProjectReviewersField' => 'DifferentialCustomField', 'DifferentialProjectsField' => 'DifferentialCoreCustomField', 'DifferentialQueryConduitAPIMethod' => 'DifferentialConduitAPIMethod', @@ -4603,7 +4678,6 @@ phutil_register_library_map(array( 'DifferentialRevisionControlSystem' => 'Phobject', 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType', - 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine', 'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType', @@ -4730,6 +4804,9 @@ phutil_register_library_map(array( 'DiffusionGitBranch' => 'Phobject', 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', + 'DiffusionGitLFSAuthenticateWorkflow' => 'DiffusionGitSSHWorkflow', + 'DiffusionGitLFSResponse' => 'AphrontResponse', + 'DiffusionGitLFSTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionGitRequest' => 'DiffusionRequest', @@ -4948,13 +5025,18 @@ phutil_register_library_map(array( 'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule', 'DoorkeeperBridge' => 'Phobject', 'DoorkeeperBridgeAsana' => 'DoorkeeperBridge', + 'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge', + 'DoorkeeperBridgeGitHubIssue' => 'DoorkeeperBridgeGitHub', + 'DoorkeeperBridgeGitHubUser' => 'DoorkeeperBridgeGitHub', 'DoorkeeperBridgeJIRA' => 'DoorkeeperBridge', 'DoorkeeperBridgeJIRATestCase' => 'PhabricatorTestCase', + 'DoorkeeperBridgedObjectCurtainExtension' => 'PHUICurtainExtension', 'DoorkeeperDAO' => 'PhabricatorLiskDAO', 'DoorkeeperExternalObject' => array( 'DoorkeeperDAO', 'PhabricatorPolicyInterface', ), + 'DoorkeeperExternalObjectPHIDType' => 'PhabricatorPHIDType', 'DoorkeeperExternalObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DoorkeeperFeedStoryPublisher' => 'Phobject', 'DoorkeeperFeedWorker' => 'FeedPushWorker', @@ -5013,6 +5095,7 @@ phutil_register_library_map(array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), + 'DrydockCommandError' => 'Phobject', 'DrydockCommandInterface' => 'DrydockInterface', 'DrydockCommandQuery' => 'DrydockQuery', 'DrydockConsoleController' => 'DrydockController', @@ -5170,6 +5253,7 @@ phutil_register_library_map(array( ), 'FundInitiativeBackController' => 'FundController', 'FundInitiativeCloseController' => 'FundController', + 'FundInitiativeCommentController' => 'FundController', 'FundInitiativeEditController' => 'FundController', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', 'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine', @@ -5180,6 +5264,7 @@ phutil_register_library_map(array( 'FundInitiativeReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'FundInitiativeSearchEngine' => 'PhabricatorApplicationSearchEngine', 'FundInitiativeTransaction' => 'PhabricatorApplicationTransaction', + 'FundInitiativeTransactionComment' => 'PhabricatorApplicationTransactionComment', 'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'FundInitiativeViewController' => 'FundController', 'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec', @@ -5290,6 +5375,8 @@ phutil_register_library_map(array( 'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildableViewController' => 'HarbormasterController', 'HarbormasterBuiltinBuildStepGroup' => 'HarbormasterBuildStepGroup', + 'HarbormasterCircleCIBuildStepImplementation' => 'HarbormasterBuildStepImplementation', + 'HarbormasterCircleCIHookController' => 'HarbormasterController', 'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod', 'HarbormasterController' => 'PhabricatorController', 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', @@ -5420,6 +5507,7 @@ phutil_register_library_map(array( 'HeraldRulePHIDType' => 'PhabricatorPHIDType', 'HeraldRuleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HeraldRuleSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'HeraldRuleSerializer' => 'Phobject', 'HeraldRuleTestCase' => 'PhabricatorTestCase', 'HeraldRuleTransaction' => 'PhabricatorApplicationTransaction', 'HeraldRuleTransactionComment' => 'PhabricatorApplicationTransactionComment', @@ -5587,6 +5675,7 @@ phutil_register_library_map(array( 'PhabricatorSpacesInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'DoorkeeperBridgedObjectInterface', ), 'ManiphestTaskAssignHeraldAction' => 'HeraldAction', 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', @@ -5654,22 +5743,50 @@ phutil_register_library_map(array( 'MultimeterViewer' => 'MultimeterDimension', 'NuanceConduitAPIMethod' => 'ConduitAPIMethod', 'NuanceConsoleController' => 'NuanceController', + 'NuanceContentSource' => 'PhabricatorContentSource', 'NuanceController' => 'PhabricatorController', - 'NuanceCreateItemConduitAPIMethod' => 'NuanceConduitAPIMethod', 'NuanceDAO' => 'PhabricatorLiskDAO', + 'NuanceGitHubEventItemType' => 'NuanceItemType', + 'NuanceGitHubImportCursor' => 'NuanceImportCursor', + 'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor', + 'NuanceGitHubRawEvent' => 'Phobject', + 'NuanceGitHubRawEventTestCase' => 'PhabricatorTestCase', + 'NuanceGitHubRepositoryImportCursor' => 'NuanceGitHubImportCursor', + 'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition', + 'NuanceImportCursor' => 'Phobject', + 'NuanceImportCursorData' => array( + 'NuanceDAO', + 'PhabricatorPolicyInterface', + ), + 'NuanceImportCursorDataQuery' => 'NuanceQuery', + 'NuanceImportCursorPHIDType' => 'PhabricatorPHIDType', 'NuanceItem' => array( 'NuanceDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), - 'NuanceItemEditController' => 'NuanceController', + 'NuanceItemActionController' => 'NuanceController', + 'NuanceItemCommand' => array( + 'NuanceDAO', + 'PhabricatorPolicyInterface', + ), + 'NuanceItemCommandQuery' => 'NuanceQuery', + 'NuanceItemController' => 'NuanceController', 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', + 'NuanceItemListController' => 'NuanceItemController', + 'NuanceItemManageController' => 'NuanceController', 'NuanceItemPHIDType' => 'PhabricatorPHIDType', 'NuanceItemQuery' => 'NuanceQuery', + 'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceItemTransaction' => 'NuanceTransaction', 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'NuanceItemType' => 'Phobject', + 'NuanceItemUpdateWorker' => 'NuanceWorker', 'NuanceItemViewController' => 'NuanceController', + 'NuanceManagementImportWorkflow' => 'NuanceManagementWorkflow', + 'NuanceManagementUpdateWorkflow' => 'NuanceManagementWorkflow', + 'NuanceManagementWorkflow' => 'PhabricatorManagementWorkflow', 'NuancePhabricatorFormSourceDefinition' => 'NuanceSourceDefinition', 'NuanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'NuanceQueue' => array( @@ -5677,55 +5794,47 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), + 'NuanceQueueController' => 'NuanceController', 'NuanceQueueDatasource' => 'PhabricatorTypeaheadDatasource', - 'NuanceQueueEditController' => 'NuanceController', + 'NuanceQueueEditController' => 'NuanceQueueController', + 'NuanceQueueEditEngine' => 'PhabricatorEditEngine', 'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor', - 'NuanceQueueListController' => 'NuanceController', + 'NuanceQueueListController' => 'NuanceQueueController', 'NuanceQueuePHIDType' => 'PhabricatorPHIDType', 'NuanceQueueQuery' => 'NuanceQuery', 'NuanceQueueSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceQueueTransaction' => 'NuanceTransaction', 'NuanceQueueTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'NuanceQueueViewController' => 'NuanceController', - 'NuanceRequestor' => array( - 'NuanceDAO', - 'PhabricatorPolicyInterface', - 'PhabricatorApplicationTransactionInterface', - ), - 'NuanceRequestorEditController' => 'NuanceController', - 'NuanceRequestorEditor' => 'PhabricatorApplicationTransactionEditor', - 'NuanceRequestorPHIDType' => 'PhabricatorPHIDType', - 'NuanceRequestorQuery' => 'NuanceQuery', - 'NuanceRequestorSource' => 'NuanceDAO', - 'NuanceRequestorTransaction' => 'NuanceTransaction', - 'NuanceRequestorTransactionComment' => 'PhabricatorApplicationTransactionComment', - 'NuanceRequestorTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'NuanceRequestorViewController' => 'NuanceController', + 'NuanceQueueViewController' => 'NuanceQueueController', 'NuanceSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'NuanceSource' => array( 'NuanceDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', + 'PhabricatorNgramsInterface', ), 'NuanceSourceActionController' => 'NuanceController', - 'NuanceSourceCreateController' => 'NuanceController', + 'NuanceSourceController' => 'NuanceController', 'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefinition' => 'Phobject', 'NuanceSourceDefinitionTestCase' => 'PhabricatorTestCase', - 'NuanceSourceEditController' => 'NuanceController', + 'NuanceSourceEditController' => 'NuanceSourceController', + 'NuanceSourceEditEngine' => 'PhabricatorEditEngine', 'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor', - 'NuanceSourceListController' => 'NuanceController', + 'NuanceSourceListController' => 'NuanceSourceController', 'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability', + 'NuanceSourceNameNgrams' => 'PhabricatorSearchNgrams', 'NuanceSourcePHIDType' => 'PhabricatorPHIDType', 'NuanceSourceQuery' => 'NuanceQuery', 'NuanceSourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceSourceTransaction' => 'NuanceTransaction', 'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'NuanceSourceViewController' => 'NuanceController', + 'NuanceSourceViewController' => 'NuanceSourceController', 'NuanceTransaction' => 'PhabricatorApplicationTransaction', + 'NuanceWorker' => 'PhabricatorWorker', 'OwnersConduitAPIMethod' => 'ConduitAPIMethod', 'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler', @@ -5757,6 +5866,9 @@ phutil_register_library_map(array( 'PHUIColorPalletteExample' => 'PhabricatorUIExample', 'PHUICrumbView' => 'AphrontView', 'PHUICrumbsView' => 'AphrontView', + 'PHUICurtainExtension' => 'Phobject', + 'PHUICurtainPanelView' => 'AphrontTagView', + 'PHUICurtainView' => 'AphrontTagView', 'PHUIDiffInlineCommentDetailView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentEditView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentRowScaffold' => 'AphrontView', @@ -5877,6 +5989,7 @@ phutil_register_library_map(array( 'PassphraseSSHPrivateKeyTextCredentialType' => 'PassphraseSSHPrivateKeyCredentialType', 'PassphraseSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PassphraseSecret' => 'PassphraseDAO', + 'PassphraseTokenCredentialType' => 'PassphraseCredentialType', 'PasteConduitAPIMethod' => 'ConduitAPIMethod', 'PasteCreateConduitAPIMethod' => 'PasteConduitAPIMethod', 'PasteCreateMailReceiver' => 'PhabricatorMailReceiver', @@ -6053,6 +6166,7 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthLoginHandler' => 'Phobject', + 'PhabricatorAuthLogoutConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod', 'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', @@ -6070,6 +6184,8 @@ phutil_register_library_map(array( 'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController', 'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController', + 'PhabricatorAuthOneTimeLoginTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', + 'PhabricatorAuthPasswordResetTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorAuthProvider' => 'Phobject', 'PhabricatorAuthProviderConfig' => array( 'PhabricatorAuthDAO', @@ -6102,16 +6218,21 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhabricatorAuthSessionEngine' => 'Phobject', + 'PhabricatorAuthSessionEngineExtension' => 'Phobject', + 'PhabricatorAuthSessionEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', + 'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorAuthTemporaryToken' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthTemporaryTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorAuthTemporaryTokenType' => 'Phobject', + 'PhabricatorAuthTemporaryTokenTypeModule' => 'PhabricatorConfigModule', 'PhabricatorAuthTerminateSessionController' => 'PhabricatorAuthController', 'PhabricatorAuthTryFactorAction' => 'PhabricatorSystemAction', 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', @@ -6121,6 +6242,13 @@ phutil_register_library_map(array( 'PhabricatorBadgeHasRecipientEdgeType' => 'PhabricatorEdgeType', 'PhabricatorBadgesApplication' => 'PhabricatorApplication', 'PhabricatorBadgesArchiveController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesAward' => array( + 'PhabricatorBadgesDAO', + 'PhabricatorDestructibleInterface', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorBadgesAwardController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesAwardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorBadgesBadge' => array( 'PhabricatorBadgesDAO', 'PhabricatorPolicyInterface', @@ -6129,12 +6257,14 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'PhabricatorBadgesCommentController' => 'PhabricatorBadgesController', 'PhabricatorBadgesController' => 'PhabricatorController', 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability', 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO', 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorBadgesEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController', @@ -6143,11 +6273,13 @@ phutil_register_library_map(array( 'PhabricatorBadgesListController' => 'PhabricatorBadgesController', 'PhabricatorBadgesMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorBadgesQuality' => 'Phobject', 'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorBadgesRecipientsListView' => 'AphrontView', 'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController', 'PhabricatorBadgesReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec', + 'PhabricatorBadgesSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorBadgesTransactionComment' => 'PhabricatorApplicationTransactionComment', @@ -6179,6 +6311,7 @@ phutil_register_library_map(array( 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuildbotController' => 'PhabricatorController', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', + 'PhabricatorBulkContentSource' => 'PhabricatorContentSource', 'PhabricatorBusyUIExample' => 'PhabricatorUIExample', 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', 'PhabricatorCacheGeneralGarbageCollector' => 'PhabricatorGarbageCollector', @@ -6274,6 +6407,7 @@ phutil_register_library_map(array( 'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', + 'PhabricatorConduitContentSource' => 'PhabricatorContentSource', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitEditField' => 'PhabricatorEditField', @@ -6374,7 +6508,9 @@ phutil_register_library_map(array( 'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConsoleApplication' => 'PhabricatorApplication', + 'PhabricatorConsoleContentSource' => 'PhabricatorContentSource', 'PhabricatorContentSource' => 'Phobject', + 'PhabricatorContentSourceModule' => 'PhabricatorConfigModule', 'PhabricatorContentSourceView' => 'AphrontView', 'PhabricatorContributedToObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorController' => 'AphrontController', @@ -6440,6 +6576,7 @@ phutil_register_library_map(array( 'PhabricatorDaemonBulkJobMonitorController' => 'PhabricatorDaemonController', 'PhabricatorDaemonBulkJobViewController' => 'PhabricatorDaemonController', 'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController', + 'PhabricatorDaemonContentSource' => 'PhabricatorContentSource', 'PhabricatorDaemonController' => 'PhabricatorController', 'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO', 'PhabricatorDaemonEventListener' => 'PhabricatorEventListener', @@ -6490,7 +6627,6 @@ phutil_register_library_map(array( 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType', 'PhabricatorDashboardEditController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardHistoryController' => 'PhabricatorDashboardController', 'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO', 'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController', 'PhabricatorDashboardLayoutConfig' => 'Phobject', @@ -6631,6 +6767,7 @@ phutil_register_library_map(array( 'PhabricatorElasticFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', 'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorEmailAddressesSettingsPanel' => 'PhabricatorSettingsPanel', + 'PhabricatorEmailContentSource' => 'PhabricatorContentSource', 'PhabricatorEmailFormatSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', @@ -6678,6 +6815,7 @@ phutil_register_library_map(array( 'PhabricatorFactSimpleSpec' => 'PhabricatorFactSpec', 'PhabricatorFactSpec' => 'Phobject', 'PhabricatorFactUpdateIterator' => 'PhutilBufferedIterator', + 'PhabricatorFaxContentSource' => 'PhabricatorContentSource', 'PhabricatorFeedApplication' => 'PhabricatorApplication', 'PhabricatorFeedBuilder' => 'Phobject', 'PhabricatorFeedConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -6707,6 +6845,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorFileAccessTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorFileBundleLoader' => 'Phobject', 'PhabricatorFileChunk' => array( 'PhabricatorFileDAO', @@ -6826,6 +6965,7 @@ phutil_register_library_map(array( 'PhabricatorHandlePool' => 'Phobject', 'PhabricatorHandlePoolTestCase' => 'PhabricatorTestCase', 'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorHandleRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorHandlesEditField' => 'PhabricatorPHIDListEditField', 'PhabricatorHarbormasterApplication' => 'PhabricatorApplication', 'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -6838,6 +6978,7 @@ phutil_register_library_map(array( 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', 'PhabricatorHelpMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorHeraldApplication' => 'PhabricatorApplication', + 'PhabricatorHeraldContentSource' => 'PhabricatorContentSource', 'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorHomeApplication' => 'PhabricatorApplication', 'PhabricatorHomeController' => 'PhabricatorController', @@ -6872,6 +7013,7 @@ phutil_register_library_map(array( 'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase', + 'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource', 'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', @@ -6885,6 +7027,7 @@ phutil_register_library_map(array( 'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase', 'PhabricatorLinkProfilePanel' => 'PhabricatorProfilePanel', 'PhabricatorLipsumArtist' => 'Phobject', + 'PhabricatorLipsumContentSource' => 'PhabricatorContentSource', 'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow', 'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist', @@ -7060,6 +7203,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationsApplication' => 'PhabricatorApplication', 'PhabricatorNuanceApplication' => 'PhabricatorApplication', 'PhabricatorOAuth1AuthProvider' => 'PhabricatorOAuthAuthProvider', + 'PhabricatorOAuth1SecretTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorOAuth2AuthProvider' => 'PhabricatorOAuthAuthProvider', 'PhabricatorOAuthAuthProvider' => 'PhabricatorAuthProvider', 'PhabricatorOAuthClientAuthorization' => array( @@ -7068,10 +7212,11 @@ phutil_register_library_map(array( ), 'PhabricatorOAuthClientAuthorizationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOAuthClientController' => 'PhabricatorOAuthServerController', - 'PhabricatorOAuthClientDeleteController' => 'PhabricatorOAuthClientController', + 'PhabricatorOAuthClientDisableController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthClientEditController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthClientListController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthClientSecretController' => 'PhabricatorOAuthClientController', + 'PhabricatorOAuthClientTestController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthClientViewController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthResponse' => 'AphrontResponse', 'PhabricatorOAuthServer' => 'Phobject', @@ -7083,6 +7228,7 @@ phutil_register_library_map(array( 'PhabricatorOAuthServerClient' => array( 'PhabricatorOAuthServerDAO', 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorOAuthServerClientAuthorizationPHIDType' => 'PhabricatorPHIDType', @@ -7092,10 +7238,13 @@ phutil_register_library_map(array( 'PhabricatorOAuthServerController' => 'PhabricatorController', 'PhabricatorOAuthServerCreateClientsCapability' => 'PhabricatorPolicyCapability', 'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO', + 'PhabricatorOAuthServerEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorOAuthServerEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorOAuthServerScope' => 'Phobject', 'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase', - 'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController', + 'PhabricatorOAuthServerTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorOAuthServerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorObjectHandle' => array( 'Phobject', 'PhabricatorPolicyInterface', @@ -7119,6 +7268,7 @@ phutil_register_library_map(array( 'PhabricatorObjectSelectorDialog' => 'Phobject', 'PhabricatorObjectUsesCredentialsEdgeType' => 'PhabricatorEdgeType', 'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery', + 'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource', 'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorOpcodeCacheSpec' => 'PhabricatorCacheSpec', 'PhabricatorOwnerPathQuery' => 'Phobject', @@ -7276,6 +7426,7 @@ phutil_register_library_map(array( 'PhabricatorPholioConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPholioMockTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorPhortuneApplication' => 'PhabricatorApplication', + 'PhabricatorPhortuneContentSource' => 'PhabricatorContentSource', 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow', 'PhabricatorPhortuneManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorPhragmentApplication' => 'PhabricatorApplication', @@ -7512,6 +7663,7 @@ phutil_register_library_map(array( 'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView', 'PhabricatorProjectWorkboardBackgroundColor' => 'Phobject', 'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel', + 'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', @@ -7571,6 +7723,7 @@ phutil_register_library_map(array( 'PhabricatorSubscribableInterface', 'PhabricatorMentionableInterface', 'HarbormasterBuildableInterface', + 'HarbormasterCircleCIBuildableInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFulltextInterface', @@ -7590,6 +7743,12 @@ phutil_register_library_map(array( 'PhabricatorRepositoryEngine' => 'Phobject', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', + 'PhabricatorRepositoryGitLFSRef' => array( + 'PhabricatorRepositoryDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorRepositoryGitLFSRefQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryGraphCache' => 'Phobject', 'PhabricatorRepositoryGraphStream' => 'Phobject', 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', @@ -7882,6 +8041,7 @@ phutil_register_library_map(array( 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsApplication' => 'PhabricatorApplication', + 'PhabricatorSubscriptionsCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorSubscriptionsEditController' => 'PhabricatorController', 'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor', @@ -7955,6 +8115,7 @@ phutil_register_library_map(array( 'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener', 'PhabricatorTokenizerEditField' => 'PhabricatorPHIDListEditField', 'PhabricatorTokensApplication' => 'PhabricatorApplication', + 'PhabricatorTokensCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', 'PhabricatorTransactions' => 'Phobject', @@ -7986,7 +8147,9 @@ phutil_register_library_map(array( 'PhabricatorUIExampleRenderController' => 'PhabricatorController', 'PhabricatorUIExamplesApplication' => 'PhabricatorApplication', 'PhabricatorUSEnglishTranslation' => 'PhutilTranslation', + 'PhabricatorUnitTestContentSource' => 'PhabricatorContentSource', 'PhabricatorUnitsTestCase' => 'PhabricatorTestCase', + 'PhabricatorUnknownContentSource' => 'PhabricatorContentSource', 'PhabricatorUnsubscribedFromObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorUser' => array( 'PhabricatorUserDAO', @@ -8042,6 +8205,7 @@ phutil_register_library_map(array( 'PhabricatorVeryWowEnglishTranslation' => 'PhutilTranslation', 'PhabricatorViewerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorWatcherHasObjectEdgeType' => 'PhabricatorEdgeType', + 'PhabricatorWebContentSource' => 'PhabricatorContentSource', 'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorWorker' => 'Phobject', 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask', @@ -8441,6 +8605,7 @@ phutil_register_library_map(array( 'PhragmentZIPController' => 'PhragmentController', 'PhrequentConduitAPIMethod' => 'ConduitAPIMethod', 'PhrequentController' => 'PhabricatorController', + 'PhrequentCurtainExtension' => 'PHUICurtainExtension', 'PhrequentDAO' => 'PhabricatorLiskDAO', 'PhrequentListController' => 'PhrequentController', 'PhrequentPopConduitAPIMethod' => 'PhrequentConduitAPIMethod', diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index cd1347411a..be23895bec 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -25,11 +25,18 @@ class AphrontDefaultApplicationConfiguration $content_type = idx($_SERVER, 'CONTENT_TYPE'); $is_form_data = preg_match('@^multipart/form-data@i', $content_type); - $raw_input = PhabricatorStartup::getRawInput(); - if (strlen($raw_input) && !$is_form_data) { - $data += $parser->parseQueryString($raw_input); - } else if ($_POST) { - $data += $_POST; + $request_method = idx($_SERVER, 'REQUEST_METHOD'); + if ($request_method === 'PUT') { + // For PUT requests, do nothing: in particular, do NOT read input. This + // allows us to stream input later and process very large PUT requests, + // like those coming from Git LFS. + } else { + $raw_input = PhabricatorStartup::getRawInput(); + if (strlen($raw_input) && !$is_form_data) { + $data += $parser->parseQueryString($raw_input); + } else if ($_POST) { + $data += $_POST; + } } $data += $parser->parseQueryString(idx($_SERVER, 'QUERY_STRING', '')); diff --git a/src/applications/almanac/controller/AlmanacBindingEditController.php b/src/applications/almanac/controller/AlmanacBindingEditController.php index f20011ded9..5f1fe30043 100644 --- a/src/applications/almanac/controller/AlmanacBindingEditController.php +++ b/src/applications/almanac/controller/AlmanacBindingEditController.php @@ -99,25 +99,35 @@ final class AlmanacBindingEditController $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) - ->setHeaderText($title) + ->setHeaderText(pht('Binding')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($service->getName(), $service_uri); if ($is_new) { $crumbs->addTextCrumb(pht('Create Binding')); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Create Binding')) + ->setHeaderIcon('fa-plus-square'); } else { $crumbs->addTextCrumb(pht('Edit Binding')); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Create Binding')) + ->setHeaderIcon('fa-pencil'); } + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - )); - + ->appendChild($view); } } diff --git a/src/applications/almanac/controller/AlmanacBindingViewController.php b/src/applications/almanac/controller/AlmanacBindingViewController.php index b75565525c..ead3e1b4c1 100644 --- a/src/applications/almanac/controller/AlmanacBindingViewController.php +++ b/src/applications/almanac/controller/AlmanacBindingViewController.php @@ -26,26 +26,23 @@ final class AlmanacBindingViewController $title = pht('Binding %s', $binding->getID()); - $property_list = $this->buildPropertyList($binding); - $action_list = $this->buildActionList($binding); - $property_list->setActionList($action_list); + $properties = $this->buildPropertyList($binding); + $details = $this->buildPropertySection($binding); + $curtain = $this->buildCurtain($binding); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($title) - ->setPolicyObject($binding); + ->setPolicyObject($binding) + ->setHeaderIcon('fa-object-group'); if ($binding->getIsDisabled()) { $header->setStatus('fa-ban', 'red', pht('Disabled')); } - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($property_list); - + $issue = null; if ($binding->getService()->isClusterService()) { - $this->addClusterMessage( - $box, + $issue = $this->addClusterMessage( pht('The service for this binding is a cluster service.'), pht( 'The service for this binding is a cluster service. You do not '. @@ -56,24 +53,33 @@ final class AlmanacBindingViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($service->getName(), $service_uri); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $binding, new AlmanacBindingTransactionQuery()); $timeline->setShouldTerminate(true); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $issue, + $this->buildAlmanacPropertiesTable($binding), + $timeline, + )) + ->addPropertySection(pht('DETAILS'), $details); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $box, - $this->buildAlmanacPropertiesTable($binding), - $timeline, + $view, )); } - private function buildPropertyList(AlmanacBinding $binding) { + private function buildPropertySection(AlmanacBinding $binding) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) @@ -98,23 +104,36 @@ final class AlmanacBindingViewController return $properties; } - private function buildActionList(AlmanacBinding $binding) { + private function buildPropertyList(AlmanacBinding $binding) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($binding); + $properties->invokeWillRenderEvent(); + + return $properties; + } + + private function buildCurtain(AlmanacBinding $binding) { $viewer = $this->getViewer(); - $id = $binding->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $binding, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $binding->getID(); + $edit_uri = $this->getApplicationURI("binding/edit/{$id}/"); + $disable_uri = $this->getApplicationURI("binding/disable/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($binding); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Binding')) - ->setHref($this->getApplicationURI("binding/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); @@ -126,17 +145,15 @@ final class AlmanacBindingViewController $disable_text = pht('Disable Binding'); } - $disable_href = $this->getApplicationURI("binding/disable/{$id}/"); - - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon($disable_icon) ->setName($disable_text) - ->setHref($disable_href) + ->setHref($disable_uri) ->setWorkflow(true) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } } diff --git a/src/applications/almanac/controller/AlmanacConsoleController.php b/src/applications/almanac/controller/AlmanacConsoleController.php index 9b83db9daa..cd9ebf610a 100644 --- a/src/applications/almanac/controller/AlmanacConsoleController.php +++ b/src/applications/almanac/controller/AlmanacConsoleController.php @@ -61,18 +61,25 @@ final class AlmanacConsoleController extends AlmanacController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Console')) ->setObjectList($menu); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Almanac Console')) + ->setHeaderIcon('fa-server'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + )); + return $this->newPage() ->setTitle(pht('Almanac Console')) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - )); + ->appendChild($view); } diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php index f6bf697e31..efc4334132 100644 --- a/src/applications/almanac/controller/AlmanacDeviceViewController.php +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -23,8 +23,7 @@ final class AlmanacDeviceViewController $title = pht('Device %s', $device->getName()); - $properties = $this->buildPropertyList($device); - $actions = $this->buildActionList($device); + $curtain = $this->buildCurtain($device); $header = id(new PHUIHeaderView()) ->setUser($viewer) @@ -55,6 +54,7 @@ final class AlmanacDeviceViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $issue, $interfaces, @@ -62,9 +62,7 @@ final class AlmanacDeviceViewController $this->buildSSHKeysTable($device), $this->buildServicesTable($device), $timeline, - )) - ->setPropertyList($properties) - ->setActionList($actions); + )); return $this->newPage() ->setTitle($title) @@ -75,37 +73,28 @@ final class AlmanacDeviceViewController )); } - private function buildPropertyList(AlmanacDevice $device) { + private function buildCurtain(AlmanacDevice $device) { $viewer = $this->getViewer(); - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($device); - - return $properties; - } - - private function buildActionList(AlmanacDevice $device) { - $viewer = $this->getViewer(); - $id = $device->getID(); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $device, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $device->getID(); + $edit_uri = $this->getApplicationURI("device/edit/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($device); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Device')) - ->setHref($this->getApplicationURI("device/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } private function buildInterfaceList(AlmanacDevice $device) { diff --git a/src/applications/almanac/controller/AlmanacInterfaceEditController.php b/src/applications/almanac/controller/AlmanacInterfaceEditController.php index 6d750bfbf5..d223360003 100644 --- a/src/applications/almanac/controller/AlmanacInterfaceEditController.php +++ b/src/applications/almanac/controller/AlmanacInterfaceEditController.php @@ -131,24 +131,36 @@ final class AlmanacInterfaceEditController $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) - ->setHeaderText($title) + ->setHeaderText(pht('Interface')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($device->getName(), $device_uri); if ($is_new) { $crumbs->addTextCrumb(pht('Create Interface')); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Create Interface')) + ->setHeaderIcon('fa-plus-square'); } else { $crumbs->addTextCrumb(pht('Edit Interface')); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Interface')) + ->setHeaderIcon('fa-pencil'); } + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - )); + ->appendChild($view); + } } diff --git a/src/applications/almanac/controller/AlmanacNamespaceViewController.php b/src/applications/almanac/controller/AlmanacNamespaceViewController.php index b999f219fc..a673bdf648 100644 --- a/src/applications/almanac/controller/AlmanacNamespaceViewController.php +++ b/src/applications/almanac/controller/AlmanacNamespaceViewController.php @@ -21,8 +21,7 @@ final class AlmanacNamespaceViewController $title = pht('Namespace %s', $namespace->getName()); - $properties = $this->buildPropertyList($namespace); - $actions = $this->buildActionList($namespace); + $curtain = $this->buildCurtain($namespace); $header = id(new PHUIHeaderView()) ->setUser($viewer) @@ -41,11 +40,10 @@ final class AlmanacNamespaceViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $timeline, - )) - ->setPropertyList($properties) - ->setActionList($actions); + )); return $this->newPage() ->setTitle($title) @@ -56,39 +54,28 @@ final class AlmanacNamespaceViewController )); } - private function buildPropertyList(AlmanacNamespace $namespace) { + private function buildCurtain(AlmanacNamespace $namespace) { $viewer = $this->getViewer(); - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($namespace); - - $properties->invokeWillRenderEvent(); - - return $properties; - } - - private function buildActionList(AlmanacNamespace $namespace) { - $viewer = $this->getViewer(); - $id = $namespace->getID(); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $namespace, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $namespace->getID(); + $edit_uri = $this->getApplicationURI("namespace/edit/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($namespace); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Namespace')) - ->setHref($this->getApplicationURI("namespace/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } } diff --git a/src/applications/almanac/controller/AlmanacNetworkViewController.php b/src/applications/almanac/controller/AlmanacNetworkViewController.php index 11e6eaf799..271ae1a0fb 100644 --- a/src/applications/almanac/controller/AlmanacNetworkViewController.php +++ b/src/applications/almanac/controller/AlmanacNetworkViewController.php @@ -21,8 +21,7 @@ final class AlmanacNetworkViewController $title = pht('Network %s', $network->getName()); - $properties = $this->buildPropertyList($network); - $actions = $this->buildActionList($network); + $curtain = $this->buildCurtain($network); $header = id(new PHUIHeaderView()) ->setUser($viewer) @@ -41,11 +40,10 @@ final class AlmanacNetworkViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $timeline, - )) - ->setPropertyList($properties) - ->setActionList($actions); + )); return $this->newPage() ->setTitle($title) @@ -56,39 +54,29 @@ final class AlmanacNetworkViewController )); } - private function buildPropertyList(AlmanacNetwork $network) { + + private function buildCurtain(AlmanacNetwork $network) { $viewer = $this->getViewer(); - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($network); - - $properties->invokeWillRenderEvent(); - - return $properties; - } - - private function buildActionList(AlmanacNetwork $network) { - $viewer = $this->getViewer(); - $id = $network->getID(); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $network, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $network->getID(); + $edit_uri = $this->getApplicationURI("network/edit/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($network); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Network')) - ->setHref($this->getApplicationURI("network/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } } diff --git a/src/applications/almanac/controller/AlmanacServiceEditController.php b/src/applications/almanac/controller/AlmanacServiceEditController.php index b09deaa95d..710e27a966 100644 --- a/src/applications/almanac/controller/AlmanacServiceEditController.php +++ b/src/applications/almanac/controller/AlmanacServiceEditController.php @@ -75,8 +75,12 @@ final class AlmanacServiceEditController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Create Service')); + $crumbs->setBorder(true); $title = pht('Choose Service Type'); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Create Service')) + ->setHeaderIcon('fa-plus-square'); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -88,13 +92,21 @@ final class AlmanacServiceEditController $box = id(new PHUIObjectBoxView()) ->setFormErrors($errors) - ->setHeaderText($title) + ->setHeaderText(pht('Service')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($box); + ->appendChild($view); + } } diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php index 0b59a47442..1036dc9e78 100644 --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -23,8 +23,7 @@ final class AlmanacServiceViewController $title = pht('Service %s', $service->getName()); - $properties = $this->buildPropertyList($service); - $actions = $this->buildActionList($service); + $curtain = $this->buildCurtain($service); $details = $this->buildPropertySection($service); $header = id(new PHUIHeaderView()) @@ -55,36 +54,19 @@ final class AlmanacServiceViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $issue, $details, $bindings, $this->buildAlmanacPropertiesTable($service), $timeline, - )) - ->setPropertyList($properties) - ->setActionList($actions); + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); - } - - private function buildPropertyList( - AlmanacService $service) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($service); - - $view->invokeWillRenderEvent(); - - return $view; + ->appendChild($view); } private function buildPropertySection( @@ -104,27 +86,28 @@ final class AlmanacServiceViewController ->appendChild($properties); } - private function buildActionList(AlmanacService $service) { + private function buildCurtain(AlmanacService $service) { $viewer = $this->getViewer(); - $id = $service->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $service, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $service->getID(); + $edit_uri = $this->getApplicationURI("service/edit/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($service); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Service')) - ->setHref($this->getApplicationURI("service/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } private function buildBindingList(AlmanacService $service) { diff --git a/src/applications/almanac/editor/AlmanacBindingPropertyEditEngine.php b/src/applications/almanac/editor/AlmanacBindingPropertyEditEngine.php index ef51d755c5..a122681414 100644 --- a/src/applications/almanac/editor/AlmanacBindingPropertyEditEngine.php +++ b/src/applications/almanac/editor/AlmanacBindingPropertyEditEngine.php @@ -13,4 +13,8 @@ final class AlmanacBindingPropertyEditEngine return $object->getURI(); } + protected function getObjectName() { + return pht('Property'); + } + } diff --git a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php index f8ace15caf..1d325e403b 100644 --- a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php @@ -53,6 +53,10 @@ final class AlmanacDeviceEditEngine return pht('Create Device'); } + protected function getObjectName() { + return pht('Device'); + } + protected function getEditorURI() { return '/almanac/device/edit/'; } diff --git a/src/applications/almanac/editor/AlmanacDevicePropertyEditEngine.php b/src/applications/almanac/editor/AlmanacDevicePropertyEditEngine.php index 9f10219591..122d0decd7 100644 --- a/src/applications/almanac/editor/AlmanacDevicePropertyEditEngine.php +++ b/src/applications/almanac/editor/AlmanacDevicePropertyEditEngine.php @@ -13,4 +13,8 @@ final class AlmanacDevicePropertyEditEngine return $object->getURI(); } + protected function getObjectName() { + return pht('Property'); + } + } diff --git a/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php b/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php index 0aaba76d3c..3b8c4ba2a6 100644 --- a/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacNamespaceEditEngine.php @@ -53,6 +53,10 @@ final class AlmanacNamespaceEditEngine return pht('Create Namespace'); } + protected function getObjectName() { + return pht('Namespace'); + } + protected function getEditorURI() { return '/almanac/namespace/edit/'; } diff --git a/src/applications/almanac/editor/AlmanacNetworkEditEngine.php b/src/applications/almanac/editor/AlmanacNetworkEditEngine.php index a99079ff11..e474e81291 100644 --- a/src/applications/almanac/editor/AlmanacNetworkEditEngine.php +++ b/src/applications/almanac/editor/AlmanacNetworkEditEngine.php @@ -53,6 +53,10 @@ final class AlmanacNetworkEditEngine return pht('Create Network'); } + protected function getObjectName() { + return pht('Network'); + } + protected function getEditorURI() { return '/almanac/network/edit/'; } diff --git a/src/applications/almanac/editor/AlmanacServiceEditEngine.php b/src/applications/almanac/editor/AlmanacServiceEditEngine.php index 00e4e076de..e64cfce3a2 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditEngine.php +++ b/src/applications/almanac/editor/AlmanacServiceEditEngine.php @@ -65,6 +65,10 @@ final class AlmanacServiceEditEngine return pht('Create Service'); } + protected function getObjectName() { + return pht('Service'); + } + protected function getEditorURI() { return '/almanac/service/edit/'; } diff --git a/src/applications/almanac/editor/AlmanacServicePropertyEditEngine.php b/src/applications/almanac/editor/AlmanacServicePropertyEditEngine.php index a56a10d575..505f89d36e 100644 --- a/src/applications/almanac/editor/AlmanacServicePropertyEditEngine.php +++ b/src/applications/almanac/editor/AlmanacServicePropertyEditEngine.php @@ -13,4 +13,8 @@ final class AlmanacServicePropertyEditEngine return $object->getURI(); } + protected function getObjectName() { + return pht('Property'); + } + } diff --git a/src/applications/almanac/management/AlmanacManagementWorkflow.php b/src/applications/almanac/management/AlmanacManagementWorkflow.php index 840bc8b972..0f1dd6d773 100644 --- a/src/applications/almanac/management/AlmanacManagementWorkflow.php +++ b/src/applications/almanac/management/AlmanacManagementWorkflow.php @@ -3,7 +3,6 @@ abstract class AlmanacManagementWorkflow extends PhabricatorManagementWorkflow { - protected function loadServices(array $names) { if (!$names) { return array(); @@ -37,7 +36,7 @@ abstract class AlmanacManagementWorkflow $editor = id(new AlmanacServiceEditor()) ->setActor($this->getViewer()) ->setActingAsPHID($almanac_phid) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->setContinueOnMissingFields(true); $editor->applyTransactions($service, array($xaction)); diff --git a/src/applications/almanac/query/AlmanacServiceSearchEngine.php b/src/applications/almanac/query/AlmanacServiceSearchEngine.php index 1a9509a2c2..a2fd9c23b7 100644 --- a/src/applications/almanac/query/AlmanacServiceSearchEngine.php +++ b/src/applications/almanac/query/AlmanacServiceSearchEngine.php @@ -15,10 +15,6 @@ final class AlmanacServiceSearchEngine return new AlmanacServiceQuery(); } - public function newResultObject() { - return new AlmanacService(); - } - protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); diff --git a/src/applications/almanac/storage/AlmanacDevice.php b/src/applications/almanac/storage/AlmanacDevice.php index f46b03600f..6c7f3cb57f 100644 --- a/src/applications/almanac/storage/AlmanacDevice.php +++ b/src/applications/almanac/storage/AlmanacDevice.php @@ -246,7 +246,7 @@ final class AlmanacDevice } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/almanac/storage/AlmanacNamespace.php b/src/applications/almanac/storage/AlmanacNamespace.php index 6a3baca637..4bbb4d4090 100644 --- a/src/applications/almanac/storage/AlmanacNamespace.php +++ b/src/applications/almanac/storage/AlmanacNamespace.php @@ -210,7 +210,7 @@ final class AlmanacNamespace } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/almanac/storage/AlmanacNetwork.php b/src/applications/almanac/storage/AlmanacNetwork.php index 064b612999..2f37ab4778 100644 --- a/src/applications/almanac/storage/AlmanacNetwork.php +++ b/src/applications/almanac/storage/AlmanacNetwork.php @@ -116,7 +116,7 @@ final class AlmanacNetwork } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php index b280de9ba8..37c3a44ba6 100644 --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -251,7 +251,7 @@ final class AlmanacService } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php index c96f22e338..ca959d4aef 100644 --- a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php +++ b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php @@ -37,9 +37,11 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject { $color = 'red'; break; case self::NEEDS_AUDIT: - case self::PARTIALLY_AUDITED: $color = 'orange'; break; + case self::PARTIALLY_AUDITED: + $color = 'yellow'; + break; case self::FULLY_AUDITED: $color = 'green'; break; @@ -53,11 +55,11 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject { public static function getStatusIcon($code) { switch ($code) { case self::CONCERN_RAISED: - $icon = 'fa-exclamation-triangle'; + $icon = 'fa-exclamation-circle'; break; case self::NEEDS_AUDIT: case self::PARTIALLY_AUDITED: - $icon = 'fa-exclamation-triangle'; + $icon = 'fa-exclamation-circle'; break; case self::FULLY_AUDITED: $icon = 'fa-check'; diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 51efdf175b..3df9013301 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -636,6 +636,8 @@ final class PhabricatorAuditEditor } } + $phids[] = $this->getActingAsPHID(); + return $phids; } diff --git a/src/applications/audit/storage/PhabricatorAuditInlineComment.php b/src/applications/audit/storage/PhabricatorAuditInlineComment.php index fc28bce720..ecb627efec 100644 --- a/src/applications/audit/storage/PhabricatorAuditInlineComment.php +++ b/src/applications/audit/storage/PhabricatorAuditInlineComment.php @@ -34,8 +34,7 @@ final class PhabricatorAuditInlineComment public function getTransactionCommentForSave() { $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array()); + PhabricatorOldWorldContentSource::SOURCECONST); $this->proxy ->setViewPolicy('public') diff --git a/src/applications/audit/storage/PhabricatorAuditTransaction.php b/src/applications/audit/storage/PhabricatorAuditTransaction.php index 20203a6f68..e1f7a9d08a 100644 --- a/src/applications/audit/storage/PhabricatorAuditTransaction.php +++ b/src/applications/audit/storage/PhabricatorAuditTransaction.php @@ -117,12 +117,37 @@ final class PhabricatorAuditTransaction return 'red'; case PhabricatorAuditActionConstants::ACCEPT: return 'green'; + case PhabricatorAuditActionConstants::RESIGN: + return 'black'; + case PhabricatorAuditActionConstants::CLOSE: + return 'indigo'; } } return parent::getColor(); } + public function getIcon() { + + $type = $this->getTransactionType(); + + switch ($type) { + case PhabricatorAuditActionConstants::ACTION: + switch ($this->getNewValue()) { + case PhabricatorAuditActionConstants::CONCERN: + return 'fa-exclamation-circle'; + case PhabricatorAuditActionConstants::ACCEPT: + return 'fa-check'; + case PhabricatorAuditActionConstants::RESIGN: + return 'fa-plane'; + case PhabricatorAuditActionConstants::CLOSE: + return 'fa-check'; + } + } + + return parent::getIcon(); + } + public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); diff --git a/src/applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php b/src/applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php new file mode 100644 index 0000000000..2dd485d8f0 --- /dev/null +++ b/src/applications/auth/conduit/PhabricatorAuthLogoutConduitAPIMethod.php @@ -0,0 +1,51 @@ +getUser(); + + // Destroy all web sessions. + $engine = id(new PhabricatorAuthSessionEngine()); + $engine->terminateLoginSessions($viewer); + + // If we were called via OAuth, destroy the OAuth token. + $oauth_token = $request->getOAuthToken(); + if ($oauth_token) { + $oauth_token->delete(); + } + + return null; + } + +} diff --git a/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php b/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php index 799a8e691e..664a97885f 100644 --- a/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php @@ -66,15 +66,12 @@ final class PhabricatorAuthConfirmLinkController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Confirm Link'), $panel_uri); $crumbs->addTextCrumb($provider->getProviderName()); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $dialog, - ), - array( - 'title' => pht('Confirm External Account Link'), - )); + return $this->newPage() + ->setTitle(pht('Confirm External Account Link')) + ->setCrumbs($crumbs) + ->appendChild($dialog); } diff --git a/src/applications/auth/controller/PhabricatorAuthController.php b/src/applications/auth/controller/PhabricatorAuthController.php index 76161e6c77..ca4b89f571 100644 --- a/src/applications/auth/controller/PhabricatorAuthController.php +++ b/src/applications/auth/controller/PhabricatorAuthController.php @@ -7,11 +7,9 @@ abstract class PhabricatorAuthController extends PhabricatorController { $view->setTitle($title); $view->setErrors($messages); - return $this->buildApplicationPage( - $view, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($view); } diff --git a/src/applications/auth/controller/PhabricatorAuthLinkController.php b/src/applications/auth/controller/PhabricatorAuthLinkController.php index d50bcf1d8a..44176a278e 100644 --- a/src/applications/auth/controller/PhabricatorAuthLinkController.php +++ b/src/applications/auth/controller/PhabricatorAuthLinkController.php @@ -116,15 +116,12 @@ final class PhabricatorAuthLinkController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Link Account'), $panel_uri); $crumbs->addTextCrumb($provider->getProviderName($name)); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $form, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form); } } diff --git a/src/applications/auth/controller/PhabricatorAuthLoginController.php b/src/applications/auth/controller/PhabricatorAuthLoginController.php index be610e223a..b9b2a8d876 100644 --- a/src/applications/auth/controller/PhabricatorAuthLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthLoginController.php @@ -236,7 +236,6 @@ final class PhabricatorAuthLoginController $content) { $crumbs = $this->buildApplicationCrumbs(); - $crumbs->setBorder(true); if ($this->getRequest()->getUser()->isLoggedIn()) { $crumbs->addTextCrumb(pht('Link Account'), $provider->getSettingsURI()); @@ -245,15 +244,12 @@ final class PhabricatorAuthLoginController } $crumbs->addTextCrumb($provider->getProviderName()); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => pht('Login'), - )); + return $this->newPage() + ->setTitle(pht('Login')) + ->setCrumbs($crumbs) + ->appendChild($content); } public function buildProviderErrorResponse( diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php b/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php index 0d07470560..ba28816375 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsApprovalController.php @@ -28,11 +28,10 @@ final class PhabricatorAuthNeedsApprovalController ->appendChild($wait_for_approval) ->addCancelButton('/', pht('Wait Patiently')); - return $this->buildApplicationPage( - $dialog, - array( - 'title' => pht('Wait For Approval'), - )); + return $this->newPage() + ->setTitle(pht('Wait For Approval')) + ->appendChild($dialog); + } } diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php index aaf3864156..f3e2562a1c 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php @@ -76,15 +76,16 @@ final class PhabricatorAuthNeedsMultiFactorController )); } - return $this->buildApplicationPage( - array( - $crumbs, - $help, - $panel, - ), - array( - 'title' => pht('Add Multi-Factor Authentication'), - )); + $view = array( + $help, + $panel, + ); + + return $this->newPage() + ->setTitle(pht('Add Multi-Factor Authentication')) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index ccc66ffeb7..d98879d0ed 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -105,23 +105,17 @@ final class PhabricatorAuthOneTimeLoginController // the link in the "Welcome" email is good enough, without requiring users // to go through a second round of email verification. + $editor = id(new PhabricatorUserEditor()) + ->setActor($target_user); + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); // Nuke the token and all other outstanding password reset tokens. // There is no particular security benefit to destroying them all, but // it should reduce HackerOne reports of nebulous harm. - - PhabricatorAuthTemporaryToken::revokeTokens( - $target_user, - array($target_user->getPHID()), - array( - PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE, - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE, - )); + $editor->revokePasswordResetLinks($target_user); if ($target_email) { - id(new PhabricatorUserEditor()) - ->setActor($target_user) - ->verifyEmail($target_user, $target_email); + $editor->verifyEmail($target_user, $target_email); } unset($unguarded); @@ -133,12 +127,13 @@ final class PhabricatorAuthOneTimeLoginController // We're going to let the user reset their password without knowing // the old one. Generate a one-time token for that. $key = Filesystem::readRandomCharacters(16); + $password_type = + PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE; $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($target_user->getPHID()) - ->setTokenType( - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE) + ->setTokenResource($target_user->getPHID()) + ->setTokenType($password_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php index 579b44cdbd..81812bfc67 100644 --- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php +++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php @@ -505,6 +505,7 @@ final class PhabricatorAuthRegisterController $crumbs->addTextCrumb($provider->getProviderName()); $title = pht('Phabricator Registration'); } + $crumbs->setBorder(true); $welcome_view = null; if ($is_setup) { @@ -519,7 +520,6 @@ final class PhabricatorAuthRegisterController } $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) ->setForm($form) ->setFormErrors($errors); @@ -528,16 +528,21 @@ final class PhabricatorAuthRegisterController $invite_header = $this->renderInviteHeader($invite); } - return $this->buildApplicationPage( - array( - $crumbs, - $welcome_view, - $invite_header, - $object_box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $welcome_view, + $invite_header, + $object_box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function loadDefaultAccount() { diff --git a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php index c1f0c21cb1..6d516916eb 100644 --- a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php +++ b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php @@ -11,7 +11,7 @@ final class PhabricatorAuthRevokeTokenController $query = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs(array($viewer->getPHID())); + ->withTokenResources(array($viewer->getPHID())); if (!$is_all) { $query->withIDs(array($id)); } diff --git a/src/applications/auth/controller/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php index 8f4a374241..633339a356 100644 --- a/src/applications/auth/controller/PhabricatorAuthStartController.php +++ b/src/applications/auth/controller/PhabricatorAuthStartController.php @@ -29,6 +29,7 @@ final class PhabricatorAuthStartController // it and warn the user they may need to nuke their cookies. $session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); + $did_clear = $request->getStr('cleared'); if (strlen($session_token)) { $kind = PhabricatorAuthSessionEngine::getSessionKindFromToken( @@ -39,18 +40,34 @@ final class PhabricatorAuthStartController // be logged in, so we can just continue. break; default: - // The session cookie is invalid, so clear it. + // The session cookie is invalid, so try to clear it. $request->clearCookie(PhabricatorCookies::COOKIE_USERNAME); $request->clearCookie(PhabricatorCookies::COOKIE_SESSION); - return $this->renderError( - pht( - 'Your login session is invalid. Try reloading the page and '. - 'logging in again. If that does not work, clear your browser '. - 'cookies.')); + // We've previously tried to clear the cookie but we ended up back + // here, so it didn't work. Hard fatal instead of trying again. + if ($did_clear) { + return $this->renderError( + pht( + 'Your login session is invalid, and clearing the session '. + 'cookie was unsuccessful. Try clearing your browser cookies.')); + } + + $redirect_uri = $request->getRequestURI(); + $redirect_uri->setQueryParam('cleared', 1); + return id(new AphrontRedirectResponse())->setURI($redirect_uri); } } + // If we just cleared the session cookie and it worked, clean up after + // ourselves by redirecting to get rid of the "cleared" parameter. The + // the workflow will continue normally. + if ($did_clear) { + $redirect_uri = $request->getRequestURI(); + $redirect_uri->setQueryParam('cleared', null); + return id(new AphrontRedirectResponse())->setURI($redirect_uri); + } + $providers = PhabricatorAuthProvider::getAllEnabledProviders(); foreach ($providers as $key => $provider) { if (!$provider->shouldAllowLogin()) { @@ -189,16 +206,17 @@ final class PhabricatorAuthStartController $crumbs->addTextCrumb(pht('Login')); $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $header, - $invite_message, - $out, - ), - array( - 'title' => pht('Login to Phabricator'), - )); + $title = pht('Login to Phabricator'); + $view = array( + $header, + $invite_message, + $out, + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } diff --git a/src/applications/auth/controller/PhabricatorDisabledUserController.php b/src/applications/auth/controller/PhabricatorDisabledUserController.php index 39e390d44a..26163c3034 100644 --- a/src/applications/auth/controller/PhabricatorDisabledUserController.php +++ b/src/applications/auth/controller/PhabricatorDisabledUserController.php @@ -15,8 +15,7 @@ final class PhabricatorDisabledUserController return new Aphront404Response(); } - return id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle(pht('Account Disabled')) ->addCancelButton('/logout/', pht('Okay')) ->appendParagraph(pht('Your account has been disabled.')); diff --git a/src/applications/auth/controller/PhabricatorEmailLoginController.php b/src/applications/auth/controller/PhabricatorEmailLoginController.php index 26609133ea..e9cf693514 100644 --- a/src/applications/auth/controller/PhabricatorEmailLoginController.php +++ b/src/applications/auth/controller/PhabricatorEmailLoginController.php @@ -144,6 +144,7 @@ final class PhabricatorEmailLoginController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Reset Password')); + $crumbs->setBorder(true); $dialog = new AphrontDialogView(); $dialog->setUser($request->getUser()); @@ -152,14 +153,11 @@ final class PhabricatorEmailLoginController $dialog->addSubmitButton(pht('Send Email')); $dialog->setSubmitURI('/login/email/'); - return $this->buildApplicationPage( - array( - $crumbs, - $dialog, - ), - array( - 'title' => pht('Forgot Password'), - )); + return $this->newPage() + ->setTitle(pht('Forgot Password')) + ->setCrumbs($crumbs) + ->appendChild($dialog); + } } diff --git a/src/applications/auth/controller/PhabricatorEmailVerificationController.php b/src/applications/auth/controller/PhabricatorEmailVerificationController.php index 83a370139c..e8138339af 100644 --- a/src/applications/auth/controller/PhabricatorEmailVerificationController.php +++ b/src/applications/auth/controller/PhabricatorEmailVerificationController.php @@ -77,15 +77,13 @@ final class PhabricatorEmailVerificationController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Verify Email')); + $crumbs->setBorder(true); + + return $this->newPage() + ->setTitle(pht('Verify Email')) + ->setCrumbs($crumbs) + ->appendChild($dialog); - return $this->buildApplicationPage( - array( - $crumbs, - $dialog, - ), - array( - 'title' => pht('Verify Email'), - )); } } diff --git a/src/applications/auth/controller/PhabricatorLogoutController.php b/src/applications/auth/controller/PhabricatorLogoutController.php index de3ac50e5d..f4b61f88a3 100644 --- a/src/applications/auth/controller/PhabricatorLogoutController.php +++ b/src/applications/auth/controller/PhabricatorLogoutController.php @@ -29,13 +29,6 @@ final class PhabricatorLogoutController $viewer = $this->getViewer(); if ($request->isFormPost()) { - - $log = PhabricatorUserLog::initializeNewLog( - $viewer, - $viewer->getPHID(), - PhabricatorUserLog::ACTION_LOGOUT); - $log->save(); - // Destroy the user's session in the database so logout works even if // their cookies have some issues. We'll detect cookie issues when they // try to login again and tell them to clear any junk. @@ -45,8 +38,10 @@ final class PhabricatorLogoutController ->setViewer($viewer) ->withSessionKeys(array($phsid)) ->executeOne(); + if ($session) { - $session->delete(); + $engine = new PhabricatorAuthSessionEngine(); + $engine->logoutSession($viewer, $session); } } $request->clearCookie(PhabricatorCookies::COOKIE_SESSION); @@ -56,14 +51,11 @@ final class PhabricatorLogoutController } if ($viewer->getPHID()) { - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle(pht('Log out of Phabricator?')) ->appendChild(pht('Are you sure you want to log out?')) ->addSubmitButton(pht('Logout')) ->addCancelButton('/'); - - return id(new AphrontDialogResponse())->setDialog($dialog); } return id(new AphrontRedirectResponse())->setURI('/'); diff --git a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php index 779196382d..a944a5b5c8 100644 --- a/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php +++ b/src/applications/auth/controller/PhabricatorMustVerifyEmailController.php @@ -53,14 +53,15 @@ final class PhabricatorMustVerifyEmailController ->appendParagraph($send_again) ->addSubmitButton(pht('Send Another Email')); - return $this->buildApplicationPage( - array( - $sent, - $dialog, - ), - array( - 'title' => pht('Must Verify Email'), - )); + $view = array( + $sent, + $dialog, + ); + + return $this->newPage() + ->setTitle(pht('Must Verify Email')) + ->appendChild($view); + } } diff --git a/src/applications/auth/controller/config/PhabricatorAuthEditController.php b/src/applications/auth/controller/config/PhabricatorAuthEditController.php index 21ba6ef99a..049edfacef 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthEditController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthEditController.php @@ -176,15 +176,33 @@ final class PhabricatorAuthEditController $button = pht('Add Provider'); } $crumb = pht('Add Provider'); - $title = pht('Add Authentication Provider'); + $title = pht('Add Auth Provider'); + $header_icon = 'fa-plus-square'; $cancel_uri = $this->getApplicationURI('/config/new/'); } else { $button = pht('Save'); $crumb = pht('Edit Provider'); - $title = pht('Edit Authentication Provider'); + $title = pht('Edit Auth Provider'); + $header_icon = 'fa-pencil'; $cancel_uri = $this->getApplicationURI(); } + $header = id(new PHUIHeaderView()) + ->setHeader(pht('%s: %s', $title, $provider->getProviderName())) + ->setHeaderIcon($header_icon); + + if ($config->getIsEnabled()) { + $status_name = pht('Enabled'); + $status_color = 'green'; + $status_icon = 'fa-check'; + $header->setStatus($status_icon, $status_color, $status_name); + } else if (!$is_new) { + $status_name = pht('Disabled'); + $status_color = 'indigo'; + $status_icon = 'fa-ban'; + $header->setStatus($status_icon, $status_color, $status_name); + } + $config_name = 'auth.email-domains'; $config_href = '/config/edit/'.$config_name.'/'; @@ -253,32 +271,8 @@ final class PhabricatorAuthEditController 'Phabricator will automatically login with this provider if it is '. 'the only available provider.')); - $status_tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE); - if ($is_new) { - $status_tag - ->setName(pht('New Provider')) - ->setBackgroundColor('blue'); - } else if ($config->getIsEnabled()) { - $status_tag - ->setName(pht('Enabled')) - ->setBackgroundColor('green'); - } else { - $status_tag - ->setName(pht('Disabled')) - ->setBackgroundColor('red'); - } - $form = id(new AphrontFormView()) ->setUser($viewer) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Provider')) - ->setValue($provider->getProviderName())) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Status')) - ->setValue($status_tag)) ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel(pht('Allow')) @@ -348,6 +342,7 @@ final class PhabricatorAuthEditController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($crumb); + $crumbs->setBorder(true); $timeline = null; if (!$is_new) { @@ -358,23 +353,28 @@ final class PhabricatorAuthEditController foreach ($xactions as $xaction) { $xaction->setProvider($provider); } + $timeline->setShouldTerminate(true); } $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Provider')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $form_box, $footer, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/auth/controller/config/PhabricatorAuthListController.php b/src/applications/auth/controller/config/PhabricatorAuthListController.php index 6df32f50a5..cb9c21b35f 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthListController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthListController.php @@ -3,9 +3,8 @@ final class PhabricatorAuthListController extends PhabricatorAuthProviderConfigController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $configs = id(new PhabricatorAuthProviderConfigQuery()) ->setViewer($viewer) @@ -93,6 +92,7 @@ final class PhabricatorAuthListController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Auth Providers')); + $crumbs->setBorder(true); $domains_key = 'auth.email-domains'; $domains_link = $this->renderConfigLink($domains_key); @@ -155,24 +155,29 @@ final class PhabricatorAuthListController ->setDisabled(!$can_manage) ->setText(pht('Add Provider')); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Authentication Providers')) - ->addActionLink($button); - $list->setFlush(true); $list = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->setInfoView($warning) + ->setHeaderText(pht('Providers')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($list); - return $this->buildApplicationPage( - array( - $crumbs, + $title = pht('Auth Providers'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-key') + ->addActionLink($button); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $warning, $list, - ), - array( - 'title' => pht('Authentication Providers'), )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function renderConfigLink($key) { diff --git a/src/applications/auth/controller/config/PhabricatorAuthNewController.php b/src/applications/auth/controller/config/PhabricatorAuthNewController.php index 18b4429ea9..dbd43f9ea8 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthNewController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthNewController.php @@ -80,21 +80,32 @@ final class PhabricatorAuthNewController ->setValue(pht('Continue'))); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Add Authentication Provider')) + ->setHeaderText(pht('Provider')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Add Provider')); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, + $title = pht('Add Auth Provider'); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $form_box, - ), - array( - 'title' => pht('Add Authentication Provider'), )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 77ba80bccb..1547d6bea8 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -39,17 +39,6 @@ final class PhabricatorAuthSessionEngine extends Phobject { const KIND_UNKNOWN = '?'; - /** - * Temporary tokens for one time logins. - */ - const ONETIME_TEMPORARY_TOKEN_TYPE = 'login:onetime'; - - - /** - * Temporary tokens for password recovery after one time login. - */ - const PASSWORD_TEMPORARY_TOKEN_TYPE = 'login:password'; - const ONETIME_RECOVER = 'recover'; const ONETIME_RESET = 'reset'; const ONETIME_WELCOME = 'welcome'; @@ -308,6 +297,24 @@ final class PhabricatorAuthSessionEngine extends Phobject { } } + public function logoutSession( + PhabricatorUser $user, + PhabricatorAuthSession $session) { + + $log = PhabricatorUserLog::initializeNewLog( + $user, + $user->getPHID(), + PhabricatorUserLog::ACTION_LOGOUT); + $log->save(); + + $extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions(); + foreach ($extensions as $extension) { + $extension->didLogout($user, array($session)); + } + + $session->delete(); + } + /* -( High Security )------------------------------------------------------ */ @@ -642,11 +649,12 @@ final class PhabricatorAuthSessionEngine extends Phobject { $key = Filesystem::readRandomCharacters(32); $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key); + $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE; $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($user->getPHID()) - ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) + ->setTokenResource($user->getPHID()) + ->setTokenType($onetime_type) ->setTokenExpires(time() + phutil_units('1 day in seconds')) ->setTokenCode($key_hash) ->save(); @@ -685,11 +693,12 @@ final class PhabricatorAuthSessionEngine extends Phobject { $key = null) { $key_hash = $this->getOneTimeLoginKeyHash($user, $email, $key); + $onetime_type = PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE; return id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) - ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) + ->withTokenResources(array($user->getPHID())) + ->withTokenTypes(array($onetime_type)) ->withTokenCodes(array($key_hash)) ->withExpired(false) ->executeOne(); diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngineExtension.php b/src/applications/auth/engine/PhabricatorAuthSessionEngineExtension.php new file mode 100644 index 0000000000..267f5be7b4 --- /dev/null +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngineExtension.php @@ -0,0 +1,23 @@ +getPhobjectClassConstant('EXTENSIONKEY'); + } + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + + abstract public function getExtensionName(); + + public function didLogout(PhabricatorUser $user, array $sessions) { + return; + } + +} diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php b/src/applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php new file mode 100644 index 0000000000..9468321d2d --- /dev/null +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngineExtensionModule.php @@ -0,0 +1,49 @@ +getViewer(); + + $extensions = PhabricatorAuthSessionEngineExtension::getAllExtensions(); + + $rows = array(); + foreach ($extensions as $extension) { + $rows[] = array( + get_class($extension), + $extension->getExtensionKey(), + $extension->getExtensionName(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setNoDataString( + pht('There are no registered session engine extensions.')) + ->setHeaders( + array( + pht('Class'), + pht('Key'), + pht('Name'), + )) + ->setColumnClasses( + array( + null, + null, + 'wide pri', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('SessionEngine Extensions')) + ->setTable($table); + } + +} diff --git a/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php b/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php new file mode 100644 index 0000000000..02f62e76be --- /dev/null +++ b/src/applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php @@ -0,0 +1,17 @@ +getStr('totpkey'); if (strlen($key)) { // If the user is providing a key, make sure it's a key we generated. @@ -36,8 +36,8 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { $temporary_token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) - ->withTokenTypes(array(self::TEMPORARY_TOKEN_TYPE)) + ->withTokenResources(array($user->getPHID())) + ->withTokenTypes(array($totp_token_type)) ->withExpired(false) ->withTokenCodes(array(PhabricatorHash::digest($key))) ->executeOne(); @@ -55,8 +55,8 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($user->getPHID()) - ->setTokenType(self::TEMPORARY_TOKEN_TYPE) + ->setTokenResource($user->getPHID()) + ->setTokenType($totp_token_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); diff --git a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php index a22707b6cc..530bf30583 100644 --- a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php +++ b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php @@ -9,8 +9,6 @@ abstract class PhabricatorOAuth1AuthProvider const PROPERTY_CONSUMER_SECRET = 'oauth1:consumer:secret'; const PROPERTY_PRIVATE_KEY = 'oauth1:private:key'; - const TEMPORARY_TOKEN_TYPE = 'oauth1:request:secret'; - protected function getIDKey() { return self::PROPERTY_CONSUMER_KEY; } @@ -215,13 +213,14 @@ abstract class PhabricatorOAuth1AuthProvider private function saveHandshakeTokenSecret($client_code, $secret) { + $secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE; $key = $this->getHandshakeTokenKeyFromClientCode($client_code); - $type = $this->getTemporaryTokenType(self::TEMPORARY_TOKEN_TYPE); + $type = $this->getTemporaryTokenType($secret_type); // Wipe out an existing token, if one exists. $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($key)) + ->withTokenResources(array($key)) ->withTokenTypes(array($type)) ->executeOne(); if ($token) { @@ -230,7 +229,7 @@ abstract class PhabricatorOAuth1AuthProvider // Save the new secret. id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($key) + ->setTokenResource($key) ->setTokenType($type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode($secret) @@ -238,12 +237,13 @@ abstract class PhabricatorOAuth1AuthProvider } private function loadHandshakeTokenSecret($client_code) { + $secret_type = PhabricatorOAuth1SecretTemporaryTokenType::TOKENTYPE; $key = $this->getHandshakeTokenKeyFromClientCode($client_code); - $type = $this->getTemporaryTokenType(self::TEMPORARY_TOKEN_TYPE); + $type = $this->getTemporaryTokenType($secret_type); $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($key)) + ->withTokenResources(array($key)) ->withTokenTypes(array($type)) ->withExpired(false) ->executeOne(); @@ -263,6 +263,9 @@ abstract class PhabricatorOAuth1AuthProvider // others' toes if a user starts Mediawiki and Bitbucket auth at the // same time. + // TODO: This isn't really a proper use of the table and should get + // cleaned up some day: the type should be constant. + return $core_type.':'.$this->getProviderConfig()->getID(); } diff --git a/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php b/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php new file mode 100644 index 0000000000..b104427bfa --- /dev/null +++ b/src/applications/auth/provider/PhabricatorOAuth1SecretTemporaryTokenType.php @@ -0,0 +1,17 @@ +getProviderConfig(); + return $config->getProperty(self::PROPERTY_PHABRICATOR_URI); + } + } diff --git a/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php b/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php index aecd92a4a7..72141f75f0 100644 --- a/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php +++ b/src/applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php @@ -4,8 +4,9 @@ final class PhabricatorAuthTemporaryTokenQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $ids; - private $objectPHIDs; + private $tokenResources; private $tokenTypes; + private $userPHIDs; private $expired; private $tokenCodes; @@ -14,8 +15,8 @@ final class PhabricatorAuthTemporaryTokenQuery return $this; } - public function withObjectPHIDs(array $object_phids) { - $this->objectPHIDs = $object_phids; + public function withTokenResources(array $resources) { + $this->tokenResources = $resources; return $this; } @@ -34,41 +35,39 @@ final class PhabricatorAuthTemporaryTokenQuery return $this; } - protected function loadPage() { - $table = new PhabricatorAuthTemporaryToken(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + public function withUserPHIDs(array $phids) { + $this->userPHIDs = $phids; + return $this; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + public function newResultObject() { + return new PhabricatorAuthTemporaryToken(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->objectPHIDs !== null) { + if ($this->tokenResources !== null) { $where[] = qsprintf( - $conn_r, - 'objectPHID IN (%Ls)', - $this->objectPHIDs); + $conn, + 'tokenResource IN (%Ls)', + $this->tokenResources); } if ($this->tokenTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenType IN (%Ls)', $this->tokenTypes); } @@ -76,12 +75,12 @@ final class PhabricatorAuthTemporaryTokenQuery if ($this->expired !== null) { if ($this->expired) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenExpires <= %d', time()); } else { $where[] = qsprintf( - $conn_r, + $conn, 'tokenExpires > %d', time()); } @@ -89,14 +88,19 @@ final class PhabricatorAuthTemporaryTokenQuery if ($this->tokenCodes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenCode IN (%Ls)', $this->tokenCodes); } - $where[] = $this->buildPagingClause($conn_r); + if ($this->userPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'userPHID IN (%Ls)', + $this->userPHIDs); + } - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/auth/query/PhabricatorExternalAccountQuery.php b/src/applications/auth/query/PhabricatorExternalAccountQuery.php index 9bb611015d..b34199ce60 100644 --- a/src/applications/auth/query/PhabricatorExternalAccountQuery.php +++ b/src/applications/auth/query/PhabricatorExternalAccountQuery.php @@ -62,19 +62,12 @@ final class PhabricatorExternalAccountQuery return $this; } + public function newResultObject() { + return new PhabricatorExternalAccount(); + } + protected function loadPage() { - $table = new PhabricatorExternalAccount(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $accounts) { @@ -116,61 +109,59 @@ final class PhabricatorExternalAccountQuery return $accounts; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - $where[] = $this->buildPagingClause($conn_r); - - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->accountTypes) { + if ($this->accountTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'accountType IN (%Ls)', $this->accountTypes); } - if ($this->accountDomains) { + if ($this->accountDomains !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'accountDomain IN (%Ls)', $this->accountDomains); } - if ($this->accountIDs) { + if ($this->accountIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'accountID IN (%Ls)', $this->accountIDs); } - if ($this->userPHIDs) { + if ($this->userPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'userPHID IN (%Ls)', $this->userPHIDs); } - if ($this->accountSecrets) { + if ($this->accountSecrets !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'accountSecret IN (%Ls)', $this->accountSecrets); } - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php index 8314b652ce..e1453b4383 100644 --- a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php +++ b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php @@ -44,9 +44,9 @@ final class PhabricatorAuthProviderConfigTransaction switch ($this->getTransactionType()) { case self::TYPE_ENABLE: if ($new) { - return 'fa-play'; + return 'fa-check'; } else { - return 'fa-pause'; + return 'fa-ban'; } } @@ -62,7 +62,7 @@ final class PhabricatorAuthProviderConfigTransaction if ($new) { return 'green'; } else { - return 'red'; + return 'indigo'; } } diff --git a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php index e379a6aaeb..be08f5db2d 100644 --- a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php +++ b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php @@ -3,42 +3,58 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO implements PhabricatorPolicyInterface { - // TODO: OAuth1 stores a client identifier here, which is not a real PHID. - // At some point, we should rename this column to be a little more generic. - protected $objectPHID; - + // NOTE: This is usually a PHID, but may be some other kind of resource + // identifier for some token types. + protected $tokenResource; protected $tokenType; protected $tokenExpires; protected $tokenCode; + protected $userPHID; + protected $properties; protected function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( + 'tokenResource' => 'phid', 'tokenType' => 'text64', 'tokenExpires' => 'epoch', 'tokenCode' => 'text64', + 'userPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_token' => array( - 'columns' => array('objectPHID', 'tokenType', 'tokenCode'), + 'columns' => array('tokenResource', 'tokenType', 'tokenCode'), 'unique' => true, ), 'key_expires' => array( 'columns' => array('tokenExpires'), ), + 'key_user' => array( + 'columns' => array('userPHID'), + ), ), ) + parent::getConfiguration(); } + private function newTokenTypeImplementation() { + $types = PhabricatorAuthTemporaryTokenType::getAllTypes(); + + $type = idx($types, $this->tokenType); + if ($type) { + return clone $type; + } + + return null; + } + public function getTokenReadableTypeName() { - // Eventually, it would be nice to let applications implement token types - // so we can put this in modular subclasses. - switch ($this->tokenType) { - case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: - return pht('One-Time Login Token'); - case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: - return pht('Password Reset Token'); + $type = $this->newTokenTypeImplementation(); + if ($type) { + return $type->getTokenReadableTypeName($this); } return $this->tokenType; @@ -49,10 +65,9 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO return false; } - switch ($this->tokenType) { - case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: - case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: - return true; + $type = $this->newTokenTypeImplementation(); + if ($type) { + return $type->isTokenRevocable($this); } return false; @@ -67,12 +82,12 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO public static function revokeTokens( PhabricatorUser $viewer, - array $object_phids, + array $token_resources, array $token_types) { $tokens = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs($object_phids) + ->withTokenResources($token_resources) ->withTokenTypes($token_types) ->withExpired(false) ->execute(); @@ -82,6 +97,15 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO } } + public function getTemporaryTokenProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setTemporaryTokenProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php b/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php new file mode 100644 index 0000000000..b5b0b35271 --- /dev/null +++ b/src/applications/auth/tokentype/PhabricatorAuthOneTimeLoginTemporaryTokenType.php @@ -0,0 +1,21 @@ +getPhobjectClassConstant('TOKENTYPE', 64); + } + + final public static function getAllTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getTokenTypeConstant') + ->execute(); + } + +} diff --git a/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php b/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php new file mode 100644 index 0000000000..8f4ad9ea9b --- /dev/null +++ b/src/applications/auth/tokentype/PhabricatorAuthTemporaryTokenTypeModule.php @@ -0,0 +1,47 @@ +getViewer(); + + $types = PhabricatorAuthTemporaryTokenType::getAllTypes(); + + $rows = array(); + foreach ($types as $type) { + $rows[] = array( + get_class($type), + $type->getTokenTypeConstant(), + $type->getTokenTypeDisplayName(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Class'), + pht('Key'), + pht('Name'), + )) + ->setColumnClasses( + array( + null, + null, + 'wide pri', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Temporary Token Types')) + ->setTable($table); + } + +} diff --git a/src/applications/badges/application/PhabricatorBadgesApplication.php b/src/applications/badges/application/PhabricatorBadgesApplication.php index 887a11833d..5423609fda 100644 --- a/src/applications/badges/application/PhabricatorBadgesApplication.php +++ b/src/applications/badges/application/PhabricatorBadgesApplication.php @@ -39,12 +39,14 @@ final class PhabricatorBadgesApplication extends PhabricatorApplication { '/badges/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorBadgesListController', + 'award/(?:(?P\d+)/)?' + => 'PhabricatorBadgesAwardController', 'create/' => 'PhabricatorBadgesEditController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorBadgesCommentController', - 'edit/(?:(?P\d+)/)?' - => 'PhabricatorBadgesEditController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorBadgesEditController', 'archive/(?:(?P\d+)/)?' => 'PhabricatorBadgesArchiveController', 'view/(?:(?P\d+)/)?' diff --git a/src/applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php b/src/applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php new file mode 100644 index 0000000000..42be0c2ba3 --- /dev/null +++ b/src/applications/badges/conduit/PhabricatorBadgesEditConduitAPIMethod.php @@ -0,0 +1,19 @@ + array( + 'rarity' => 140, + 'name' => pht('Poor'), + 'color' => 'grey', + ), + self::COMMON => array( + 'rarity' => 120, + 'name' => pht('Common'), + 'color' => 'white', + ), + self::UNCOMMON => array( + 'rarity' => 100, + 'name' => pht('Uncommon'), + 'color' => 'green', + ), + self::RARE => array( + 'rarity' => 80, + 'name' => pht('Rare'), + 'color' => 'blue', + ), + self::EPIC => array( + 'rarity' => 60, + 'name' => pht('Epic'), + 'color' => 'indigo', + ), + self::LEGENDARY => array( + 'rarity' => 40, + 'name' => pht('Legendary'), + 'color' => 'orange', + ), + self::HEIRLOOM => array( + 'rarity' => 20, + 'name' => pht('Heirloom'), + 'color' => 'yellow', + ), + ); + } + + public static function getDropdownQualityMap() { + $map = self::getQualityMap(); + return ipull($map, 'name'); + } +} diff --git a/src/applications/badges/controller/PhabricatorBadgesAwardController.php b/src/applications/badges/controller/PhabricatorBadgesAwardController.php new file mode 100644 index 0000000000..0475dd6277 --- /dev/null +++ b/src/applications/badges/controller/PhabricatorBadgesAwardController.php @@ -0,0 +1,85 @@ +getViewer(); + $id = $request->getURIData('id'); + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$user) { + return new Aphront404Response(); + } + + $view_uri = '/p/'.$user->getUsername(); + + if ($request->isFormPost()) { + $xactions = array(); + $badge_phid = $request->getStr('badgePHID'); + $badge = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withPHIDs(array($badge_phid)) + ->needRecipients(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_EDIT, + PhabricatorPolicyCapability::CAN_VIEW, + )) + ->executeOne(); + if (!$badge) { + return new Aphront404Response(); + } + $award_phids = array($user->getPHID()); + + $xactions[] = id(new PhabricatorBadgesTransaction()) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_AWARD) + ->setNewValue($award_phids); + + $editor = id(new PhabricatorBadgesEditor($badge)) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($badge, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI($view_uri); + } + + $badges = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withStatuses(array( + PhabricatorBadgesBadge::STATUS_ACTIVE, + )) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + $options = mpull($badges, 'getName', 'getPHID'); + asort($options); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Badge')) + ->setName('badgePHID') + ->setOptions($options)); + + $dialog = $this->newDialog() + ->setTitle(pht('Grant Badge')) + ->appendForm($form) + ->addCancelButton($view_uri) + ->addSubmitButton(pht('Award')); + + return $dialog; + } + +} diff --git a/src/applications/badges/controller/PhabricatorBadgesEditController.php b/src/applications/badges/controller/PhabricatorBadgesEditController.php index 3ac1aebe58..725068d7a6 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditController.php @@ -2,7 +2,6 @@ final class PhabricatorBadgesEditController extends PhabricatorBadgesController { - public function handleRequest(AphrontRequest $request) { return id(new PhabricatorBadgesEditEngine()) ->setController($this) diff --git a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php index 70e3d8f029..a73c6777af 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php @@ -6,6 +6,7 @@ final class PhabricatorBadgesEditRecipientsController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); + $xactions = array(); $badge = id(new PhabricatorBadgesQuery()) ->setViewer($viewer) @@ -21,30 +22,23 @@ final class PhabricatorBadgesEditRecipientsController return new Aphront404Response(); } - $recipient_phids = $badge->getRecipientPHIDs(); $view_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); + $awards = $badge->getAwards(); + $recipient_phids = mpull($awards, 'getRecipientPHID'); if ($request->isFormPost()) { - $recipient_spec = array(); - - $remove = $request->getStr('remove'); - if ($remove) { - $recipient_spec['-'] = array_fuse(array($remove)); - } + $award_phids = array(); $add_recipients = $request->getArr('phids'); if ($add_recipients) { - $recipient_spec['+'] = array_fuse($add_recipients); + foreach ($add_recipients as $phid) { + $award_phids[] = $phid; + } } - $type_recipient = PhabricatorBadgeHasRecipientEdgeType::EDGECONST; - - $xactions = array(); - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $type_recipient) - ->setNewValue($recipient_spec); + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_AWARD) + ->setNewValue($award_phids); $editor = id(new PhabricatorBadgesEditor($badge)) ->setActor($viewer) diff --git a/src/applications/badges/controller/PhabricatorBadgesListController.php b/src/applications/badges/controller/PhabricatorBadgesListController.php index c47f38fc5d..5c462e8341 100644 --- a/src/applications/badges/controller/PhabricatorBadgesListController.php +++ b/src/applications/badges/controller/PhabricatorBadgesListController.php @@ -16,16 +16,9 @@ final class PhabricatorBadgesListController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - PhabricatorBadgesCreateCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Badge')) - ->setHref($this->getApplicationURI('create/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new PhabricatorBadgesEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php index bc467b703d..c77a5f33ea 100644 --- a/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php @@ -21,7 +21,8 @@ final class PhabricatorBadgesRemoveRecipientsController return new Aphront404Response(); } - $recipient_phids = $badge->getRecipientPHIDs(); + $awards = $badge->getAwards(); + $recipient_phids = mpull($awards, 'getRecipientPHID'); $remove_phid = $request->getStr('phid'); if (!in_array($remove_phid, $recipient_phids)) { @@ -31,17 +32,10 @@ final class PhabricatorBadgesRemoveRecipientsController $view_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); if ($request->isFormPost()) { - $recipient_spec = array(); - $recipient_spec['-'] = array($remove_phid => $remove_phid); - - $type_recipient = PhabricatorBadgeHasRecipientEdgeType::EDGECONST; - $xactions = array(); - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $type_recipient) - ->setNewValue($recipient_spec); + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_REVOKE) + ->setNewValue(array($remove_phid)); $editor = id(new PhabricatorBadgesEditor($badge)) ->setActor($viewer) diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php index 3858965d41..4539440588 100644 --- a/src/applications/badges/controller/PhabricatorBadgesViewController.php +++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php @@ -43,15 +43,15 @@ final class PhabricatorBadgesViewController ->setStatus($status_icon, $status_color, $status_name) ->setHeaderIcon('fa-trophy'); - $properties = $this->buildPropertyListView($badge); - $actions = $this->buildActionListView($badge); + $curtain = $this->buildCurtain($badge); $details = $this->buildDetailsView($badge); $timeline = $this->buildTransactionTimeline( $badge, new PhabricatorBadgesTransactionQuery()); - $recipient_phids = $badge->getRecipientPHIDs(); + $awards = $badge->getAwards(); + $recipient_phids = mpull($awards, 'getRecipientPHID'); $recipient_phids = array_reverse($recipient_phids); $handles = $this->loadViewerHandles($recipient_phids); @@ -60,40 +60,25 @@ final class PhabricatorBadgesViewController ->setHandles($handles) ->setUser($viewer); - $add_comment = $this->buildCommentForm($badge); + $comment_view = id(new PhabricatorBadgesEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($badge); $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $recipient_list, $timeline, - $add_comment, + $comment_view, )) - ->setPropertyList($properties) - ->setActionList($actions) - ->addPropertySection(pht('BADGE DETAILS'), $details); + ->addPropertySection(pht('DESCRIPTION'), $details); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($badge->getPHID())) - ->appendChild( - array( - $view, - )); - } - - private function buildPropertyListView( - PhabricatorBadgesBadge $badge) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($badge); - - $view->invokeWillRenderEvent(); - - return $view; + ->appendChild($view); } private function buildDetailsView( @@ -103,25 +88,8 @@ final class PhabricatorBadgesViewController $view = id(new PHUIPropertyListView()) ->setUser($viewer); - $quality = idx($badge->getQualityNameMap(), $badge->getQuality()); - - $view->addProperty( - pht('Quality'), - $quality); - - $view->addProperty( - pht('Icon'), - id(new PhabricatorBadgesIconSet()) - ->getIconLabel($badge->getIcon())); - - $view->addProperty( - pht('Flavor'), - $badge->getFlavor()); - $description = $badge->getDescription(); if (strlen($description)) { - $view->addSectionHeader( - pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent( new PHUIRemarkupView($viewer, $description)); } @@ -137,73 +105,55 @@ final class PhabricatorBadgesViewController return $view; } - private function buildActionListView(PhabricatorBadgesBadge $badge) { + private function buildCurtain(PhabricatorBadgesBadge $badge) { $viewer = $this->getViewer(); - $id = $badge->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $badge, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($badge); + $id = $badge->getID(); + $edit_uri = $this->getApplicationURI("/edit/{$id}/"); + $archive_uri = $this->getApplicationURI("/archive/{$id}/"); + $award_uri = $this->getApplicationURI("/recipients/{$id}/"); - $view->addAction( + $curtain = $this->newCurtainView($badge); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Badge')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) - ->setHref($this->getApplicationURI("/edit/{$id}/"))); + ->setHref($edit_uri)); if ($badge->isArchived()) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Badge')) ->setIcon('fa-check') ->setDisabled(!$can_edit) ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("/archive/{$id}/"))); + ->setHref($archive_uri)); } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Badge')) ->setIcon('fa-ban') ->setDisabled(!$can_edit) ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("/archive/{$id}/"))); + ->setHref($archive_uri)); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName('Add Recipients') ->setIcon('fa-users') ->setDisabled(!$can_edit) ->setWorkflow(true) - ->setHref($this->getApplicationURI("/recipients/{$id}/"))); + ->setHref($award_uri)); - return $view; - } - - private function buildCommentForm(PhabricatorBadgesBadge $badge) { - $viewer = $this->getViewer(); - - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $add_comment_header = $is_serious - ? pht('Add Comment') - : pht('Render Honors'); - - $draft = PhabricatorDraft::newFromUserAndKey($viewer, $badge->getPHID()); - - return id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($badge->getPHID()) - ->setDraft($draft) - ->setHeaderText($add_comment_header) - ->setAction($this->getApplicationURI('/comment/'.$badge->getID().'/')) - ->setSubmitButtonName(pht('Add Comment')); + return $curtain; } } diff --git a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php index 49a8e85022..937dc1bd0e 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php @@ -34,7 +34,7 @@ final class PhabricatorBadgesEditEngine } protected function getObjectEditTitleText($object) { - return pht('Edit %s', $object->getName()); + return pht('Edit Badge: %s', $object->getName()); } protected function getObjectEditShortText($object) { @@ -45,18 +45,35 @@ final class PhabricatorBadgesEditEngine return pht('Create Badge'); } + protected function getObjectName() { + return pht('Badge'); + } + + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('edit/'); + } + protected function getCommentViewHeaderText($object) { - return pht('Add Comment'); + return pht('Render Honors'); } protected function getCommentViewButtonText($object) { - return pht('Submit'); + return pht('Salute'); } protected function getObjectViewURI($object) { return $object->getViewURI(); } + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhabricatorBadgesCreateCapability::CAPABILITY); + } + protected function buildCustomEditFields($object) { return array( @@ -64,12 +81,14 @@ final class PhabricatorBadgesEditEngine ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Badge name.')) + ->setConduitTypeDescription(pht('New badge name.')) ->setTransactionType(PhabricatorBadgesTransaction::TYPE_NAME) ->setValue($object->getName()), id(new PhabricatorTextEditField()) ->setKey('flavor') ->setLabel(pht('Flavor text')) ->setDescription(pht('Short description of the badge.')) + ->setConduitTypeDescription(pht('New badge flavor.')) ->setValue($object->getFlavor()) ->setTransactionType(PhabricatorBadgesTransaction::TYPE_FLAVOR), id(new PhabricatorIconSetEditField()) @@ -83,13 +102,16 @@ final class PhabricatorBadgesEditEngine id(new PhabricatorSelectEditField()) ->setKey('quality') ->setLabel(pht('Quality')) + ->setDescription(pht('Color and rarity of the badge.')) + ->setConduitTypeDescription(pht('New badge quality.')) ->setValue($object->getQuality()) ->setTransactionType(PhabricatorBadgesTransaction::TYPE_QUALITY) - ->setOptions($object->getQualityNameMap()), + ->setOptions(PhabricatorBadgesQuality::getDropdownQualityMap()), id(new PhabricatorRemarkupEditField()) ->setKey('description') ->setLabel(pht('Description')) ->setDescription(pht('Badge long description.')) + ->setConduitTypeDescription(pht('New badge description.')) ->setTransactionType(PhabricatorBadgesTransaction::TYPE_DESCRIPTION) ->setValue($object->getDescription()), ); diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php index fd0b14ad46..c8dc23df3d 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditor.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php @@ -20,6 +20,8 @@ final class PhabricatorBadgesEditor $types[] = PhabricatorBadgesTransaction::TYPE_ICON; $types[] = PhabricatorBadgesTransaction::TYPE_STATUS; $types[] = PhabricatorBadgesTransaction::TYPE_QUALITY; + $types[] = PhabricatorBadgesTransaction::TYPE_AWARD; + $types[] = PhabricatorBadgesTransaction::TYPE_REVOKE; $types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_EDGE; @@ -44,6 +46,11 @@ final class PhabricatorBadgesEditor return $object->getQuality(); case PhabricatorBadgesTransaction::TYPE_STATUS: return $object->getStatus(); + case PhabricatorBadgesTransaction::TYPE_AWARD: + $award_phids = mpull($object->getAwards(), 'getRecipientPHID'); + return $award_phids; + case PhabricatorBadgesTransaction::TYPE_REVOKE: + return null; } return parent::getCustomTransactionOldValue($object, $xaction); @@ -59,8 +66,11 @@ final class PhabricatorBadgesEditor case PhabricatorBadgesTransaction::TYPE_DESCRIPTION: case PhabricatorBadgesTransaction::TYPE_ICON: case PhabricatorBadgesTransaction::TYPE_STATUS: - case PhabricatorBadgesTransaction::TYPE_QUALITY: + case PhabricatorBadgesTransaction::TYPE_AWARD: + case PhabricatorBadgesTransaction::TYPE_REVOKE: return $xaction->getNewValue(); + case PhabricatorBadgesTransaction::TYPE_QUALITY: + return (int)$xaction->getNewValue(); } return parent::getCustomTransactionNewValue($object, $xaction); @@ -90,6 +100,9 @@ final class PhabricatorBadgesEditor case PhabricatorBadgesTransaction::TYPE_STATUS: $object->setStatus($xaction->getNewValue()); return; + case PhabricatorBadgesTransaction::TYPE_AWARD: + case PhabricatorBadgesTransaction::TYPE_REVOKE: + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -108,6 +121,34 @@ final class PhabricatorBadgesEditor case PhabricatorBadgesTransaction::TYPE_STATUS: case PhabricatorBadgesTransaction::TYPE_QUALITY: return; + case PhabricatorBadgesTransaction::TYPE_REVOKE: + $revoked_recipient_phids = $xaction->getNewValue(); + $awards = $object->getAwards(); + $awards = mpull($awards, null, 'getRecipientPHID'); + + foreach ($revoked_recipient_phids as $phid) { + $awards[$phid]->delete(); + } + $object->attachAwards($awards); + return; + case PhabricatorBadgesTransaction::TYPE_AWARD: + $recipient_phids = $xaction->getNewValue(); + $awards = $object->getAwards(); + $awards = mpull($awards, null, 'getRecipientPHID'); + + foreach ($recipient_phids as $phid) { + $award = idx($awards, $phid); + if (!$award) { + $award = PhabricatorBadgesAward::initializeNewBadgesAward( + $this->getActor(), + $object, + $phid); + $award->save(); + $awards[] = $award; + } + } + $object->attachAwards($awards); + return; } return parent::applyCustomExternalTransaction($object, $xaction); diff --git a/src/applications/badges/query/PhabricatorBadgesAwardQuery.php b/src/applications/badges/query/PhabricatorBadgesAwardQuery.php new file mode 100644 index 0000000000..355feec066 --- /dev/null +++ b/src/applications/badges/query/PhabricatorBadgesAwardQuery.php @@ -0,0 +1,87 @@ +setViewer($this->getViewer()) + ->withRecipientPHIDs(mpull($awards, null, 'getRecipientPHID')) + ->execute(); + + $badges = mpull($badges, null, 'getPHID'); + + foreach ($awards as $key => $award) { + $award_badge = idx($badges, $award->getBadgePHID()); + if ($award_badge === null) { + $this->didRejectResult($award); + unset($awards[$key]); + continue; + } + + $award->attachBadge($award_badge); + } + + return $awards; + } + + public function withBadgePHIDs(array $phids) { + $this->badgePHIDs = $phids; + return $this; + } + + public function withRecipientPHIDs(array $phids) { + $this->recipientPHIDs = $phids; + return $this; + } + + public function withAwarderPHIDs(array $phids) { + $this->awarderPHIDs = $phids; + return $this; + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + public function newResultObject() { + return new PhabricatorBadgesAward(); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->badgePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'badgePHID IN (%Ls)', + $this->badgePHIDs); + } + + if ($this->recipientPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'recipientPHID IN (%Ls)', + $this->recipientPHIDs); + } + + if ($this->awarderPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'awarderPHID IN (%Ls)', + $this->awarderPHIDs); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorBadgesApplication'; + } + +} diff --git a/src/applications/badges/query/PhabricatorBadgesQuery.php b/src/applications/badges/query/PhabricatorBadgesQuery.php index 91d7111253..c5d8b2dd12 100644 --- a/src/applications/badges/query/PhabricatorBadgesQuery.php +++ b/src/applications/badges/query/PhabricatorBadgesQuery.php @@ -50,22 +50,17 @@ final class PhabricatorBadgesQuery } protected function didFilterPage(array $badges) { - if ($this->needRecipients) { - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs(mpull($badges, 'getPHID')) - ->withEdgeTypes( - array( - PhabricatorBadgeHasRecipientEdgeType::EDGECONST, - )); - $edge_query->execute(); + $query = id(new PhabricatorBadgesAwardQuery()) + ->setViewer($this->getViewer()) + ->withBadgePHIDs(mpull($badges, 'getPHID')) + ->execute(); + + $awards = mgroup($query, 'getBadgePHID'); foreach ($badges as $badge) { - $phids = $edge_query->getDestinationPHIDs( - array( - $badge->getPHID(), - )); - $badge->attachRecipientPHIDs($phids); + $badge_awards = idx($awards, $badge->getPHID(), array()); + $badge->attachAwards($badge_awards); } } @@ -110,4 +105,36 @@ final class PhabricatorBadgesQuery return 'PhabricatorBadgesApplication'; } + public function getBuiltinOrders() { + return array( + 'quality' => array( + 'vector' => array('quality', 'id'), + 'name' => pht('Rarity (Rarest First)'), + ), + 'shoddiness' => array( + 'vector' => array('-quality', '-id'), + 'name' => pht('Rarity (Most Common First)'), + ), + ) + parent::getBuiltinOrders(); + } + + public function getOrderableColumns() { + return array( + 'quality' => array( + 'table' => $this->getPrimaryTableAlias(), + 'column' => 'quality', + 'reverse' => true, + 'type' => 'int', + ), + ) + parent::getOrderableColumns(); + } + + protected function getPagingValueMap($cursor, array $keys) { + $badge = $this->loadCursorObject($cursor); + return array( + 'quality' => $badge->getQuality(), + 'id' => $badge->getID(), + ); + } + } diff --git a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php index 4bad4d2c65..025ad3b53f 100644 --- a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php +++ b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php @@ -34,9 +34,7 @@ final class PhabricatorBadgesSearchEngine id(new PhabricatorSearchCheckboxesField()) ->setKey('qualities') ->setLabel(pht('Quality')) - ->setOptions( - id(new PhabricatorBadgesBadge()) - ->getQualityNameMap()), + ->setOptions(PhabricatorBadgesQuality::getDropdownQualityMap()), id(new PhabricatorSearchCheckboxesField()) ->setKey('statuses') ->setLabel(pht('Status')) @@ -110,8 +108,9 @@ final class PhabricatorBadgesSearchEngine $list = id(new PHUIObjectItemListView()); foreach ($badges as $badge) { + $quality_name = PhabricatorBadgesQuality::getQualityName( + $badge->getQuality()); - $quality = idx($badge->getQualityNameMap(), $badge->getQuality()); $mini_badge = id(new PHUIBadgeMiniView()) ->setHeader($badge->getName()) ->setIcon($badge->getIcon()) @@ -121,7 +120,7 @@ final class PhabricatorBadgesSearchEngine ->setHeader($badge->getName()) ->setBadge($mini_badge) ->setHref('/badges/view/'.$badge->getID().'/') - ->addAttribute($quality) + ->addAttribute($quality_name) ->addAttribute($badge->getFlavor()); if ($badge->isArchived()) { diff --git a/src/applications/badges/storage/PhabricatorBadgesAward.php b/src/applications/badges/storage/PhabricatorBadgesAward.php new file mode 100644 index 0000000000..851ac87002 --- /dev/null +++ b/src/applications/badges/storage/PhabricatorBadgesAward.php @@ -0,0 +1,83 @@ +setRecipientPHID($recipient_phid) + ->setBadgePHID($badge->getPHID()) + ->setAwarderPHID($actor->getPHID()) + ->attachBadge($badge); + } + + protected function getConfiguration() { + return array( + self::CONFIG_KEY_SCHEMA => array( + 'key_badge' => array( + 'columns' => array('badgePHID', 'recipientPHID'), + 'unique' => true, + ), + 'key_recipient' => array( + 'columns' => array('recipientPHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function attachBadge(PhabricatorBadgesBadge $badge) { + $this->badge = $badge; + return $this; + } + + public function getBadge() { + return $this->assertAttached($this->badge); + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $this->openTransaction(); + $this->delete(); + $this->saveTransaction(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + return $this->getBadge()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php index 254b02a6eb..95ceb7a9e8 100644 --- a/src/applications/badges/storage/PhabricatorBadgesBadge.php +++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php @@ -7,7 +7,8 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO PhabricatorSubscribableInterface, PhabricatorTokenReceiverInterface, PhabricatorFlaggableInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface { protected $name; protected $flavor; @@ -19,21 +20,12 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO protected $status; protected $creatorPHID; - private $recipientPHIDs = self::ATTACHABLE; + private $awards = self::ATTACHABLE; const STATUS_ACTIVE = 'open'; const STATUS_ARCHIVED = 'closed'; const DEFAULT_ICON = 'fa-star'; - const DEFAULT_QUALITY = 'green'; - - const POOR = 'grey'; - const COMMON = 'white'; - const UNCOMMON = 'green'; - const RARE = 'blue'; - const EPIC = 'indigo'; - const LEGENDARY = 'orange'; - const HEIRLOOM = 'yellow'; public static function getStatusNameMap() { return array( @@ -42,18 +34,6 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO ); } - public static function getQualityNameMap() { - return array( - self::POOR => pht('Poor'), - self::COMMON => pht('Common'), - self::UNCOMMON => pht('Uncommon'), - self::RARE => pht('Rare'), - self::EPIC => pht('Epic'), - self::LEGENDARY => pht('Legendary'), - self::HEIRLOOM => pht('Heirloom'), - ); - } - public static function initializeNewBadge(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) @@ -67,9 +47,11 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO return id(new PhabricatorBadgesBadge()) ->setIcon(self::DEFAULT_ICON) - ->setQuality(self::DEFAULT_QUALITY) + ->setQuality(PhabricatorBadgesQuality::DEFAULT_QUALITY) ->setCreatorPHID($actor->getPHID()) ->setEditPolicy($edit_policy) + ->setFlavor('') + ->setDescription('') ->setStatus(self::STATUS_ACTIVE); } @@ -81,7 +63,7 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO 'flavor' => 'text255', 'description' => 'text', 'icon' => 'text255', - 'quality' => 'text255', + 'quality' => 'uint32', 'status' => 'text32', 'mailKey' => 'bytes20', ), @@ -102,13 +84,13 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO return ($this->getStatus() == self::STATUS_ARCHIVED); } - public function attachRecipientPHIDs(array $phids) { - $this->recipientPHIDs = $phids; + public function attachAwards(array $awards) { + $this->awards = $awards; return $this; } - public function getRecipientPHIDs() { - return $this->assertAttached($this->recipientPHIDs); + public function getAwards() { + return $this->assertAttached($this->awards); } public function getViewURI() { @@ -181,10 +163,6 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO return ($this->creatorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ @@ -201,9 +179,50 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { + $awards = id(new PhabricatorBadgesAwardQuery()) + ->setViewer($engine->getViewer()) + ->withBadgePHIDs(array($this->getPHID())) + ->execute(); + + foreach ($awards as $award) { + $engine->destroyObjectPermanently($award); + } + $this->openTransaction(); $this->delete(); $this->saveTransaction(); } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the badge.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('creatorPHID') + ->setType('phid') + ->setDescription(pht('User PHID of the creator.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('string') + ->setDescription(pht('Active or archived status of the badge.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'creatorPHID' => $this->getCreatorPHID(), + 'status' => $this->getStatus(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/badges/storage/PhabricatorBadgesTransaction.php b/src/applications/badges/storage/PhabricatorBadgesTransaction.php index c2e934390d..f088c2d973 100644 --- a/src/applications/badges/storage/PhabricatorBadgesTransaction.php +++ b/src/applications/badges/storage/PhabricatorBadgesTransaction.php @@ -9,6 +9,8 @@ final class PhabricatorBadgesTransaction const TYPE_ICON = 'badges:icon'; const TYPE_STATUS = 'badges:status'; const TYPE_FLAVOR = 'badges:flavor'; + const TYPE_AWARD = 'badges:award'; + const TYPE_REVOKE = 'badges:revoke'; const MAILTAG_DETAILS = 'badges:details'; const MAILTAG_COMMENT = 'badges:comment'; @@ -71,6 +73,18 @@ final class PhabricatorBadgesTransaction $this->renderHandleLink($author_phid)); } break; + case self::TYPE_STATUS: + switch ($new) { + case PhabricatorBadgesBadge::STATUS_ACTIVE: + return pht( + '%s activated this badge.', + $this->renderHandleLink($author_phid)); + case PhabricatorBadgesBadge::STATUS_ARCHIVED: + return pht( + '%s archived this badge.', + $this->renderHandleLink($author_phid)); + } + break; case self::TYPE_ICON: if ($old === null) { return pht( @@ -97,9 +111,8 @@ final class PhabricatorBadgesTransaction $this->renderHandleLink($author_phid), $new); } else { - $qual_map = PhabricatorBadgesBadge::getQualityNameMap(); - $qual_new = idx($qual_map, $new, $new); - $qual_old = idx($qual_map, $old, $old); + $qual_new = PhabricatorBadgesQuality::getQualityName($new); + $qual_old = PhabricatorBadgesQuality::getQualityName($old); return pht( '%s updated the quality for this badge from "%s" to "%s".', $this->renderHandleLink($author_phid), @@ -107,6 +120,26 @@ final class PhabricatorBadgesTransaction $qual_new); } break; + case self::TYPE_AWARD: + if (!is_array($new)) { + $new = array(); + } + $handles = $this->renderHandleList($new); + return pht( + '%s awarded this badge to %s recipient(s): %s.', + $this->renderHandleLink($author_phid), + new PhutilNumber(count($new)), + $handles); + case self::TYPE_REVOKE: + if (!is_array($new)) { + $new = array(); + } + $handles = $this->renderHandleList($new); + return pht( + '%s revoked this badge from %s recipient(s): %s.', + $this->renderHandleLink($author_phid), + new PhutilNumber(count($new)), + $handles); } return parent::getTitle(); @@ -169,6 +202,28 @@ final class PhabricatorBadgesTransaction $this->renderHandleLink($object_phid)); } break; + case self::TYPE_AWARD: + if (!is_array($new)) { + $new = array(); + } + $handles = $this->renderHandleList($new); + return pht( + '%s awarded %s to %s recipient(s): %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + new PhutilNumber(count($new)), + $handles); + case self::TYPE_REVOKE: + if (!is_array($new)) { + $new = array(); + } + $handles = $this->renderHandleList($new); + return pht( + '%s revoked %s from %s recipient(s): %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + new PhutilNumber(count($new)), + $handles); } return parent::getTitleForFeed(); @@ -221,4 +276,24 @@ final class PhabricatorBadgesTransaction $this->getOldValue(), $this->getNewValue()); } + + public function getRequiredHandlePHIDs() { + $phids = parent::getRequiredHandlePHIDs(); + + $type = $this->getTransactionType(); + switch ($type) { + case self::TYPE_AWARD: + case self::TYPE_REVOKE: + $new = $this->getNewValue(); + if (!is_array($new)) { + $new = array(); + } + foreach ($new as $phid) { + $phids[] = $phid; + } + break; + } + + return $phids; + } } diff --git a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php index 6b633bed5b..7b2ceaa38b 100644 --- a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php +++ b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php @@ -16,11 +16,11 @@ final class PhabricatorBadgesRecipientsListView extends AphrontView { } public function render() { - - $viewer = $this->user; + $viewer = $this->getViewer(); $badge = $this->badge; $handles = $this->handles; + $awards = mpull($badge->getAwards(), null, 'getRecipientPHID'); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -28,14 +28,24 @@ final class PhabricatorBadgesRecipientsListView extends AphrontView { PhabricatorPolicyCapability::CAN_EDIT); $list = id(new PHUIObjectItemListView()) - ->setNoDataString(pht('This badge does not have any recipients.')); + ->setNoDataString(pht('This badge does not have any recipients.')) + ->setFlush(true); foreach ($handles as $handle) { $remove_uri = '/badges/recipients/'. $badge->getID().'/remove/?phid='.$handle->getPHID(); + $award = $awards[$handle->getPHID()]; + $awarder_handle = $viewer->renderHandle($award->getAwarderPHID()); + $award_date = phabricator_date($award->getDateCreated(), $viewer); + $awarder_info = pht( + 'Awarded by %s on %s', + $awarder_handle->render(), + $award_date); + $item = id(new PHUIObjectItemView()) ->setHeader($handle->getFullName()) + ->setSubhead($awarder_info) ->setHref($handle->getURI()) ->setImageURI($handle->getImageURI()); @@ -52,7 +62,8 @@ final class PhabricatorBadgesRecipientsListView extends AphrontView { } $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Recipients')) + ->setHeaderText(pht('RECIPIENTS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($list); return $box; diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index d2b62a4af1..f600378ee4 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -468,7 +468,32 @@ abstract class PhabricatorController extends AphrontController { public function newApplicationMenu() { return id(new PHUIApplicationMenuView()) - ->setViewer($this->getRequest()->getUser()); + ->setViewer($this->getViewer()); + } + + public function newCurtainView($object) { + $viewer = $this->getViewer(); + + $action_list = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + // NOTE: Applications (objects of class PhabricatorApplication) can't + // currently be set here, although they don't need any of the extensions + // anyway. This should probably work differently than it does, though. + if ($object instanceof PhabricatorLiskDAO) { + $action_list->setObject($object); + } + + $curtain = id(new PHUICurtainView()) + ->setViewer($viewer) + ->setActionList($action_list); + + $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); + foreach ($panels as $panel) { + $curtain->addPanel($panel); + } + + return $curtain; } protected function buildTransactionTimeline( diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 389d7b7358..10ed2b84e0 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -69,7 +69,8 @@ final class PhabricatorCalendarEventEditController $recurrence_end_date_value->setOptional(true); $submit_label = pht('Create'); - $page_title = pht('Create Event'); + $title = pht('Create Event'); + $header_icon = 'fa-plus-square'; $redirect = 'created'; $subscribers = array(); $invitees = array($user_phid); @@ -121,7 +122,8 @@ final class PhabricatorCalendarEventEditController ->setOptional(true); $submit_label = pht('Update'); - $page_title = pht('Update Event'); + $title = pht('Edit Event: %s', $event->getName()); + $header_icon = 'fa-pencil'; $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $event->getPHID()); @@ -540,7 +542,7 @@ final class PhabricatorCalendarEventEditController if ($request->isAjax()) { return $this->newDialog() - ->setTitle($page_title) + ->setTitle($title) ->setWidth(AphrontDialogView::WIDTH_FULL) ->appendForm($form) ->addCancelButton($cancel_uri) @@ -554,30 +556,35 @@ final class PhabricatorCalendarEventEditController $form->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($page_title) + ->setHeaderText(pht('Event')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setValidationException($validation_exception) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); if (!$this->isCreate()) { $crumbs->addTextCrumb('E'.$event->getId(), '/E'.$event->getId()); + $crumb_title = pht('Edit Event'); + } else { + $crumb_title = pht('Create Event'); } - $crumbs->addTextCrumb($page_title); + $crumbs->addTextCrumb($crumb_title); + $crumbs->setBorder(true); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($page_title) - ->setValidationException($validation_exception) - ->appendChild($form); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $page_title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 8d13f85e61..5880db1210 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -63,8 +63,7 @@ final class PhabricatorCalendarEventViewController } $header = $this->buildHeaderView($event); - $actions = $this->buildActionView($event); - $properties = $this->buildPropertyListView($event); + $curtain = $this->buildCurtain($event); $details = $this->buildPropertySection($event); $description = $this->buildDescriptionView($event); @@ -90,11 +89,13 @@ final class PhabricatorCalendarEventViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setMainColumn($timeline) - ->setPropertyList($properties) + ->setMainColumn(array( + $timeline, + $add_comment_form, + )) + ->setCurtain($curtain) ->addPropertySection(pht('DETAILS'), $details) - ->addPropertySection(pht('DESCRIPTION'), $description) - ->setActionList($actions); + ->addPropertySection(pht('DESCRIPTION'), $description); return $this->newPage() ->setTitle($page_title) @@ -148,16 +149,12 @@ final class PhabricatorCalendarEventViewController return $header; } - private function buildActionView(PhabricatorCalendarEvent $event) { + private function buildCurtain(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); $id = $event->getID(); $is_cancelled = $event->getIsCancelled(); $is_attending = $event->getIsUserAttending($viewer->getPHID()); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($event); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $event, @@ -178,8 +175,10 @@ final class PhabricatorCalendarEventViewController $edit_uri = "event/edit/{$id}/"; } + $curtain = $this->newCurtainView($event); + if ($edit_label && $edit_uri) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($edit_label) ->setIcon('fa-pencil') @@ -189,14 +188,14 @@ final class PhabricatorCalendarEventViewController } if ($is_attending) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Decline Event')) ->setIcon('fa-user-times') ->setHref($this->getApplicationURI("event/join/{$id}/")) ->setWorkflow(true)); } else { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Join Event')) ->setIcon('fa-user-plus') @@ -230,7 +229,7 @@ final class PhabricatorCalendarEventViewController } if ($is_cancelled) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($reinstate_label) ->setIcon('fa-plus') @@ -238,7 +237,7 @@ final class PhabricatorCalendarEventViewController ->setDisabled($cancel_disabled) ->setWorkflow(true)); } else { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($cancel_label) ->setIcon('fa-times') @@ -247,20 +246,7 @@ final class PhabricatorCalendarEventViewController ->setWorkflow(true)); } - return $actions; - } - - private function buildPropertyListView( - PhabricatorCalendarEvent $event) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($event); - - $properties->invokeWillRenderEvent(); - - return $properties; + return $curtain; } private function buildPropertySection( diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 36aa905b3e..8432a157e2 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -531,10 +531,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return ($phid == $this->getUserPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 13cef002cf..7d87d28ff2 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -71,6 +71,7 @@ final class CelerityDefaultPostprocessor 'hoverselectedgrey' => '#bbc4ca', 'hoverselectedblue' => '#e6e9ee', 'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)', + 'timeline' => '#d5d8e1', // Alphas 'alphawhite' => '255,255,255', diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php index cdff189633..530c26770a 100644 --- a/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php +++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelListController.php @@ -32,13 +32,10 @@ final class PhabricatorChatLogChannelListController ->setHeaderText('Channel List') ->setObjectList($list); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Channel List'), - )); + return $this->newPage() + ->setTitle(pht('Channel List')) + ->setCrumbs($crumbs) + ->appendChild($box); + } } diff --git a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php index fe505ecf60..2c6e58da50 100644 --- a/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php +++ b/src/applications/chatlog/controller/PhabricatorChatLogChannelLogController.php @@ -248,14 +248,11 @@ final class PhabricatorChatLogChannelLogController $form, '#'); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Channel Log'), - )); + return $this->newPage() + ->setTitle(pht('Channel Log')) + ->setCrumbs($crumbs) + ->appendChild($box); + } /** diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php index 89bddad542..017d96ae8b 100644 --- a/src/applications/conduit/call/ConduitCall.php +++ b/src/applications/conduit/call/ConduitCall.php @@ -65,10 +65,6 @@ final class ConduitCall extends Phobject { return $this->handler->shouldAllowUnguardedWrites(); } - public function getRequiredScope() { - return $this->handler->getRequiredScope(); - } - public function getErrorDescription($code) { return $this->handler->getErrorDescription($code); } diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index ce215468d9..005b23d505 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -45,7 +45,6 @@ final class PhabricatorConduitAPIController $auth_error = null; $conduit_username = '-'; if ($call->shouldRequireAuthentication()) { - $metadata['scope'] = $call->getRequiredScope(); $auth_error = $this->authenticateUser($api_request, $metadata, $method); // If we've explicitly authenticated the user here and either done // CSRF validation or are using a non-web authentication mechanism. @@ -185,11 +184,6 @@ final class PhabricatorConduitAPIController // First, verify the signature. try { $protocol_data = $metadata; - - // TODO: We should stop writing this into the protocol data when - // processing a request. - unset($protocol_data['scope']); - ConduitClient::verifySignature( $method, $api_request->getAllParameters(), @@ -362,11 +356,8 @@ final class PhabricatorConduitAPIController $user); } - // handle oauth $access_token = idx($metadata, 'access_token'); - $method_scope = idx($metadata, 'scope'); - if ($access_token && - $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) { + if ($access_token) { $token = id(new PhabricatorOAuthServerAccessToken()) ->loadOneWhere('token = %s', $access_token); if (!$token) { @@ -377,25 +368,35 @@ final class PhabricatorConduitAPIController } $oauth_server = new PhabricatorOAuthServer(); - $valid = $oauth_server->validateAccessToken($token, - $method_scope); - if (!$valid) { + $authorization = $oauth_server->authorizeToken($token); + if (!$authorization) { return array( 'ERR-INVALID-AUTH', - pht('Access token is invalid.'), + pht('Access token is invalid or expired.'), ); } - // valid token, so let's log in the user! - $user_phid = $token->getUserPHID(); - $user = id(new PhabricatorUser()) - ->loadOneWhere('phid = %s', $user_phid); + $user = id(new PhabricatorPeopleQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($token->getUserPHID())) + ->executeOne(); if (!$user) { return array( 'ERR-INVALID-AUTH', pht('Access token is for invalid user.'), ); } + + $ok = $this->authorizeOAuthMethodAccess($authorization, $method); + if (!$ok) { + return array( + 'ERR-OAUTH-ACCESS', + pht('You do not have authorization to call this method.'), + ); + } + + $api_request->setOAuthToken($token); + return $this->validateAuthenticatedUser( $api_request, $user); @@ -510,19 +511,22 @@ final class PhabricatorConduitAPIController 'wide', )); - $param_panel = new PHUIObjectBoxView(); - $param_panel->setHeaderText(pht('Method Parameters')); - $param_panel->setTable($param_table); + $param_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Method Parameters')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($param_table); - $result_panel = new PHUIObjectBoxView(); - $result_panel->setHeaderText(pht('Method Result')); - $result_panel->setTable($result_table); + $result_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Method Result')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($result_table); $method_uri = $this->getApplicationURI('method/'.$method.'/'); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($method, $method_uri) - ->addTextCrumb(pht('Call')); + ->addTextCrumb(pht('Call')) + ->setBorder(true); $example_panel = null; if ($request && $method_implementation) { @@ -532,16 +536,26 @@ final class PhabricatorConduitAPIController $params); } - return $this->buildApplicationPage( - array( - $crumbs, + $title = pht('Method Call Result'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-exchange'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $param_panel, $result_panel, $example_panel, - ), - array( - 'title' => pht('Method Call Result'), )); + + $title = pht('Method Call Result'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function renderAPIValue($value) { @@ -642,4 +656,30 @@ final class PhabricatorConduitAPIController return array($metadata, $params); } + private function authorizeOAuthMethodAccess( + PhabricatorOAuthClientAuthorization $authorization, + $method_name) { + + $method = ConduitAPIMethod::getConduitMethod($method_name); + if (!$method) { + return false; + } + + $required_scope = $method->getRequiredScope(); + switch ($required_scope) { + case ConduitAPIMethod::SCOPE_ALWAYS: + return true; + case ConduitAPIMethod::SCOPE_NEVER: + return false; + } + + $authorization_scope = $authorization->getScope(); + if (!empty($authorization_scope[$required_scope])) { + return true; + } + + return false; + } + + } diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php index a0481126c6..5ee76b44f7 100644 --- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php +++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php @@ -85,37 +85,41 @@ final class PhabricatorConduitConsoleController $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setHeader($method->getAPIMethodName()); + ->setHeader($method->getAPIMethodName()) + ->setHeaderIcon('fa-tty'); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Call Method')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - $content = array(); - $properties = $this->buildMethodProperties($method); $info_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('API Method: %s', $method->getAPIMethodName())) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($properties); - $content[] = $info_box; - $content[] = $method->getMethodDocumentation(); - $content[] = $form_box; - $content[] = $this->renderExampleBox($method, null); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($method->getAPIMethodName()); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => $method->getAPIMethodName(), + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $info_box, + $method->getMethodDocumentation(), + $form_box, + $this->renderExampleBox($method, null), )); + + $title = $method->getAPIMethodName(); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildMethodProperties(ConduitAPIMethod $method) { @@ -142,6 +146,36 @@ final class PhabricatorConduitConsoleController pht('Errors'), $error_description); + + $scope = $method->getRequiredScope(); + switch ($scope) { + case ConduitAPIMethod::SCOPE_ALWAYS: + $oauth_icon = 'fa-globe green'; + $oauth_description = pht( + 'OAuth clients may always call this method.'); + break; + case ConduitAPIMethod::SCOPE_NEVER: + $oauth_icon = 'fa-ban red'; + $oauth_description = pht( + 'OAuth clients may never call this method.'); + break; + default: + $oauth_icon = 'fa-unlock-alt blue'; + $oauth_description = pht( + 'OAuth clients may call this method after requesting access to '. + 'the "%s" scope.', + $scope); + break; + } + + $view->addProperty( + pht('OAuth Scope'), + array( + id(new PHUIIconView())->setIcon($oauth_icon), + ' ', + $oauth_description, + )); + $view->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent( diff --git a/src/applications/conduit/controller/PhabricatorConduitController.php b/src/applications/conduit/controller/PhabricatorConduitController.php index 4fa11dbfad..000d01f888 100644 --- a/src/applications/conduit/controller/PhabricatorConduitController.php +++ b/src/applications/conduit/controller/PhabricatorConduitController.php @@ -56,6 +56,7 @@ abstract class PhabricatorConduitController extends PhabricatorController { return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Examples')) ->setInfoView($info_view) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($arc_example, pht('arc call-conduit')) ->addPropertyList($curl_example, pht('cURL')) ->addPropertyList($php_example, pht('PHP')); diff --git a/src/applications/conduit/controller/PhabricatorConduitTokenController.php b/src/applications/conduit/controller/PhabricatorConduitTokenController.php index b5501a2805..fe6d676b68 100644 --- a/src/applications/conduit/controller/PhabricatorConduitTokenController.php +++ b/src/applications/conduit/controller/PhabricatorConduitTokenController.php @@ -55,19 +55,26 @@ final class PhabricatorConduitTokenController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Install Certificate')); + $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Certificate Token')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => pht('Certificate Install Token'), - )); + $title = pht('Certificate Install Token'); + + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($object_box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php index 4fd7c416bb..5b6c16bb93 100644 --- a/src/applications/conduit/method/ConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitAPIMethod.php @@ -15,6 +15,8 @@ abstract class ConduitAPIMethod const METHOD_STATUS_UNSTABLE = 'unstable'; const METHOD_STATUS_DEPRECATED = 'deprecated'; + const SCOPE_NEVER = 'scope.never'; + const SCOPE_ALWAYS = 'scope.always'; /** * Get a short, human-readable text summary of the method. @@ -108,8 +110,7 @@ abstract class ConduitAPIMethod } public function getRequiredScope() { - // by default, conduit methods are not accessible via OAuth - return PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE; + return self::SCOPE_NEVER; } public function executeMethod(ConduitAPIRequest $request) { diff --git a/src/applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php b/src/applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php index 44acf3e0d3..2cb84b1f83 100644 --- a/src/applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php @@ -24,6 +24,10 @@ final class ConduitGetCapabilitiesConduitAPIMethod extends ConduitAPIMethod { return 'dict'; } + public function getRequiredScope() { + return self::SCOPE_ALWAYS; + } + protected function execute(ConduitAPIRequest $request) { $authentication = array( 'token', diff --git a/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php b/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php index 04e8b6d05b..a63d004e5e 100644 --- a/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php +++ b/src/applications/conduit/method/ConduitQueryConduitAPIMethod.php @@ -18,6 +18,10 @@ final class ConduitQueryConduitAPIMethod extends ConduitAPIMethod { return 'dict'; } + public function getRequiredScope() { + return self::SCOPE_ALWAYS; + } + protected function execute(ConduitAPIRequest $request) { $methods = id(new PhabricatorConduitMethodQuery()) ->setViewer($request->getUser()) diff --git a/src/applications/conduit/protocol/ConduitAPIRequest.php b/src/applications/conduit/protocol/ConduitAPIRequest.php index 831c9de43e..47cc31fba0 100644 --- a/src/applications/conduit/protocol/ConduitAPIRequest.php +++ b/src/applications/conduit/protocol/ConduitAPIRequest.php @@ -5,6 +5,7 @@ final class ConduitAPIRequest extends Phobject { protected $params; private $user; private $isClusterRequest = false; + private $oauthToken; public function __construct(array $params) { $this->params = $params; @@ -48,6 +49,16 @@ final class ConduitAPIRequest extends Phobject { return $this->user; } + public function setOAuthToken( + PhabricatorOAuthServerAccessToken $oauth_token) { + $this->oauthToken = $oauth_token; + return $this; + } + + public function getOAuthToken() { + return $this->oauthToken; + } + public function setIsClusterRequest($is_cluster_request) { $this->isClusterRequest = $is_cluster_request; return $this; @@ -57,4 +68,9 @@ final class ConduitAPIRequest extends Phobject { return $this->isClusterRequest; } + public function newContentSource() { + return PhabricatorContentSource::newForSource( + PhabricatorConduitContentSource::SOURCECONST); + } + } diff --git a/src/applications/config/check/PhabricatorMailSetupCheck.php b/src/applications/config/check/PhabricatorMailSetupCheck.php index ff789d4aee..2b8e4e12d5 100644 --- a/src/applications/config/check/PhabricatorMailSetupCheck.php +++ b/src/applications/config/check/PhabricatorMailSetupCheck.php @@ -64,6 +64,19 @@ final class PhabricatorMailSetupCheck extends PhabricatorSetupCheck { ->addPhabricatorConfig('amazon-ses.secret-key'); } + if (!PhabricatorEnv::getEnvConfig('amazon-ses.endpoint')) { + $message = pht( + 'Amazon SES is selected as the mail adapter, but no SES endpoint '. + 'is configured. Provide an SES endpoint or choose a different '. + 'mail adapter.'); + + $this->newIssue('config.amazon-ses.endpoint') + ->setName(pht('Amazon SES Endpoint Not Set')) + ->setMessage($message) + ->addRelatedPhabricatorConfig('metamta.mail-adapter') + ->addPhabricatorConfig('amazon-ses.endpoint'); + } + $address_key = 'metamta.default-address'; $options = PhabricatorApplicationConfigOptions::loadAllOptions(); $default = $options[$address_key]->getDefault(); diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php index d805168eb1..7f15273b88 100644 --- a/src/applications/config/controller/PhabricatorConfigAllController.php +++ b/src/applications/config/controller/PhabricatorConfigAllController.php @@ -64,12 +64,10 @@ final class PhabricatorConfigAllController $nav->setCrumbs($crumbs); $nav->appendChild($panel); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); } } diff --git a/src/applications/config/controller/PhabricatorConfigCacheController.php b/src/applications/config/controller/PhabricatorConfigCacheController.php index 67fbf6e120..0f5681dea6 100644 --- a/src/applications/config/controller/PhabricatorConfigCacheController.php +++ b/src/applications/config/controller/PhabricatorConfigCacheController.php @@ -25,11 +25,9 @@ final class PhabricatorConfigCacheController $data_box, )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } private function renderCodeBox() { diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php index 22df5104e4..32ec2fa70b 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php @@ -159,11 +159,9 @@ final class PhabricatorConfigDatabaseIssueController $table_box, )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php index d886e98d74..1b2f9dbcb4 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -94,11 +94,9 @@ final class PhabricatorConfigDatabaseStatusController $nav->setCrumbs($crumbs); $nav->appendChild($body); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 0c93c86f09..ab246b56ce 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -227,11 +227,13 @@ final class PhabricatorConfigEditController ->setValue($examples)); } - $title = pht('Edit %s', $key); + $title = pht('Edit Option: %s', $key); + $header_icon = 'fa-pencil'; $short = pht('Edit'); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Config Option')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); if ($error_view) { @@ -246,21 +248,25 @@ final class PhabricatorConfigEditController } $crumbs->addTextCrumb($key, '/config/edit/'.$key); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $config_entry, new PhabricatorConfigTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - $timeline, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function readRequest( diff --git a/src/applications/config/controller/PhabricatorConfigGroupController.php b/src/applications/config/controller/PhabricatorConfigGroupController.php index 4569491427..af5ed41222 100644 --- a/src/applications/config/controller/PhabricatorConfigGroupController.php +++ b/src/applications/config/controller/PhabricatorConfigGroupController.php @@ -17,22 +17,26 @@ final class PhabricatorConfigGroupController $list = $this->buildOptionList($options->getOptions()); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) ->setObjectList($list); $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb(pht('Config'), $this->getApplicationURI()) - ->addTextCrumb($options->getName(), $this->getApplicationURI()); + ->addTextCrumb($options->getName(), $this->getApplicationURI()) + ->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-sliders'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildOptionList(array $options) { diff --git a/src/applications/config/controller/PhabricatorConfigHistoryController.php b/src/applications/config/controller/PhabricatorConfigHistoryController.php index d86fb79878..9fc538332f 100644 --- a/src/applications/config/controller/PhabricatorConfigHistoryController.php +++ b/src/applications/config/controller/PhabricatorConfigHistoryController.php @@ -40,13 +40,9 @@ final class PhabricatorConfigHistoryController $nav->setCrumbs($crumbs); $nav->appendChild($timeline); - return $this->buildApplicationPage( - array( - $nav, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } } diff --git a/src/applications/config/controller/PhabricatorConfigIgnoreController.php b/src/applications/config/controller/PhabricatorConfigIgnoreController.php index 80a859c147..cfe5a225ef 100644 --- a/src/applications/config/controller/PhabricatorConfigIgnoreController.php +++ b/src/applications/config/controller/PhabricatorConfigIgnoreController.php @@ -32,14 +32,12 @@ final class PhabricatorConfigIgnoreController throw new Exception(pht('Unrecognized verb: %s', $verb)); } - $dialog = id(new AphrontDialogView()) - ->setUser($request->getUser()) + return $this->newDialog() ->setTitle($title) ->appendChild($body) ->addSubmitButton($submit_title) ->addCancelButton($issue_uri); - return id(new AphrontDialogResponse())->setDialog($dialog); } public function manageApplication($issue) { diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php index 89b8ea7cd6..5f30a0411d 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -66,11 +66,9 @@ final class PhabricatorConfigIssueListController $nav->setCrumbs($crumbs); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } private function buildIssueList(array $issues, $group) { diff --git a/src/applications/config/controller/PhabricatorConfigIssueViewController.php b/src/applications/config/controller/PhabricatorConfigIssueViewController.php index e8d6e188a4..b1d1c299a5 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueViewController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueViewController.php @@ -36,14 +36,10 @@ final class PhabricatorConfigIssueViewController ->addTextCrumb(pht('Setup Issues'), $this->getApplicationURI('issue/')) ->addTextCrumb($title, $request->getRequestURI()); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($content); } private function renderIssue(PhabricatorSetupIssue $issue) { diff --git a/src/applications/config/controller/PhabricatorConfigListController.php b/src/applications/config/controller/PhabricatorConfigListController.php index 9e87995ebc..220d47cfcd 100644 --- a/src/applications/config/controller/PhabricatorConfigListController.php +++ b/src/applications/config/controller/PhabricatorConfigListController.php @@ -35,11 +35,9 @@ final class PhabricatorConfigListController $nav->setCrumbs($crumbs); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } private function buildConfigOptionsList(array $groups, $type) { diff --git a/src/applications/config/controller/PhabricatorConfigModuleController.php b/src/applications/config/controller/PhabricatorConfigModuleController.php index 76343ecc11..fee5cb9756 100644 --- a/src/applications/config/controller/PhabricatorConfigModuleController.php +++ b/src/applications/config/controller/PhabricatorConfigModuleController.php @@ -14,10 +14,10 @@ final class PhabricatorConfigModuleController $module = $all_modules[$key]; $content = $module->renderModuleStatus($request); - $name = $module->getModuleName(); + $title = $module->getModuleName(); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($name); + $crumbs->addTextCrumb($title); $nav = $this->buildSideNavView(); $nav->selectFilter('module/'.$key.'/'); @@ -27,11 +27,9 @@ final class PhabricatorConfigModuleController $content, )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $name, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } } diff --git a/src/applications/config/controller/PhabricatorConfigWelcomeController.php b/src/applications/config/controller/PhabricatorConfigWelcomeController.php index e2d704e0b7..435ce6f01e 100644 --- a/src/applications/config/controller/PhabricatorConfigWelcomeController.php +++ b/src/applications/config/controller/PhabricatorConfigWelcomeController.php @@ -18,11 +18,9 @@ final class PhabricatorConfigWelcomeController $nav->setCrumbs($crumbs); $nav->appendChild($this->buildWelcomeScreen($request)); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); } public function buildWelcomeScreen(AphrontRequest $request) { diff --git a/src/applications/config/option/PhabricatorAWSConfigOptions.php b/src/applications/config/option/PhabricatorAWSConfigOptions.php index faed807ae3..6647930859 100644 --- a/src/applications/config/option/PhabricatorAWSConfigOptions.php +++ b/src/applications/config/option/PhabricatorAWSConfigOptions.php @@ -27,6 +27,18 @@ final class PhabricatorAWSConfigOptions $this->newOption('amazon-ses.secret-key', 'string', null) ->setHidden(true) ->setDescription(pht('Secret key for Amazon SES.')), + $this->newOption('amazon-ses.endpoint', 'string', null) + ->setLocked(true) + ->setDescription( + pht( + 'SES endpoint domain name. You can find a list of available '. + 'regions and endpoints in the AWS documentation.')) + ->addExample( + 'email.us-east-1.amazonaws.com', + pht('US East (N. Virginia, Older default endpoint)')) + ->addExample( + 'email.us-west-2.amazonaws.com', + pht('US West (Oregon)')), $this->newOption('amazon-s3.access-key', 'string', null) ->setLocked(true) ->setDescription(pht('Access key for Amazon S3.')), diff --git a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php index 03dd615481..a4eeb3ed86 100644 --- a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php @@ -147,7 +147,7 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { id(new ConpherenceEditor()) ->setActor($creator) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->setContinueOnNoEffect(true) ->applyTransactions($conpherence, $xactions); @@ -166,7 +166,7 @@ final class ConpherenceRoomTestCase extends ConpherenceTestCase { id(new ConpherenceEditor()) ->setActor($actor) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->setContinueOnNoEffect(true) ->applyTransactions($room, $xactions); } diff --git a/src/applications/conpherence/__tests__/ConpherenceTestCase.php b/src/applications/conpherence/__tests__/ConpherenceTestCase.php index ce9dc8ceee..1ad87d8af8 100644 --- a/src/applications/conpherence/__tests__/ConpherenceTestCase.php +++ b/src/applications/conpherence/__tests__/ConpherenceTestCase.php @@ -14,7 +14,7 @@ abstract class ConpherenceTestCase extends PhabricatorTestCase { ); $editor = id(new ConpherenceEditor()) ->setActor($actor) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->applyTransactions($conpherence, $xactions); } @@ -31,7 +31,7 @@ abstract class ConpherenceTestCase extends PhabricatorTestCase { ); $editor = id(new ConpherenceEditor()) ->setActor($actor) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->applyTransactions($conpherence, $xactions); } @@ -45,7 +45,7 @@ abstract class ConpherenceTestCase extends PhabricatorTestCase { $editor = id(new ConpherenceEditor()) ->setActor($actor) - ->setContentSource(PhabricatorContentSource::newConsoleSource()); + ->setContentSource($this->newContentSource()); $xactions = $editor->generateTransactionsFromText( $actor, diff --git a/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php index d58b11ffa5..30540bf242 100644 --- a/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php @@ -42,7 +42,7 @@ final class ConpherenceCreateThreadConduitAPIMethod $participant_phids, $title, $message, - PhabricatorContentSource::newFromConduitRequest($request)); + $request->newContentSource()); if ($errors) { foreach ($errors as $error_code) { diff --git a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php index d5f3267a06..ebcf7f9a04 100644 --- a/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php +++ b/src/applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php @@ -58,7 +58,7 @@ final class ConpherenceUpdateThreadConduitAPIMethod throw new ConduitException('ERR_USAGE_ROOM_NOT_FOUND'); } - $source = PhabricatorContentSource::newFromConduitRequest($request); + $source = $request->newContentSource(); $editor = id(new ConpherenceEditor()) ->setContentSource($source) ->setActor($user); diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php index dbabc9b7d9..6f06a36fb0 100644 --- a/src/applications/conpherence/controller/ConpherenceListController.php +++ b/src/applications/conpherence/controller/ConpherenceListController.php @@ -127,11 +127,9 @@ final class ConpherenceListController extends ConpherenceController { $layout->setHeader($this->buildHeaderPaneContent( $conpherence, $policy_objects)); - $response = $this->buildApplicationPage( - $layout, - array( - 'title' => $title, - )); + $response = $this->newPage() + ->setTitle($title) + ->appendChild($layout); break; } diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index ca2a87b6d2..2f01979df6 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -131,12 +131,10 @@ final class ConpherenceViewController extends ->setLatestTransactionID($data['latest_transaction_id']) ->setRole('thread'); - return $this->buildApplicationPage( - $layout, - array( - 'title' => $title, - 'pageObjects' => array($conpherence->getPHID()), - )); + return $this->newPage() + ->setTitle($title) + ->setPageObjectPHIDs(array($conpherence->getPHID())) + ->appendChild($layout); } private function renderFormContent() { diff --git a/src/applications/conpherence/mail/ConpherenceReplyHandler.php b/src/applications/conpherence/mail/ConpherenceReplyHandler.php index 7ec5d7f445..0a07cbad29 100644 --- a/src/applications/conpherence/mail/ConpherenceReplyHandler.php +++ b/src/applications/conpherence/mail/ConpherenceReplyHandler.php @@ -48,11 +48,7 @@ final class ConpherenceReplyHandler extends PhabricatorMailReplyHandler { $conpherence->attachParticipants($participants); } - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_EMAIL, - array( - 'id' => $mail->getID(), - )); + $content_source = $mail->newContentSource(); $editor = id(new ConpherenceEditor()) ->setActor($user) diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php index a6cdacc047..ea89c1591b 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php @@ -8,7 +8,6 @@ final class PhabricatorCountdownEditController $id = $request->getURIData('id'); if ($id) { - $page_title = pht('Edit Countdown'); $countdown = id(new PhabricatorCountdownQuery()) ->setViewer($viewer) ->withIDs(array($id)) @@ -28,8 +27,9 @@ final class PhabricatorCountdownEditController $countdown->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); + $title = pht('Edit Countdown: %s', $countdown->getTitle()); } else { - $page_title = pht('Create Countdown'); + $title = pht('Create Countdown'); $countdown = PhabricatorCountdown::initializeNewCountdown($viewer); $date_value = AphrontFormDateControlValue::newFromEpoch( $viewer, PhabricatorTime::getNow()); @@ -116,6 +116,7 @@ final class PhabricatorCountdownEditController } $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); $cancel_uri = '/countdown/'; if ($countdown->getID()) { @@ -123,9 +124,11 @@ final class PhabricatorCountdownEditController $crumbs->addTextCrumb('C'.$countdown->getID(), $cancel_uri); $crumbs->addTextCrumb(pht('Edit')); $submit_label = pht('Save Changes'); + $header_icon = 'fa-pencil'; } else { $crumbs->addTextCrumb(pht('Create Countdown')); $submit_label = pht('Create Countdown'); + $header_icon = 'fa-plus-square'; } $policies = id(new PhabricatorPolicyQuery()) @@ -180,16 +183,25 @@ final class PhabricatorCountdownEditController ->setValue($submit_label)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($page_title) + ->setHeaderText(pht('Countdown')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); + return $this->newPage() - ->setTitle($page_title) + ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $form_box, + $view, )); } diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 9a983f0ed9..6e259df555 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -49,8 +49,7 @@ final class PhabricatorCountdownViewController ->setStatus($icon, $color, $status) ->setHeaderIcon('fa-rocket'); - $actions = $this->buildActionListView($countdown); - $properties = $this->buildPropertyListView($countdown); + $curtain = $this->buildCurtain($countdown); $subheader = $this->buildSubheaderView($countdown); $timeline = $this->buildTransactionTimeline( @@ -67,9 +66,8 @@ final class PhabricatorCountdownViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) - ->setMainColumn($content) - ->setPropertyList($properties) - ->setActionList($actions); + ->setCurtain($curtain) + ->setMainColumn($content); return $this->newPage() ->setTitle($title) @@ -78,28 +76,22 @@ final class PhabricatorCountdownViewController array( $countdown->getPHID(), )) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } - private function buildActionListView(PhabricatorCountdown $countdown) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildCurtain(PhabricatorCountdown $countdown) { + $viewer = $this->getViewer(); $id = $countdown->getID(); - $view = id(new PhabricatorActionListView()) - ->setObject($countdown) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $countdown, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain = $this->newCurtainView($countdown); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Countdown')) @@ -107,7 +99,7 @@ final class PhabricatorCountdownViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-times') ->setName(pht('Delete Countdown')) @@ -115,17 +107,7 @@ final class PhabricatorCountdownViewController ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $view; - } - - private function buildPropertyListView( - PhabricatorCountdown $countdown) { - $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($countdown); - $view->invokeWillRenderEvent(); - return $view; + return $curtain; } private function buildSubheaderView( diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php index 1a864907eb..52a395f0c2 100644 --- a/src/applications/countdown/storage/PhabricatorCountdown.php +++ b/src/applications/countdown/storage/PhabricatorCountdown.php @@ -70,9 +70,6 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php b/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php index f32892cf4c..f78bfdd7df 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php @@ -23,13 +23,14 @@ final class PhabricatorDaemonBulkJobViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Bulk Jobs'), '/daemon/bulk/'); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $properties = $this->renderProperties($job); - $actions = $this->renderActions($job); - $properties->setActionList($actions); + $curtain = $this->buildCurtainView($job); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); $timeline = $this->buildTransactionTimeline( @@ -37,15 +38,22 @@ final class PhabricatorDaemonBulkJobViewController new PhabricatorWorkerBulkJobTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-hourglass'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $box, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function renderProperties(PhabricatorWorkerBulkJob $job) { @@ -64,12 +72,9 @@ final class PhabricatorDaemonBulkJobViewController return $view; } - private function renderActions(PhabricatorWorkerBulkJob $job) { + private function buildCurtainView(PhabricatorWorkerBulkJob $job) { $viewer = $this->getViewer(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($job); + $curtain = $this->newCurtainView($job); if ($job->isConfirming()) { $continue_uri = $job->getMonitorURI(); @@ -77,13 +82,13 @@ final class PhabricatorDaemonBulkJobViewController $continue_uri = $job->getDoneURI(); } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($continue_uri) ->setIcon('fa-arrow-circle-o-right') ->setName(pht('Continue'))); - return $actions; + return $curtain; } } diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php index 9f54726bc9..a488ae3a63 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php @@ -117,16 +117,15 @@ final class PhabricatorDaemonConsoleController 'n', )); - $completed_panel = new PHUIObjectBoxView(); - $completed_panel->setHeaderText( - pht('Recently Completed Tasks (Last 15m)')); - $completed_panel->setTable($completed_table); + $completed_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recently Completed Tasks (Last 15m)')) + ->setTable($completed_table); $daemon_table = new PhabricatorDaemonLogListView(); $daemon_table->setUser($viewer); $daemon_table->setDaemonLogs($logs); - $daemon_panel = new PHUIObjectBoxView(); + $daemon_panel = id(new PHUIObjectBoxView()); $daemon_panel->setHeaderText(pht('Active Daemons')); $daemon_panel->setObjectList($daemon_table); @@ -218,11 +217,10 @@ final class PhabricatorDaemonConsoleController $triggers_panel, )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Console'), - )); + return $this->newPage() + ->setTitle(pht('Console')) + ->appendChild($nav); + } private function buildTriggersTable(array $triggers) { diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogEventViewController.php b/src/applications/daemon/controller/PhabricatorDaemonLogEventViewController.php index 772ee87fd1..208a20b9a0 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonLogEventViewController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonLogEventViewController.php @@ -17,9 +17,9 @@ final class PhabricatorDaemonLogEventViewController ->setCombinedLog(true) ->setShowFullMessage(true); - $log_panel = new PHUIObjectBoxView(); - $log_panel->setHeaderText(pht('Combined Log')); - $log_panel->appendChild($event_view); + $log_panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($event_view); $daemon_id = $event->getLogID(); @@ -27,17 +27,21 @@ final class PhabricatorDaemonLogEventViewController ->addTextCrumb( pht('Daemon %s', $daemon_id), $this->getApplicationURI("log/{$daemon_id}/")) - ->addTextCrumb(pht('Event %s', $event->getID())); + ->addTextCrumb(pht('Event %s', $event->getID())) + ->setBorder(true); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Combined Log')) + ->setHeaderIcon('fa-file-text'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($log_panel); + + return $this->newPage() + ->setTitle(pht('Combined Daemon Log')) + ->appendChild($view); - return $this->buildApplicationPage( - array( - $crumbs, - $log_panel, - ), - array( - 'title' => pht('Combined Daemon Log'), - )); } } diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogListController.php b/src/applications/daemon/controller/PhabricatorDaemonLogListController.php index c1de0b892f..e5ef050d99 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonLogListController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonLogListController.php @@ -31,11 +31,10 @@ final class PhabricatorDaemonLogListController $nav->appendChild($box); $nav->appendChild($pager); - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('All Daemons'), - )); + return $this->newPage() + ->setTitle(pht('All Daemons')) + ->appendChild($nav); + } } diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php index 32af8f6f13..f2f5121898 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php @@ -22,9 +22,11 @@ final class PhabricatorDaemonLogViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Daemon %s', $log->getID())); + $crumbs->setBorder(true); $header = id(new PHUIHeaderView()) - ->setHeader($log->getDaemon()); + ->setHeader($log->getDaemon()) + ->setHeaderIcon('fa-pied-piper-alt'); $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE); @@ -32,32 +34,38 @@ final class PhabricatorDaemonLogViewController $status = $log->getStatus(); switch ($status) { case PhabricatorDaemonLog::STATUS_UNKNOWN: - $tag->setBackgroundColor(PHUITagView::COLOR_ORANGE); - $tag->setName(pht('Unknown')); + $color = 'orange'; + $name = pht('Unknown'); + $icon = 'fa-warning'; break; case PhabricatorDaemonLog::STATUS_RUNNING: - $tag->setBackgroundColor(PHUITagView::COLOR_GREEN); - $tag->setName(pht('Running')); + $color = 'green'; + $name = pht('Running'); + $icon = 'fa-rocket'; break; case PhabricatorDaemonLog::STATUS_DEAD: - $tag->setBackgroundColor(PHUITagView::COLOR_RED); - $tag->setName(pht('Dead')); + $color = 'red'; + $name = pht('Dead'); + $icon = 'fa-times'; break; case PhabricatorDaemonLog::STATUS_WAIT: - $tag->setBackgroundColor(PHUITagView::COLOR_BLUE); - $tag->setName(pht('Waiting')); + $color = 'blue'; + $name = pht('Waiting'); + $icon = 'fa-clock-o'; break; case PhabricatorDaemonLog::STATUS_EXITING: - $tag->setBackgroundColor(PHUITagView::COLOR_YELLOW); - $tag->setName(pht('Exiting')); + $color = 'yellow'; + $name = pht('Exiting'); + $icon = 'fa-check'; break; case PhabricatorDaemonLog::STATUS_EXITED: - $tag->setBackgroundColor(PHUITagView::COLOR_GREY); - $tag->setName(pht('Exited')); + $color = 'bluegrey'; + $name = pht('Exited'); + $icon = 'fa-check'; break; } - $header->addTag($tag); + $header->setStatus($icon, $color, $name); $properties = $this->buildPropertyListView($log); @@ -65,23 +73,26 @@ final class PhabricatorDaemonLogViewController ->setUser($viewer) ->setEvents($events); - $event_panel = new PHUIObjectBoxView(); - $event_panel->setHeaderText(pht('Events')); - $event_panel->appendChild($event_view); + $event_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Events')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($event_view); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $object_box, $event_panel, - ), - array( - 'title' => pht('Daemon Log'), )); + + return $this->newPage() + ->setTitle(pht('Daemon Log')) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function buildPropertyListView(PhabricatorDaemonLog $daemon) { diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php index ad8f673fd7..ad15d41b9d 100644 --- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php +++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php @@ -31,17 +31,18 @@ final class PhabricatorWorkerTaskDetailController $title = pht('Task %d', $task->getID()); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Task %d (%s)', + ->setHeader(pht('Task %d: %s', $task->getID(), - $task->getTaskClass())); + $task->getTaskClass())) + ->setHeaderIcon('fa-sort'); $properties = $this->buildPropertyListView($task); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); - $retry_head = id(new PHUIHeaderView()) ->setHeader(pht('Retries')); @@ -49,6 +50,7 @@ final class PhabricatorWorkerTaskDetailController $retry_box = id(new PHUIObjectBoxView()) ->setHeader($retry_head) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($retry_info); $content = array( @@ -59,15 +61,16 @@ final class PhabricatorWorkerTaskDetailController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($content); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildPropertyListView( diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php index a2beba3cd2..447a614600 100644 --- a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php +++ b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php @@ -158,8 +158,10 @@ abstract class PhabricatorDaemonManagementWorkflow $this->printLaunchingDaemons($daemons, $debug); + $trace = PhutilArgumentParser::isTraceModeEnabled(); + $flags = array(); - if ($debug || PhabricatorEnv::getEnvConfig('phd.trace')) { + if ($trace || PhabricatorEnv::getEnvConfig('phd.trace')) { $flags[] = '--trace'; } diff --git a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php index 4c0caa6fd7..039906e718 100644 --- a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php +++ b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php @@ -24,12 +24,9 @@ final class PhabricatorDaemonLogEventsView extends AphrontView { } public function render() { + $viewer = $this->getViewer(); $rows = array(); - if (!$this->user) { - throw new PhutilInvalidStateException('setUser'); - } - foreach ($this->events as $event) { // Limit display log size. If a daemon gets stuck in an output loop this @@ -83,8 +80,8 @@ final class PhabricatorDaemonLogEventsView extends AphrontView { $row = array( $event->getLogType(), - phabricator_date($event->getEpoch(), $this->user), - phabricator_time($event->getEpoch(), $this->user), + phabricator_date($event->getEpoch(), $viewer), + phabricator_time($event->getEpoch(), $viewer), array( $message, $more, diff --git a/src/applications/daemon/view/PhabricatorDaemonLogListView.php b/src/applications/daemon/view/PhabricatorDaemonLogListView.php index 6c96509505..046d1a29f5 100644 --- a/src/applications/daemon/view/PhabricatorDaemonLogListView.php +++ b/src/applications/daemon/view/PhabricatorDaemonLogListView.php @@ -11,11 +11,9 @@ final class PhabricatorDaemonLogListView extends AphrontView { } public function render() { - $rows = array(); + $viewer = $this->getViewer(); - if (!$this->user) { - throw new PhutilInvalidStateException('setUser'); - } + $rows = array(); $list = new PHUIObjectItemListView(); $list->setFlush(true); @@ -27,7 +25,7 @@ final class PhabricatorDaemonLogListView extends AphrontView { ->setObjectName(pht('Daemon %s', $id)) ->setHeader($log->getDaemon()) ->setHref("/daemon/log/{$id}/") - ->addIcon('none', phabricator_datetime($epoch, $this->user)); + ->addIcon('none', phabricator_datetime($epoch, $viewer)); $status = $log->getStatus(); switch ($status) { diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php index 9653256630..3e67f76f07 100644 --- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php +++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php @@ -27,7 +27,6 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { 'view/(?P\d+)/' => 'PhabricatorDashboardViewController', 'archive/(?P\d+)/' => 'PhabricatorDashboardArchiveController', 'manage/(?P\d+)/' => 'PhabricatorDashboardManageController', - 'history/(?P\d+)/' => 'PhabricatorDashboardHistoryController', 'create/' => 'PhabricatorDashboardEditController', 'copy/(?:(?P\d+)/)?' => 'PhabricatorDashboardCopyController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardEditController', diff --git a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php index 16fdac5577..6dc4a6b8b2 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardEditController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardEditController.php @@ -49,7 +49,7 @@ final class PhabricatorDashboardEditController if ($is_new) { $title = pht('Create Dashboard'); - $header = pht('Create Dashboard'); + $header_icon = 'fa-plus-square'; $button = pht('Create Dashboard'); $cancel_uri = $this->getApplicationURI(); @@ -58,11 +58,11 @@ final class PhabricatorDashboardEditController $id = $dashboard->getID(); $cancel_uri = $this->getApplicationURI('manage/'.$id.'/'); - $title = pht('Edit Dashboard %d', $dashboard->getID()); - $header = pht('Edit Dashboard "%s"', $dashboard->getName()); + $title = pht('Edit Dashboard: %s', $dashboard->getName()); + $header_icon = 'fa-pencil'; $button = pht('Save Changes'); - $crumbs->addTextCrumb(pht('Dashboard %d', $id), $cancel_uri); + $crumbs->addTextCrumb($dashboard->getName(), $cancel_uri); $crumbs->addTextCrumb(pht('Edit')); } @@ -140,6 +140,12 @@ final class PhabricatorDashboardEditController ->setName('name') ->setValue($v_name) ->setError($e_name)) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Layout Mode')) + ->setName('layout_mode') + ->setValue($v_layout_mode) + ->setOptions($layout_mode_options)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') @@ -151,13 +157,7 @@ final class PhabricatorDashboardEditController ->setName('editPolicy') ->setPolicyObject($dashboard) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Layout Mode')) - ->setName('layout_mode') - ->setValue($v_layout_mode) - ->setOptions($layout_mode_options)); + ->setPolicies($policies)); $form->appendControl( id(new AphrontFormTokenizerControl()) @@ -172,18 +172,25 @@ final class PhabricatorDashboardEditController ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($header) + ->setHeaderText(pht('Dashboard')) ->setForm($form) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setValidationException($validation_exception); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function processTemplateRequest(AphrontRequest $request) { diff --git a/src/applications/dashboard/controller/PhabricatorDashboardHistoryController.php b/src/applications/dashboard/controller/PhabricatorDashboardHistoryController.php deleted file mode 100644 index ab303b23e6..0000000000 --- a/src/applications/dashboard/controller/PhabricatorDashboardHistoryController.php +++ /dev/null @@ -1,48 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $dashboard_view_uri = $this->getApplicationURI('view/'.$id.'/'); - $dashboard_manage_uri = $this->getApplicationURI('manage/'.$id.'/'); - - $dashboard = id(new PhabricatorDashboardQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$dashboard) { - return new Aphront404Response(); - } - - $title = $dashboard->getName(); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->setBorder(true); - $crumbs->addTextCrumb( - pht('Dashboard %d', $dashboard->getID()), - $dashboard_view_uri); - $crumbs->addTextCrumb( - pht('Manage'), - $dashboard_manage_uri); - $crumbs->addTextCrumb(pht('History')); - - $timeline = $this->buildTransactionTimeline( - $dashboard, - new PhabricatorDashboardTransactionQuery()); - $timeline->setShouldTerminate(true); - - return $this->buildApplicationPage( - array( - $crumbs, - $timeline, - ), - array( - 'title' => $title, - )); - } - -} diff --git a/src/applications/dashboard/controller/PhabricatorDashboardManageController.php b/src/applications/dashboard/controller/PhabricatorDashboardManageController.php index 6600ad0d34..475716d18d 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardManageController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardManageController.php @@ -34,25 +34,25 @@ final class PhabricatorDashboardManageController pht('Dashboard %d', $dashboard->getID()), $dashboard_uri); $crumbs->addTextCrumb(pht('Manage')); + $crumbs->setBorder(true); $header = $this->buildHeaderView($dashboard); - $actions = $this->buildActionView($dashboard); + $curtain = $this->buildCurtainview($dashboard); $properties = $this->buildPropertyView($dashboard); - $properties->setActionList($actions); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $timeline = $this->buildTransactionTimeline( + $dashboard, + new PhabricatorDashboardTransactionQuery()); + $info_view = null; if (!$can_edit) { $no_edit = pht( 'You do not have permission to edit this dashboard. If you want to '. 'make changes, make a copy first.'); - $box->setInfoView( - id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->setErrors(array($no_edit))); + $info_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->setErrors(array($no_edit)); } $rendered_dashboard = id(new PhabricatorDashboardRenderingEngine()) @@ -61,19 +61,30 @@ final class PhabricatorDashboardManageController ->setArrangeMode($can_edit) ->renderDashboard(); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $rendered_dashboard, - ), - array( - 'title' => $title, - )); + $dashboard_box = id(new PHUIBoxView()) + ->addClass('dashboard-preview-box') + ->appendChild($rendered_dashboard); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $info_view, + $properties, + $timeline, + )) + ->setFooter($dashboard_box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function buildHeaderView(PhabricatorDashboard $dashboard) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); + $id = $dashboard->getID(); if ($dashboard->isArchived()) { $status_icon = 'fa-ban'; @@ -87,33 +98,33 @@ final class PhabricatorDashboardManageController PhabricatorDashboard::getStatusNameMap(), $dashboard->getStatus()); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View Dashboard')) + ->setIcon('fa-columns') + ->setHref($this->getApplicationURI("view/{$id}/")); + return id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($dashboard->getName()) ->setPolicyObject($dashboard) - ->setStatus($status_icon, $status_color, $status_name); + ->setStatus($status_icon, $status_color, $status_name) + ->setHeaderIcon('fa-dashboard') + ->addActionLink($button); } - private function buildActionView(PhabricatorDashboard $dashboard) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtainView(PhabricatorDashboard $dashboard) { + $viewer = $this->getViewer(); $id = $dashboard->getID(); - $actions = id(new PhabricatorActionListView()) - ->setObject($dashboard) - ->setUser($viewer); + $curtain = $this->newCurtainView($dashboard); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $dashboard, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Dashboard')) - ->setIcon('fa-columns') - ->setHref($this->getApplicationURI("view/{$id}/"))); - - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Dashboard')) ->setIcon('fa-pencil') @@ -121,7 +132,7 @@ final class PhabricatorDashboardManageController ->setDisabled(!$can_edit)); if ($dashboard->isArchived()) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Dashboard')) ->setIcon('fa-check') @@ -129,7 +140,7 @@ final class PhabricatorDashboardManageController ->setDisabled(!$can_edit) ->setWorkflow($can_edit)); } else { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Dashboard')) ->setIcon('fa-ban') @@ -138,7 +149,7 @@ final class PhabricatorDashboardManageController ->setWorkflow($can_edit)); } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Copy Dashboard')) ->setIcon('fa-files-o') @@ -158,28 +169,21 @@ final class PhabricatorDashboardManageController $title_install = pht('Install Dashboard'); $href_install = "install/{$id}/"; } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($title_install) ->setIcon('fa-wrench') ->setHref($this->getApplicationURI($href_install)) ->setWorkflow(true)); - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View History')) - ->setIcon('fa-history') - ->setHref($this->getApplicationURI("history/{$id}/"))); - - return $actions; + return $curtain; } private function buildPropertyView(PhabricatorDashboard $dashboard) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($dashboard); + ->setUser($viewer); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, @@ -193,9 +197,10 @@ final class PhabricatorDashboardManageController pht('Panels'), $viewer->renderHandleList($dashboard->getPanelPHIDs())); - $properties->invokeWillRenderEvent(); - - return $properties; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addPropertyList($properties); } } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php index beccd3551a..790b8d11da 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php @@ -90,18 +90,18 @@ final class PhabricatorDashboardPanelEditController } if ($is_create) { - $title = pht('New Panel'); - $header = pht('Create New Panel'); + $title = pht('Create New Panel'); $button = pht('Create Panel'); + $header_icon = 'fa-plus-square'; if ($dashboard) { $cancel_uri = $manage_uri; } else { $cancel_uri = $this->getApplicationURI('panel/'); } } else { - $title = pht('Edit %s', $panel->getMonogram()); - $header = pht('Edit %s %s', $panel->getMonogram(), $panel->getName()); + $title = pht('Edit Panel: %s', $panel->getName()); $button = pht('Save Panel'); + $header_icon = 'fa-pencil'; if ($dashboard) { $cancel_uri = $manage_uri; } else { @@ -260,10 +260,11 @@ final class PhabricatorDashboardPanelEditController '/'.$panel->getMonogram()); $crumbs->addTextCrumb(pht('Edit')); } + $crumbs->setBorder(true); if ($request->isAjax()) { return $this->newDialog() - ->setTitle($header) + ->setTitle($title) ->setSubmitURI($submit_uri) ->setWidth(AphrontDialogView::WIDTH_FORM) ->setValidationException($validation_exception) @@ -279,18 +280,23 @@ final class PhabricatorDashboardPanelEditController } $box = id(new PHUIObjectBoxView()) - ->setHeaderText($header) + ->setHeaderText(pht('Panel')) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function processPanelTypeRequest(AphrontRequest $request) { @@ -349,26 +355,33 @@ final class PhabricatorDashboardPanelEditController } $title = pht('Create Dashboard Panel'); + $header_icon = 'fa-plus-square'; $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Panels'), $this->getApplicationURI('panel/')); $crumbs->addTextCrumb(pht('New Panel')); + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Panel')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function processPanelCloneRequest( diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php index b1718421ea..0a0e7e30c0 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php @@ -50,20 +50,18 @@ final class PhabricatorDashboardPanelRenderController $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb(pht('Panels'), $this->getApplicationURI('panel/')) ->addTextCrumb($panel->getMonogram(), '/'.$panel->getMonogram()) - ->addTextCrumb(pht('Standalone View')); + ->addTextCrumb(pht('Standalone View')) + ->setBorder(true); $view = id(new PHUIBoxView()) ->addClass('dashboard-view') ->appendChild($rendered_panel); - return $this->buildApplicationPage( - array( - $crumbs, - $view, - ), - array( - 'title' => array(pht('Panel'), $panel->getName()), - )); + return $this->newPage() + ->setTitle(array(pht('Panel'), $panel->getName())) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php index bc13374596..b16f60abd7 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php @@ -25,19 +25,15 @@ final class PhabricatorDashboardPanelViewController pht('Panels'), $this->getApplicationURI('panel/')); $crumbs->addTextCrumb($panel->getMonogram()); + $crumbs->setBorder(true); $header = $this->buildHeaderView($panel); - $actions = $this->buildActionView($panel); + $curtain = $this->buildCurtainView($panel); $properties = $this->buildPropertyView($panel); + $timeline = $this->buildTransactionTimeline( $panel, new PhabricatorDashboardPanelTransactionQuery()); - $timeline->setShouldTerminate(true); - - $properties->setActionList($actions); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) @@ -45,31 +41,41 @@ final class PhabricatorDashboardPanelViewController ->setParentPanelPHIDs(array()) ->renderPanel(); - $view = id(new PHUIBoxView()) - ->addMargin(PHUI::MARGIN_LARGE_LEFT) - ->addMargin(PHUI::MARGIN_LARGE_RIGHT) - ->addMargin(PHUI::MARGIN_LARGE_TOP) + $preview = id(new PHUIBoxView()) + ->addClass('dashboard-preview-box') ->appendChild($rendered_panel); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $view, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $properties, $timeline, - ), - array( - 'title' => $title, - )); + )) + ->setFooter($rendered_panel); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildHeaderView(PhabricatorDashboardPanel $panel) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); + $id = $panel->getID(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View Panel')) + ->setIcon('fa-columns') + ->setHref($this->getApplicationURI("panel/render/{$id}/")); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($panel->getName()) - ->setPolicyObject($panel); + ->setPolicyObject($panel) + ->setHeaderIcon('fa-columns') + ->addActionLink($button); if (!$panel->getIsArchived()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); @@ -79,20 +85,18 @@ final class PhabricatorDashboardPanelViewController return $header; } - private function buildActionView(PhabricatorDashboardPanel $panel) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtainView(PhabricatorDashboardPanel $panel) { + $viewer = $this->getViewer(); $id = $panel->getID(); - $actions = id(new PhabricatorActionListView()) - ->setObject($panel) - ->setUser($viewer); + $curtain = $this->newCurtainView($panel); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $panel, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Panel')) ->setIcon('fa-pencil') @@ -108,7 +112,7 @@ final class PhabricatorDashboardPanelViewController $archive_icon = 'fa-check'; } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($archive_text) ->setIcon($archive_icon) @@ -116,21 +120,14 @@ final class PhabricatorDashboardPanelViewController ->setDisabled(!$can_edit) ->setWorkflow(true)); - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Standalone')) - ->setIcon('fa-eye') - ->setHref($this->getApplicationURI("panel/render/{$id}/"))); - - return $actions; + return $curtain; } private function buildPropertyView(PhabricatorDashboardPanel $panel) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($panel); + ->setUser($viewer); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, @@ -167,7 +164,10 @@ final class PhabricatorDashboardPanelViewController ? $viewer->renderHandleList($dashboard_phids) : phutil_tag('em', array(), $does_not_appear)); - return $properties; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addPropertyList($properties); } } diff --git a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php index 4caa68d2e5..dd2214df24 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardViewController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardViewController.php @@ -36,14 +36,10 @@ final class PhabricatorDashboardViewController $rendered_dashboard = $this->buildEmptyView(); } - return $this->buildApplicationPage( - array( - $crumbs, - $rendered_dashboard, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($rendered_dashboard); } protected function buildApplicationCrumbs() { diff --git a/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php index bd097c4b2f..e613647935 100644 --- a/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php @@ -45,13 +45,11 @@ final class DifferentialCloseConduitAPIMethod ->setTransactionType(DifferentialTransaction::TYPE_ACTION) ->setNewValue(DifferentialAction::ACTION_CLOSE); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_CONDUIT, - array()); + $content_source = $request->newContentSource(); $editor = id(new DifferentialTransactionEditor()) ->setActor($viewer) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect(true); diff --git a/src/applications/differential/conduit/DifferentialConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialConduitAPIMethod.php index 1e5f03164c..e5b71c87bb 100644 --- a/src/applications/differential/conduit/DifferentialConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialConduitAPIMethod.php @@ -138,7 +138,7 @@ abstract class DifferentialConduitAPIMethod extends ConduitAPIMethod { $editor = id(new DifferentialTransactionEditor()) ->setActor($viewer) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true); diff --git a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php index c5d2bdb9dc..8f4b154876 100644 --- a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php @@ -77,7 +77,7 @@ final class DifferentialCreateCommentConduitAPIMethod $editor = id(new DifferentialTransactionEditor()) ->setActor($viewer) ->setDisableEmail($request->getValue('silent')) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true); diff --git a/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php index 7a9ffdc6c1..8a0da78865 100644 --- a/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php @@ -144,7 +144,7 @@ final class DifferentialCreateDiffConduitAPIMethod id(new DifferentialDiffEditor()) ->setActor($viewer) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) ->applyTransactions($diff, $xactions); diff --git a/src/applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php index 75eb12b4f0..6f8c2e8640 100644 --- a/src/applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php @@ -85,7 +85,7 @@ final class DifferentialCreateRawDiffConduitAPIMethod id(new DifferentialDiffEditor()) ->setActor($viewer) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) ->setLookupRepository(false) // respect user choice ->applyTransactions($diff, $xactions); diff --git a/src/applications/differential/constants/DifferentialChangeType.php b/src/applications/differential/constants/DifferentialChangeType.php index 2bf4b217a7..7ee3c89b65 100644 --- a/src/applications/differential/constants/DifferentialChangeType.php +++ b/src/applications/differential/constants/DifferentialChangeType.php @@ -81,6 +81,21 @@ final class DifferentialChangeType extends Phobject { return idx($icons, $type, 'fa-file'); } + public static function getIconColorForFileType($type) { + static $icons = array( + self::FILE_TEXT => 'black', + self::FILE_IMAGE => 'black', + self::FILE_BINARY => 'green', + self::FILE_DIRECTORY => 'blue', + self::FILE_SYMLINK => 'blue', + self::FILE_DELETED => 'red', + self::FILE_NORMAL => 'black', + self::FILE_SUBMODULE => 'blue', + ); + + return idx($icons, $type, 'black'); + } + public static function isOldLocationChangeType($type) { static $types = array( self::TYPE_MOVE_AWAY => true, diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php index 01bdd395a7..ff0da2f821 100644 --- a/src/applications/differential/controller/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/DifferentialChangesetViewController.php @@ -275,6 +275,7 @@ final class DifferentialChangesetViewController extends DifferentialController { ->setRenderURI('/differential/changeset/') ->setDiff($diff) ->setTitle(pht('Standalone View')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setParser($parser); if ($revision_id) { @@ -296,16 +297,20 @@ final class DifferentialChangesetViewController extends DifferentialController { } $crumbs->addTextCrumb($changeset->getDisplayFilename()); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $detail, - ), - array( - 'title' => pht('Changeset View'), - 'device' => false, - )); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Changeset View')) + ->setHeaderIcon('fa-gear'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($detail); + + return $this->newPage() + ->setTitle(pht('Changeset View')) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildRawFileResponse( diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 46ac290c09..43252c07f6 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -28,7 +28,8 @@ abstract class DifferentialController extends PhabricatorController { $viewer = $this->getViewer(); $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $have_owners = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorOwnersApplication', @@ -190,6 +191,12 @@ abstract class DifferentialController extends PhabricatorController { } } + // Cast duration to a float since it used to be a string in some + // cases. + if (isset($map['duration'])) { + $map['duration'] = (double)$map['duration']; + } + return $map; } diff --git a/src/applications/differential/controller/DifferentialDiffCreateController.php b/src/applications/differential/controller/DifferentialDiffCreateController.php index 398d33e159..e54d78d6a5 100644 --- a/src/applications/differential/controller/DifferentialDiffCreateController.php +++ b/src/applications/differential/controller/DifferentialDiffCreateController.php @@ -140,10 +140,12 @@ final class DifferentialDiffCreateController extends DifferentialController { $title = pht('Update Diff'); $header = pht('Update Diff'); $button = pht('Continue'); + $header_icon = 'fa-upload'; } else { $title = pht('Create Diff'); $header = pht('Create New Diff'); $button = pht('Create Diff'); + $header_icon = 'fa-plus-square'; } $form @@ -197,15 +199,12 @@ final class DifferentialDiffCreateController extends DifferentialController { ->setValue($button)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($header) + ->setHeaderText(pht('Diff')) ->setValidationException($validation_exception) ->setForm($form) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setFormErrors($errors); - if ($info_view) { - $form_box->setInfoView($info_view); - } - $crumbs = $this->buildApplicationCrumbs(); if ($revision) { $crumbs->addTextCrumb( @@ -213,15 +212,23 @@ final class DifferentialDiffCreateController extends DifferentialController { '/'.$revision->getMonogram()); } $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $info_view, $form_box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index 716a183b5b..b2c497f6ac 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -126,27 +126,40 @@ final class DifferentialDiffViewController extends DifferentialController { ->setRenderingReferences($refs) ->setStandaloneURI('/differential/changeset/') ->setDiff($diff) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTitle(pht('Diff %d', $diff->getID())) ->setUser($request->getUser()); + $title = pht('Diff %d', $diff->getID()); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Diff %d', $diff->getID())); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title); $prop_box = id(new PHUIObjectBoxView()) ->setHeader($property_head) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($property_view) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + + )) + ->setFooter(array( $prop_box, $table_of_contents, $details, - ), - array( - 'title' => pht('Diff View'), )); + + $page = $this->newPage() + ->setTitle(pht('Diff View')) + ->setCrumbs($crumbs) + ->appendChild($view); + return $page; } private function loadSelectableRevisions( diff --git a/src/applications/differential/controller/DifferentialRevisionEditController.php b/src/applications/differential/controller/DifferentialRevisionEditController.php index a52538ca55..a21e1b2592 100644 --- a/src/applications/differential/controller/DifferentialRevisionEditController.php +++ b/src/applications/differential/controller/DifferentialRevisionEditController.php @@ -171,35 +171,44 @@ final class DifferentialRevisionEditController $crumbs = $this->buildApplicationCrumbs(); if ($revision->getID()) { if ($diff) { + $header_icon = 'fa-upload'; $title = pht('Update Differential Revision'); $crumbs->addTextCrumb( 'D'.$revision->getID(), '/differential/diff/'.$diff->getID().'/'); } else { + $header_icon = 'fa-pencil'; $title = pht('Edit Differential Revision'); $crumbs->addTextCrumb( 'D'.$revision->getID(), '/D'.$revision->getID()); } } else { + $header_icon = 'fa-plus-square'; $title = pht('Create New Differential Revision'); } $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText('Revision') ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index ccd24220b5..61fffd9b2a 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -40,7 +40,6 @@ final class DifferentialRevisionViewController extends DifferentialController { $revision->attachActiveDiff(last($diffs)); $diff_vs = $request->getInt('vs'); - $target_id = $request->getInt('id'); $target = idx($diffs, $target_id, end($diffs)); @@ -210,14 +209,10 @@ final class DifferentialRevisionViewController extends DifferentialController { $commits_for_links = array(); } - $revision_detail = id(new DifferentialRevisionDetailView()) - ->setUser($viewer) - ->setRevision($revision) - ->setDiff(end($diffs)) - ->setCustomFields($field_list) - ->setURI($request->getRequestURI()); - - $actions = $this->getRevisionActions($revision); + $header = $this->buildHeader($revision); + $subheader = $this->buildSubheaderView($revision); + $details = $this->buildDetails($revision, $field_list); + $curtain = $this->buildCurtain($revision); $whitespace = $request->getStr( 'whitespace', @@ -232,21 +227,16 @@ final class DifferentialRevisionViewController extends DifferentialController { $symbol_indexes = array(); } - $revision_detail->setActions($actions); - $revision_detail->setUser($viewer); - - $revision_detail_box = $revision_detail->render(); - $revision_warnings = $this->buildRevisionWarnings( $revision, $field_list, $warning_handle_map, $handles); + $info_view = null; if ($revision_warnings) { - $revision_warnings = id(new PHUIInfoView()) + $info_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors($revision_warnings); - $revision_detail_box->setInfoView($revision_warnings); } $detail_diffs = array_select_keys( @@ -277,39 +267,31 @@ final class DifferentialRevisionViewController extends DifferentialController { $comment_view->setQuoteTargetID('comment-content'); } - $wrap_id = celerity_generate_unique_node_id(); - $comment_view = phutil_tag( - 'div', - array( - 'id' => $wrap_id, - ), - $comment_view); + $changeset_view = id(new DifferentialChangesetListView()) + ->setChangesets($changesets) + ->setVisibleChangesets($visible_changesets) + ->setStandaloneURI('/differential/changeset/') + ->setRawFileURIs( + '/differential/changeset/?view=old', + '/differential/changeset/?view=new') + ->setUser($viewer) + ->setDiff($target) + ->setRenderingReferences($rendering_references) + ->setVsMap($vs_map) + ->setWhitespace($whitespace) + ->setSymbolIndexes($symbol_indexes) + ->setTitle(pht('Diff %s', $target->getID())) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - $changeset_view = new DifferentialChangesetListView(); - $changeset_view->setChangesets($changesets); - $changeset_view->setVisibleChangesets($visible_changesets); + if ($repository) { + $changeset_view->setRepository($repository); + } if (!$viewer_is_anonymous) { $changeset_view->setInlineCommentControllerURI( '/differential/comment/inline/edit/'.$revision->getID().'/'); } - $changeset_view->setStandaloneURI('/differential/changeset/'); - $changeset_view->setRawFileURIs( - '/differential/changeset/?view=old', - '/differential/changeset/?view=new'); - - $changeset_view->setUser($viewer); - $changeset_view->setDiff($target); - $changeset_view->setRenderingReferences($rendering_references); - $changeset_view->setVsMap($vs_map); - $changeset_view->setWhitespace($whitespace); - if ($repository) { - $changeset_view->setRepository($repository); - } - $changeset_view->setSymbolIndexes($symbol_indexes); - $changeset_view->setTitle(pht('Diff %s', $target->getID())); - $diff_history = id(new DifferentialRevisionUpdateHistoryView()) ->setUser($viewer) ->setDiffs($diffs) @@ -344,71 +326,9 @@ final class DifferentialRevisionViewController extends DifferentialController { $comment_form = null; if (!$viewer_is_anonymous) { - $draft = id(new PhabricatorDraft())->loadOneWhere( - 'authorPHID = %s AND draftKey = %s', - $viewer->getPHID(), - 'differential-comment-'.$revision->getID()); - - $reviewers = array(); - $ccs = array(); - if ($draft) { - $reviewers = idx($draft->getMetadata(), 'reviewers', array()); - $ccs = idx($draft->getMetadata(), 'ccs', array()); - if ($reviewers || $ccs) { - $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs)); - $reviewers = array_select_keys($handles, $reviewers); - $ccs = array_select_keys($handles, $ccs); - } - } - - $comment_form = new DifferentialAddCommentView(); - $comment_form->setRevision($revision); - - $review_warnings = array(); - foreach ($field_list->getFields() as $field) { - $review_warnings[] = $field->getWarningsForDetailView(); - } - $review_warnings = array_mergev($review_warnings); - - if ($review_warnings) { - $review_warnings_panel = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->setErrors($review_warnings); - $comment_form->setInfoView($review_warnings_panel); - } - - $comment_form->setActions($this->getRevisionCommentActions($revision)); - $action_uri = $this->getApplicationURI( - 'comment/save/'.$revision->getID().'/'); - - $comment_form->setActionURI($action_uri); - $comment_form->setUser($viewer); - $comment_form->setDraft($draft); - $comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID')); - $comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID')); - - // TODO: This just makes the "Z" key work. Generalize this and remove - // it at some point. - $comment_form = phutil_tag( - 'div', - array( - 'class' => 'differential-add-comment-panel', - ), - $comment_form); + $comment_form = $this->buildCommentForm($revision, $field_list); } - $pane_id = celerity_generate_unique_node_id(); - Javelin::initBehavior( - 'differential-keyboard-navigation', - array( - 'haunt' => $pane_id, - )); - Javelin::initBehavior('differential-user-select'); - - $page_pane = id(new DifferentialPrimaryPaneView()) - ->setID($pane_id) - ->appendChild($comment_view); - $signatures = DifferentialRequiredSignaturesField::loadForRevision( $revision); $missing_signatures = false; @@ -418,21 +338,17 @@ final class DifferentialRevisionViewController extends DifferentialController { } } + $footer = array(); + $signature_message = null; if ($missing_signatures) { $signature_message = id(new PHUIInfoView()) - ->setErrors( - array( - array( - phutil_tag('strong', array(), pht('Content Hidden:')), - ' ', - pht( - 'The content of this revision is hidden until the author has '. - 'signed all of the required legal agreements.'), - ), - )); - $page_pane->appendChild($signature_message); + ->setTitle(pht('Content Hidden')) + ->appendChild( + pht( + 'The content of this revision is hidden until the author has '. + 'signed all of the required legal agreements.')); } else { - $page_pane->appendChild( + $footer[] = array( $diff_history, $warning, @@ -440,37 +356,28 @@ final class DifferentialRevisionViewController extends DifferentialController { $toc_view, $other_view, $changeset_view, - )); + ); } if ($comment_form) { - $page_pane->appendChild($comment_form); + $footer[] = $comment_form; } else { // TODO: For now, just use this to get "Login to Comment". - $page_pane->appendChild( - id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setRequestURI($request->getRequestURI())); + $footer[] = id(new PhabricatorApplicationTransactionCommentView()) + ->setUser($viewer) + ->setRequestURI($request->getRequestURI()); } $object_id = 'D'.$revision->getID(); - $operations_box = $this->buildOperationsBox($revision); - $content = array( - $operations_box, - $revision_detail_box, - $diff_detail_box, - $unit_box, - $page_pane, - ); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($object_id, '/'.$object_id); + $crumbs->setBorder(true); $prefs = $viewer->loadPreferences(); - $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; + $nav = null; if ($prefs->getPreference($pref_filetree)) { $collapsed = $prefs->getPreference( PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED, @@ -481,74 +388,223 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setBaseURI(new PhutilURI('/D'.$revision->getID())) ->setCollapsed((bool)$collapsed) ->build($changesets); - $nav->appendChild($content); - $nav->setCrumbs($crumbs); - $content = $nav; - } else { - array_unshift($content, $crumbs); } - return $this->buildApplicationPage( - $content, + // Haunt Mode + $pane_id = celerity_generate_unique_node_id(); + Javelin::initBehavior( + 'differential-keyboard-navigation', array( - 'title' => $object_id.' '.$revision->getTitle(), - 'pageObjects' => array($revision->getPHID()), + 'haunt' => $pane_id, )); + Javelin::initBehavior('differential-user-select'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setCurtain($curtain) + ->setID($pane_id) + ->setMainColumn(array( + $operations_box, + $info_view, + $details, + $diff_detail_box, + $unit_box, + $comment_view, + $signature_message, + )) + ->setFooter($footer); + + $page = $this->newPage() + ->setTitle($object_id.' '.$revision->getTitle()) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($revision->getPHID())) + ->appendChild($view); + + if ($nav) { + $page->setNavigation($nav); + } + + return $page; } - private function getRevisionActions(DifferentialRevision $revision) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader(DifferentialRevision $revision) { + $view = id(new PHUIHeaderView()) + ->setHeader($revision->getTitle($revision)) + ->setUser($this->getViewer()) + ->setPolicyObject($revision) + ->setHeaderIcon('fa-cog'); + + $status = $revision->getStatus(); + $status_name = + DifferentialRevisionStatus::renderFullDescription($status); + + $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); + + return $view; + } + + private function buildSubheaderView(DifferentialRevision $revision) { + $viewer = $this->getViewer(); + + $author_phid = $revision->getAuthorPHID(); + + $author = $viewer->renderHandle($author_phid)->render(); + $date = phabricator_datetime($revision->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $handles = $viewer->loadHandles(array($author_phid)); + $image_uri = $handles[$author_phid]->getImageURI(); + $image_href = $handles[$author_phid]->getURI(); + + $content = pht('Authored by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + + private function buildDetails( + DifferentialRevision $revision, + $custom_fields) { + $viewer = $this->getViewer(); + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + if ($custom_fields) { + $custom_fields->appendFieldsToPropertyList( + $revision, + $viewer, + $properties); + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('DETAILS')); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + } + + private function buildCurtain(DifferentialRevision $revision) { + $viewer = $this->getViewer(); $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); + $curtain = $this->newCurtainView($revision); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $revision, PhabricatorPolicyCapability::CAN_EDIT); - $actions = array(); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setHref("/differential/revision/edit/{$revision_id}/") + ->setName(pht('Edit Revision')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setHref("/differential/revision/edit/{$revision_id}/") - ->setName(pht('Edit Revision')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-upload') - ->setHref("/differential/revision/update/{$revision_id}/") - ->setName(pht('Update Diff')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-upload') + ->setHref("/differential/revision/update/{$revision_id}/") + ->setName(pht('Update Diff')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); $this->requireResource('phabricator-object-selector-css'); $this->requireResource('javelin-behavior-phabricator-object-selector'); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-link') - ->setName(pht('Edit Dependencies')) - ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/") - ->setWorkflow(true) - ->setDisabled(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-link') + ->setName(pht('Edit Dependencies')) + ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/") + ->setWorkflow(true) + ->setDisabled(!$can_edit)); $maniphest = 'PhabricatorManiphestApplication'; if (PhabricatorApplication::isClassInstalled($maniphest)) { - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-anchor') - ->setName(pht('Edit Maniphest Tasks')) - ->setHref("/search/attach/{$revision_phid}/TASK/") - ->setWorkflow(true) - ->setDisabled(!$can_edit); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-anchor') + ->setName(pht('Edit Maniphest Tasks')) + ->setHref("/search/attach/{$revision_phid}/TASK/") + ->setWorkflow(true) + ->setDisabled(!$can_edit)); } $request_uri = $this->getRequest()->getRequestURI(); - $actions[] = id(new PhabricatorActionView()) - ->setIcon('fa-download') - ->setName(pht('Download Raw Diff')) - ->setHref($request_uri->alter('download', 'true')); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-download') + ->setName(pht('Download Raw Diff')) + ->setHref($request_uri->alter('download', 'true'))); - return $actions; + return $curtain; + } + + private function buildCommentForm( + DifferentialRevision $revision, + $field_list) { + + $viewer = $this->getViewer(); + + $draft = id(new PhabricatorDraft())->loadOneWhere( + 'authorPHID = %s AND draftKey = %s', + $viewer->getPHID(), + 'differential-comment-'.$revision->getID()); + + $reviewers = array(); + $ccs = array(); + if ($draft) { + $reviewers = idx($draft->getMetadata(), 'reviewers', array()); + $ccs = idx($draft->getMetadata(), 'ccs', array()); + if ($reviewers || $ccs) { + $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs)); + $reviewers = array_select_keys($handles, $reviewers); + $ccs = array_select_keys($handles, $ccs); + } + } + + $comment_form = id(new DifferentialAddCommentView()) + ->setRevision($revision); + + $review_warnings = array(); + foreach ($field_list->getFields() as $field) { + $review_warnings[] = $field->getWarningsForDetailView(); + } + $review_warnings = array_mergev($review_warnings); + + if ($review_warnings) { + $review_warnings_panel = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors($review_warnings); + $comment_form->setInfoView($review_warnings_panel); + } + + $action_uri = $this->getApplicationURI( + 'comment/save/'.$revision->getID().'/'); + + $comment_form->setActions($this->getRevisionCommentActions($revision)) + ->setActionURI($action_uri) + ->setUser($viewer) + ->setDraft($draft) + ->setReviewers(mpull($reviewers, 'getFullName', 'getPHID')) + ->setCCs(mpull($ccs, 'getFullName', 'getPHID')); + + // TODO: This just makes the "Z" key work. Generalize this and remove + // it at some point. + $comment_form = phutil_tag( + 'div', + array( + 'class' => 'differential-add-comment-panel', + ), + $comment_form); + return $comment_form; } private function getRevisionCommentActions(DifferentialRevision $revision) { @@ -556,7 +612,7 @@ final class DifferentialRevisionViewController extends DifferentialController { DifferentialAction::ACTION_COMMENT => true, ); - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $viewer_phid = $viewer->getPHID(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); @@ -812,11 +868,12 @@ final class DifferentialRevisionViewController extends DifferentialController { $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recent Similar Open Revisions')); + ->setHeader(pht('Recent Similar Revisions')); $view = id(new DifferentialRevisionListView()) ->setHeader($header) ->setRevisions($revisions) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUser($viewer); $phids = $view->getRequiredHandlePHIDs(); @@ -843,7 +900,7 @@ final class DifferentialRevisionViewController extends DifferentialController { assert_instances_of($changesets, 'DifferentialChangeset'); assert_instances_of($vs_changesets, 'DifferentialChangeset'); - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); id(new DifferentialHunkQuery()) ->setViewer($viewer) @@ -976,7 +1033,8 @@ final class DifferentialRevisionViewController extends DifferentialController { } $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Diff Detail')) + ->setHeaderText(pht('DIFF DETAIL')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUser($viewer); $last_tab = null; @@ -1059,7 +1117,8 @@ final class DifferentialRevisionViewController extends DifferentialController { } $box_view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Active Operations')); + ->setHeaderText(pht('ACTIVE OPERATIONS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return id(new DrydockRepositoryOperationStatusView()) ->setUser($viewer) @@ -1072,6 +1131,10 @@ final class DifferentialRevisionViewController extends DifferentialController { DifferentialRevision $revision) { $viewer = $this->getViewer(); + if (!$diff->getBuildable()) { + return null; + } + if (!$diff->getUnitMessages()) { return null; } diff --git a/src/applications/differential/customfield/DifferentialAuthorField.php b/src/applications/differential/customfield/DifferentialAuthorField.php index bac57755ab..fbd74dd2ab 100644 --- a/src/applications/differential/customfield/DifferentialAuthorField.php +++ b/src/applications/differential/customfield/DifferentialAuthorField.php @@ -20,7 +20,7 @@ final class DifferentialAuthorField } public function shouldAppearInPropertyView() { - return true; + return false; } public function renderPropertyViewLabel() { diff --git a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php index d0bd5917dc..6c178f44e0 100644 --- a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php +++ b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php @@ -70,8 +70,6 @@ final class DifferentialHovercardEngineExtension $hovercard->addField(pht('Summary'), $summary); } - $tag = DifferentialRevisionDetailView::renderTagForRevision($revision); - $hovercard->addTag($tag); } } diff --git a/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php index 5150dba315..1ad102e03c 100644 --- a/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php +++ b/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php @@ -68,9 +68,7 @@ final class PhabricatorDifferentialAttachCommitWorkflow ->setViewer($viewer) ->setAuthorPHID($differential_phid); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_CONSOLE, - array()); + $content_source = $this->newContentSource(); $extraction_engine->updateRevisionWithCommit( $revision, diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index b5e4545d85..f6c7309ec7 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1000,7 +1000,8 @@ final class DifferentialChangesetParser extends Phobject { } } - $this->comments = msort($this->comments, 'getID'); + $this->comments = $this->reorderAndThreadComments($this->comments); + foreach ($this->comments as $comment) { $final = $comment->getLineNumber() + $comment->getLineLength(); @@ -1569,4 +1570,67 @@ final class DifferentialChangesetParser extends Phobject { return array($old_back, $new_back); } + private function reorderAndThreadComments(array $comments) { + $comments = msort($comments, 'getID'); + + // Build an empty map of all the comments we actually have. If a comment + // is a reply but the parent has gone missing, we don't want it to vanish + // completely. + $comment_phids = mpull($comments, 'getPHID'); + $replies = array_fill_keys($comment_phids, array()); + + // Now, remove all comments which are replies, leaving only the top-level + // comments. + foreach ($comments as $key => $comment) { + $reply_phid = $comment->getReplyToCommentPHID(); + if (isset($replies[$reply_phid])) { + $replies[$reply_phid][] = $comment; + unset($comments[$key]); + } + } + + // For each top level comment, add the comment, then add any replies + // to it. Do this recursively so threads are shown in threaded order. + $results = array(); + foreach ($comments as $comment) { + $results[] = $comment; + $phid = $comment->getPHID(); + $descendants = $this->getInlineReplies($replies, $phid, 1); + foreach ($descendants as $descendant) { + $results[] = $descendant; + } + } + + // If we have anything left, they were cyclic references. Just dump + // them in a the end. This should be impossible, but users are very + // creative. + foreach ($replies as $phid => $comments) { + foreach ($comments as $comment) { + $results[] = $comment; + } + } + + return $results; + } + + private function getInlineReplies(array &$replies, $phid, $depth) { + $comments = idx($replies, $phid, array()); + unset($replies[$phid]); + + $results = array(); + foreach ($comments as $comment) { + $results[] = $comment; + $descendants = $this->getInlineReplies( + $replies, + $comment->getPHID(), + $depth + 1); + foreach ($descendants as $descendant) { + $results[] = $descendant; + } + } + + return $results; + } + + } diff --git a/src/applications/differential/query/DifferentialDiffQuery.php b/src/applications/differential/query/DifferentialDiffQuery.php index 1616d58ac8..23c016446c 100644 --- a/src/applications/differential/query/DifferentialDiffQuery.php +++ b/src/applications/differential/query/DifferentialDiffQuery.php @@ -6,7 +6,9 @@ final class DifferentialDiffQuery private $ids; private $phids; private $revisionIDs; + private $needChangesets = false; + private $needProperties; public function withIDs(array $ids) { $this->ids = $ids; @@ -28,19 +30,17 @@ final class DifferentialDiffQuery return $this; } + public function needProperties($need_properties) { + $this->needProperties = $need_properties; + return $this; + } + + public function newResultObject() { + return new DifferentialDiff(); + } + protected function loadPage() { - $table = new DifferentialDiff(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $diffs) { @@ -76,6 +76,23 @@ final class DifferentialDiffQuery return $diffs; } + protected function didFilterPage(array $diffs) { + if ($this->needProperties) { + $properties = id(new DifferentialDiffProperty())->loadAllWhere( + 'diffID IN (%Ld)', + mpull($diffs, 'getID')); + + $properties = mgroup($properties, 'getDiffID'); + foreach ($diffs as $diff) { + $map = idx($properties, $diff->getID(), array()); + $map = mpull($map, 'getData', 'getName'); + $diff->attachDiffProperties($map); + } + } + + return $diffs; + } + private function loadChangesets(array $diffs) { id(new DifferentialChangesetQuery()) ->setViewer($this->getViewer()) @@ -88,32 +105,31 @@ final class DifferentialDiffQuery return $diffs; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->revisionIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'revisionID IN (%Ld)', $this->revisionIDs); } - $where[] = $this->buildPagingClause($conn_r); - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php index 16752b9402..0932f63454 100644 --- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php @@ -376,7 +376,7 @@ final class DifferentialChangesetTwoUpRenderer if (!$new) { $th_new = phutil_tag('th', array()); } else { - $th_new = phutil_tag('th', array('id' => "C{$id}OL1"), 1); + $th_new = phutil_tag('th', array('id' => "C{$id}NL1"), 1); } $output = hsprintf( diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index 913abad3ab..0e32855c26 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -5,6 +5,7 @@ final class DifferentialDiff implements PhabricatorPolicyInterface, HarbormasterBuildableInterface, + HarbormasterCircleCIBuildableInterface, PhabricatorApplicationTransactionInterface, PhabricatorDestructibleInterface { @@ -524,6 +525,72 @@ final class DifferentialDiff ); } + +/* -( HarbormasterCircleCIBuildableInterface )----------------------------- */ + + + public function getCircleCIGitHubRepositoryURI() { + $diff_phid = $this->getPHID(); + $repository_phid = $this->getRepositoryPHID(); + if (!$repository_phid) { + throw new Exception( + pht( + 'This diff ("%s") is not associated with a repository. A diff '. + 'must belong to a tracked repository to be built by CircleCI.', + $diff_phid)); + } + + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($repository_phid)) + ->executeOne(); + if (!$repository) { + throw new Exception( + pht( + 'This diff ("%s") is associated with a repository ("%s") which '. + 'could not be loaded.', + $diff_phid, + $repository_phid)); + } + + $staging_uri = $repository->getStagingURI(); + if (!$staging_uri) { + throw new Exception( + pht( + 'This diff ("%s") is associated with a repository ("%s") that '. + 'does not have a Staging Area configured. You must configure a '. + 'Staging Area to use CircleCI integration.', + $diff_phid, + $repository_phid)); + } + + $path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath( + $staging_uri); + if (!$path) { + throw new Exception( + pht( + 'This diff ("%s") is associated with a repository ("%s") that '. + 'does not have a Staging Area ("%s") that is hosted on GitHub. '. + 'CircleCI can only build from GitHub, so the Staging Area for '. + 'the repository must be hosted there.', + $diff_phid, + $repository_phid, + $staging_uri)); + } + + return $staging_uri; + } + + public function getCircleCIBuildIdentifierType() { + return 'tag'; + } + + public function getCircleCIBuildIdentifier() { + $ref = $this->getStagingRef(); + $ref = preg_replace('(^refs/tags/)', '', $ref); + return $ref; + } + public function getStagingRef() { // TODO: We're just hoping to get lucky. Instead, `arc` should store // where it sent changes and we should only provide staging details diff --git a/src/applications/differential/storage/DifferentialInlineComment.php b/src/applications/differential/storage/DifferentialInlineComment.php index 7fe9299428..c27d59bbe3 100644 --- a/src/applications/differential/storage/DifferentialInlineComment.php +++ b/src/applications/differential/storage/DifferentialInlineComment.php @@ -18,8 +18,7 @@ final class DifferentialInlineComment public function getTransactionCommentForSave() { $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LEGACY, - array()); + PhabricatorOldWorldContentSource::SOURCECONST); $this->proxy ->setViewPolicy('public') diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index e67208c53a..6ca586c747 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -485,10 +485,6 @@ final class DifferentialRevision extends DifferentialDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorCustomFieldInterface )------------------------------------ */ diff --git a/src/applications/differential/view/DifferentialAddCommentView.php b/src/applications/differential/view/DifferentialAddCommentView.php index 5524869a77..f32c935e12 100644 --- a/src/applications/differential/view/DifferentialAddCommentView.php +++ b/src/applications/differential/view/DifferentialAddCommentView.php @@ -50,6 +50,7 @@ final class DifferentialAddCommentView extends AphrontView { } public function render() { + $viewer = $this->getViewer(); $this->requireResource('differential-revision-add-comment-css'); $revision = $this->revision; @@ -73,7 +74,7 @@ final class DifferentialAddCommentView extends AphrontView { $form = new AphrontFormView(); $form ->setWorkflow(true) - ->setUser($this->user) + ->setViewer($viewer) ->setAction($this->actionURI) ->addHiddenInput('revision_id', $revision->getID()) ->appendChild( @@ -108,7 +109,7 @@ final class DifferentialAddCommentView extends AphrontView { ->setID('comment-content') ->setLabel(pht('Comment')) ->setValue($this->draft ? $this->draft->getDraft() : null) - ->setUser($this->user)) + ->setViewer($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Submit'))); @@ -162,7 +163,7 @@ final class DifferentialAddCommentView extends AphrontView { $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $header_text = $is_serious ? pht('Add Comment') - : pht('Leap Into Action'); + : pht('Leap Into Action!'); $header = id(new PHUIHeaderView()) ->setHeader($header_text); diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index ec509d62dd..f0c611c609 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -8,6 +8,8 @@ final class DifferentialChangesetListView extends AphrontView { private $inlineURI; private $renderURI = '/differential/changeset/'; private $whitespace; + private $background; + private $header; private $standaloneURI; private $leftRawFileURI; @@ -112,7 +114,19 @@ final class DifferentialChangesetListView extends AphrontView { return $this; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + + public function setHeader($header) { + $this->header = $header; + return $this; + } + public function render() { + $viewer = $this->getViewer(); + $this->requireResource('differential-changeset-view-css'); $changesets = $this->changesets; @@ -148,7 +162,7 @@ final class DifferentialChangesetListView extends AphrontView { )); $renderer = DifferentialChangesetParser::getDefaultRendererForViewer( - $this->getUser()); + $viewer); $output = array(); $ids = array(); @@ -163,7 +177,7 @@ final class DifferentialChangesetListView extends AphrontView { $ref = $this->references[$key]; $detail = id(new DifferentialChangesetDetailView()) - ->setUser($this->getUser()); + ->setUser($viewer); $uniq_id = 'diff-'.$changeset->getAnchorName(); $detail->setID($uniq_id); @@ -238,8 +252,12 @@ final class DifferentialChangesetListView extends AphrontView { )); } - $header = id(new PHUIHeaderView()) - ->setHeader($this->getTitle()); + if ($this->header) { + $header = $this->header; + } else { + $header = id(new PHUIHeaderView()) + ->setHeader($this->getTitle()); + } $content = phutil_tag( 'div', @@ -251,6 +269,7 @@ final class DifferentialChangesetListView extends AphrontView { $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground($this->background) ->setCollapsed(true) ->appendChild($content); @@ -261,6 +280,7 @@ final class DifferentialChangesetListView extends AphrontView { DifferentialChangesetDetailView $detail, $ref, DifferentialChangeset $changeset) { + $viewer = $this->getViewer(); $meta = array(); @@ -280,7 +300,7 @@ final class DifferentialChangesetListView extends AphrontView { try { $meta['diffusionURI'] = (string)$repository->getDiffusionBrowseURIForPath( - $this->user, + $viewer, $changeset->getAbsoluteRepositoryPath($repository, $this->diff), idx($changeset->getMetadata(), 'line:first'), $this->getBranch()); @@ -308,13 +328,12 @@ final class DifferentialChangesetListView extends AphrontView { } } - $user = $this->user; - if ($user && $repository) { + if ($viewer && $repository) { $path = ltrim( $changeset->getAbsoluteRepositoryPath($repository, $this->diff), '/'); $line = idx($changeset->getMetadata(), 'line:first', 1); - $editor_link = $user->loadEditorLink($path, $line, $repository); + $editor_link = $viewer->loadEditorLink($path, $line, $repository); if ($editor_link) { $meta['editor'] = $editor_link; } else { diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php index 4da850539d..639b62fc4b 100644 --- a/src/applications/differential/view/DifferentialLocalCommitsView.php +++ b/src/applications/differential/view/DifferentialLocalCommitsView.php @@ -17,10 +17,7 @@ final class DifferentialLocalCommitsView extends AphrontView { } public function render() { - $user = $this->user; - if (!$user) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); $local = $this->localCommits; if (!$local) { @@ -94,7 +91,7 @@ final class DifferentialLocalCommitsView extends AphrontView { idx($commit, 'date'), idx($commit, 'time')); if ($date) { - $date = phabricator_datetime($date, $user); + $date = phabricator_datetime($date, $viewer); } $row[] = $date; @@ -130,6 +127,7 @@ final class DifferentialLocalCommitsView extends AphrontView { return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Local Commits')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } diff --git a/src/applications/differential/view/DifferentialPrimaryPaneView.php b/src/applications/differential/view/DifferentialPrimaryPaneView.php deleted file mode 100644 index cfa4215fd5..0000000000 --- a/src/applications/differential/view/DifferentialPrimaryPaneView.php +++ /dev/null @@ -1,23 +0,0 @@ -id = $id; - return $this; - } - - public function render() { - - return phutil_tag( - 'div', - array( - 'class' => 'differential-primary-pane', - 'id' => $this->id, - ), - $this->renderChildren()); - } - -} diff --git a/src/applications/differential/view/DifferentialRevisionDetailView.php b/src/applications/differential/view/DifferentialRevisionDetailView.php deleted file mode 100644 index a8436811c2..0000000000 --- a/src/applications/differential/view/DifferentialRevisionDetailView.php +++ /dev/null @@ -1,121 +0,0 @@ -uri = $uri; - return $this; - } - public function getURI() { - return $this->uri; - } - - public function setDiff(DifferentialDiff $diff) { - $this->diff = $diff; - return $this; - } - private function getDiff() { - return $this->diff; - } - - public function setRevision(DifferentialRevision $revision) { - $this->revision = $revision; - return $this; - } - - public function setActions(array $actions) { - $this->actions = $actions; - return $this; - } - private function getActions() { - return $this->actions; - } - - public function setActionList(PhabricatorActionListView $list) { - $this->actionList = $list; - return $this; - } - - public function getActionList() { - return $this->actionList; - } - - public function setCustomFields(PhabricatorCustomFieldList $list) { - $this->customFields = $list; - return $this; - } - - public function render() { - - $this->requireResource('differential-core-view-css'); - - $revision = $this->revision; - $user = $this->getUser(); - - $header = $this->renderHeader($revision); - - $actions = id(new PhabricatorActionListView()) - ->setUser($user) - ->setObject($revision); - foreach ($this->getActions() as $action) { - $actions->addAction($action); - } - - $properties = id(new PHUIPropertyListView()) - ->setUser($user) - ->setObject($revision); - - $properties->setHasKeyboardShortcuts(true); - $properties->setActionList($actions); - $this->setActionList($actions); - - $field_list = $this->customFields; - if ($field_list) { - $field_list->appendFieldsToPropertyList( - $revision, - $user, - $properties); - } - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - - return $object_box; - } - - private function renderHeader(DifferentialRevision $revision) { - $view = id(new PHUIHeaderView()) - ->setHeader($revision->getTitle($revision)) - ->setUser($this->getUser()) - ->setPolicyObject($revision); - - $status = $revision->getStatus(); - $status_name = - DifferentialRevisionStatus::renderFullDescription($status); - - $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); - - return $view; - } - - public static function renderTagForRevision( - DifferentialRevision $revision) { - - $status = $revision->getStatus(); - $status_name = - ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); - - return id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setName($status_name); - } - -} diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 2c14bbc4a5..fbafb5a6cc 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -11,6 +11,7 @@ final class DifferentialRevisionListView extends AphrontView { private $header; private $noDataString; private $noBox; + private $background = null; public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; @@ -38,6 +39,11 @@ final class DifferentialRevisionListView extends AphrontView { return $this; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + public function getRequiredHandlePHIDs() { $phids = array(); foreach ($this->revisions as $revision) { @@ -57,10 +63,7 @@ final class DifferentialRevisionListView extends AphrontView { } public function render() { - $user = $this->user; - if (!$user) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); $fresh = PhabricatorEnv::getEnvConfig('differential.days-fresh'); if ($fresh) { @@ -83,12 +86,12 @@ final class DifferentialRevisionListView extends AphrontView { foreach ($this->revisions as $revision) { $item = id(new PHUIObjectItemView()) - ->setUser($user); + ->setUser($viewer); $icons = array(); $phid = $revision->getPHID(); - $flag = $revision->getFlag($user); + $flag = $revision->getFlag($viewer); if ($flag) { $flag_class = PhabricatorFlagColor::getCSSClass($flag->getColor()); $icons['flag'] = phutil_tag( @@ -99,7 +102,7 @@ final class DifferentialRevisionListView extends AphrontView { ''); } - if ($revision->getDrafts($user)) { + if ($revision->getDrafts($viewer)) { $icons['draft'] = true; } @@ -195,6 +198,7 @@ final class DifferentialRevisionListView extends AphrontView { if ($this->header && !$this->noBox) { $list->setFlush(true); $list = id(new PHUIObjectBoxView()) + ->setBackground($this->background) ->setObjectList($list); if ($this->header instanceof PHUIHeaderView) { diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php index 082344a492..5ce766afb9 100644 --- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php +++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php @@ -305,7 +305,7 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView { return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Revision Update History')) - ->setFlush(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($content); } diff --git a/src/applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php index 5543b759d0..51a494425f 100644 --- a/src/applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php @@ -95,7 +95,7 @@ final class DiffusionCreateCommentConduitAPIMethod id(new PhabricatorAuditEditor()) ->setActor($request->getUser()) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setDisableEmail($request->getValue('silent')) ->setContinueOnMissingFields(true) ->applyTransactions($commit, $xactions); diff --git a/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php index 5ca2783838..be2f07f2c6 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php @@ -79,7 +79,8 @@ final class DiffusionQueryPathsConduitAPIMethod $offset = (int)$request->getValue('offset'); if (strlen($pattern)) { - $pattern = '/'.preg_quote($pattern, '/').'/'; + // Add delimiters to the regex pattern. + $pattern = '('.$pattern.')'; } $results = array(); diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index 3a025e90c5..5c76469b5e 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -48,26 +48,37 @@ final class DiffusionBranchTableController extends DiffusionController { ->withRepository($repository) ->execute(); - $view = id(new DiffusionBranchTableView()) + $table = id(new DiffusionBranchTableView()) ->setUser($viewer) ->setBranches($branches) ->setCommits($commits) ->setDiffusionRequest($drequest); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Branches')) - ->setTable($view); - - $content = $panel; + $content = id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); } $crumbs = $this->buildCrumbs( array( 'branches' => true, )); + $crumbs->setBorder(true); $pager_box = $this->renderTablePagerBox($pager); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Branches')) + ->setHeaderIcon('fa-code-fork'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $content, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -77,8 +88,7 @@ final class DiffusionBranchTableController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index a2e9a3a4b3..a79613cfa9 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -55,20 +55,17 @@ final class DiffusionBrowseController extends DiffusionController { } private function browseSearch() { + $drequest = $this->getDiffusionRequest(); + $header = $this->buildHeaderView($drequest); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); + $search_form = $this->renderSearchForm(); + $search_results = $this->renderSearchResults(); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content = array(); - - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = false); - $content[] = $this->renderSearchResults(); + $search_form = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Search')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($search_form); $crumbs = $this->buildCrumbs( array( @@ -76,6 +73,14 @@ final class DiffusionBrowseController extends DiffusionController { 'path' => true, 'view' => 'browse', )); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $search_form, + $search_results, + )); return $this->newPage() ->setTitle( @@ -84,7 +89,7 @@ final class DiffusionBrowseController extends DiffusionController { $drequest->getRepository()->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild($view); } private function browseFile() { @@ -187,7 +192,25 @@ final class DiffusionBrowseController extends DiffusionController { } $data = $file->loadFileData(); - if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { + + $ref = $this->getGitLFSRef($repository, $data); + if ($ref) { + if ($view == 'git-lfs') { + $file = $this->loadGitLFSFile($ref); + + // Rename the file locally so we generate a better vanity URI for + // it. In storage, it just has a name like "lfs-13f9a94c0923...", + // since we don't get any hints about possible human-readable names + // at upload time. + $basename = basename($drequest->getPath()); + $file->makeEphemeral(); + $file->setName($basename); + + return $file->getRedirectResponse(); + } else { + $corpus = $this->buildGitLFSCorpus($ref); + } + } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { $file_uri = $file->getBestURI(); if ($file->isViewableImage()) { @@ -218,20 +241,18 @@ final class DiffusionBrowseController extends DiffusionController { require_celerity_resource('diffusion-source-css'); // Render the page. - $view = $this->buildActionView($drequest); - $action_list = $this->enrichActionView( + $view = $this->buildCurtain($drequest); + $curtain = $this->enrichCurtain( $view, $drequest, $show_blame, $show_color); - $properties = $this->buildPropertyView($drequest, $action_list); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); + $properties = $this->buildPropertyView($drequest); + $header = $this->buildHeaderView($drequest); + $header->setHeaderIcon('fa-file-code-o'); $content = array(); - $content[] = $object_box; $follow = $request->getStr('follow'); if ($follow) { @@ -277,17 +298,31 @@ final class DiffusionBrowseController extends DiffusionController { 'path' => true, 'view' => 'browse', )); + $crumbs->setBorder(true); $basename = basename($this->getDiffusionRequest()->getPath()); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $content, + )); + + if ($properties) { + $view->addPropertySection(pht('DETAILS'), $properties); + } + + $title = array($basename, $repository->getDisplayName()); + return $this->newPage() - ->setTitle( - array( - $basename, - $repository->getDisplayName(), - )) + ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild( + array( + $view, + )); + } public function browseDirectory( @@ -300,23 +335,21 @@ final class DiffusionBrowseController extends DiffusionController { $reason = $results->getReasonForEmptyResultSet(); - $content = array(); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); + $curtain = $this->buildCurtain($drequest); + $details = $this->buildPropertyView($drequest); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); + $header = $this->buildHeaderView($drequest); + $header->setHeaderIcon('fa-folder-open'); - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = true); + $search_form = $this->renderSearchForm(); + $empty_result = null; + $browse_panel = null; if (!$results->isValidResults()) { $empty_result = new DiffusionEmptyResultView(); $empty_result->setDiffusionRequest($drequest); $empty_result->setDiffusionBrowseResultSet($results); $empty_result->setView($request->getStr('view')); - $content[] = $empty_result; } else { $phids = array(); foreach ($results->getPaths() as $result) { @@ -331,21 +364,30 @@ final class DiffusionBrowseController extends DiffusionController { $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); - $browse_table = new DiffusionBrowseTableView(); - $browse_table->setDiffusionRequest($drequest); - $browse_table->setHandles($handles); - $browse_table->setPaths($results->getPaths()); - $browse_table->setUser($request->getUser()); + $browse_table = id(new DiffusionBrowseTableView()) + ->setDiffusionRequest($drequest) + ->setHandles($handles) + ->setPaths($results->getPaths()) + ->setUser($request->getUser()); - $browse_panel = new PHUIObjectBoxView(); - $browse_panel->setHeaderText($drequest->getPath(), '/'); - $browse_panel->setTable($browse_table); + $browse_header = id(new PHUIHeaderView()) + ->setHeader(nonempty(basename($drequest->getPath()), '/')) + ->setHeaderIcon('fa-folder-open'); - $content[] = $browse_panel; + $browse_panel = id(new PHUIObjectBoxView()) + ->setHeader($browse_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($browse_table); + + $browse_panel->setShowHide( + array(pht('Show Search')), + pht('Hide Search'), + $search_form, + '#'); } - $content[] = $this->buildOpenRevisions(); - $content[] = $this->renderDirectoryReadme($results); + $open_revisions = $this->buildOpenRevisions(); + $readme = $this->renderDirectoryReadme($results); $crumbs = $this->buildCrumbs( array( @@ -355,18 +397,34 @@ final class DiffusionBrowseController extends DiffusionController { )); $pager_box = $this->renderTablePagerBox($pager); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $empty_result, + $browse_panel, + )) + ->setFooter(array( + $open_revisions, + $readme, + $pager_box, + )); + + if ($details) { + $view->addPropertySection(pht('DETAILS'), $details); + } return $this->newPage() - ->setTitle( - array( + ->setTitle(array( nonempty(basename($drequest->getPath()), '/'), $repository->getDisplayName(), )) ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } @@ -431,6 +489,7 @@ final class DiffusionBrowseController extends DiffusionController { $box = id(new PHUIObjectBoxView()) ->setHeaderText($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $pager_box = $this->renderTablePagerBox($pager); @@ -697,12 +756,14 @@ final class DiffusionBrowseController extends DiffusionController { $edit = $this->renderEditButton(); $file = $this->renderFileButton(); $header = id(new PHUIHeaderView()) - ->setHeader(pht('File Contents')) + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->setHeaderIcon('fa-file-code-o') ->addActionLink($edit) ->addActionLink($file); $corpus = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($corpus) ->setCollapsed(true); @@ -737,16 +798,16 @@ final class DiffusionBrowseController extends DiffusionController { return $corpus; } - private function enrichActionView( - PhabricatorActionListView $view, + private function enrichCurtain( + PHUICurtainView $curtain, DiffusionRequest $drequest, $show_blame, $show_color) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $base_uri = $this->getRequest()->getRequestURI(); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Last Change')) ->setHref( @@ -766,7 +827,7 @@ final class DiffusionBrowseController extends DiffusionController { $blame_value = 1; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($blame_text) ->setHref($base_uri->alter('blame', $blame_value)) @@ -784,7 +845,7 @@ final class DiffusionBrowseController extends DiffusionController { $highlight_value = 1; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($highlight_text) ->setHref($base_uri->alter('color', $highlight_value)) @@ -809,14 +870,57 @@ final class DiffusionBrowseController extends DiffusionController { ))->alter('lint', ''); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($lint_text) ->setHref($href) ->setIcon('fa-exclamation-triangle') ->setDisabled(!$href)); - return $view; + + $repository = $drequest->getRepository(); + + $owners = 'PhabricatorOwnersApplication'; + if (PhabricatorApplication::isClassInstalled($owners)) { + $package_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl( + $repository->getPHID(), + array( + $drequest->getPath(), + )); + + $package_query->execute(); + + $packages = $package_query->getControllingPackagesForPath( + $repository->getPHID(), + $drequest->getPath()); + + if ($packages) { + $ownership = id(new PHUIStatusListView()) + ->setUser($viewer); + + foreach ($packages as $package) { + $icon = 'fa-list-alt'; + $color = 'grey'; + + $item = id(new PHUIStatusItemView()) + ->setIcon($icon, $color) + ->setTarget($viewer->renderHandle($package->getPHID())); + + $ownership->addItem($item); + } + } else { + $ownership = phutil_tag('em', array(), pht('None')); + } + + $curtain->newPanel() + ->setHeaderText(pht('Owners')) + ->appendChild($ownership); + } + + return $curtain; } private function renderEditButton() { @@ -844,7 +948,7 @@ final class DiffusionBrowseController extends DiffusionController { return $button; } - private function renderFileButton($file_uri = null) { + private function renderFileButton($file_uri = null, $label = null) { $base_uri = $this->getRequest()->getRequestURI(); @@ -858,6 +962,10 @@ final class DiffusionBrowseController extends DiffusionController { $icon = 'fa-file-text'; } + if ($label !== null) { + $text = $label; + } + $button = id(new PHUIButtonView()) ->setTag('a') ->setText($text) @@ -867,13 +975,28 @@ final class DiffusionBrowseController extends DiffusionController { return $button; } + private function renderGitLFSButton() { + $viewer = $this->getViewer(); + + $uri = $this->getRequest()->getRequestURI(); + $href = $uri->alter('view', 'git-lfs'); + + $text = pht('Download from Git LFS'); + $icon = 'fa-download'; + + return id(new PHUIButtonView()) + ->setTag('a') + ->setText($text) + ->setHref($href) + ->setIcon($icon); + } private function buildDisplayRows( array $lines, array $blame_list, array $blame_commits, - $show_color, - $show_blame) { + $show_blame, + $show_color) { $request = $this->getRequest(); $viewer = $this->getViewer(); @@ -1110,27 +1233,29 @@ final class DiffusionBrowseController extends DiffusionController { "\xC2\xAB"); } - $row[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-blame-link', - ), - $before_link); + if ($show_blame) { + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-link', + ), + $before_link); - $object_links = array(); - $object_links[] = $commit_link; - if ($revision_link) { - $object_links[] = phutil_tag('span', array(), '/'); - $object_links[] = $revision_link; + $object_links = array(); + $object_links[] = $commit_link; + if ($revision_link) { + $object_links[] = phutil_tag('span', array(), '/'); + $object_links[] = $revision_link; + } + + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-rev-link', + ), + $object_links); } - $row[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-rev-link', - ), - $object_links); - $line_link = phutil_tag( 'a', array( @@ -1265,11 +1390,13 @@ final class DiffusionBrowseController extends DiffusionController { $file = $this->renderFileButton($file_uri); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Image')) - ->addActionLink($file); + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->addActionLink($file) + ->setHeaderIcon('fa-file-image-o'); return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); } @@ -1282,11 +1409,12 @@ final class DiffusionBrowseController extends DiffusionController { $file = $this->renderFileButton($file_uri); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')) + ->setHeader(pht('DETAILS')) ->addActionLink($file); $box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($text); return $box; @@ -1298,7 +1426,7 @@ final class DiffusionBrowseController extends DiffusionController { ->appendChild($message); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')); + ->setHeader(pht('DETAILS')); $box = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -1461,12 +1589,12 @@ final class DiffusionBrowseController extends DiffusionController { return "{$summary}\n{$date} \xC2\xB7 {$author}"; } - protected function renderSearchForm($collapsed) { + protected function renderSearchForm() { $drequest = $this->getDiffusionRequest(); $forms = array(); $form = id(new AphrontFormView()) - ->setUser($this->getRequest()->getUser()) + ->setUser($this->getViewer()) ->setMethod('GET'); switch ($drequest->getRepository()->getVersionControlSystem()) { @@ -1492,22 +1620,10 @@ final class DiffusionBrowseController extends DiffusionController { break; } - $filter = new AphrontListFilterView(); - $filter->appendChild($forms); + require_celerity_resource('diffusion-icons-css'); + $form_box = phutil_tag_div('diffusion-search-boxen', $forms); - if ($collapsed) { - $filter->setCollapsed( - pht('Show Search'), - pht('Hide Search'), - pht('Search for file names or content in this directory.'), - '#'); - } - - $filter = id(new PHUIBoxView()) - ->addClass('mlt mlb') - ->appendChild($filter); - - return $filter; + return $form_box; } protected function markupText($text) { @@ -1526,28 +1642,29 @@ final class DiffusionBrowseController extends DiffusionController { } protected function buildHeaderView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); + + $tag = $this->renderCommitHashTag($drequest); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($this->renderPathLinks($drequest, $mode = 'browse')) - ->setPolicyObject($drequest->getRepository()); + ->addTag($tag); return $header; } - protected function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + protected function buildCurtain(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView($drequest); $history_uri = $drequest->generateURI( array( 'action' => 'history', )); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View History')) ->setHref($history_uri) @@ -1559,40 +1676,22 @@ final class DiffusionBrowseController extends DiffusionController { 'commit' => '', 'action' => 'browse', )); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Jump to HEAD')) ->setHref($head_uri) ->setIcon('fa-home') ->setDisabled(!$behind_head)); - return $view; + return $curtain; } protected function buildPropertyView( - DiffusionRequest $drequest, - PhabricatorActionListView $actions) { + DiffusionRequest $drequest) { $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $stable_commit = $drequest->getStableCommit(); - - $view->addProperty( - pht('Commit'), - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $stable_commit, - )), - ), - $drequest->getRepository()->formatCommitName($stable_commit))); + ->setUser($viewer); if ($drequest->getSymbolicType() == 'tag') { $symbolic = $drequest->getSymbolicCommit(); @@ -1616,47 +1715,11 @@ final class DiffusionBrowseController extends DiffusionController { } } - $repository = $drequest->getRepository(); - - $owners = 'PhabricatorOwnersApplication'; - if (PhabricatorApplication::isClassInstalled($owners)) { - $package_query = id(new PhabricatorOwnersPackageQuery()) - ->setViewer($viewer) - ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) - ->withControl( - $repository->getPHID(), - array( - $drequest->getPath(), - )); - - $package_query->execute(); - - $packages = $package_query->getControllingPackagesForPath( - $repository->getPHID(), - $drequest->getPath()); - - if ($packages) { - $ownership = id(new PHUIStatusListView()) - ->setUser($viewer); - - foreach ($packages as $package) { - $icon = 'fa-list-alt'; - $color = 'grey'; - - $item = id(new PHUIStatusItemView()) - ->setIcon($icon, $color) - ->setTarget($viewer->renderHandle($package->getPHID())); - - $ownership->addItem($item); - } - } else { - $ownership = phutil_tag('em', array(), pht('None')); - } - - $view->addProperty(pht('Packages'), $ownership); + if ($view->hasAnyProperties()) { + return $view; } - return $view; + return null; } private function buildOpenRevisions() { @@ -1865,4 +1928,90 @@ final class DiffusionBrowseController extends DiffusionController { $corpus); } + private function getGitLFSRef(PhabricatorRepository $repository, $data) { + if (!$repository->canUseGitLFS()) { + return null; + } + + $lfs_pattern = '(^version https://git-lfs\\.github\\.com/spec/v1[\r\n])'; + if (!preg_match($lfs_pattern, $data)) { + return null; + } + + $matches = null; + if (!preg_match('(^oid sha256:(.*)$)m', $data, $matches)) { + return null; + } + + $hash = $matches[1]; + $hash = trim($hash); + + return id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($this->getViewer()) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes(array($hash)) + ->executeOne(); + } + + private function buildGitLFSCorpus(PhabricatorRepositoryGitLFSRef $ref) { + // TODO: We should probably test if we can load the file PHID here and + // show the user an error if we can't, rather than making them click + // through to hit an error. + + $header = id(new PHUIHeaderView()) + ->setHeader(basename($this->getDiffusionRequest()->getPath())) + ->setHeaderIcon('fa-archive'); + + $severity = PHUIInfoView::SEVERITY_NOTICE; + + $messages = array(); + $messages[] = pht( + 'This %s file is stored in Git Large File Storage.', + phutil_format_bytes($ref->getByteSize())); + + try { + $file = $this->loadGitLFSFile($ref); + $data = $this->renderGitLFSButton(); + $header->addActionLink($data); + } catch (Exception $ex) { + $severity = PHUIInfoView::SEVERITY_ERROR; + $messages[] = pht('The data for this file could not be loaded.'); + } + + $raw = $this->renderFileButton(null, pht('View Raw LFS Pointer')); + $header->addActionLink($raw); + + $corpus = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setCollapsed(true); + + if ($messages) { + $corpus->setInfoView( + id(new PHUIInfoView()) + ->setSeverity($severity) + ->setErrors($messages)); + } + + return $corpus; + } + + private function loadGitLFSFile(PhabricatorRepositoryGitLFSRef $ref) { + $viewer = $this->getViewer(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($ref->getFilePHID())) + ->executeOne(); + if (!$file) { + throw new Exception( + pht( + 'Failed to load file object for Git LFS ref "%s"!', + $ref->getObjectHash())); + } + + return $file; + } + + } diff --git a/src/applications/diffusion/controller/DiffusionChangeController.php b/src/applications/diffusion/controller/DiffusionChangeController.php index 86b371c38b..90258134c4 100644 --- a/src/applications/diffusion/controller/DiffusionChangeController.php +++ b/src/applications/diffusion/controller/DiffusionChangeController.php @@ -15,8 +15,6 @@ final class DiffusionChangeController extends DiffusionController { $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); - $content = array(); - $data = $this->callConduitWithDiffusionRequest( 'diffusion.diffquery', array( @@ -42,9 +40,11 @@ final class DiffusionChangeController extends DiffusionController { 0 => $changeset, ); + $changeset_header = $this->buildChangesetHeader($drequest); + $changeset_view = new DifferentialChangesetListView(); - $changeset_view->setTitle(pht('Change')); $changeset_view->setChangesets($changesets); + $changeset_view->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $changeset_view->setVisibleChangesets($changesets); $changeset_view->setRenderingReferences( array( @@ -68,11 +68,11 @@ final class DiffusionChangeController extends DiffusionController { $changeset_view->setWhitespace( DifferentialChangesetParser::WHITESPACE_SHOW_ALL); $changeset_view->setUser($viewer); + $changeset_view->setHeader($changeset_header); // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); - $content[] = $changeset_view->render(); $crumbs = $this->buildCrumbs( array( @@ -80,19 +80,18 @@ final class DiffusionChangeController extends DiffusionController { 'path' => true, 'view' => 'change', )); + $crumbs->setBorder(true); $links = $this->renderPathLinks($drequest, $mode = 'browse'); + $header = $this->buildHeader($drequest, $links); - $header = id(new PHUIHeaderView()) - ->setHeader($links) - ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); + ->setMainColumn(array( + )) + ->setFooter(array( + $changeset_view, + )); return $this->newPage() ->setTitle( @@ -103,25 +102,41 @@ final class DiffusionChangeController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, + $view, )); } - private function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader( + DiffusionRequest $drequest, + $links) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $tag = $this->renderCommitHashTag($drequest); + + $header = id(new PHUIHeaderView()) + ->setHeader($links) + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()) + ->addTag($tag); + + return $header; + } + + private function buildChangesetHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Changes')); $history_uri = $drequest->generateURI( array( 'action' => 'history', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View History')) + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('View History')) ->setHref($history_uri) ->setIcon('fa-clock-o')); @@ -130,13 +145,14 @@ final class DiffusionChangeController extends DiffusionController { 'action' => 'browse', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Browse Content')) + $header->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Browse Content')) ->setHref($browse_uri) ->setIcon('fa-files-o')); - return $view; + return $header; } protected function buildPropertyView( diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 6736b99430..69c40b044f 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -24,8 +24,7 @@ final class DiffusionCommitController extends DiffusionController { } $drequest = $this->getDiffusionRequest(); - - $user = $request->getUser(); + $viewer = $request->getUser(); if ($request->getStr('diff')) { return $this->buildRawDiffResponse($drequest); @@ -33,9 +32,8 @@ final class DiffusionCommitController extends DiffusionController { $repository = $drequest->getRepository(); - $content = array(); $commit = id(new DiffusionCommitQuery()) - ->setViewer($request->getUser()) + ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers(array($drequest->getCommit())) ->needCommitData(true) @@ -45,6 +43,7 @@ final class DiffusionCommitController extends DiffusionController { $crumbs = $this->buildCrumbs(array( 'commit' => true, )); + $crumbs->setBorder(true); if (!$commit) { if (!$this->getCommitExists()) { @@ -58,22 +57,22 @@ final class DiffusionCommitController extends DiffusionController { 'Failed to load the commit because the commit has not been '. 'parsed yet.')); - return $this->buildApplicationPage( - array( - $crumbs, - $error, - ), - array( - 'title' => pht('Commit Still Parsing'), - )); + $title = pht('Commit Still Parsing'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($error); + } $audit_requests = $commit->getAudits(); $this->auditAuthorityPHIDs = - PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); + PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($viewer); $commit_data = $commit->getCommitData(); $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); + $error_panel = null; if ($is_foreign) { $subpath = $commit_data->getCommitDetail('svn-subpath'); @@ -87,43 +86,41 @@ final class DiffusionCommitController extends DiffusionController { "didn't affect the tracked subdirectory ('%s'), so no ". "information is available.", $subpath)); - $content[] = $error_panel; } else { $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); - $engine->setConfig('viewer', $user); + $engine->setConfig('viewer', $viewer); - $headsup_view = id(new PHUIHeaderView()) + $commit_tag = $this->renderCommitHashTag($drequest); + $header = id(new PHUIHeaderView()) ->setHeader(nonempty($commit->getSummary(), pht('Commit Detail'))) - ->setSubheader(pht('Commit: %s', $commit->getCommitIdentifier())); + ->setHeaderIcon('fa-code-fork') + ->addTag($commit_tag); - $headsup_actions = $this->renderHeadsupActionList($commit, $repository); + if ($commit->getAuditStatus()) { + $icon = PhabricatorAuditCommitStatusConstants::getStatusIcon( + $commit->getAuditStatus()); + $color = PhabricatorAuditCommitStatusConstants::getStatusColor( + $commit->getAuditStatus()); + $status = PhabricatorAuditCommitStatusConstants::getStatusName( + $commit->getAuditStatus()); - $commit_properties = $this->loadCommitProperties( + $header->setStatus($icon, $color, $status); + } + + $curtain = $this->buildCurtain($commit, $repository); + $subheader = $this->buildSubheaderView($commit, $commit_data); + $details = $this->buildPropertyListView( $commit, $commit_data, $audit_requests); - $property_list = id(new PHUIPropertyListView()) - ->setHasKeyboardShortcuts(true) - ->setUser($user) - ->setObject($commit); - foreach ($commit_properties as $key => $value) { - $property_list->addProperty($key, $value); - } $message = $commit_data->getCommitMessage(); $revision = $commit->getCommitIdentifier(); $message = $this->linkBugtraq($message); - $message = $engine->markupText($message); - $property_list->invokeWillRenderEvent(); - $property_list->setActionList($headsup_actions); - $detail_list = new PHUIPropertyListView(); - $detail_list->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); $detail_list->addTextContent( phutil_tag( 'div', @@ -132,19 +129,14 @@ final class DiffusionCommitController extends DiffusionController { ), $message)); - $headsup_view->setTall(true); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($headsup_view) - ->setFormErrors($this->getCommitErrors()) - ->addPropertyList($property_list) - ->addPropertyList($detail_list); - - $content[] = $object_box; + if ($this->getCommitErrors()) { + $error_panel = id(new PHUIInfoView()) + ->appendChild($this->getCommitErrors()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + } } - $content[] = $this->buildComments($commit); - + $timeline = $this->buildComments($commit); $hard_limit = 1000; if ($commit->isImported()) { @@ -161,10 +153,10 @@ final class DiffusionCommitController extends DiffusionController { $changes = array_slice($changes, 0, $hard_limit); } - $content[] = $this->buildMergesTable($commit); + $merge_table = $this->buildMergesTable($commit); $highlighted_audits = $commit->getAuthorityAudits( - $user, + $viewer, $this->auditAuthorityPHIDs); $count = count($changes); @@ -179,32 +171,35 @@ final class DiffusionCommitController extends DiffusionController { } $show_changesets = false; + $info_panel = null; + $change_list = null; + $change_table = null; if ($bad_commit) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Bad Commit'), $bad_commit['description']); } else if ($is_foreign) { // Don't render anything else. } else if (!$commit->isImported()) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Still Importing...'), pht( 'This commit is still importing. Changes will be visible once '. 'the import finishes.')); } else if (!count($changes)) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Empty Commit'), pht( 'This commit is empty and does not affect any paths.')); } else if ($was_limited) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Enormous Commit'), pht( 'This commit is enormous, and affects more than %d files. '. 'Changes are not shown.', $hard_limit)); } else if (!$this->getCommitExists()) { - $content[] = $this->renderStatusMessage( + $info_panel = $this->renderStatusMessage( pht('Commit No Longer Exists'), pht('This commit no longer exists in the repository.')); } else { @@ -214,13 +209,11 @@ final class DiffusionCommitController extends DiffusionController { // changes inline even if there are more than the soft limit. $show_all_details = $request->getBool('show_all'); - $change_panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader(pht('Changes (%s)', new PhutilNumber($count))); - $change_panel->setID('toc'); + $change_header = id(new PHUIHeaderView()) + ->setHeader(pht('Changes (%s)', new PhutilNumber($count))); + $warning_view = null; if ($count > self::CHANGES_LIMIT && !$show_all_details) { - $button = id(new PHUIButtonView()) ->setText(pht('Show All Changes')) ->setHref('?show_all=true') @@ -233,23 +226,19 @@ final class DiffusionCommitController extends DiffusionController { ->appendChild( pht('This commit is very large. Load each file individually.')); - $change_panel->setInfoView($warning_view); - $header->addActionLink($button); + $change_header->addActionLink($button); } $changesets = DiffusionPathChange::convertToDifferentialChangesets( - $user, + $viewer, $changes); // TODO: This table and panel shouldn't really be separate, but we need // to clean up the "Load All Files" interaction first. $change_table = $this->buildTableOfContents( - $changesets); - - $change_panel->setTable($change_table); - $change_panel->setHeader($header); - - $content[] = $change_panel; + $changesets, + $change_header, + $warning_view); $vcs = $repository->getVersionControlSystem(); switch ($vcs) { @@ -296,7 +285,7 @@ final class DiffusionCommitController extends DiffusionController { } else { $visible_changesets = array(); $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments( - $user, + $viewer, $commit->getPHID()); $path_ids = mpull($inlines, null, 'getPathID'); foreach ($changesets as $key => $changeset) { @@ -313,10 +302,10 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setChangesets($changesets); $change_list->setVisibleChangesets($visible_changesets); $change_list->setRenderingReferences($references); - $change_list->setRenderURI( - $repository->getPathURI('diff/')); + $change_list->setRenderURI($repository->getPathURI('diff/')); $change_list->setRepository($repository); - $change_list->setUser($user); + $change_list->setUser($viewer); + $change_list->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); // TODO: Try to setBranch() to something reasonable here? @@ -332,48 +321,74 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/'); - $content[] = $change_list->render(); } - $content[] = $this->renderAddCommentPanel($commit, $audit_requests); + $add_comment = $this->renderAddCommentPanel($commit, $audit_requests); - $prefs = $user->loadPreferences(); + $prefs = $viewer->loadPreferences(); $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED; $show_filetree = $prefs->getPreference($pref_filetree); $collapsed = $prefs->getPreference($pref_collapse); + $nav = null; if ($show_changesets && $show_filetree) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setTitle($commit->getDisplayName()) ->setBaseURI(new PhutilURI($commit->getURI())) ->build($changesets) ->setCrumbs($crumbs) - ->setCollapsed((bool)$collapsed) - ->appendChild($content); - $content = $nav; - } else { - $content = array($crumbs, $content); + ->setCollapsed((bool)$collapsed); } - return $this->buildApplicationPage( - $content, - array( - 'title' => $commit->getDisplayName(), - 'pageObjects' => array($commit->getPHID()), + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setMainColumn(array( + $error_panel, + $timeline, + $merge_table, + $info_panel, + )) + ->setFooter(array( + $change_table, + $change_list, + $add_comment, + )) + ->addPropertySection(pht('DESCRIPTION'), $detail_list) + ->addPropertySection(pht('DETAILS'), $details) + ->setCurtain($curtain); + + $page = $this->newPage() + ->setTitle($commit->getDisplayName()) + ->setCrumbs($crumbs) + ->setPageObjectPHIDS(array($commit->getPHID())) + ->appendChild( + array( + $view, )); + + if ($nav) { + $page->setNavigation($nav); + } + + return $page; + } - private function loadCommitProperties( + private function buildPropertyListView( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, array $audit_requests) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $commit_phid = $commit->getPHID(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + $view = id(new PHUIPropertyListView()) + ->setUser($this->getRequest()->getUser()); + $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($commit_phid)) ->withEdgeTypes(array( @@ -437,31 +452,6 @@ final class DiffusionCommitController extends DiffusionController { $props = array(); - if ($commit->getAuditStatus()) { - $status = PhabricatorAuditCommitStatusConstants::getStatusName( - $commit->getAuditStatus()); - $tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_STATE) - ->setName($status); - - switch ($commit->getAuditStatus()) { - case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT: - $tag->setBackgroundColor(PHUITagView::COLOR_ORANGE); - break; - case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED: - $tag->setBackgroundColor(PHUITagView::COLOR_RED); - break; - case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED: - $tag->setBackgroundColor(PHUITagView::COLOR_BLUE); - break; - case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED: - $tag->setBackgroundColor(PHUITagView::COLOR_GREEN); - break; - } - - $props['Status'] = $tag; - } - if ($audit_requests) { $user_requests = array(); $other_requests = array(); @@ -474,37 +464,21 @@ final class DiffusionCommitController extends DiffusionController { } if ($user_requests) { - $props['Auditors'] = $this->renderAuditStatusView( - $user_requests); + $view->addProperty( + pht('Auditors'), + $this->renderAuditStatusView($user_requests)); } if ($other_requests) { - $props['Project/Package Auditors'] = $this->renderAuditStatusView( - $other_requests); + $view->addProperty( + pht('Project/Package Auditors'), + $this->renderAuditStatusView($other_requests)); } } $author_phid = $data->getCommitDetail('authorPHID'); $author_name = $data->getAuthorName(); - - if (!$repository->isSVN()) { - $authored_info = id(new PHUIStatusItemView()); - - $author_epoch = $data->getCommitDetail('authorEpoch'); - if ($author_epoch !== null) { - $authored_info->setNote( - phabricator_datetime($author_epoch, $viewer)); - } - - if ($author_phid) { - $authored_info->setTarget($handles[$author_phid]->renderLink()); - } else if (strlen($author_name)) { - $authored_info->setTarget($author_name); - } - - $props['Authored'] = id(new PHUIStatusListView()) - ->addItem($authored_info); - } + $author_epoch = $data->getCommitDetail('authorEpoch'); $committed_info = id(new PHUIStatusItemView()) ->setNote(phabricator_datetime($commit->getEpoch(), $viewer)); @@ -521,8 +495,11 @@ final class DiffusionCommitController extends DiffusionController { $committed_info->setTarget($author_name); } - $props['Committed'] = id(new PHUIStatusListView()) - ->addItem($committed_info); + $committed_list = new PHUIStatusListView(); + $committed_list->addItem($committed_info); + $view->addProperty( + pht('Committed'), + $committed_list); if ($push_logs) { $pushed_list = new PHUIStatusListView(); @@ -534,36 +511,49 @@ final class DiffusionCommitController extends DiffusionController { $pushed_list->addItem($pushed_item); } - $props['Pushed'] = $pushed_list; + $view->addProperty( + pht('Pushed'), + $pushed_list); } $reviewer_phid = $data->getCommitDetail('reviewerPHID'); if ($reviewer_phid) { - $props['Reviewer'] = $handles[$reviewer_phid]->renderLink(); + $view->addProperty( + pht('Reviewer'), + $handles[$reviewer_phid]->renderLink()); } if ($revision_phid) { - $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); + $view->addProperty( + pht('Differential Revision'), + $handles[$revision_phid]->renderLink()); } $parents = $this->getCommitParents(); if ($parents) { - $props['Parents'] = $viewer->renderHandleList(mpull($parents, 'getPHID')); + $view->addProperty( + pht('Parents'), + $viewer->renderHandleList(mpull($parents, 'getPHID'))); } if ($this->getCommitExists()) { - $props['Branches'] = phutil_tag( + $view->addProperty( + pht('Branches'), + phutil_tag( 'span', array( 'id' => 'commit-branches', ), - pht('Unknown')); - $props['Tags'] = phutil_tag( + pht('Unknown'))); + + $view->addProperty( + pht('Tags'), + phutil_tag( 'span', array( 'id' => 'commit-tags', ), - pht('Unknown')); + pht('Unknown'))); $identifier = $commit->getCommitIdentifier(); $root = $repository->getPathURI("commit/{$identifier}"); @@ -586,16 +576,21 @@ final class DiffusionCommitController extends DiffusionController { ), $ref_data['ref']); } - $props['References'] = phutil_implode_html(', ', $ref_links); + $view->addProperty( + pht('References'), + phutil_implode_html(', ', $ref_links)); } if ($reverts_phids) { - $props[pht('Reverts')] = $viewer->renderHandleList($reverts_phids); + $view->addProperty( + pht('Reverts'), + $viewer->renderHandleList($reverts_phids)); } if ($reverted_by_phids) { - $props[pht('Reverted By')] = $viewer->renderHandleList( - $reverted_by_phids); + $view->addProperty( + pht('Reverted By'), + $viewer->renderHandleList($reverted_by_phids)); } if ($task_phids) { @@ -604,12 +599,62 @@ final class DiffusionCommitController extends DiffusionController { $task_list[] = $handles[$phid]->renderLink(); } $task_list = phutil_implode_html(phutil_tag('br'), $task_list); - $props['Tasks'] = $task_list; + $view->addProperty( + pht('Tasks'), + $task_list); } - return $props; + return $view; } + private function buildSubheaderView( + PhabricatorRepositoryCommit $commit, + PhabricatorRepositoryCommitData $data) { + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + if ($repository->isSVN()) { + return null; + } + + $author_phid = $data->getCommitDetail('authorPHID'); + $author_name = $data->getAuthorName(); + $author_epoch = $data->getCommitDetail('authorEpoch'); + $date = null; + if ($author_epoch !== null) { + $date = phabricator_datetime($author_epoch, $viewer); + } + + if ($author_phid) { + $handles = $viewer->loadHandles(array($author_phid)); + $image_uri = $handles[$author_phid]->getImageURI(); + $image_href = $handles[$author_phid]->getURI(); + $author = $handles[$author_phid]->renderLink(); + } else if (strlen($author_name)) { + $author = $author_name; + $image_uri = null; + $image_href = null; + } else { + return null; + } + + $author = phutil_tag('strong', array(), $author); + if ($date) { + $content = pht('Authored by %s on %s.', $author, $date); + } else { + $content = pht('Authored by %s.', $author); + } + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + + } + + private function buildComments(PhabricatorRepositoryCommit $commit) { $timeline = $this->buildTransactionTimeline( $commit, @@ -624,11 +669,11 @@ final class DiffusionCommitController extends DiffusionController { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); - if (!$user->isLoggedIn()) { + if (!$viewer->isLoggedIn()) { return id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($user) + ->setUser($viewer) ->setRequestURI($request->getRequestURI()); } @@ -643,7 +688,7 @@ final class DiffusionCommitController extends DiffusionController { $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', - $user->getPHID(), + $viewer->getPHID(), 'diffusion-audit-'.$commit->getID()); if ($draft) { $draft = $draft->getDraft(); @@ -657,7 +702,7 @@ final class DiffusionCommitController extends DiffusionController { $auditor_source = new DiffusionAuditorDatasource(); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setAction('/audit/addcomment/') ->addHiddenInput('commit', $commit->getPHID()) ->appendChild( @@ -690,7 +735,7 @@ final class DiffusionCommitController extends DiffusionController { ->setName('content') ->setValue($draft) ->setID('audit-content') - ->setUser($user)) + ->setUser($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Submit'))); @@ -776,13 +821,13 @@ final class DiffusionCommitController extends DiffusionController { PhabricatorRepositoryCommit $commit, array $audit_requests) { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); - $user = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); - $user_is_author = ($commit->getAuthorPHID() == $user->getPHID()); + $user_is_author = ($commit->getAuthorPHID() == $viewer->getPHID()); $user_request = null; foreach ($audit_requests as $audit_request) { - if ($audit_request->getAuditorPHID() == $user->getPHID()) { + if ($audit_request->getAuditorPHID() == $viewer->getPHID()) { $user_request = $audit_request; break; } @@ -876,9 +921,10 @@ final class DiffusionCommitController extends DiffusionController { $history_table->loadRevisions(); - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Merged Changes')); - $panel->setTable($history_table); + $panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Merged Changes')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($history_table); if ($caption) { $panel->setInfoView($caption); } @@ -886,19 +932,16 @@ final class DiffusionCommitController extends DiffusionController { return $panel; } - private function renderHeadsupActionList( + private function buildCurtain( PhabricatorRepositoryCommit $commit, PhabricatorRepository $repository) { $request = $this->getRequest(); - $user = $request->getUser(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($user) - ->setObject($commit); + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($commit); $can_edit = PhabricatorPolicyFilter::hasCapability( - $user, + $viewer, $commit, PhabricatorPolicyCapability::CAN_EDIT); @@ -911,7 +954,7 @@ final class DiffusionCommitController extends DiffusionController { ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); - $actions->addAction($action); + $curtain->addAction($action); require_celerity_resource('phabricator-object-selector-css'); require_celerity_resource('javelin-behavior-phabricator-object-selector'); @@ -924,16 +967,16 @@ final class DiffusionCommitController extends DiffusionController { ->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/') ->setWorkflow(true) ->setDisabled(!$can_edit); - $actions->addAction($action); + $curtain->addAction($action); } $action = id(new PhabricatorActionView()) ->setName(pht('Download Raw Diff')) ->setHref($request->getRequestURI()->alter('diff', true)) ->setIcon('fa-download'); - $actions->addAction($action); + $curtain->addAction($action); - return $actions; + return $curtain; } private function buildRawDiffResponse(DiffusionRequest $drequest) { @@ -1017,12 +1060,22 @@ final class DiffusionCommitController extends DiffusionController { return $parser->processCorpus($corpus); } - private function buildTableOfContents(array $changesets) { + private function buildTableOfContents( + array $changesets, + $header, + $info_view) { + $drequest = $this->getDiffusionRequest(); $viewer = $this->getViewer(); $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + + if ($info_view) { + $toc_view->setInfoView($info_view); + } // TODO: This is hacky, we just want access to the linkX() methods on // DiffusionView. diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index c83990cc15..a77b000db9 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -291,6 +291,7 @@ abstract class DiffusionController extends PhabricatorController { return id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle($title) + ->setFlush(true) ->appendChild($body); } @@ -300,6 +301,27 @@ abstract class DiffusionController extends PhabricatorController { ->appendChild($pager); } + protected function renderCommitHashTag(DiffusionRequest $drequest) { + $stable_commit = $drequest->getStableCommit(); + $commit = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'commit', + 'commit' => $stable_commit, + )), + ), + $drequest->getRepository()->formatCommitName($stable_commit, true)); + + $tag = id(new PHUITagView()) + ->setName($commit) + ->setShade('indigo') + ->setType(PHUITagView::TYPE_SHADE); + + return $tag; + } + protected function renderDirectoryReadme(DiffusionBrowseResultSet $browse) { $readme_path = $browse->getReadmePath(); if ($readme_path === null) { diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 59248b7acf..eca8fe2a2f 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -40,8 +40,6 @@ final class DiffusionHistoryController extends DiffusionController { $history = $pager->sliceResults($history); $show_graph = !strlen($drequest->getPath()); - $content = array(); - $history_table = id(new DiffusionHistoryTableView()) ->setUser($request->getUser()) ->setDiffusionRequest($drequest) @@ -55,23 +53,13 @@ final class DiffusionHistoryController extends DiffusionController { $history_table->setIsTail(!$pager->getHasMorePages()); } - $history_panel = new PHUIObjectBoxView(); - $history_panel->setHeaderText(pht('History')); - $history_panel->setTable($history_table); + $history_header = $this->buildHistoryHeader($drequest); + $history_panel = id(new PHUIObjectBoxView()) + ->setHeader($history_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($history_table); - $content[] = $history_panel; - - $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setPolicyObject($repository) - ->setHeader($this->renderPathLinks($drequest, $mode = 'history')); - - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $header = $this->buildHeader($drequest, $repository); $crumbs = $this->buildCrumbs( array( @@ -79,9 +67,17 @@ final class DiffusionHistoryController extends DiffusionController { 'path' => true, 'view' => 'history', )); + $crumbs->setBorder(true); $pager_box = $this->renderTablePagerBox($pager); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $history_panel, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -91,28 +87,39 @@ final class DiffusionHistoryController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, - $pager_box, + $view, )); } - private function buildActionView(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + private function buildHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $tag = $this->renderCommitHashTag($drequest); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setPolicyObject($drequest->getRepository()) + ->addTag($tag) + ->setHeader($this->renderPathLinks($drequest, $mode = 'history')) + ->setHeaderIcon('fa-clock-o'); + + return $header; + + } + + private function buildHistoryHeader(DiffusionRequest $drequest) { + $viewer = $this->getViewer(); $browse_uri = $drequest->generateURI( array( 'action' => 'browse', )); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Browse Content')) - ->setHref($browse_uri) - ->setIcon('fa-files-o')); + $browse_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Browse')) + ->setHref($browse_uri) + ->setIcon('fa-files-o'); // TODO: Sometimes we do have a change view, we need to look at the most // recent history entry to figure it out. @@ -130,41 +137,18 @@ final class DiffusionHistoryController extends DiffusionController { ->alter('copies', true); } - $view->addAction( - id(new PhabricatorActionView()) - ->setName($branch_name) - ->setIcon('fa-code-fork') - ->setHref($branch_uri)); + $branch_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText($branch_name) + ->setIcon('fa-code-fork') + ->setHref($branch_uri); - return $view; - } + $header = id(new PHUIHeaderView()) + ->setHeader(pht('History')) + ->addActionLink($browse_button) + ->addActionLink($branch_button); - protected function buildPropertyView( - DiffusionRequest $drequest, - PhabricatorActionListView $actions) { - - $viewer = $this->getRequest()->getUser(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); - - $stable_commit = $drequest->getStableCommit(); - - $view->addProperty( - pht('Commit'), - phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $stable_commit, - )), - ), - $drequest->getRepository()->formatCommitName($stable_commit))); - - return $view; + return $header; } } diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index 0fea238249..8a4debfcea 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -103,7 +103,7 @@ final class DiffusionLastModifiedController extends DiffusionController { $modified = DiffusionView::linkCommit( $drequest->getRepository(), $commit->getCommitIdentifier()); - $date = phabricator_datetime($epoch, $viewer); + $date = $viewer->formatShortDateTime($epoch); } else { $modified = ''; $date = ''; diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index dcbb217d99..fbd059796a 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -157,6 +157,7 @@ final class DiffusionLintController extends DiffusionController { $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Lint')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $title = array('Lint'); @@ -179,7 +180,7 @@ final class DiffusionLintController extends DiffusionController { $header = id(new PHUIHeaderView()) ->setHeader($this->renderPathLinks($drequest, 'lint')) ->setUser($viewer) - ->setPolicyObject($drequest->getRepository()); + ->setHeaderIcon('fa-code'); $actions = $this->buildActionView($drequest); $properties = $this->buildPropertyView( $drequest, @@ -189,18 +190,28 @@ final class DiffusionLintController extends DiffusionController { $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); } else { $object_box = null; + $header = id(new PHUIHeaderView()) + ->setHeader(pht('All Lint')) + ->setHeaderIcon('fa-code'); } + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $object_box, + $content, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $content, + $view, )); } @@ -444,6 +455,7 @@ final class DiffusionLintController extends DiffusionController { $content[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Lint Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $crumbs = $this->buildCrumbs( @@ -454,6 +466,16 @@ final class DiffusionLintController extends DiffusionController { )); $pager_box = $this->renderTablePagerBox($pager); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName())) + ->setHeaderIcon('fa-code'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $content, + $pager_box, + )); return $this->newPage() ->setTitle( @@ -464,8 +486,7 @@ final class DiffusionLintController extends DiffusionController { ->setCrumbs($crumbs) ->appendChild( array( - $content, - $pager_box, + $view, )); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 6504ee50e1..792bcc4249 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -16,11 +16,14 @@ final class DiffusionRepositoryController extends DiffusionController { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $content = array(); - $crumbs = $this->buildCrumbs(); + $crumbs->setBorder(true); - $content[] = $this->buildPropertiesTable($drequest->getRepository()); + $header = $this->buildHeaderView($repository); + $curtain = $this->buildCurtain($repository); + $property_table = $this->buildPropertiesTable($repository); + $description = $this->buildDescriptionView($repository); + $locate_file = $this->buildLocateFile(); // Before we do any work, make sure we're looking at a some content: we're // on a valid branch, and the repository is not empty. @@ -68,14 +71,24 @@ final class DiffusionRepositoryController extends DiffusionController { } if ($page_has_content) { - $content[] = $this->buildNormalContent($drequest); + $content = $this->buildNormalContent($drequest); } else { - $content[] = id(new PHUIInfoView()) + $content = id(new PHUIInfoView()) ->setTitle($empty_title) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors(array($empty_message)); } + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $property_table, + $description, + $locate_file, + )) + ->setFooter($content); + return $this->newPage() ->setTitle( array( @@ -83,7 +96,9 @@ final class DiffusionRepositoryController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild(array( + $view, + )); } @@ -206,13 +221,13 @@ final class DiffusionRepositoryController extends DiffusionController { return $content; } - private function buildPropertiesTable(PhabricatorRepository $repository) { - $user = $this->getRequest()->getUser(); - + private function buildHeaderView(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) ->setHeader($repository->getName()) - ->setUser($user) - ->setPolicyObject($repository); + ->setUser($viewer) + ->setPolicyObject($repository) + ->setHeaderIcon('fa-code'); if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); @@ -227,12 +242,64 @@ final class DiffusionRepositoryController extends DiffusionController { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } + return $header; + } - $actions = $this->buildActionList($repository); + private function buildCurtain(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + + $edit_uri = $repository->getPathURI('edit/'); + $curtain = $this->newCurtainView($repository); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + PhabricatorPolicyCapability::CAN_EDIT); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Repository')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit)); + + if ($repository->isHosted()) { + $push_uri = $this->getApplicationURI( + 'pushlog/?repositories='.$repository->getMonogram()); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Push Logs')) + ->setIcon('fa-list-alt') + ->setHref($push_uri)); + } + + return $curtain; + } + + private function buildDescriptionView(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $description = $repository->getDetail('description'); + if (strlen($description)) { + $description = new PHUIRemarkupView($viewer, $description); + $view->addTextContent($description); + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DESCRIPTION')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); + } + return null; + } + + private function buildPropertiesTable(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setObject($repository) - ->setUser($user); + ->setUser($viewer); if ($repository->isHosted()) { $ssh_uri = $repository->getSSHCloneURIObject(); @@ -286,21 +353,10 @@ final class DiffusionRepositoryController extends DiffusionController { } } - $view->invokeWillRenderEvent(); - - $description = $repository->getDetail('description'); - if (strlen($description)) { - $description = new PHUIRemarkupView($user, $description); - $view->addSectionHeader( - pht('Description'), PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($description); - } - - $view->setActionList($actions); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($view); + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); $info = null; $drequest = $this->getDiffusionRequest(); @@ -344,7 +400,7 @@ final class DiffusionRepositoryController extends DiffusionController { } private function buildBranchListTable(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); if ($drequest->getBranch() === null) { return null; @@ -379,7 +435,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setBranches($branches) ->setCommits($commits); - $panel = new PHUIObjectBoxView(); + $panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = new PHUIHeaderView(); $header->setHeader(pht('Branches')); @@ -388,7 +445,7 @@ final class DiffusionRepositoryController extends DiffusionController { } $button = new PHUIButtonView(); - $button->setText(pht('Show All Branches')); + $button->setText(pht('Show All')); $button->setTag('a'); $button->setIcon('fa-code-fork'); $button->setHref($drequest->generateURI( @@ -404,7 +461,7 @@ final class DiffusionRepositoryController extends DiffusionController { } private function buildTagListTable(DiffusionRequest $drequest) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $repository = $drequest->getRepository(); switch ($repository->getVersionControlSystem()) { @@ -469,46 +526,11 @@ final class DiffusionRepositoryController extends DiffusionController { $panel->setHeader($header); $panel->setTable($view); + $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); return $panel; } - private function buildActionList(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); - - $edit_uri = $repository->getPathURI('edit/'); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($repository); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Repository')) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setWorkflow(!$can_edit) - ->setDisabled(!$can_edit)); - - if ($repository->isHosted()) { - $push_uri = $this->getApplicationURI( - 'pushlog/?repositories='.$repository->getMonogram()); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Push Logs')) - ->setIcon('fa-list-alt') - ->setHref($push_uri)); - } - - return $view; - } - private function buildHistoryTable( $history_results, $history, @@ -551,7 +573,7 @@ final class DiffusionRepositoryController extends DiffusionController { ->setIcon('fa-list-alt'); $button = id(new PHUIButtonView()) - ->setText(pht('View Full History')) + ->setText(pht('View History')) ->setHref($drequest->generateURI( array( 'action' => 'history', @@ -559,7 +581,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setTag('a') ->setIcon($icon); - $panel = new PHUIObjectBoxView(); + $panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = id(new PHUIHeaderView()) ->setHeader(pht('Recent Commits')) ->addActionLink($button); @@ -569,6 +592,46 @@ final class DiffusionRepositoryController extends DiffusionController { return $panel; } + private function buildLocateFile() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $locate_panel = null; + if ($repository->canUsePathTree()) { + Javelin::initBehavior( + 'diffusion-locate-file', + array( + 'controlID' => 'locate-control', + 'inputID' => 'locate-input', + 'browseBaseURI' => (string)$drequest->generateURI( + array( + 'action' => 'browse', + )), + 'uri' => (string)$drequest->generateURI( + array( + 'action' => 'pathtree', + )), + )); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTypeaheadControl()) + ->setHardpointID('locate-control') + ->setID('locate-input') + ->setLabel(pht('Locate File'))); + $form_box = id(new PHUIBoxView()) + ->appendChild($form->buildLayoutView()); + $locate_panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Locate File')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($form_box); + } + return $locate_panel; + } + private function buildBrowseTable( $browse_results, $browse_paths, @@ -606,9 +669,10 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_uri = $drequest->generateURI(array('action' => 'browse')); - $browse_panel = new PHUIObjectBoxView(); + $browse_panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Repository')); + ->setHeader($repository->getName()); $icon = id(new PHUIIconView()) ->setIcon('fa-folder-open'); @@ -621,38 +685,6 @@ final class DiffusionRepositoryController extends DiffusionController { $header->addActionLink($button); $browse_panel->setHeader($header); - - $locate_panel = null; - if ($repository->canUsePathTree()) { - Javelin::initBehavior( - 'diffusion-locate-file', - array( - 'controlID' => 'locate-control', - 'inputID' => 'locate-input', - 'browseBaseURI' => (string)$drequest->generateURI( - array( - 'action' => 'browse', - )), - 'uri' => (string)$drequest->generateURI( - array( - 'action' => 'pathtree', - )), - )); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTypeaheadControl()) - ->setHardpointID('locate-control') - ->setID('locate-input') - ->setLabel(pht('Locate File'))); - $form_box = id(new PHUIBoxView()) - ->appendChild($form->buildLayoutView()); - $locate_panel = id(new PHUIObjectBoxView()) - ->setHeaderText('Locate File') - ->appendChild($form_box); - } - $browse_panel->setTable($browse_table); $pager->setURI($browse_uri, 'offset'); @@ -664,7 +696,6 @@ final class DiffusionRepositoryController extends DiffusionController { } return array( - $locate_panel, $browse_panel, $pager_box, ); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php index 20a685928e..3239efb964 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php @@ -262,10 +262,26 @@ final class DiffusionRepositoryCreateController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form); + ->appendChild($view); + } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php index 8459498374..b8f95f36fc 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php @@ -53,6 +53,10 @@ final class DiffusionRepositoryEditActionsController $title = pht('Edit Actions (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($repository) @@ -97,13 +101,21 @@ final class DiffusionRepositoryEditActionsController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Actions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); + } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php index e47bde3902..038cc0f0cd 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php @@ -49,6 +49,10 @@ final class DiffusionRepositoryEditAutomationController $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $form = id(new AphrontFormView()) ->setUser($viewer) ->appendRemarkupInstructions( @@ -69,14 +73,21 @@ final class DiffusionRepositoryEditAutomationController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Automation')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php index e23a57e655..10f4b13ef9 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -105,6 +105,10 @@ final class DiffusionRepositoryEditBasicController $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( @@ -144,16 +148,23 @@ final class DiffusionRepositoryEditBasicController ->appendChild(id(new PHUIFormDividerControl())) ->appendRemarkupInstructions($this->getReadmeInstructions()); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Basic Information')) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getReadmeInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php index c256336ec2..927af295d4 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php @@ -98,6 +98,9 @@ final class DiffusionRepositoryEditBranchesController $crumbs->addTextCrumb(pht('Edit Branches')); $title = pht('Edit Branches (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) @@ -213,14 +216,21 @@ final class DiffusionRepositoryEditBranchesController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Branches')) ->setValidationException($validation_exception) - ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); } private function processBranches($string) { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index 1e3cb553da..714568f187 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -20,6 +20,7 @@ abstract class DiffusionRepositoryEditController $crumbs->addTextCrumb(pht('Edit'), $edit_uri); } } + $crumbs->setBorder(true); return $crumbs; } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php index 0059c9d6c0..256e69ac59 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php @@ -50,6 +50,9 @@ final class DiffusionRepositoryEditEncodingController $crumbs->addTextCrumb(pht('Edit Encoding')); $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($user) @@ -65,15 +68,22 @@ final class DiffusionRepositoryEditEncodingController ->setValue(pht('Save Encoding')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Encoding')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getEncodingInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php index be40b794b3..0c759eba59 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php @@ -57,6 +57,9 @@ final class DiffusionRepositoryEditHostingController $crumbs->addTextCrumb(pht('Edit Hosting')); $title = pht('Edit Hosting (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $hosted_control = id(new AphrontFormRadioButtonControl()) ->setName('hosting') @@ -95,14 +98,21 @@ final class DiffusionRepositoryEditHostingController ->setValue(pht('Save and Continue')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Hosting')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } public function handleProtocols(PhabricatorRepository $repository) { @@ -155,7 +165,9 @@ final class DiffusionRepositoryEditHostingController $crumbs->addTextCrumb(pht('Edit Protocols')); $title = pht('Edit Protocols (%s)', $repository->getName()); - + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $rw_message = pht( 'Phabricator will serve a read-write copy of this repository.'); @@ -256,14 +268,21 @@ final class DiffusionRepositoryEditHostingController ->setValue(pht('Save Changes')) ->addCancelButton($prev_uri, pht('Back'))); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Protocols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 9274d83fdd..9a10ddcd1f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -38,16 +38,16 @@ final class DiffusionRepositoryEditMainController $title = pht('Edit %s', $repository->getName()); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); if ($repository->isTracked()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { $header->setStatus('fa-ban', 'dark', pht('Inactive')); } - $basic_actions = $this->buildBasicActions($repository); - $basic_properties = - $this->buildBasicProperties($repository, $basic_actions); + $curtain = $this->buildCurtain($repository); + $basic_properties = $this->buildBasicProperties($repository); $policy_actions = $this->buildPolicyActions($repository); $policy_properties = @@ -119,16 +119,14 @@ final class DiffusionRepositoryEditMainController $boxes = array(); - $boxes[] = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($basic_properties); - $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Policies')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($policy_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Hosting')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($hosting_properties); if ($repository->canMirror()) { @@ -156,6 +154,7 @@ final class DiffusionRepositoryEditMainController $boxes[] = id(new PHUIObjectBoxView()) ->setFormErrors($mirror_info) ->setHeaderText(pht('Mirrors')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($mirror_properties); $boxes[] = $mirror_list; @@ -164,73 +163,88 @@ final class DiffusionRepositoryEditMainController if ($remote_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Remote')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($remote_properties); } if ($storage_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Storage')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($storage_properties); } if ($staging_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Staging')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($staging_properties); } if ($automation_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Automation')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($automation_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Text Encoding')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($encoding_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Symbols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($symbols_properties); if ($branches_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Branches')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($branches_properties); } if ($subversion_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Subversion')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($subversion_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Actions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($actions_properties); - return $this->buildApplicationPage( - array( - $crumbs, + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $basic_properties) + ->setMainColumn(array( $boxes, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } - private function buildBasicActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtain(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView($repository); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Basic Information')) ->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/')); - $view->addAction($edit); + $curtain->addAction($edit); $edit = id(new PhabricatorActionView()) ->setIcon('fa-refresh') @@ -238,7 +252,7 @@ final class DiffusionRepositoryEditMainController ->setWorkflow(true) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/update/')); - $view->addAction($edit); + $curtain->addAction($edit); $activate = id(new PhabricatorActionView()) ->setHref( @@ -255,9 +269,9 @@ final class DiffusionRepositoryEditMainController ->setName(pht('Activate Repository')); } - $view->addAction($activate); + $curtain->addAction($activate); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Repository')) ->setIcon('fa-times') @@ -266,19 +280,16 @@ final class DiffusionRepositoryEditMainController ->setDisabled(true) ->setWorkflow(true)); - return $view; + return $curtain; } private function buildBasicProperties( - PhabricatorRepository $repository, - PhabricatorActionListView $actions) { + PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($repository) - ->setActionList($actions); + ->setUser($viewer); $type = PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()); @@ -322,7 +333,7 @@ final class DiffusionRepositoryEditMainController } private function buildEncodingActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -341,7 +352,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -358,7 +369,7 @@ final class DiffusionRepositoryEditMainController } private function buildPolicyActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -377,7 +388,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -410,7 +421,7 @@ final class DiffusionRepositoryEditMainController } private function buildBranchesActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -429,7 +440,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -459,7 +470,7 @@ final class DiffusionRepositoryEditMainController } private function buildSubversionActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -478,7 +489,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -498,7 +509,7 @@ final class DiffusionRepositoryEditMainController } private function buildActionsActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -517,7 +528,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -539,7 +550,7 @@ final class DiffusionRepositoryEditMainController } private function buildRemoteActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -558,7 +569,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -579,7 +590,7 @@ final class DiffusionRepositoryEditMainController } private function buildStorageActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -598,7 +609,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -799,7 +810,7 @@ final class DiffusionRepositoryEditMainController private function buildRepositoryStatus( PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $is_cluster = $repository->getAlmanacServicePHID(); $view = new PHUIStatusListView(); @@ -1186,7 +1197,7 @@ final class DiffusionRepositoryEditMainController private function buildMirrorActions( PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $mirror_actions = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -1209,7 +1220,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $mirror_properties = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -1260,11 +1271,14 @@ final class DiffusionRepositoryEditMainController $mirror_list->addItem($item); } - return $mirror_list; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Configured Mirrors')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($mirror_list); } private function buildSymbolsActions(PhabricatorRepository $repository) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -1283,7 +1297,7 @@ final class DiffusionRepositoryEditMainController PhabricatorRepository $repository, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php index deb8fad669..2d158e61ea 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php @@ -45,7 +45,10 @@ final class DiffusionRepositoryEditStagingController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Staging')); - $title = pht('Edit %s', $repository->getName()); + $title = pht('Edit Staging (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -68,14 +71,21 @@ final class DiffusionRepositoryEditStagingController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Staging')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php index 711844188a..f3a492e3b3 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php @@ -22,6 +22,9 @@ final class DiffusionRepositoryEditStorageController $crumbs->addTextCrumb(pht('Edit Storage')); $title = pht('Edit %s', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $service_phid = $repository->getAlmanacServicePHID(); if ($service_phid) { @@ -57,15 +60,21 @@ final class DiffusionRepositoryEditStorageController id(new AphrontFormSubmitControl()) ->addCancelButton($edit_uri, pht('Done'))); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setForm($form) - ->setFormErrors($errors); + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Storage')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php index 93b9193f14..6fcc316135 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php @@ -63,6 +63,9 @@ final class DiffusionRepositoryEditSubversionController $crumbs->addTextCrumb(pht('Edit Subversion Info')); $title = pht('Edit Subversion Info (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) @@ -96,13 +99,20 @@ final class DiffusionRepositoryEditSubversionController ->addCancelButton($edit_uri)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Subversion')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($form_box); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php index 6076d2df65..06ce23032a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php @@ -59,7 +59,10 @@ final class DiffusionRepositorySymbolsController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Symbols')); - $title = pht('Edit %s', $repository->getName()); + $title = pht('Edit Symbols (%s)', $repository->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); $form = id(new AphrontFormView()) ->setUser($viewer) @@ -85,15 +88,22 @@ final class DiffusionRepositorySymbolsController ->setValue(pht('Save')) ->addCancelButton($edit_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Symbols')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form) ->setFormErrors($errors); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($object_box); + ->appendChild($view); } private function getInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 84010d76af..80fb224163 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -5,7 +5,13 @@ final class DiffusionServeController extends DiffusionController { private $serviceViewer; private $serviceRepository; + private $isGitLFSRequest; + private $gitLFSToken; + private $gitLFSInput; + public function setServiceViewer(PhabricatorUser $viewer) { + $this->getRequest()->setUser($viewer); + $this->serviceViewer = $viewer; return $this; } @@ -23,6 +29,14 @@ final class DiffusionServeController extends DiffusionController { return $this->serviceRepository; } + public function getIsGitLFSRequest() { + return $this->isGitLFSRequest; + } + + public function getGitLFSToken() { + return $this->gitLFSToken; + } + public function isVCSRequest(AphrontRequest $request) { $identifier = $this->getRepositoryIdentifierFromRequest($request); if ($identifier === null) { @@ -31,6 +45,10 @@ final class DiffusionServeController extends DiffusionController { $content_type = $request->getHTTPHeader('Content-Type'); $user_agent = idx($_SERVER, 'HTTP_USER_AGENT'); + $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type'); + + // This may have a "charset" suffix, so only match the prefix. + $lfs_pattern = '(^application/vnd\\.git-lfs\\+json(;|\z))'; $vcs = null; if ($request->getExists('service')) { @@ -46,6 +64,14 @@ final class DiffusionServeController extends DiffusionController { } else if ($content_type == 'application/x-git-receive-pack-request') { // We get this for `git-receive-pack`. $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + } else if (preg_match($lfs_pattern, $content_type)) { + // This is a Git LFS HTTP API request. + $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + $this->isGitLFSRequest = true; + } else if ($request_type == 'git-lfs') { + // This is a Git LFS object content request. + $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; + $this->isGitLFSRequest = true; } else if ($request->getExists('cmd')) { // Mercurial also sends an Accept header like // "application/mercurial-0.1", and a User-Agent like @@ -142,7 +168,17 @@ final class DiffusionServeController extends DiffusionController { $username = $_SERVER['PHP_AUTH_USER']; $password = new PhutilOpaqueEnvelope($_SERVER['PHP_AUTH_PW']); - $viewer = $this->authenticateHTTPRepositoryUser($username, $password); + // Try Git LFS auth first since we can usually reject it without doing + // any queries, since the username won't match the one we expect or the + // request won't be LFS. + $viewer = $this->authenticateGitLFSUser($username, $password); + + // If that failed, try normal auth. Note that we can use normal auth on + // LFS requests, so this isn't strictly an alternative to LFS auth. + if (!$viewer) { + $viewer = $this->authenticateHTTPRepositoryUser($username, $password); + } + if (!$viewer) { return new PhabricatorVCSResponse( 403, @@ -202,6 +238,11 @@ final class DiffusionServeController extends DiffusionController { } } + $response = $this->validateGitLFSRequest($repository, $viewer); + if ($response) { + return $response; + } + $this->setServiceRepository($repository); if (!$repository->isTracked()) { @@ -212,46 +253,66 @@ final class DiffusionServeController extends DiffusionController { $is_push = !$this->isReadOnlyRequest($repository); - switch ($repository->getServeOverHTTP()) { - case PhabricatorRepository::SERVE_READONLY: - if ($is_push) { + if ($this->getIsGitLFSRequest() && $this->getGitLFSToken()) { + // We allow git LFS requests over HTTP even if the repository does not + // otherwise support HTTP reads or writes, as long as the user is using a + // token from SSH. If they're using HTTP username + password auth, they + // have to obey the normal HTTP rules. + } else { + switch ($repository->getServeOverHTTP()) { + case PhabricatorRepository::SERVE_READONLY: + if ($is_push) { + return new PhabricatorVCSResponse( + 403, + pht('This repository is read-only over HTTP.')); + } + break; + case PhabricatorRepository::SERVE_READWRITE: + // We'll check for push capability below. + break; + case PhabricatorRepository::SERVE_OFF: + default: return new PhabricatorVCSResponse( 403, - pht('This repository is read-only over HTTP.')); - } - break; - case PhabricatorRepository::SERVE_READWRITE: - if ($is_push) { - $can_push = PhabricatorPolicyFilter::hasCapability( - $viewer, - $repository, - DiffusionPushCapability::CAPABILITY); - if (!$can_push) { - if ($viewer->isLoggedIn()) { - return new PhabricatorVCSResponse( - 403, - pht('You do not have permission to push to this repository.')); - } else { - if ($allow_auth) { - return new PhabricatorVCSResponse( - 401, - pht('You must log in to push to this repository.')); - } else { - return new PhabricatorVCSResponse( - 403, - pht( - 'Pushing to this repository requires authentication, '. - 'which is forbidden over HTTP.')); - } - } + pht('This repository is not available over HTTP.')); + } + } + + if ($is_push) { + $can_push = PhabricatorPolicyFilter::hasCapability( + $viewer, + $repository, + DiffusionPushCapability::CAPABILITY); + if (!$can_push) { + if ($viewer->isLoggedIn()) { + $error_code = 403; + $error_message = pht( + 'You do not have permission to push to this repository ("%s").', + $repository->getDisplayName()); + + if ($this->getIsGitLFSRequest()) { + return DiffusionGitLFSResponse::newErrorResponse( + $error_code, + $error_message); + } else { + return new PhabricatorVCSResponse( + $error_code, + $error_message); + } + } else { + if ($allow_auth) { + return new PhabricatorVCSResponse( + 401, + pht('You must log in to push to this repository.')); + } else { + return new PhabricatorVCSResponse( + 403, + pht( + 'Pushing to this repository requires authentication, '. + 'which is forbidden over HTTP.')); } } - break; - case PhabricatorRepository::SERVE_OFF: - default: - return new PhabricatorVCSResponse( - 403, - pht('This repository is not available over HTTP.')); + } } $vcs_type = $repository->getVersionControlSystem(); @@ -324,6 +385,14 @@ final class DiffusionServeController extends DiffusionController { PhabricatorRepository $repository, PhabricatorUser $viewer) { + // We can serve Git LFS requests first, since we don't need to proxy them. + // It's also important that LFS requests never fall through to standard + // service pathways, because that would let you use LFS tokens to read + // normal repository data. + if ($this->getIsGitLFSRequest()) { + return $this->serveGitLFSRequest($repository, $viewer); + } + // If this repository is hosted on a service, we need to proxy the request // to a host which can serve it. $is_cluster_request = $this->getRequest()->isProxiedClusterRequest(); @@ -363,6 +432,10 @@ final class DiffusionServeController extends DiffusionController { // TODO: This implementation is safe by default, but very incomplete. + if ($this->getIsGitLFSRequest()) { + return $this->isGitLFSReadOnlyRequest($repository); + } + switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $service = $request->getStr('service'); @@ -514,6 +587,52 @@ final class DiffusionServeController extends DiffusionController { return $base_path; } + private function authenticateGitLFSUser( + $username, + PhutilOpaqueEnvelope $password) { + + // Never accept these credentials for requests which aren't LFS requests. + if (!$this->getIsGitLFSRequest()) { + return null; + } + + // If we have the wrong username, don't bother checking if the token + // is right. + if ($username !== DiffusionGitLFSTemporaryTokenType::HTTP_USERNAME) { + return null; + } + + $lfs_pass = $password->openEnvelope(); + $lfs_hash = PhabricatorHash::digest($lfs_pass); + + $token = id(new PhabricatorAuthTemporaryTokenQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withTokenTypes(array(DiffusionGitLFSTemporaryTokenType::TOKENTYPE)) + ->withTokenCodes(array($lfs_hash)) + ->withExpired(false) + ->executeOne(); + if (!$token) { + return null; + } + + $user = id(new PhabricatorPeopleQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($token->getUserPHID())) + ->executeOne(); + + if (!$user) { + return null; + } + + if (!$user->isUserActivated()) { + return null; + } + + $this->gitLFSToken = $token; + + return $user; + } + private function authenticateHTTPRepositoryUser( $username, PhutilOpaqueEnvelope $password) { @@ -739,4 +858,326 @@ final class DiffusionServeController extends DiffusionController { ); } + private function validateGitLFSRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + if (!$this->getIsGitLFSRequest()) { + return null; + } + + if (!$repository->canUseGitLFS()) { + return new PhabricatorVCSResponse( + 403, + pht( + 'The requested repository ("%s") does not support Git LFS.', + $repository->getDisplayName())); + } + + // If this is using an LFS token, sanity check that we're using it on the + // correct repository. This shouldn't really matter since the user could + // just request a proper token anyway, but it suspicious and should not + // be permitted. + + $token = $this->getGitLFSToken(); + if ($token) { + $resource = $token->getTokenResource(); + if ($resource !== $repository->getPHID()) { + return new PhabricatorVCSResponse( + 403, + pht( + 'The authentication token provided in the request is bound to '. + 'a different repository than the requested repository ("%s").', + $repository->getDisplayName())); + } + } + + return null; + } + + private function serveGitLFSRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + + if (!$this->getIsGitLFSRequest()) { + throw new Exception(pht('This is not a Git LFS request!')); + } + + $path = $this->getGitLFSRequestPath($repository); + $matches = null; + + if (preg_match('(^upload/(.*)\z)', $path, $matches)) { + $oid = $matches[1]; + return $this->serveGitLFSUploadRequest($repository, $viewer, $oid); + } else if ($path == 'objects/batch') { + return $this->serveGitLFSBatchRequest($repository, $viewer); + } else { + return DiffusionGitLFSResponse::newErrorResponse( + 404, + pht( + 'Git LFS operation "%s" is not supported by this server.', + $path)); + } + } + + private function serveGitLFSBatchRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer) { + + $input = $this->getGitLFSInput(); + + $operation = idx($input, 'operation'); + switch ($operation) { + case 'upload': + $want_upload = true; + break; + case 'download': + $want_upload = false; + break; + default: + return DiffusionGitLFSResponse::newErrorResponse( + 404, + pht( + 'Git LFS batch operation "%s" is not supported by this server.', + $operation)); + } + + $objects = idx($input, 'objects', array()); + + $hashes = array(); + foreach ($objects as $object) { + $hashes[] = idx($object, 'oid'); + } + + if ($hashes) { + $refs = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes($hashes) + ->execute(); + $refs = mpull($refs, null, 'getObjectHash'); + } else { + $refs = array(); + } + + $file_phids = mpull($refs, 'getFilePHID'); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + $authorization = null; + $output = array(); + foreach ($objects as $object) { + $oid = idx($object, 'oid'); + $size = idx($object, 'size'); + $ref = idx($refs, $oid); + $error = null; + + // NOTE: If we already have a ref for this object, we only emit a + // "download" action. The client should not upload the file again. + + $actions = array(); + if ($ref) { + $file = idx($files, $ref->getFilePHID()); + if ($file) { + // Git LFS may prompt users for authentication if the action does + // not provide an "Authorization" header and does not have a query + // parameter named "token". See here for discussion: + // + $no_authorization = 'Basic '.base64_encode('none'); + + $get_uri = $file->getCDNURIWithToken(); + $actions['download'] = array( + 'href' => $get_uri, + 'header' => array( + 'Authorization' => $no_authorization, + ), + ); + } else { + $error = array( + 'code' => 404, + 'message' => pht( + 'Object "%s" was previously uploaded, but no longer exists '. + 'on this server.', + $oid), + ); + } + } else if ($want_upload) { + if (!$authorization) { + // Here, we could reuse the existing authorization if we have one, + // but it's a little simpler to just generate a new one + // unconditionally. + $authorization = $this->newGitLFSHTTPAuthorization( + $repository, + $viewer, + $operation); + } + + $put_uri = $repository->getGitLFSURI("info/lfs/upload/{$oid}"); + + $actions['upload'] = array( + 'href' => $put_uri, + 'header' => array( + 'Authorization' => $authorization, + 'X-Phabricator-Request-Type' => 'git-lfs', + ), + ); + } + + $object = array( + 'oid' => $oid, + 'size' => $size, + ); + + if ($actions) { + $object['actions'] = $actions; + } + + if ($error) { + $object['error'] = $error; + } + + $output[] = $object; + } + + $output = array( + 'objects' => $output, + ); + + return id(new DiffusionGitLFSResponse()) + ->setContent($output); + } + + private function serveGitLFSUploadRequest( + PhabricatorRepository $repository, + PhabricatorUser $viewer, + $oid) { + + $ref = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withObjectHashes(array($oid)) + ->executeOne(); + if ($ref) { + return DiffusionGitLFSResponse::newErrorResponse( + 405, + pht( + 'Content for object "%s" is already known to this server. It can '. + 'not be uploaded again.', + $oid)); + } + + // Remove the execution time limit because uploading large files may take + // a while. + set_time_limit(0); + + $request_stream = new AphrontRequestStream(); + $request_iterator = $request_stream->getIterator(); + $hashing_iterator = id(new PhutilHashingIterator($request_iterator)) + ->setAlgorithm('sha256'); + + $source = id(new PhabricatorIteratorFileUploadSource()) + ->setName('lfs-'.$oid) + ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE) + ->setIterator($hashing_iterator); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file = $source->uploadFile(); + unset($unguarded); + + $hash = $hashing_iterator->getHash(); + if ($hash !== $oid) { + return DiffusionGitLFSResponse::newErrorResponse( + 400, + pht( + 'Uploaded data is corrupt or invalid. Expected hash "%s", actual '. + 'hash "%s".', + $oid, + $hash)); + } + + $ref = id(new PhabricatorRepositoryGitLFSRef()) + ->setRepositoryPHID($repository->getPHID()) + ->setObjectHash($hash) + ->setByteSize($file->getByteSize()) + ->setAuthorPHID($viewer->getPHID()) + ->setFilePHID($file->getPHID()); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + // Attach the file to the repository to give users permission + // to access it. + $file->attachToObject($repository->getPHID()); + $ref->save(); + unset($unguarded); + + // This is just a plain HTTP 200 with no content, which is what `git lfs` + // expects. + return new DiffusionGitLFSResponse(); + } + + private function newGitLFSHTTPAuthorization( + PhabricatorRepository $repository, + PhabricatorUser $viewer, + $operation) { + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( + $repository, + $viewer, + $operation); + + unset($unguarded); + + return $authorization; + } + + private function getGitLFSRequestPath(PhabricatorRepository $repository) { + $request_path = $this->getRequestDirectoryPath($repository); + + $matches = null; + if (preg_match('(^/info/lfs(?:\z|/)(.*))', $request_path, $matches)) { + return $matches[1]; + } + + return null; + } + + private function getGitLFSInput() { + if (!$this->gitLFSInput) { + $input = PhabricatorStartup::getRawInput(); + $input = phutil_json_decode($input); + $this->gitLFSInput = $input; + } + + return $this->gitLFSInput; + } + + private function isGitLFSReadOnlyRequest(PhabricatorRepository $repository) { + if (!$this->getIsGitLFSRequest()) { + return false; + } + + $path = $this->getGitLFSRequestPath($repository); + + if ($path === 'objects/batch') { + $input = $this->getGitLFSInput(); + $operation = idx($input, 'operation'); + switch ($operation) { + case 'download': + return true; + default: + return false; + } + } + + return false; + } + + } diff --git a/src/applications/diffusion/controller/DiffusionSymbolController.php b/src/applications/diffusion/controller/DiffusionSymbolController.php index defe09d58c..3aaccffd22 100644 --- a/src/applications/diffusion/controller/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/DiffusionSymbolController.php @@ -134,17 +134,24 @@ final class DiffusionSymbolController extends DiffusionController { $table->setNoDataString( pht('No matching symbol could be found in any indexed repository.')); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Similar Symbols')) - ->setTable($table); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Similar Symbols')) + ->setHeaderIcon('fa-bullseye'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Find Symbol')); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $table, + )); return $this->newPage() ->setTitle(pht('Find Symbol')) ->setCrumbs($crumbs) - ->appendChild($panel); + ->appendChild($view); } } diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index 4749675bbb..5abd93c37d 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -45,6 +45,11 @@ final class DiffusionTagListController extends DiffusionController { $tags = $pager->sliceResults($tags); $content = null; + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Tags')) + ->setHeaderIcon('fa-tags'); + if (!$tags) { $content = $this->renderStatusMessage( pht('No Tags'), @@ -69,11 +74,7 @@ final class DiffusionTagListController extends DiffusionController { $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); - $panel = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Tags')) - ->appendChild($view); - - $content = $panel; + $content = $view; } $crumbs = $this->buildCrumbs( @@ -81,9 +82,22 @@ final class DiffusionTagListController extends DiffusionController { 'tags' => true, 'commit' => $drequest->getSymbolicCommit(), )); + $crumbs->setBorder(true); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($repository->getDisplayName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($view); $pager_box = $this->renderTablePagerBox($pager); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + $pager_box, + )); + return $this->newPage() ->setTitle( array( @@ -91,11 +105,7 @@ final class DiffusionTagListController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild( - array( - $content, - $pager_box, - )); + ->appendChild($view); } } diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index 53dc417be4..857ae76da3 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -770,10 +770,10 @@ final class DiffusionCommitHookEngine extends Phobject { } $stray_heads = array(); + $head_map = array(); if ($old_heads && !$new_heads) { // This is a branch deletion with "--close-branch". - $head_map = array(); foreach ($old_heads as $old_head) { $head_map[$old_head] = array(self::EMPTY_HASH); } @@ -798,7 +798,6 @@ final class DiffusionCommitHookEngine extends Phobject { '{node}\1'); } - $head_map = array(); foreach (new FutureIterator($dfutures) as $future_head => $dfuture) { list($stdout) = $dfuture->resolvex(); $descendant_heads = array_filter(explode("\1", $stdout)); diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php new file mode 100644 index 0000000000..362a4887ec --- /dev/null +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php @@ -0,0 +1,108 @@ +setName('git-lfs-authenticate'); + $this->setArguments( + array( + array( + 'name' => 'argv', + 'wildcard' => true, + ), + )); + } + + protected function identifyRepository() { + return $this->loadRepositoryWithPath($this->getLFSPathArgument()); + } + + private function getLFSPathArgument() { + return $this->getLFSArgument(0); + } + + private function getLFSOperationArgument() { + return $this->getLFSArgument(1); + } + + private function getLFSArgument($position) { + $args = $this->getArgs(); + $argv = $args->getArg('argv'); + + if (!isset($argv[$position])) { + throw new Exception( + pht( + 'Expected `git-lfs-authenticate `, but received '. + 'too few arguments.')); + } + + return $argv[$position]; + } + + protected function executeRepositoryOperations() { + $operation = $this->getLFSOperationArgument(); + + // NOTE: We aren't checking write access here, even for "upload". The + // HTTP endpoint should be able to do that for us. + + switch ($operation) { + case 'upload': + case 'download': + break; + default: + throw new Exception( + pht( + 'Git LFS operation "%s" is not supported by this server.', + $operation)); + } + + $repository = $this->getRepository(); + + if (!$repository->isGit()) { + throw new Exception( + pht( + 'Repository "%s" is not a Git repository. Git LFS is only '. + 'supported for Git repositories.', + $repository->getDisplayName())); + } + + if (!$repository->canUseGitLFS()) { + throw new Exception( + pht('Git LFS is not enabled for this repository.')); + } + + // NOTE: This is usually the same as the default URI (which does not + // need to be specified in the response), but the protocol or domain may + // differ in some situations. + + $lfs_uri = $repository->getGitLFSURI('info/lfs'); + + // Generate a temporary token to allow the user to acces LFS over HTTP. + // This works even if normal HTTP repository operations are not available + // on this host, and does not require the user to have a VCS password. + + $user = $this->getUser(); + + $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( + $repository, + $user, + $operation); + + $headers = array( + 'authorization' => $authorization, + ); + + $result = array( + 'header' => $headers, + 'href' => $lfs_uri, + ); + $result = phutil_json_encode($result); + + $this->writeIO($result); + $this->waitForGitClient(); + + return 0; + } + +} diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php new file mode 100644 index 0000000000..0973072ba4 --- /dev/null +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSTemporaryTokenType.php @@ -0,0 +1,42 @@ +setTokenResource($repository->getPHID()) + ->setTokenType(self::TOKENTYPE) + ->setTokenCode($lfs_hash) + ->setUserPHID($viewer->getPHID()) + ->setTemporaryTokenProperty('lfs.operation', $operation) + ->setTokenExpires($ttl) + ->save(); + + $authorization_header = base64_encode($lfs_user.':'.$lfs_pass); + return 'Basic '.$authorization_header; + } + +} diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index ec76468332..4aeca4c38e 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -364,7 +364,7 @@ final class DiffusionCommitQuery if ($repo === null) { if ($this->defaultRepository) { - $repo = $this->defaultRepository->getCallsign(); + $repo = $this->defaultRepository->getPHID(); } } @@ -375,7 +375,7 @@ final class DiffusionCommitQuery $bare[] = $commit_identifier; } else { $refs[] = array( - 'callsign' => $repo, + 'repository' => $repo, 'identifier' => $commit_identifier, ); } @@ -392,17 +392,16 @@ final class DiffusionCommitQuery } if ($refs) { - $callsigns = ipull($refs, 'callsign'); + $repositories = ipull($refs, 'repository'); $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) - ->withIdentifiers($callsigns); + ->withIdentifiers($repositories); $repos->execute(); $repos = $repos->getIdentifierMap(); foreach ($refs as $key => $ref) { - $repo = idx($repos, $ref['callsign']); - + $repo = idx($repos, $ref['repository']); if (!$repo) { continue; } diff --git a/src/applications/diffusion/response/DiffusionGitLFSResponse.php b/src/applications/diffusion/response/DiffusionGitLFSResponse.php new file mode 100644 index 0000000000..513dc577e9 --- /dev/null +++ b/src/applications/diffusion/response/DiffusionGitLFSResponse.php @@ -0,0 +1,37 @@ +setHTTPResponseCode($code) + ->setContent( + array( + 'message' => $message, + )); + } + + public function setContent(array $content) { + $this->content = phutil_json_encode($content); + return $this; + } + + public function buildResponseString() { + return $this->content; + } + + public function getHeaders() { + $headers = array( + array('Content-Type', 'application/vnd.git-lfs+json'), + ); + + return array_merge(parent::getHeaders(), $headers); + } + +} diff --git a/src/applications/diffusion/view/DiffusionBranchTableView.php b/src/applications/diffusion/view/DiffusionBranchTableView.php index 462f296bcb..b75228a0ce 100644 --- a/src/applications/diffusion/view/DiffusionBranchTableView.php +++ b/src/applications/diffusion/view/DiffusionBranchTableView.php @@ -39,7 +39,7 @@ final class DiffusionBranchTableView extends DiffusionView { $commit = idx($commits, $branch->getCommitIdentifier()); if ($commit) { $details = $commit->getSummary(); - $datetime = phabricator_datetime($commit->getEpoch(), $viewer); + $datetime = $viewer->formatShortDateTime($commit->getEpoch()); $buildable = idx($buildables, $commit->getPHID()); if ($buildable) { $build_status = $this->renderBuildable($buildable); @@ -147,7 +147,7 @@ final class DiffusionBranchTableView extends DiffusionView { '', 'wide', '', - '', + 'right', )); $view->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php index 6354c5dc5e..ffc5ce61ed 100644 --- a/src/applications/diffusion/view/DiffusionBrowseTableView.php +++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php @@ -90,7 +90,6 @@ final class DiffusionBrowseTableView extends DiffusionView { $browse_link, idx($dict, 'lint'), $dict['commit'], - $dict['author'], $dict['details'], $dict['date'], ); @@ -120,7 +119,6 @@ final class DiffusionBrowseTableView extends DiffusionView { pht('Path'), ($lint ? $lint : pht('Lint')), pht('Modified'), - pht('Author/Committer'), pht('Details'), pht('Committed'), )); @@ -130,9 +128,8 @@ final class DiffusionBrowseTableView extends DiffusionView { '', '', '', - '', 'wide', - '', + 'right', )); $view->setColumnVisibility( array( @@ -142,7 +139,6 @@ final class DiffusionBrowseTableView extends DiffusionView { true, true, true, - true, )); $view->setDeviceVisibility( @@ -150,7 +146,6 @@ final class DiffusionBrowseTableView extends DiffusionView { true, true, false, - true, false, true, false, diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index e62c4382c9..a9fb34e683 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -95,7 +95,7 @@ final class DiffusionHistoryTableView extends DiffusionView { $epoch = $history->getEpoch(); if ($epoch) { - $committed = phabricator_datetime($epoch, $viewer); + $committed = $viewer->formatShortDateTime($epoch); } else { $committed = null; } @@ -195,7 +195,7 @@ final class DiffusionHistoryTableView extends DiffusionView { '', '', 'wide', - '', + 'right', )); $view->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionPushLogListView.php b/src/applications/diffusion/view/DiffusionPushLogListView.php index 7cc02a49b4..73a44794e8 100644 --- a/src/applications/diffusion/view/DiffusionPushLogListView.php +++ b/src/applications/diffusion/view/DiffusionPushLogListView.php @@ -88,7 +88,7 @@ final class DiffusionPushLogListView extends AphrontView { // TODO: Make these human-readable. $log->getChangeFlags(), $log->getPushEvent()->getRejectCode(), - phabricator_datetime($log->getEpoch(), $viewer), + $viewer->formatShortDateTime($log->getEpoch()), ); } @@ -119,7 +119,7 @@ final class DiffusionPushLogListView extends AphrontView { 'wide', 'n', 'n', - 'date', + 'right', )); return $table; diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index 3b27284f5e..df59925522 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -28,6 +28,7 @@ final class DiffusionTagListView extends DiffusionView { public function render() { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + $viewer = $this->getViewer(); $buildables = $this->loadBuildables($this->commits); $has_builds = false; @@ -100,7 +101,7 @@ final class DiffusionTagListView extends DiffusionView { $build, $author, $description, - phabricator_datetime($tag->getEpoch(), $this->user), + $viewer->formatShortDateTime($tag->getEpoch()), ); } @@ -123,6 +124,7 @@ final class DiffusionTagListView extends DiffusionView { '', '', 'wide', + 'right', )) ->setColumnVisibility( array( diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 331c172866..5aa1fad33c 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -95,7 +95,9 @@ abstract class DiffusionView extends AphrontView { } $icon = DifferentialChangeType::getIconForFileType($file_type); - $icon_view = id(new PHUIIconView())->setIcon($icon); + $color = DifferentialChangeType::getIconColorForFileType($file_type); + $icon_view = id(new PHUIIconView()) + ->setIcon($icon.' '.$color); // If we're rendering a file or directory name, don't show the tooltip. if ($display_name !== null) { diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php index 1785ae6741..bf69497b16 100644 --- a/src/applications/diviner/controller/DivinerAtomController.php +++ b/src/applications/diviner/controller/DivinerAtomController.php @@ -240,14 +240,12 @@ final class DivinerAtomController extends DivinerController { $prop_list = phutil_tag_div('phui-document-view-pro-box', $prop_list); - return $this->buildApplicationPage( - array( - $crumbs, + return $this->newPage() + ->setTitle($symbol->getTitle()) + ->setCrumbs($crumbs) + ->appendChild(array( $document, $prop_list, - ), - array( - 'title' => $symbol->getTitle(), )); } diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php index 74b2a8f3d9..1aa8790ffa 100644 --- a/src/applications/diviner/controller/DivinerBookController.php +++ b/src/applications/diviner/controller/DivinerBookController.php @@ -92,13 +92,11 @@ final class DivinerBookController extends DivinerController { $document->appendChild($preface_view); $document->appendChild($out); - return $this->buildApplicationPage( - array( - $crumbs, + return $this->newPage() + ->setTitle($book->getTitle()) + ->setCrumbs($crumbs) + ->appendChild(array( $document, - ), - array( - 'title' => $book->getTitle(), )); } diff --git a/src/applications/diviner/controller/DivinerBookEditController.php b/src/applications/diviner/controller/DivinerBookEditController.php index b5c3e2dea5..2b1f90579b 100644 --- a/src/applications/diviner/controller/DivinerBookEditController.php +++ b/src/applications/diviner/controller/DivinerBookEditController.php @@ -57,8 +57,10 @@ final class DivinerBookEditController extends DivinerController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Basics')); + $crumbs->setBorder(true); - $title = pht('Edit %s', $book->getTitle()); + $title = pht('Edit Book: %s', $book->getTitle()); + $header_icon = 'fa-pencil'; $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) @@ -104,8 +106,9 @@ final class DivinerBookEditController extends DivinerController { ->setValue(pht('Save')) ->addCancelButton($view_uri)); - $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Book')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $timeline = $this->buildTransactionTimeline( @@ -113,15 +116,21 @@ final class DivinerBookEditController extends DivinerController { new DivinerLiveBookTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/diviner/controller/DivinerFindController.php b/src/applications/diviner/controller/DivinerFindController.php index b9e165fc1d..1bedd65b06 100644 --- a/src/applications/diviner/controller/DivinerFindController.php +++ b/src/applications/diviner/controller/DivinerFindController.php @@ -84,11 +84,12 @@ final class DivinerFindController extends DivinerController { $list = $this->renderAtomList($atoms); - return $this->buildApplicationPage( - $list, - array( - 'title' => array(pht('Find'), pht('"%s"', $query_text)), + return $this->newPage() + ->setTitle(array(pht('Find'), pht('"%s"', $query_text))) + ->appendChild(array( + $list, )); + } } diff --git a/src/applications/diviner/controller/DivinerMainController.php b/src/applications/diviner/controller/DivinerMainController.php index e85769060f..e7d179c077 100644 --- a/src/applications/diviner/controller/DivinerMainController.php +++ b/src/applications/diviner/controller/DivinerMainController.php @@ -65,13 +65,11 @@ final class DivinerMainController extends DivinerController { $document->appendChild($text); } - return $this->buildApplicationPage( - array( - $crumbs, + return $this->newPage() + ->setTitle(pht('Documentation Books')) + ->setCrumbs($crumbs) + ->appendChild(array( $document, - ), - array( - 'title' => pht('Documentation Books'), )); } } diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridge.php b/src/applications/doorkeeper/bridge/DoorkeeperBridge.php index 2061150aba..b09a1933c1 100644 --- a/src/applications/doorkeeper/bridge/DoorkeeperBridge.php +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridge.php @@ -3,6 +3,7 @@ abstract class DoorkeeperBridge extends Phobject { private $viewer; + private $context = array(); private $throwOnMissingLink; public function setThrowOnMissingLink($throw_on_missing_link) { @@ -19,6 +20,15 @@ abstract class DoorkeeperBridge extends Phobject { return $this->viewer; } + final public function setContext($context) { + $this->context = $context; + return $this; + } + + final public function getContextProperty($key, $default = null) { + return idx($this->context, $key, $default); + } + public function isEnabled() { return true; } diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php new file mode 100644 index 0000000000..19ad54d977 --- /dev/null +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php @@ -0,0 +1,50 @@ +getApplicationType() != self::APPTYPE_GITHUB) { + return false; + } + + if ($ref->getApplicationDomain() != self::APPDOMAIN_GITHUB) { + return false; + } + + return true; + } + + protected function getGitHubAccessToken() { + $context_token = $this->getContextProperty('github.token'); + if ($context_token) { + return $context_token->openEnvelope(); + } + + // TODO: Do a bunch of work to fetch the viewer's linked account if + // they have one. + + return $this->didFailOnMissingLink(); + } + + protected function parseGitHubIssueID($id) { + $matches = null; + if (!preg_match('(^([^/]+)/([^/]+)#([1-9]\d*)\z)', $id, $matches)) { + throw new Exception( + pht( + 'GitHub Issue ID "%s" is not properly formatted. Expected an ID '. + 'in the form "owner/repository#123".', + $id)); + } + + return array( + $matches[1], + $matches[2], + (int)$matches[3], + ); + } + + +} diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php new file mode 100644 index 0000000000..0558b7d8f6 --- /dev/null +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php @@ -0,0 +1,107 @@ +getObjectType() !== self::OBJTYPE_GITHUB_ISSUE) { + return false; + } + + return true; + } + + public function pullRefs(array $refs) { + $token = $this->getGitHubAccessToken(); + if (!strlen($token)) { + return null; + } + + $template = id(new PhutilGitHubFuture()) + ->setAccessToken($token); + + $futures = array(); + $id_map = mpull($refs, 'getObjectID', 'getObjectKey'); + foreach ($id_map as $key => $id) { + list($user, $repository, $number) = $this->parseGitHubIssueID($id); + $uri = "/repos/{$user}/{$repository}/issues/{$number}"; + $data = array(); + $futures[$key] = id(clone $template) + ->setRawGitHubQuery($uri, $data); + } + + $results = array(); + $failed = array(); + foreach (new FutureIterator($futures) as $key => $future) { + try { + $results[$key] = $future->resolve(); + } catch (Exception $ex) { + if (($ex instanceof HTTPFutureResponseStatus) && + ($ex->getStatusCode() == 404)) { + // TODO: Do we end up here for deleted objects and invisible + // objects? + } else { + phlog($ex); + $failed[$key] = $ex; + } + } + } + + $viewer = $this->getViewer(); + + foreach ($refs as $ref) { + $ref->setAttribute('name', pht('GitHub Issue %s', $ref->getObjectID())); + + $did_fail = idx($failed, $ref->getObjectKey()); + if ($did_fail) { + $ref->setSyncFailed(true); + continue; + } + + $result = idx($results, $ref->getObjectKey()); + if (!$result) { + continue; + } + + $body = $result->getBody(); + + $ref->setIsVisible(true); + $ref->setAttribute('api.raw', $body); + $ref->setAttribute('name', $body['title']); + + $obj = $ref->getExternalObject(); + + $this->fillObjectFromData($obj, $result); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $obj->save(); + unset($unguarded); + } + } + + public function fillObjectFromData(DoorkeeperExternalObject $obj, $result) { + $body = $result->getBody(); + $uri = $body['html_url']; + $obj->setObjectURI($uri); + + $title = idx($body, 'title'); + $description = idx($body, 'body'); + + $created = idx($body, 'created_at'); + $created = strtotime($created); + + $state = idx($body, 'state'); + + $obj->setProperty('task.title', $title); + $obj->setProperty('task.description', $description); + $obj->setProperty('task.created', $created); + $obj->setProperty('task.state', $state); + } + +} diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php new file mode 100644 index 0000000000..3470894e5c --- /dev/null +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php @@ -0,0 +1,120 @@ +getObjectType() !== self::OBJTYPE_GITHUB_USER) { + return false; + } + + return true; + } + + public function pullRefs(array $refs) { + $token = $this->getGitHubAccessToken(); + if (!strlen($token)) { + return null; + } + + $template = id(new PhutilGitHubFuture()) + ->setAccessToken($token); + + $futures = array(); + $id_map = mpull($refs, 'getObjectID', 'getObjectKey'); + foreach ($id_map as $key => $id) { + // GitHub doesn't provide a way to query for users by ID directly, but we + // can list all users, ordered by ID, starting at some particular ID, + // with a page size of one, which will achieve the desired effect. + $one_less = ($id - 1); + $uri = "/users?since={$one_less}&per_page=1"; + + $data = array(); + $futures[$key] = id(clone $template) + ->setRawGitHubQuery($uri, $data); + } + + $results = array(); + $failed = array(); + foreach (new FutureIterator($futures) as $key => $future) { + try { + $results[$key] = $future->resolve(); + } catch (Exception $ex) { + if (($ex instanceof HTTPFutureResponseStatus) && + ($ex->getStatusCode() == 404)) { + // TODO: Do we end up here for deleted objects and invisible + // objects? + } else { + phlog($ex); + $failed[$key] = $ex; + } + } + } + + $viewer = $this->getViewer(); + + foreach ($refs as $ref) { + $ref->setAttribute('name', pht('GitHub User %s', $ref->getObjectID())); + + $did_fail = idx($failed, $ref->getObjectKey()); + if ($did_fail) { + $ref->setSyncFailed(true); + continue; + } + + $result = idx($results, $ref->getObjectKey()); + if (!$result) { + continue; + } + + $body = $result->getBody(); + if (!is_array($body) || !count($body)) { + $ref->setSyncFailed(true); + continue; + } + + $spec = head($body); + if (!is_array($spec)) { + $ref->setSyncFailed(true); + continue; + } + + // Because we're using a paging query to load each user, if a user (say, + // user ID 123) does not exist for some reason, we might get the next + // user (say, user ID 124) back. Make sure the user we got back is really + // the user we expect. + $id = idx($spec, 'id'); + if ($id !== $ref->getObjectID()) { + $ref->setSyncFailed(true); + continue; + } + + $ref->setIsVisible(true); + $ref->setAttribute('api.raw', $spec); + $ref->setAttribute('name', $spec['login']); + + $obj = $ref->getExternalObject(); + $this->fillObjectFromData($obj, $spec); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $obj->save(); + unset($unguarded); + } + } + + public function fillObjectFromData(DoorkeeperExternalObject $obj, $spec) { + $uri = $spec['html_url']; + $obj->setObjectURI($uri); + + $login = $spec['login']; + + $obj->setDisplayName(pht('%s <%s>', $login, pht('GitHub'))); + } + +} diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgedObjectInterface.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgedObjectInterface.php new file mode 100644 index 0000000000..9d829642fc --- /dev/null +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgedObjectInterface.php @@ -0,0 +1,8 @@ +viewer = $viewer; @@ -37,6 +38,10 @@ final class DoorkeeperImportEngine extends Phobject { return $this; } + public function setContextProperty($key, $value) { + $this->context[$key] = $value; + return $this; + } /** * Configure behavior if remote refs can not be retrieved because an @@ -96,6 +101,7 @@ final class DoorkeeperImportEngine extends Phobject { foreach ($bridges as $key => $bridge) { $bridge->setViewer($viewer); $bridge->setThrowOnMissingLink($this->throwOnMissingLink); + $bridge->setContext($this->context); } $working_set = $refs; diff --git a/src/applications/doorkeeper/engineextension/DoorkeeperBridgedObjectCurtainExtension.php b/src/applications/doorkeeper/engineextension/DoorkeeperBridgedObjectCurtainExtension.php new file mode 100644 index 0000000000..ae59440973 --- /dev/null +++ b/src/applications/doorkeeper/engineextension/DoorkeeperBridgedObjectCurtainExtension.php @@ -0,0 +1,31 @@ +getBridgedObject(); + if (!$xobj) { + return null; + } + + $tag = id(new DoorkeeperTagView()) + ->setExternalObject($xobj); + + return $this->newPanel() + ->setHeaderText(pht('Imported From')) + ->setOrder(5000) + ->appendChild($tag); + } + +} diff --git a/src/applications/doorkeeper/phid/DoorkeeperExternalObjectPHIDType.php b/src/applications/doorkeeper/phid/DoorkeeperExternalObjectPHIDType.php new file mode 100644 index 0000000000..be9e32cf2a --- /dev/null +++ b/src/applications/doorkeeper/phid/DoorkeeperExternalObjectPHIDType.php @@ -0,0 +1,47 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $xobj = $objects[$phid]; + + $uri = $xobj->getObjectURI(); + $name = $xobj->getDisplayName(); + $full_name = $xobj->getDisplayFullName(); + + $handle + ->setURI($uri) + ->setName($name) + ->setFullName($full_name); + } + } + +} diff --git a/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php b/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php index 770678a687..bc794cc629 100644 --- a/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php +++ b/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php @@ -16,40 +16,32 @@ final class DoorkeeperExternalObjectQuery return $this; } - protected function loadPage() { - $table = new DoorkeeperExternalObject(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + public function newResultObject() { + return new DoorkeeperExternalObject(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } - if ($this->phids) { + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->objectKeys) { + if ($this->objectKeys !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'objectKey IN (%Ls)', $this->objectKeys); } - $where[] = $this->buildPagingClause($conn_r); - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php b/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php index 3050008594..9d8d3bc967 100644 --- a/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php +++ b/src/applications/doorkeeper/storage/DoorkeeperExternalObject.php @@ -47,7 +47,7 @@ final class DoorkeeperExternalObject extends DoorkeeperDAO public function generatePHID() { return PhabricatorPHID::generateNewPHID( - PhabricatorPHIDConstants::PHID_TYPE_XOBJ); + DoorkeeperExternalObjectPHIDType::TYPECONST); } public function getProperty($key, $default = null) { @@ -83,6 +83,27 @@ final class DoorkeeperExternalObject extends DoorkeeperDAO return parent::save(); } + public function setDisplayName($display_name) { + return $this->setProperty('xobj.name.display', $display_name); + } + + public function getDisplayName() { + return $this->getProperty('xobj.name.display', pht('External Object')); + } + + public function setDisplayFullName($full_name) { + return $this->setProperty('xobj.name.display-full', $full_name); + } + + public function getDisplayFullName() { + $full_name = $this->getProperty('xobj.name.display-full'); + + if ($full_name !== null) { + return $full_name; + } + + return $this->getDisplayName(); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/DrydockCommandError/DrydockCommandError.php b/src/applications/drydock/DrydockCommandError/DrydockCommandError.php deleted file mode 100644 index d9acbe7456..0000000000 --- a/src/applications/drydock/DrydockCommandError/DrydockCommandError.php +++ /dev/null @@ -1,18 +0,0 @@ - $phase, - 'command' => (string)$command, - 'raw' => (string)$ex->getCommand(), - 'err' => $ex->getError(), - 'stdout' => $ex->getStdout(), - 'stderr' => $ex->getStderr(), - ); - return $error; - } -} diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 8b138405eb..a5d61ec067 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -4,6 +4,8 @@ final class DrydockWorkingCopyBlueprintImplementation extends DrydockBlueprintImplementation { const PHASE_SQUASHMERGE = 'squashmerge'; + const PHASE_REMOTEFETCH = 'blueprint.workingcopy.fetch.remote'; + const PHASE_MERGEFETCH = 'blueprint.workingcopy.fetch.staging'; public function isEnabled() { return true; @@ -240,11 +242,11 @@ final class DrydockWorkingCopyBlueprintImplementation $default = null; foreach ($map as $directory => $spec) { + $interface->pushWorkingDirectory("{$root}/repo/{$directory}/"); + $cmd = array(); $arg = array(); - $interface->pushWorkingDirectory("{$root}/repo/{$directory}/"); - $cmd[] = 'git clean -d --force'; $cmd[] = 'git fetch'; @@ -266,7 +268,20 @@ final class DrydockWorkingCopyBlueprintImplementation $cmd[] = 'git reset --hard origin/%s'; $arg[] = $branch; - } else if ($ref) { + } + + $this->execxv($interface, $cmd, $arg); + + if (idx($spec, 'default')) { + $default = $directory; + } + + // If we're fetching a ref from a remote, do that separately so we can + // raise a more tailored error. + if ($ref) { + $cmd = array(); + $arg = array(); + $ref_uri = $ref['uri']; $ref_ref = $ref['ref']; @@ -277,17 +292,25 @@ final class DrydockWorkingCopyBlueprintImplementation $cmd[] = 'git checkout %s --'; $arg[] = $ref_ref; - } - $cmd = implode(' && ', $cmd); - $argv = array_merge(array($cmd), $arg); + try { + $this->execxv($interface, $cmd, $arg); + } catch (CommandException $ex) { + $display_command = csprintf( + 'git fetch %R %R', + $ref_uri, + $ref_ref); - $result = call_user_func_array( - array($interface, 'execx'), - $argv); + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_REMOTEFETCH) + ->setDisplayCommand($display_command); - if (idx($spec, 'default')) { - $default = $directory; + $lease->setAttribute( + 'workingcopy.vcs.error', + $error->toDictionary()); + + throw $ex; + } } $merges = idx($spec, 'merges'); @@ -428,11 +451,29 @@ final class DrydockWorkingCopyBlueprintImplementation $src_uri = $merge['src.uri']; $src_ref = $merge['src.ref']; - $interface->execx( - 'git fetch --no-tags -- %s +%s:%s', - $src_uri, - $src_ref, - $src_ref); + + try { + $interface->execx( + 'git fetch --no-tags -- %s +%s:%s', + $src_uri, + $src_ref, + $src_ref); + } catch (CommandException $ex) { + $display_command = csprintf( + 'git fetch %R +%R:%R', + $src_uri, + $src_ref, + $src_ref); + + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_MERGEFETCH) + ->setDisplayCommand($display_command); + + $lease->setAttribute('workingcopy.vcs.error', $error->toDictionary()); + + throw $ex; + } + // NOTE: This can never actually generate a commit because we pass // "--squash", but git sometimes runs code to check that a username and @@ -443,32 +484,36 @@ final class DrydockWorkingCopyBlueprintImplementation 'drydock@phabricator', $src_ref); - // Show the user a simplified command if the operation fails and we need to - // report an error. - $show_command = csprintf( - 'git merge --squash -- %R', - $src_ref); - try { $interface->execx('%C', $real_command); } catch (CommandException $ex) { - $error = DrydockCommandError::newFromCommandException( - self::PHASE_SQUASHMERGE, - $show_command, - $ex); + $display_command = csprintf( + 'git merge --squash %R', + $src_ref); - $lease->setAttribute('workingcopy.vcs.error', $error); + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_SQUASHMERGE) + ->setDisplayCommand($display_command); + + $lease->setAttribute('workingcopy.vcs.error', $error->toDictionary()); throw $ex; } } public function getCommandError(DrydockLease $lease) { - $error = $lease->getAttribute('workingcopy.vcs.error'); - if (!$error) { - return null; - } else { - return $error; - } + return $lease->getAttribute('workingcopy.vcs.error'); } + private function execxv( + DrydockCommandInterface $interface, + array $commands, + array $arguments) { + + $commands = implode(' && ', $commands); + $argv = array_merge(array($commands), $arguments); + + return call_user_func_array(array($interface, 'execx'), $argv); + } + + } diff --git a/src/applications/drydock/controller/DrydockAuthorizationViewController.php b/src/applications/drydock/controller/DrydockAuthorizationViewController.php index bc34154e4f..80a0e5d1ef 100644 --- a/src/applications/drydock/controller/DrydockAuthorizationViewController.php +++ b/src/applications/drydock/controller/DrydockAuthorizationViewController.php @@ -26,16 +26,14 @@ final class DrydockAuthorizationViewController ->setUser($viewer) ->setPolicyObject($authorization); - $state = $authorization->getBlueprintAuthorizationState(); $icon = DrydockAuthorization::getBlueprintStateIcon($state); $name = DrydockAuthorization::getBlueprintStateName($state); $header->setStatus($icon, null, $name); - $actions = $this->buildActionListView($authorization); + $curtain = $this->buildCurtain($authorization); $properties = $this->buildPropertyListView($authorization); - $properties->setActionList($actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( @@ -45,29 +43,32 @@ final class DrydockAuthorizationViewController $blueprint->getBlueprintName(), $this->getApplicationURI("blueprint/{$blueprint_id}/")); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockAuthorization $authorization) { + private function buildCurtain(DrydockAuthorization $authorization) { $viewer = $this->getViewer(); $id = $authorization->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($authorization); + $curtain = $this->newCurtainView($authorization); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -84,7 +85,7 @@ final class DrydockAuthorizationViewController $can_authorize = $can_edit && ($state != $state_authorized); $can_decline = $can_edit && ($state != $state_declined); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($authorize_uri) ->setName(pht('Approve Authorization')) @@ -92,7 +93,7 @@ final class DrydockAuthorizationViewController ->setWorkflow(true) ->setDisabled(!$can_authorize)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($decline_uri) ->setName(pht('Decline Authorization')) @@ -100,7 +101,7 @@ final class DrydockAuthorizationViewController ->setWorkflow(true) ->setDisabled(!$can_decline)); - return $view; + return $curtain; } private function buildPropertyListView(DrydockAuthorization $authorization) { diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index 21f3a395d3..d3f73bf78c 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -19,7 +19,8 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($blueprint); + ->setPolicyObject($blueprint) + ->setHeaderIcon('fa-map-o'); if ($blueprint->getIsDisabled()) { $header->setStatus('fa-ban', 'red', pht('Disabled')); @@ -27,15 +28,12 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } - $actions = $this->buildActionListView($blueprint); - $properties = $this->buildPropertyListView($blueprint, $actions); + $curtain = $this->buildCurtain($blueprint); + $properties = $this->buildPropertyListView($blueprint); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID())); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $field_list = PhabricatorCustomField::getObjectFields( $blueprint, @@ -49,9 +47,8 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $viewer, $properties); - $resource_box = $this->buildResourceBox($blueprint); - - $authorizations_box = $this->buildAuthorizationsBox($blueprint); + $resources = $this->buildResourceBox($blueprint); + $authorizations = $this->buildAuthorizationsBox($blueprint); $timeline = $this->buildTransactionTimeline( $blueprint, @@ -61,33 +58,36 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $log_query = id(new DrydockLogQuery()) ->withBlueprintPHIDs(array($blueprint->getPHID())); - $log_box = $this->buildLogBox( + $logs = $this->buildLogBox( $log_query, $this->getApplicationURI("blueprint/{$id}/logs/query/all/")); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $resource_box, - $authorizations_box, - $log_box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties) + ->setMainColumn(array( + $resources, + $authorizations, + $logs, $timeline, - ), - array( - 'title' => $title, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockBlueprint $blueprint) { + private function buildCurtain(DrydockBlueprint $blueprint) { $viewer = $this->getViewer(); $id = $blueprint->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($blueprint); - + $curtain = $this->newCurtainView($blueprint); $edit_uri = $this->getApplicationURI("blueprint/edit/{$id}/"); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -95,7 +95,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $blueprint, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($edit_uri) ->setName(pht('Edit Blueprint')) @@ -113,7 +113,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { $disable_uri = $this->getApplicationURI("blueprint/{$id}/enable/"); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($disable_uri) ->setName($disable_name) @@ -121,19 +121,15 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { ->setWorkflow(true) ->setDisabled(!$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockBlueprint $blueprint, - PhabricatorActionListView $actions) { + DrydockBlueprint $blueprint) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($blueprint); - - $view->setActionList($actions); + ->setUser($viewer); $view->addProperty( pht('Type'), @@ -177,6 +173,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { return id(new PHUIObjectBoxView()) ->setHeader($resource_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($resource_list); } @@ -242,6 +239,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { return id(new PHUIObjectBoxView()) ->setHeader($authorizations_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($authorization_list); } diff --git a/src/applications/drydock/controller/DrydockConsoleController.php b/src/applications/drydock/controller/DrydockConsoleController.php index c6a8e25fd6..1d79f52c14 100644 --- a/src/applications/drydock/controller/DrydockConsoleController.php +++ b/src/applications/drydock/controller/DrydockConsoleController.php @@ -62,19 +62,25 @@ final class DrydockConsoleController extends DrydockController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Drydock Console')) ->setObjectList($menu); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Drydock Console'), - )); + $title = pht('Drydock Console'); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-truck'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/drydock/controller/DrydockController.php b/src/applications/drydock/controller/DrydockController.php index 17b8d34916..872a78f66f 100644 --- a/src/applications/drydock/controller/DrydockController.php +++ b/src/applications/drydock/controller/DrydockController.php @@ -102,6 +102,7 @@ abstract class DrydockController extends PhabricatorController { ->setText(pht('View All'))); return id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeader($log_header) ->setTable($log_table); } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 088b197a5f..7166b0bef6 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -21,53 +21,59 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $title = pht('Lease %d', $lease->getID()); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-link'); if ($lease->isReleasing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); } - $actions = $this->buildActionListView($lease); - $properties = $this->buildPropertyListView($lease, $actions); + $curtain = $this->buildCurtain($lease); + $properties = $this->buildPropertyListView($lease); $log_query = id(new DrydockLogQuery()) ->withLeasePHIDs(array($lease->getPHID())); - $log_box = $this->buildLogBox( + $logs = $this->buildLogBox( $log_query, $this->getApplicationURI("lease/{$id}/logs/query/all/")); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $lease_uri); + $crumbs->setBorder(true); $locks = $this->buildLocksTab($lease->getPHID()); $commands = $this->buildCommandsTab($lease->getPHID()); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Properties')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $object_box, - $log_box, - ), - array( - 'title' => $title, + $logs, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockLease $lease) { + private function buildCurtain(DrydockLease $lease) { $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($lease); - + $curtain = $this->newCurtainView($lease); $id = $lease->getID(); $can_release = $lease->canRelease(); @@ -80,7 +86,7 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $lease, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Release Lease')) ->setIcon('fa-times') @@ -88,16 +94,14 @@ final class DrydockLeaseViewController extends DrydockLeaseController { ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockLease $lease, - PhabricatorActionListView $actions) { + DrydockLease $lease) { $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); - $view->setActionList($actions); $view->addProperty( pht('Status'), diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php index c6d38791f3..5f35c91bb3 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php @@ -43,17 +43,10 @@ final class DrydockRepositoryOperationStatusController $this->getApplicationURI('operation/')); $crumbs->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $status_view, - ), - array( - 'title' => array( - $title, - pht('Status'), - ), - )); + return $this->newPage() + ->setTitle(pht('Status')) + ->setCrumbs($crumbs) + ->appendChild($status_view); } } diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php b/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php index f8c210368a..e7fbf07b35 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationViewController.php @@ -25,50 +25,52 @@ final class DrydockRepositoryOperationViewController $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($operation); + ->setPolicyObject($operation) + ->setHeaderIcon('fa-fighter-jet'); $state = $operation->getOperationState(); $icon = DrydockRepositoryOperation::getOperationStateIcon($state); $name = DrydockRepositoryOperation::getOperationStateName($state); $header->setStatus($icon, null, $name); - $actions = $this->buildActionListView($operation); + $curtain = $this->buildCurtain($operation); $properties = $this->buildPropertyListView($operation); - $properties->setActionList($actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Operations'), $this->getApplicationURI('operation/')); $crumbs->addTextCrumb($title); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $status_view = id(new DrydockRepositoryOperationStatusView()) ->setUser($viewer) ->setOperation($operation); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('Properties'), $properties) + ->setMainColumn(array( + $status_view, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $status_view, - )); + $view, + )); } - private function buildActionListView(DrydockRepositoryOperation $operation) { + private function buildCurtain(DrydockRepositoryOperation $operation) { $viewer = $this->getViewer(); $id = $operation->getID(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($operation); + $curtain = $this->newCurtainView($operation); - return $view; + return $curtain; } private function buildPropertyListView( diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index 61319fbdbc..c2ab4337f5 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -23,14 +23,15 @@ final class DrydockResourceViewController extends DrydockResourceController { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($resource) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-map'); if ($resource->isReleasing()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); } - $actions = $this->buildActionListView($resource); - $properties = $this->buildPropertyListView($resource, $actions); + $curtain = $this->buildCurtain($resource); + $properties = $this->buildPropertyListView($resource); $id = $resource->getID(); $resource_uri = $this->getApplicationURI("resource/{$id}/"); @@ -44,37 +45,42 @@ final class DrydockResourceViewController extends DrydockResourceController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Resource %d', $resource->getID())); + $crumbs->setBorder(true); $locks = $this->buildLocksTab($resource->getPHID()); $commands = $this->buildCommandsTab($resource->getPHID()); + $lease_box = $this->buildLeaseBox($resource); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Properties')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties, pht('Properties')) ->addPropertyList($locks, pht('Slot Locks')) ->addPropertyList($commands, pht('Commands')); - $lease_box = $this->buildLeaseBox($resource); - - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $object_box, $lease_box, $log_box, - ), - array( - 'title' => $title, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildActionListView(DrydockResource $resource) { + private function buildCurtain(DrydockResource $resource) { $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($resource); + $curtain = $this->newCurtainView($resource); $can_release = $resource->canRelease(); if ($resource->isReleasing()) { @@ -89,7 +95,7 @@ final class DrydockResourceViewController extends DrydockResourceController { $uri = '/resource/'.$resource->getID().'/release/'; $uri = $this->getApplicationURI($uri); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setHref($uri) ->setName(pht('Release Resource')) @@ -97,17 +103,14 @@ final class DrydockResourceViewController extends DrydockResourceController { ->setWorkflow(true) ->setDisabled(!$can_release || !$can_edit)); - return $view; + return $curtain; } private function buildPropertyListView( - DrydockResource $resource, - PhabricatorActionListView $actions) { + DrydockResource $resource) { $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setActionList($actions); - + $view = new PHUIPropertyListView(); $status = $resource->getStatus(); $status = DrydockResourceStatus::getNameForStatus($status); @@ -179,6 +182,7 @@ final class DrydockResourceViewController extends DrydockResourceController { return id(new PHUIObjectBoxView()) ->setHeader($lease_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($lease_list); } diff --git a/src/applications/drydock/editor/DrydockBlueprintEditEngine.php b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php index bec86e5d4c..06117272a9 100644 --- a/src/applications/drydock/editor/DrydockBlueprintEditEngine.php +++ b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php @@ -75,6 +75,10 @@ final class DrydockBlueprintEditEngine return pht('Create Blueprint'); } + protected function getObjectName() { + return pht('Blueprint'); + } + protected function getEditorURI() { return '/drydock/blueprint/edit/'; } diff --git a/src/applications/drydock/exception/DrydockCommandError.php b/src/applications/drydock/exception/DrydockCommandError.php new file mode 100644 index 0000000000..26314d1f89 --- /dev/null +++ b/src/applications/drydock/exception/DrydockCommandError.php @@ -0,0 +1,58 @@ +command = (string)$ex->getCommand(); + + $error->error = $ex->getError(); + $error->stdout = $ex->getStdout(); + $error->stderr = $ex->getStderr(); + + return $error; + } + + public function setPhase($phase) { + $this->phase = $phase; + return $this; + } + + public function getPhase() { + return $this->phase; + } + + public function setDisplayCommand($display_command) { + $this->displayCommand = (string)$display_command; + return $this; + } + + public function getDisplayCommand() { + return $this->displayCommand; + } + + public function toDictionary() { + $display_command = $this->getDisplayCommand(); + if ($display_command === null) { + $display_command = $this->command; + } + + return array( + 'phase' => $this->getPhase(), + 'command' => $display_command, + 'raw' => $this->command, + 'err' => $this->error, + 'stdout' => $this->stdout, + 'stderr' => $this->stderr, + ); + } + +} diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 5beb18d07b..4e306772a3 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -4,7 +4,9 @@ final class DrydockLandRepositoryOperation extends DrydockRepositoryOperationType { const OPCONST = 'land'; - const PHASE_PUSH = 'push'; + + const PHASE_PUSH = 'op.land.push'; + const PHASE_COMMIT = 'op.land.commit'; public function getOperationDescription( DrydockRepositoryOperation $operation, @@ -119,25 +121,42 @@ final class DrydockLandRepositoryOperation $committer_info['email'], "{$author_name} <{$author_email}>"); - $future - ->write($commit_message) - ->resolvex(); + $future->write($commit_message); try { - $interface->execx( - 'git push origin -- %s:%s', - 'HEAD', - $push_dst); + $future->resolvex(); } catch (CommandException $ex) { - $show_command = csprintf( - 'git push origin -- %s:%s', - 'HEAD', - $push_dst); - $error = DrydockCommandError::newFromCommandException( - self::PHASE_PUSH, - $show_command, - $ex); - $operation->setCommandError($error); + $display_command = csprintf('git commit'); + + // TODO: One reason this can fail is if the changes have already been + // merged. We could try to detect that. + + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_COMMIT) + ->setDisplayCommand($display_command); + + $operation->setCommandError($error->toDictionary()); + + throw $ex; + } + + try { + $interface->execx( + 'git push origin -- %s:%s', + 'HEAD', + $push_dst); + } catch (CommandException $ex) { + $display_command = csprintf( + 'git push origin %R:%R', + 'HEAD', + $push_dst); + + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_PUSH) + ->setDisplayCommand($display_command); + + $operation->setCommandError($error->toDictionary()); + throw $ex; } } @@ -229,6 +248,29 @@ final class DrydockLandRepositoryOperation ); } + // Check if this diff was pushed to a staging area. + $diff = id(new DifferentialDiffQuery()) + ->setViewer($viewer) + ->withIDs(array($revision->getActiveDiff()->getID())) + ->needProperties(true) + ->executeOne(); + + // Older diffs won't have this property. They may still have been pushed. + // At least for now, assume staging changes are present if the property + // is missing. This should smooth the transition to the more formal + // approach. + $has_staging = $diff->hasDiffProperty('arc.staging'); + if ($has_staging) { + $staging = $diff->getProperty('arc.staging'); + if (!is_array($staging)) { + $staging = array(); + } + $status = idx($staging, 'status'); + if ($status != ArcanistDiffWorkflow::STAGING_PUSHED) { + return $this->getBarrierToLandingFromStagingStatus($status); + } + } + // TODO: At some point we should allow installs to give "land reviewed // code" permission to more users than "push any commit", because it is // a much less powerful operation. For now, just require push so this @@ -317,4 +359,85 @@ final class DrydockLandRepositoryOperation return null; } + private function getBarrierToLandingFromStagingStatus($status) { + switch ($status) { + case ArcanistDiffWorkflow::STAGING_USER_SKIP: + return array( + 'title' => pht('Staging Area Skipped'), + 'body' => pht( + 'The diff author used the %s flag to skip pushing this change to '. + 'staging. Changes must be pushed to staging before they can be '. + 'landed from the web.', + phutil_tag('tt', array(), '--skip-staging')), + ); + case ArcanistDiffWorkflow::STAGING_DIFF_RAW: + return array( + 'title' => pht('Raw Diff Source'), + 'body' => pht( + 'The diff was generated from a raw input source, so the change '. + 'could not be pushed to staging. Changes must be pushed to '. + 'staging before they can be landed from the web.'), + ); + case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNKNOWN: + return array( + 'title' => pht('Unknown Repository'), + 'body' => pht( + 'When the diff was generated, the client was not able to '. + 'determine which repository it belonged to, so the change '. + 'was not pushed to staging. Changes must be pushed to staging '. + 'before they can be landed from the web.'), + ); + case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNAVAILABLE: + return array( + 'title' => pht('Staging Unavailable'), + 'body' => pht( + 'When this diff was generated, the server was running an older '. + 'version of Phabricator which did not support staging areas, so '. + 'the change was not pushed to staging. Changes must be pushed '. + 'to staging before they can be landed from the web.'), + ); + case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNSUPPORTED: + return array( + 'title' => pht('Repository Unsupported'), + 'body' => pht( + 'When this diff was generated, the server was running an older '. + 'version of Phabricator which did not support staging areas for '. + 'this version control system, so the chagne was not pushed to '. + 'staging. Changes must be pushed to staging before they can be '. + 'landed from the web.'), + ); + + case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNCONFIGURED: + return array( + 'title' => pht('Repository Unconfigured'), + 'body' => pht( + 'When this diff was generated, the repository was not configured '. + 'with a staging area, so the change was not pushed to staging. '. + 'Changes must be pushed to staging before they can be landed '. + 'from the web.'), + ); + case ArcanistDiffWorkflow::STAGING_CLIENT_UNSUPPORTED: + return array( + 'title' => pht('Client Support Unavailable'), + 'body' => pht( + 'When this diff was generated, the client did not support '. + 'staging areas for this version control system, so the change '. + 'was not pushed to staging. Changes must be pushed to staging '. + 'before they can be landed from the web. Updating the client '. + 'may resolve this issue.'), + ); + default: + return array( + 'title' => pht('Unknown Error'), + 'body' => pht( + 'When this diff was generated, it was not pushed to staging for '. + 'an unknown reason (the status code was "%s"). Changes must be '. + 'pushed to staging before they can be landed from the web. '. + 'The server may be running an out-of-date version of Phabricator, '. + 'and updating may provide more information about this error.', + $status), + ); + } + } + } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index a7deb73cc3..87ea777f72 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -350,7 +350,7 @@ final class DrydockBlueprint extends DrydockDAO } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php index 23ad6b81fe..aa7d1e3873 100644 --- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -48,6 +48,7 @@ final class DrydockRepositoryOperationStatusView $box_view = $this->getBoxView(); if (!$box_view) { $box_view = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeaderText(pht('Operation Status')); } $box_view->setObjectList($list); @@ -82,6 +83,20 @@ final class DrydockRepositoryOperationStatusView 'This change did not merge cleanly. This usually indicates '. 'that the change is out of date and needs to be updated.'); break; + case DrydockWorkingCopyBlueprintImplementation::PHASE_REMOTEFETCH: + $message = pht( + 'This change could not be fetched from the remote.'); + break; + case DrydockWorkingCopyBlueprintImplementation::PHASE_MERGEFETCH: + $message = pht( + 'This change could not be fetched from the remote staging '. + 'area. It may not have been pushed, or may have been removed.'); + break; + case DrydockLandRepositoryOperation::PHASE_COMMIT: + $message = pht( + 'Committing this change failed. It may already have been '. + 'merged.'); + break; case DrydockLandRepositoryOperation::PHASE_PUSH: $message = pht( 'The push failed. This usually indicates '. @@ -123,10 +138,23 @@ final class DrydockRepositoryOperationStatusView private function renderVCSErrorTable(array $vcs_error) { $rows = array(); - $rows[] = array(pht('Command'), $vcs_error['command']); + + $rows[] = array( + pht('Command'), + phutil_censor_credentials($vcs_error['command']), + ); + $rows[] = array(pht('Error'), $vcs_error['err']); - $rows[] = array(pht('Stdout'), $vcs_error['stdout']); - $rows[] = array(pht('Stderr'), $vcs_error['stderr']); + + $rows[] = array( + pht('Stdout'), + phutil_censor_credentials($vcs_error['stdout']), + ); + + $rows[] = array( + pht('Stderr'), + phutil_censor_credentials($vcs_error['stderr']), + ); $table = id(new AphrontTableView($rows)) ->setColumnClasses( diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/controller/PhabricatorFactChartController.php index 18f769cc47..16df8fca69 100644 --- a/src/applications/fact/controller/PhabricatorFactChartController.php +++ b/src/applications/fact/controller/PhabricatorFactChartController.php @@ -77,14 +77,13 @@ final class PhabricatorFactChartController extends PhabricatorFactController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Chart')); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Chart'), - )); + $title = pht('Chart'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($box); + } } diff --git a/src/applications/fact/controller/PhabricatorFactHomeController.php b/src/applications/fact/controller/PhabricatorFactHomeController.php index 8e5c9511eb..f4eeca0387 100644 --- a/src/applications/fact/controller/PhabricatorFactHomeController.php +++ b/src/applications/fact/controller/PhabricatorFactHomeController.php @@ -59,15 +59,16 @@ final class PhabricatorFactHomeController extends PhabricatorFactController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Home')); - return $this->buildApplicationPage( - array( - $crumbs, + $title = pht('Facts'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild(array( $chart_form, $panel, - ), - array( - 'title' => pht('Facts'), )); + } private function buildChartForm() { diff --git a/src/applications/feed/controller/PhabricatorFeedDetailController.php b/src/applications/feed/controller/PhabricatorFeedDetailController.php index 4fac10d1a8..f136d01202 100644 --- a/src/applications/feed/controller/PhabricatorFeedDetailController.php +++ b/src/applications/feed/controller/PhabricatorFeedDetailController.php @@ -26,19 +26,13 @@ final class PhabricatorFeedDetailController extends PhabricatorFeedController { $title = pht('Story'); - $feed_view = phutil_tag_div('phabricator-feed-frame', $feed_view); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $feed_view, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($feed_view); } } diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index 1eaafdbad0..6a4536d94a 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -155,8 +155,7 @@ final class PhabricatorFileComposeController 'defaultIcon' => $value_icon, )); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setFormID($dialog_id) ->setClass('compose-dialog') ->setTitle(pht('Compose Image')) @@ -188,8 +187,6 @@ final class PhabricatorFileComposeController ->appendChild($icon_input) ->addCancelButton('/') ->addSubmitButton(pht('Save Image')); - - return id(new AphrontDialogResponse())->setDialog($dialog); } private function getIconMap() { diff --git a/src/applications/files/controller/PhabricatorFileDeleteController.php b/src/applications/files/controller/PhabricatorFileDeleteController.php index a07fe2e91a..acca7c9b1c 100644 --- a/src/applications/files/controller/PhabricatorFileDeleteController.php +++ b/src/applications/files/controller/PhabricatorFileDeleteController.php @@ -29,17 +29,14 @@ final class PhabricatorFileDeleteController extends PhabricatorFileController { return id(new AphrontRedirectResponse())->setURI('/file/'); } - $dialog = new AphrontDialogView(); - $dialog->setUser($viewer); - $dialog->setTitle(pht('Really delete file?')); - $dialog->appendChild(hsprintf( + return $this->newDialog() + ->setTitle(pht('Really delete file?')) + ->appendChild(hsprintf( '

%s

', pht( - "Permanently delete '%s'? This action can not be undone.", - $file->getName()))); - $dialog->addSubmitButton(pht('Delete')); - $dialog->addCancelButton($file->getInfoURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); + 'Permanently delete "%s"? This action can not be undone.', + $file->getName()))) + ->addSubmitButton(pht('Delete')) + ->addCancelButton($file->getInfoURI()); } } diff --git a/src/applications/files/controller/PhabricatorFileEditController.php b/src/applications/files/controller/PhabricatorFileEditController.php index 9c416b588c..e1b34afd73 100644 --- a/src/applications/files/controller/PhabricatorFileEditController.php +++ b/src/applications/files/controller/PhabricatorFileEditController.php @@ -19,8 +19,9 @@ final class PhabricatorFileEditController extends PhabricatorFileController { return new Aphront404Response(); } - $title = pht('Edit %s', $file->getName()); + $title = pht('Edit File: %s', $file->getName()); $file_name = $file->getName(); + $header_icon = 'fa-pencil'; $view_uri = '/'.$file->getMonogram(); $error_name = true; $validation_exception = null; @@ -86,21 +87,28 @@ final class PhabricatorFileEditController extends PhabricatorFileController { $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($file->getMonogram(), $view_uri) - ->addTextCrumb(pht('Edit')); + ->addTextCrumb(pht('Edit')) + ->setBorder(true); - $object_box = id(new PHUIObjectBoxView()) + $box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 2731b7f4bb..66050b697e 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -35,7 +35,8 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($file) - ->setHeader($file->getName()); + ->setHeader($file->getName()) + ->setHeaderIcon('fa-file-o'); $ttl = $file->getTTL(); if ($ttl !== null) { @@ -55,28 +56,35 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $header->addTag($partial_tag); } - $actions = $this->buildActionView($file); + $curtain = $this->buildCurtainView($file); $timeline = $this->buildTransactionView($file); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( 'F'.$file->getID(), $this->getApplicationURI("/info/{$phid}/")); + $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header); + ->setHeaderText(pht('File')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - $this->buildPropertyViews($object_box, $file, $actions); + $this->buildPropertyViews($object_box, $file); + $title = $file->getName(); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $object_box, $timeline, - ), - array( - 'title' => $file->getName(), - 'pageObjects' => array($file->getPHID()), )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($file->getPHID())) + ->appendChild($view); + } private function buildTransactionView(PhabricatorFile $file) { @@ -108,7 +116,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { ); } - private function buildActionView(PhabricatorFile $file) { + private function buildCurtainView(PhabricatorFile $file) { $viewer = $this->getViewer(); $id = $file->getID(); @@ -118,14 +126,12 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { $file, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($file); + $curtain = $this->newCurtainView($file); $can_download = !$file->getIsPartial(); if ($file->isViewableInBrowser()) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View File')) ->setIcon('fa-file-o') @@ -133,7 +139,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { ->setDisabled(!$can_download) ->setWorkflow(!$can_download)); } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setUser($viewer) ->setRenderAsForm($can_download) @@ -145,7 +151,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { ->setWorkflow(!$can_download)); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit File')) ->setIcon('fa-pencil') @@ -153,7 +159,7 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete File')) ->setIcon('fa-times') @@ -161,24 +167,22 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { ->setWorkflow(true) ->setDisabled(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Transforms')) ->setIcon('fa-crop') ->setHref($this->getApplicationURI("/transforms/{$id}/"))); - return $view; + return $curtain; } private function buildPropertyViews( PHUIObjectBoxView $box, - PhabricatorFile $file, - PhabricatorActionListView $actions) { + PhabricatorFile $file) { $request = $this->getRequest(); $viewer = $request->getUser(); $properties = id(new PHUIPropertyListView()); - $properties->setActionList($actions); $box->addPropertyList($properties, pht('Details')); if ($file->getAuthorPHID()) { diff --git a/src/applications/files/controller/PhabricatorFileTransformListController.php b/src/applications/files/controller/PhabricatorFileTransformListController.php index 767254ad3a..026f60320e 100644 --- a/src/applications/files/controller/PhabricatorFileTransformListController.php +++ b/src/applications/files/controller/PhabricatorFileTransformListController.php @@ -113,26 +113,35 @@ final class PhabricatorFileTransformListController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($monogram, '/'.$monogram); $crumbs->addTextCrumb(pht('Transforms')); + $crumbs->setBorder(true); $dst_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('File Sources')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($dst_table); $src_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Available Transforms')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($src_table); - return $this->buildApplicationPage( - array( - $crumbs, + $title = pht('%s Transforms', $file->getName()); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-arrows-alt'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $dst_box, $src_box, - ), - array( - 'title' => array( - pht('%s %s', $monogram, $file->getName()), - pht('Tranforms'), - ), )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/files/controller/PhabricatorFileUploadController.php b/src/applications/files/controller/PhabricatorFileUploadController.php index a02b298f25..3be0ca3968 100644 --- a/src/applications/files/controller/PhabricatorFileUploadController.php +++ b/src/applications/files/controller/PhabricatorFileUploadController.php @@ -81,6 +81,7 @@ final class PhabricatorFileUploadController extends PhabricatorFileController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Upload'), $request->getRequestURI()); + $crumbs->setBorder(true); $title = pht('Upload File'); @@ -89,19 +90,26 @@ final class PhabricatorFileUploadController extends PhabricatorFileController { ->setShowIfSupportedID($support_id); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('File')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-upload'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $form_box, $global_upload, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/files/controller/PhabricatorFileUploadDialogController.php b/src/applications/files/controller/PhabricatorFileUploadDialogController.php index dd22caa74a..cf13f4d694 100644 --- a/src/applications/files/controller/PhabricatorFileUploadDialogController.php +++ b/src/applications/files/controller/PhabricatorFileUploadDialogController.php @@ -6,14 +6,12 @@ final class PhabricatorFileUploadDialogController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle(pht('Upload File')) ->appendChild(pht( 'To add files, drag and drop them into the comment text area.')) ->addCancelButton('/', pht('Close')); - return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 0a1dd6f949..439540d7d2 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -26,7 +26,6 @@ final class PhabricatorFile extends PhabricatorFileDAO PhabricatorPolicyInterface, PhabricatorDestructibleInterface { - const ONETIME_TEMPORARY_TOKEN_TYPE = 'file:onetime'; const STORAGE_FORMAT_RAW = 'raw'; const METADATA_IMAGE_WIDTH = 'width'; @@ -1119,12 +1118,13 @@ final class PhabricatorFile extends PhabricatorFileDAO protected function generateOneTimeToken() { $key = Filesystem::readRandomCharacters(16); + $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE; // Save the new secret. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $token = id(new PhabricatorAuthTemporaryToken()) - ->setObjectPHID($this->getPHID()) - ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) + ->setTokenResource($this->getPHID()) + ->setTokenType($token_type) ->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenCode(PhabricatorHash::digest($key)) ->save(); @@ -1134,10 +1134,12 @@ final class PhabricatorFile extends PhabricatorFileDAO } public function validateOneTimeToken($token_code) { + $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE; + $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withObjectPHIDs(array($this->getPHID())) - ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) + ->withTokenResources(array($this->getPHID())) + ->withTokenTypes(array($token_type)) ->withExpired(false) ->withTokenCodes(array(PhabricatorHash::digest($token_code))) ->executeOne(); @@ -1348,10 +1350,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return ($this->authorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php b/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php new file mode 100644 index 0000000000..73b5cd8c09 --- /dev/null +++ b/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php @@ -0,0 +1,17 @@ +rewind(); $this->didRewind = true; } else { - $data->next(); + if ($data->valid()) { + $data->next(); + } } if (!$data->valid()) { diff --git a/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php new file mode 100644 index 0000000000..b8d18936e6 --- /dev/null +++ b/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php @@ -0,0 +1,25 @@ +iterator = $iterator; + return $this; + } + + public function getIterator() { + return $this->iterator; + } + + protected function newDataIterator() { + return $this->getIterator(); + } + + protected function getDataLength() { + return null; + } + +} diff --git a/src/applications/fund/application/PhabricatorFundApplication.php b/src/applications/fund/application/PhabricatorFundApplication.php index 889baca20e..3a26e501e6 100644 --- a/src/applications/fund/application/PhabricatorFundApplication.php +++ b/src/applications/fund/application/PhabricatorFundApplication.php @@ -42,6 +42,7 @@ final class PhabricatorFundApplication extends PhabricatorApplication { '/fund/' => array( '(?:query/(?P[^/]+)/)?' => 'FundInitiativeListController', 'create/' => 'FundInitiativeEditController', + 'comment/(?P[1-9]\d*)/' => 'FundInitiativeCommentController', 'edit/(?:(?P\d+)/)?' => 'FundInitiativeEditController', 'close/(?P\d+)/' => 'FundInitiativeCloseController', 'back/(?P\d+)/' => 'FundInitiativeBackController', diff --git a/src/applications/fund/controller/FundInitiativeCommentController.php b/src/applications/fund/controller/FundInitiativeCommentController.php new file mode 100644 index 0000000000..2c4998a94c --- /dev/null +++ b/src/applications/fund/controller/FundInitiativeCommentController.php @@ -0,0 +1,63 @@ +getViewer(); + $id = $request->getURIData('id'); + + if (!$request->isFormPost()) { + return new Aphront400Response(); + } + + $initiative = id(new FundInitiativeQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$initiative) { + return new Aphront404Response(); + } + + $is_preview = $request->isPreviewRequest(); + $draft = PhabricatorDraft::buildFromRequest($request); + + $view_uri = '/'.$initiative->getMonogram(); + + $xactions = array(); + $xactions[] = id(new FundInitiativeTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) + ->attachComment( + id(new FundInitiativeTransactionComment()) + ->setContent($request->getStr('comment'))); + + $editor = id(new FundInitiativeEditor()) + ->setActor($viewer) + ->setContinueOnNoEffect($request->isContinueRequest()) + ->setContentSourceFromRequest($request) + ->setIsPreview($is_preview); + + try { + $xactions = $editor->applyTransactions($initiative, $xactions); + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { + return id(new PhabricatorApplicationTransactionNoEffectResponse()) + ->setCancelURI($view_uri) + ->setException($ex); + } + + if ($draft) { + $draft->replaceOrDelete(); + } + + if ($request->isAjax() && $is_preview) { + return id(new PhabricatorApplicationTransactionResponse()) + ->setViewer($viewer) + ->setTransactions($xactions) + ->setIsPreview($is_preview); + } else { + return id(new AphrontRedirectResponse()) + ->setURI($view_uri); + } + } + +} diff --git a/src/applications/fund/controller/FundInitiativeEditController.php b/src/applications/fund/controller/FundInitiativeEditController.php index 75f7d338c9..354d686bd5 100644 --- a/src/applications/fund/controller/FundInitiativeEditController.php +++ b/src/applications/fund/controller/FundInitiativeEditController.php @@ -30,13 +30,14 @@ final class FundInitiativeEditController $title = pht('Create Initiative'); $button_text = pht('Create Initiative'); $cancel_uri = $this->getApplicationURI(); + $header_icon = 'fa-plus-square'; } else { $title = pht( - 'Edit %s %s', - $initiative->getMonogram(), + 'Edit Initiative: %s', $initiative->getName()); $button_text = pht('Save Changes'); $cancel_uri = '/'.$initiative->getMonogram(); + $header_icon = 'fa-pencil'; } $e_name = true; @@ -230,20 +231,26 @@ final class FundInitiativeEditController '/'.$initiative->getMonogram()); $crumbs->addTextCrumb(pht('Edit')); } + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) - ->setHeaderText($title) + ->setHeaderText(pht('Initiative')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index f4535d574e..18b123bf3a 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -46,42 +46,29 @@ final class FundInitiativeViewController ->setStatus($status_icon, $status_color, $status_name) ->setHeaderIcon('fa-heart'); - $properties = $this->buildPropertyListView($initiative); - $actions = $this->buildActionListView($initiative); + $curtain = $this->buildCurtain($initiative); $details = $this->buildPropertySectionView($initiative); $timeline = $this->buildTransactionTimeline( $initiative, new FundInitiativeTransactionQuery()); - $timeline->setShouldTerminate(true); + + $add_comment = $this->buildCommentForm($initiative); $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setMainColumn($timeline) - ->setPropertyList($properties) - ->addPropertySection(pht('DETAILS'), $details) - ->setActionList($actions); + ->setCurtain($curtain) + ->setMainColumn(array( + $timeline, + $add_comment, + )) + ->addPropertySection(pht('DETAILS'), $details); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($initiative->getPHID())) - ->appendChild( - array( - $view, - )); - } - - private function buildPropertyListView(FundInitiative $initiative) { - $viewer = $this->getRequest()->getUser(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($initiative); - - $view->invokeWillRenderEvent(); - - return $view; + ->appendChild($view); } private function buildPropertySectionView(FundInitiative $initiative) { @@ -124,8 +111,9 @@ final class FundInitiativeViewController return $view; } - private function buildActionListView(FundInitiative $initiative) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtain(FundInitiative $initiative) { + $viewer = $this->getViewer(); + $id = $initiative->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -133,11 +121,9 @@ final class FundInitiativeViewController $initiative, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($initiative); + $curtain = $this->newCurtainView($initiative); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Initiative')) ->setIcon('fa-pencil') @@ -153,7 +139,7 @@ final class FundInitiativeViewController $close_icon = 'fa-times'; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($close_name) ->setIcon($close_icon) @@ -161,7 +147,7 @@ final class FundInitiativeViewController ->setWorkflow(true) ->setHref($this->getApplicationURI("/close/{$id}/"))); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Back Initiative')) ->setIcon('fa-money') @@ -169,13 +155,36 @@ final class FundInitiativeViewController ->setWorkflow(true) ->setHref($this->getApplicationURI("/back/{$id}/"))); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Backers')) ->setIcon('fa-bank') ->setHref($this->getApplicationURI("/backers/{$id}/"))); - return $view; + return $curtain; } + private function buildCommentForm(FundInitiative $initiative) { + $viewer = $this->getViewer(); + + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + + $add_comment_header = $is_serious + ? pht('Add Comment') + : pht('Add Liquidity'); + + $draft = PhabricatorDraft::newFromUserAndKey( + $viewer, $initiative->getPHID()); + + return id(new PhabricatorApplicationTransactionCommentView()) + ->setUser($viewer) + ->setObjectPHID($initiative->getPHID()) + ->setDraft($draft) + ->setHeaderText($add_comment_header) + ->setAction( + $this->getApplicationURI('/comment/'.$initiative->getID().'/')) + ->setSubmitButtonName(pht('Add Comment')); + } + + } diff --git a/src/applications/fund/editor/FundInitiativeEditor.php b/src/applications/fund/editor/FundInitiativeEditor.php index fc07c472ba..40fc6f0174 100644 --- a/src/applications/fund/editor/FundInitiativeEditor.php +++ b/src/applications/fund/editor/FundInitiativeEditor.php @@ -23,6 +23,7 @@ final class FundInitiativeEditor $types[] = FundInitiativeTransaction::TYPE_MERCHANT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + $types[] = PhabricatorTransactions::TYPE_COMMENT; return $types; } diff --git a/src/applications/fund/storage/FundInitiative.php b/src/applications/fund/storage/FundInitiative.php index 3375551443..517c377c44 100644 --- a/src/applications/fund/storage/FundInitiative.php +++ b/src/applications/fund/storage/FundInitiative.php @@ -178,10 +178,6 @@ final class FundInitiative extends FundDAO return ($phid == $this->getOwnerPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenRecevierInterface )---------------------------------- */ diff --git a/src/applications/fund/storage/FundInitiativeTransaction.php b/src/applications/fund/storage/FundInitiativeTransaction.php index c06d62e229..5c1f8c589d 100644 --- a/src/applications/fund/storage/FundInitiativeTransaction.php +++ b/src/applications/fund/storage/FundInitiativeTransaction.php @@ -27,7 +27,7 @@ final class FundInitiativeTransaction } public function getApplicationTransactionCommentObject() { - return null; + return new FundInitiativeTransactionComment(); } public function getRequiredHandlePHIDs() { diff --git a/src/applications/nuance/storage/NuanceRequestorTransactionComment.php b/src/applications/fund/storage/FundInitiativeTransactionComment.php similarity index 57% rename from src/applications/nuance/storage/NuanceRequestorTransactionComment.php rename to src/applications/fund/storage/FundInitiativeTransactionComment.php index 4870a65a45..3e7330ab2c 100644 --- a/src/applications/nuance/storage/NuanceRequestorTransactionComment.php +++ b/src/applications/fund/storage/FundInitiativeTransactionComment.php @@ -1,10 +1,10 @@ array( '(?P\d+)/' => 'HarbormasterLintMessagesController', ), + 'hook/' => array( + 'circleci/' => 'HarbormasterCircleCIHookController', + ), ), ); } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 25f3506c0b..320e94d642 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -22,30 +22,33 @@ final class HarbormasterBuildViewController $title = pht('Build %d', $id); - $header = id(new PHUIHeaderView()) + $page_header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($build); + ->setPolicyObject($build) + ->setHeaderIcon('fa-cubes'); if ($build->isRestarting()) { - $header->setStatus('fa-exclamation-triangle', 'red', pht('Restarting')); + $page_header->setStatus( + 'fa-exclamation-triangle', 'red', pht('Restarting')); } else if ($build->isPausing()) { - $header->setStatus('fa-exclamation-triangle', 'red', pht('Pausing')); + $page_header->setStatus( + 'fa-exclamation-triangle', 'red', pht('Pausing')); } else if ($build->isResuming()) { - $header->setStatus('fa-exclamation-triangle', 'red', pht('Resuming')); + $page_header->setStatus( + 'fa-exclamation-triangle', 'red', pht('Resuming')); } else if ($build->isAborting()) { - $header->setStatus('fa-exclamation-triangle', 'red', pht('Aborting')); + $page_header->setStatus( + 'fa-exclamation-triangle', 'red', pht('Aborting')); } - $box = id(new PHUIObjectBoxView()) - ->setHeader($header); - - $actions = $this->buildActionList($build); - $this->buildPropertyLists($box, $build, $actions); + $curtain = $this->buildCurtainView($build); + $properties = $this->buildPropertyList($build); $crumbs = $this->buildApplicationCrumbs(); $this->addBuildableCrumb($crumbs, $build->getBuildable()); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); if ($generation === null || $generation > $build->getBuildGeneration() || $generation < 0) { @@ -85,12 +88,14 @@ final class HarbormasterBuildViewController foreach ($build_targets as $build_target) { $header = id(new PHUIHeaderView()) ->setHeader($build_target->getName()) - ->setUser($viewer); + ->setUser($viewer) + ->setHeaderIcon('fa-bullseye'); $target_box = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeader($header); - $properties = new PHUIPropertyListView(); + $property_list = new PHUIPropertyListView(); $target_artifacts = idx($artifacts, $build_target->getPHID(), array()); @@ -107,13 +112,12 @@ final class HarbormasterBuildViewController if ($links) { $links = phutil_implode_html(phutil_tag('br'), $links); - $properties->addProperty( + $property_list->addProperty( pht('External Link'), $links); } $status_view = new PHUIStatusListView(); - $item = new PHUIStatusItemView(); $status = $build_target->getTargetStatus(); @@ -168,13 +172,13 @@ final class HarbormasterBuildViewController } } - $properties->addProperty( + $property_list->addProperty( pht('When'), phutil_implode_html(" \xC2\xB7 ", $when)); - $properties->addProperty(pht('Status'), $status_view); + $property_list->addProperty(pht('Status'), $status_view); - $target_box->addPropertyList($properties, pht('Overview')); + $target_box->addPropertyList($property_list, pht('Overview')); $step = $build_target->getBuildStep(); @@ -182,9 +186,9 @@ final class HarbormasterBuildViewController $description = $step->getDescription(); if ($description) { $description = new PHUIRemarkupView($viewer, $description); - $properties->addSectionHeader( + $property_list->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); - $properties->addTextContent($description); + $property_list->addTextContent($description); } } else { $target_box->setFormErrors( @@ -196,35 +200,35 @@ final class HarbormasterBuildViewController } $details = $build_target->getDetails(); - $properties = new PHUIPropertyListView(); + $property_list = new PHUIPropertyListView(); foreach ($details as $key => $value) { - $properties->addProperty($key, $value); + $property_list->addProperty($key, $value); } - $target_box->addPropertyList($properties, pht('Configuration')); + $target_box->addPropertyList($property_list, pht('Configuration')); $variables = $build_target->getVariables(); - $properties = new PHUIPropertyListView(); - $properties->addRawContent($this->buildProperties($variables)); - $target_box->addPropertyList($properties, pht('Variables')); + $property_list = new PHUIPropertyListView(); + $property_list->addRawContent($this->buildProperties($variables)); + $target_box->addPropertyList($property_list, pht('Variables')); $artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts); - $properties = new PHUIPropertyListView(); - $properties->addRawContent($artifacts_tab); - $target_box->addPropertyList($properties, pht('Artifacts')); + $property_list = new PHUIPropertyListView(); + $property_list->addRawContent($artifacts_tab); + $target_box->addPropertyList($property_list, pht('Artifacts')); $build_messages = idx($messages, $build_target->getPHID(), array()); - $properties = new PHUIPropertyListView(); - $properties->addRawContent($this->buildMessages($build_messages)); - $target_box->addPropertyList($properties, pht('Messages')); + $property_list = new PHUIPropertyListView(); + $property_list->addRawContent($this->buildMessages($build_messages)); + $target_box->addPropertyList($property_list, pht('Messages')); - $properties = new PHUIPropertyListView(); - $properties->addProperty( + $property_list = new PHUIPropertyListView(); + $property_list->addProperty( pht('Build Target ID'), $build_target->getID()); - $properties->addProperty( + $property_list->addProperty( pht('Build Target PHID'), $build_target->getPHID()); - $target_box->addPropertyList($properties, pht('Metadata')); + $target_box->addPropertyList($property_list, pht('Metadata')); $targets[] = $target_box; @@ -236,16 +240,20 @@ final class HarbormasterBuildViewController new HarbormasterBuildTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, + $view = id(new PHUITwoColumnView()) + ->setHeader($page_header) + ->setCurtain($curtain) + ->setMainColumn(array( + $properties, $targets, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function buildArtifacts( @@ -342,6 +350,7 @@ final class HarbormasterBuildViewController $log_box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($log_view); if ($is_empty) { @@ -366,7 +375,7 @@ final class HarbormasterBuildViewController 'div', array( 'id' => $hide_id, - 'class' => 'harbormaster-empty-logs-are-hidden mlr mlt mll', + 'class' => 'harbormaster-empty-logs-are-hidden', ), array( pht( @@ -432,14 +441,11 @@ final class HarbormasterBuildViewController )); } - private function buildActionList(HarbormasterBuild $build) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildCurtainView(HarbormasterBuild $build) { + $viewer = $this->getViewer(); $id = $build->getID(); - $list = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($build); + $curtain = $this->newCurtainView($build); $can_restart = $build->canRestartBuild() && @@ -465,7 +471,7 @@ final class HarbormasterBuildViewController $viewer, HarbormasterBuildCommand::COMMAND_ABORT); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Restart Build')) ->setIcon('fa-repeat') @@ -474,7 +480,7 @@ final class HarbormasterBuildViewController ->setWorkflow(true)); if ($build->canResumeBuild()) { - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Resume Build')) ->setIcon('fa-play') @@ -482,7 +488,7 @@ final class HarbormasterBuildViewController ->setDisabled(!$can_resume) ->setWorkflow(true)); } else { - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Pause Build')) ->setIcon('fa-pause') @@ -491,7 +497,7 @@ final class HarbormasterBuildViewController ->setWorkflow(true)); } - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Abort Build')) ->setIcon('fa-exclamation-triangle') @@ -499,21 +505,14 @@ final class HarbormasterBuildViewController ->setDisabled(!$can_abort) ->setWorkflow(true)); - return $list; + return $curtain; } - private function buildPropertyLists( - PHUIObjectBoxView $box, - HarbormasterBuild $build, - PhabricatorActionListView $actions) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildPropertyList(HarbormasterBuild $build) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($build) - ->setActionList($actions); - $box->addPropertyList($properties); + ->setUser($viewer); $handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) @@ -538,6 +537,12 @@ final class HarbormasterBuildViewController $properties->addProperty( pht('Status'), $this->getStatus($build)); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('PROPERTIES')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + } private function getStatus(HarbormasterBuild $build) { diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 22d598431f..fe124367df 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -35,44 +35,44 @@ final class HarbormasterBuildableViewController $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($buildable); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header); + ->setPolicyObject($buildable) + ->setHeaderIcon('fa-recycle'); $timeline = $this->buildTransactionTimeline( $buildable, new HarbormasterBuildableTransactionQuery()); $timeline->setShouldTerminate(true); - $actions = $this->buildActionList($buildable); - $this->buildPropertyLists($box, $buildable, $actions); + $curtain = $this->buildCurtainView($buildable); + $properties = $this->buildPropertyList($buildable); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($buildable->getMonogram()); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $properties, $lint, $unit, $build_list, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } - private function buildActionList(HarbormasterBuildable $buildable) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildCurtainView(HarbormasterBuildable $buildable) { + $viewer = $this->getViewer(); $id = $buildable->getID(); - $list = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($buildable); + $curtain = $this->newCurtainView($buildable); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -117,7 +117,7 @@ final class HarbormasterBuildableViewController $resume_uri = "buildable/{$id}/resume/"; $abort_uri = "buildable/{$id}/abort/"; - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-repeat') ->setName(pht('Restart All Builds')) @@ -125,7 +125,7 @@ final class HarbormasterBuildableViewController ->setWorkflow(true) ->setDisabled(!$can_restart || !$can_edit)); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pause') ->setName(pht('Pause All Builds')) @@ -133,7 +133,7 @@ final class HarbormasterBuildableViewController ->setWorkflow(true) ->setDisabled(!$can_pause || !$can_edit)); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-play') ->setName(pht('Resume All Builds')) @@ -141,7 +141,7 @@ final class HarbormasterBuildableViewController ->setWorkflow(true) ->setDisabled(!$can_resume || !$can_edit)); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-exclamation-triangle') ->setName(pht('Abort All Builds')) @@ -149,21 +149,14 @@ final class HarbormasterBuildableViewController ->setWorkflow(true) ->setDisabled(!$can_abort || !$can_edit)); - return $list; + return $curtain; } - private function buildPropertyLists( - PHUIObjectBoxView $box, - HarbormasterBuildable $buildable, - PhabricatorActionListView $actions) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildPropertyList(HarbormasterBuildable $buildable) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($buildable) - ->setActionList($actions); - $box->addPropertyList($properties); + ->setUser($viewer); $container_phid = $buildable->getContainerPHID(); $buildable_phid = $buildable->getBuildablePHID(); @@ -184,6 +177,10 @@ final class HarbormasterBuildableViewController ? pht('Manual Buildable') : pht('Automatic Buildable')); + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('PROPERTIES')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); } private function buildBuildList(HarbormasterBuildable $buildable) { @@ -279,6 +276,7 @@ final class HarbormasterBuildableViewController $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Builds')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($build_list); return $box; @@ -330,6 +328,7 @@ final class HarbormasterBuildableViewController $lint = id(new PHUIObjectBoxView()) ->setHeader($lint_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($lint_table); } else { $lint = null; diff --git a/src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php b/src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php new file mode 100644 index 0000000000..7f6d4900fe --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php @@ -0,0 +1,87 @@ +setViewer($viewer) + ->withPHIDs(array($target_phid)) + ->needBuildSteps(true) + ->executeOne(); + if ($target) { + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $this->updateTarget($target, $payload); + } + } + + $response = new AphrontWebpageResponse(); + $response->setContent(pht("Request OK\n")); + return $response; + } + + private function updateTarget( + HarbormasterBuildTarget $target, + array $payload) { + + $step = $target->getBuildStep(); + $impl = $step->getStepImplementation(); + if (!($impl instanceof HarbormasterCircleCIBuildStepImplementation)) { + throw new Exception( + pht( + 'Build target ("%s") has the wrong type of build step. Only '. + 'CircleCI build steps may be updated via the CircleCI webhook.', + $target->getPHID())); + } + + switch (idx($payload, 'status')) { + case 'success': + case 'fixed': + $message_type = HarbormasterMessageType::MESSAGE_PASS; + break; + default: + $message_type = HarbormasterMessageType::MESSAGE_FAIL; + break; + } + + $viewer = PhabricatorUser::getOmnipotentUser(); + + $api_method = 'harbormaster.sendmessage'; + $api_params = array( + 'buildTargetPHID' => $target->getPHID(), + 'type' => $message_type, + ); + + id(new ConduitCall($api_method, $api_params)) + ->setUser($viewer) + ->execute(); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterLintMessagesController.php b/src/applications/harbormaster/controller/HarbormasterLintMessagesController.php index befe0e450b..e7636dbbd8 100644 --- a/src/applications/harbormaster/controller/HarbormasterLintMessagesController.php +++ b/src/applications/harbormaster/controller/HarbormasterLintMessagesController.php @@ -45,20 +45,27 @@ final class HarbormasterLintMessagesController $crumbs = $this->buildApplicationCrumbs(); $this->addBuildableCrumb($crumbs, $buildable); $crumbs->addTextCrumb(pht('Lint')); + $crumbs->setBorder(true); $title = array( $buildable->getMonogram(), pht('Lint'), ); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $lint, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php index 847eaa6328..28bd16456d 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -24,50 +24,50 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController { $header = id(new PHUIHeaderView()) ->setHeader($plan->getName()) ->setUser($viewer) - ->setPolicyObject($plan); + ->setPolicyObject($plan) + ->setHeaderIcon('fa-ship'); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header); - - $actions = $this->buildActionList($plan); - $this->buildPropertyLists($box, $plan, $actions); + $curtain = $this->buildCurtainView($plan); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Plan %d', $id)); + $crumbs->setBorder(true); list($step_list, $has_any_conflicts, $would_deadlock) = $this->buildStepList($plan); + $error = null; if ($would_deadlock) { - $box->setFormErrors( - array( - pht( - 'This build plan will deadlock when executed, due to '. - 'circular dependencies present in the build plan. '. - 'Examine the step list and resolve the deadlock.'), - )); + $error = pht('This build plan will deadlock when executed, due to '. + 'circular dependencies present in the build plan. '. + 'Examine the step list and resolve the deadlock.'); } else if ($has_any_conflicts) { // A deadlocking build will also cause all the artifacts to be // invalid, so we just skip showing this message if that's the // case. - $box->setFormErrors( - array( - pht( - 'This build plan has conflicts in one or more build steps. '. - 'Examine the step list and resolve the listed errors.'), - )); + $error = pht('This build plan has conflicts in one or more build steps. '. + 'Examine the step list and resolve the listed errors.'); } - return $this->buildApplicationPage( - array( - $crumbs, - $box, + if ($error) { + $error = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->appendChild($error); + } + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $error, $step_list, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildStepList(HarbormasterBuildPlan $plan) { @@ -206,25 +206,24 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController { $step_box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($step_list); return array($step_box, $has_any_conflicts, $is_deadlocking); } - private function buildActionList(HarbormasterBuildPlan $plan) { + private function buildCurtainView(HarbormasterBuildPlan $plan) { $viewer = $this->getViewer(); $id = $plan->getID(); - $list = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($plan); + $curtain = $this->newCurtainView($plan); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $plan, PhabricatorPolicyCapability::CAN_EDIT); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Plan')) ->setHref($this->getApplicationURI("plan/edit/{$id}/")) @@ -233,7 +232,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController { ->setIcon('fa-pencil')); if ($plan->isDisabled()) { - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Enable Plan')) ->setHref($this->getApplicationURI("plan/disable/{$id}/")) @@ -241,7 +240,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController { ->setDisabled(!$can_edit) ->setIcon('fa-check')); } else { - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Disable Plan')) ->setHref($this->getApplicationURI("plan/disable/{$id}/")) @@ -252,7 +251,7 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController { $can_run = ($can_edit && $plan->canRunManually()); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Run Plan Manually')) ->setHref($this->getApplicationURI("plan/run/{$id}/")) @@ -260,26 +259,12 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController { ->setDisabled(!$can_run) ->setIcon('fa-play-circle')); - return $list; - } - - private function buildPropertyLists( - PHUIObjectBoxView $box, - HarbormasterBuildPlan $plan, - PhabricatorActionListView $actions) { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($plan) - ->setActionList($actions); - $box->addPropertyList($properties); - - $properties->addProperty( - pht('Created'), - phabricator_datetime($plan->getDateCreated(), $viewer)); + $curtain->addPanel( + id(new PHUICurtainPanelView()) + ->setHeaderText(pht('Created')) + ->appendChild(phabricator_datetime($plan->getDateCreated(), $viewer))); + return $curtain; } private function buildArtifactList( diff --git a/src/applications/harbormaster/controller/HarbormasterStepAddController.php b/src/applications/harbormaster/controller/HarbormasterStepAddController.php index 06412e2323..94471d7f0d 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepAddController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepAddController.php @@ -40,15 +40,13 @@ final class HarbormasterStepAddController } $groups = mgroup($all, 'getBuildStepGroupKey'); - $lists = array(); + $boxes = array(); $enabled_groups = HarbormasterBuildStepGroup::getAllEnabledGroups(); foreach ($enabled_groups as $group) { $list = id(new PHUIObjectItemListView()) - ->setHeader($group->getGroupName()) ->setNoDataString( - pht( - 'This group has no available build steps.')); + pht('This group has no available build steps.')); $steps = idx($groups, $group->getGroupKey(), array()); @@ -76,28 +74,36 @@ final class HarbormasterStepAddController $list->addItem($item); } - $lists[] = $list; + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($group->getGroupName()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($list); + + $boxes[] = $box; } $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($plan_title, $cancel_uri) - ->addTextCrumb(pht('Add Build Step')); + ->addTextCrumb(pht('Add Build Step')) + ->setBorder(true); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Add Build Step')) - ->appendChild($lists); + $title = array($plan_title, pht('Add Build Step')); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => array( - $plan_title, - pht('Add Build Step'), - ), + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Add Build Step')) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $boxes, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/harbormaster/controller/HarbormasterStepEditController.php b/src/applications/harbormaster/controller/HarbormasterStepEditController.php index 49c4563183..8cd91bb5a4 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepEditController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepEditController.php @@ -136,13 +136,19 @@ final class HarbormasterStepEditController } $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setError($e_name) - ->setValue($v_name)); + ->setUser($viewer); + + $instructions = $implementation->getEditInstructions(); + if (strlen($instructions)) { + $form->appendRemarkupInstructions($instructions); + } + + $form->appendChild( + id(new AphrontFormTextControl()) + ->setName('name') + ->setLabel(pht('Name')) + ->setError($e_name) + ->setValue($v_name)); $form->appendChild(id(new AphrontFormDividerControl())); @@ -178,14 +184,19 @@ final class HarbormasterStepEditController if ($is_new) { $submit = pht('Create Build Step'); - $header = pht('New Step: %s', $implementation->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('New Step: %s', $implementation->getName())) + ->setHeaderIcon('fa-plus-square'); $crumbs->addTextCrumb(pht('Add Step')); } else { $submit = pht('Save Build Step'); - $header = pht('Edit Step: %s', $implementation->getName()); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Step: %s', $implementation->getName())) + ->setHeaderIcon('fa-pencil'); $crumbs->addTextCrumb(pht('Step %d', $step->getID()), $cancel_uri); $crumbs->addTextCrumb(pht('Edit Step')); } + $crumbs->setBorder(true); $form->appendChild( id(new AphrontFormSubmitControl()) @@ -193,8 +204,9 @@ final class HarbormasterStepEditController ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($header) + ->setHeaderText(pht('Step')) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $variables = $this->renderBuildVariablesTable(); @@ -209,16 +221,19 @@ final class HarbormasterStepEditController $timeline->setShouldTerminate(true); } - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, $variables, $timeline, - ), - array( - 'title' => $implementation->getName(), )); + + return $this->newPage() + ->setTitle($implementation->getName()) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function renderBuildVariablesTable() { @@ -248,6 +263,7 @@ final class HarbormasterStepEditController return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Build Variables')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); } diff --git a/src/applications/harbormaster/controller/HarbormasterStepViewController.php b/src/applications/harbormaster/controller/HarbormasterStepViewController.php index faa4b62c7e..07be537fa9 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepViewController.php @@ -29,30 +29,33 @@ final class HarbormasterStepViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Plan %d', $plan_id), $plan_uri); $crumbs->addTextCrumb(pht('Step %d', $id)); + $crumbs->setBorder(true); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Build Step %d: %s', $id, $step->getName())); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Build Step %d: %s', $id, $step->getName())) + ->setHeaderIcon('fa-chevron-circle-right'); $properties = $this->buildPropertyList($step, $field_list); - $actions = $this->buildActionList($step); - $properties->setActionList($actions); - - $box->addPropertyList($properties); + $curtain = $this->buildCurtainView($step); $timeline = $this->buildTransactionTimeline( $step, new HarbormasterBuildStepTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $properties, $timeline, - ), - array( - 'title' => pht('Step %d', $id), )); + + return $this->newPage() + ->setTitle(pht('Step %d', $id)) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function buildPropertyList( @@ -61,8 +64,7 @@ final class HarbormasterStepViewController $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($step); + ->setUser($viewer); try { $implementation = $step->getStepImplementation(); @@ -92,8 +94,6 @@ final class HarbormasterStepViewController $viewer, $view); - $view->invokeWillRenderEvent(); - $description = $step->getDescription(); if (strlen($description)) { $view->addSectionHeader( @@ -103,24 +103,25 @@ final class HarbormasterStepViewController new PHUIRemarkupView($viewer, $description)); } - return $view; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('PROPERTIES')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); } - private function buildActionList(HarbormasterBuildStep $step) { + private function buildCurtainView(HarbormasterBuildStep $step) { $viewer = $this->getViewer(); $id = $step->getID(); - $list = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($step); + $curtain = $this->newCurtainView($step); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $step, PhabricatorPolicyCapability::CAN_EDIT); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Step')) ->setHref($this->getApplicationURI("step/edit/{$id}/")) @@ -128,7 +129,7 @@ final class HarbormasterStepViewController ->setDisabled(!$can_edit) ->setIcon('fa-pencil')); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Step')) ->setHref($this->getApplicationURI("step/delete/{$id}/")) @@ -136,7 +137,7 @@ final class HarbormasterStepViewController ->setDisabled(!$can_edit) ->setIcon('fa-times')); - return $list; + return $curtain; } diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php index 2e7f20a71e..05f06a4e99 100644 --- a/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php +++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php @@ -41,20 +41,27 @@ final class HarbormasterUnitMessageListController $crumbs = $this->buildApplicationCrumbs(); $this->addBuildableCrumb($crumbs, $buildable); $crumbs->addTextCrumb(pht('Unit Tests')); + $crumbs->setBorder(true); $title = array( $buildable->getMonogram(), pht('Unit Tests'), ); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($buildable->getMonogram().' '.pht('Unit Tests')); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $unit, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php index 652c5a2e23..631c332938 100644 --- a/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php @@ -38,12 +38,11 @@ final class HarbormasterUnitMessageViewController ->setStatus($status_icon, $status_color, $status_label); $properties = $this->buildPropertyListView($message); - $actions = $this->buildActionView($message, $build); - - $properties->setActionList($actions); + $curtain = $this->buildCurtainView($message, $build); $unit = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('TEST RESULT')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); $crumbs = $this->buildApplicationCrumbs(); @@ -54,22 +53,29 @@ final class HarbormasterUnitMessageViewController "/harbormaster/unit/{$buildable_id}/"); $crumbs->addTextCrumb(pht('Unit %d', $id)); + $crumbs->setBorder(true); $title = array( $display_name, $buildable->getMonogram(), ); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $unit, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($unit); + ->appendChild($view); } private function buildPropertyListView( HarbormasterBuildUnitMessage $message) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); @@ -81,6 +87,7 @@ final class HarbormasterUnitMessageViewController $details = $message->getUnitMessageDetails(); if (strlen($details)) { // TODO: Use the log view here, once it gets cleaned up. + // Shenanigans below. $details = phutil_tag( 'div', array( @@ -103,20 +110,19 @@ final class HarbormasterUnitMessageViewController return $view; } - private function buildActionView( + private function buildCurtainView( HarbormasterBuildUnitMessage $message, HarbormasterBuild $build) { $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView($build); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Build')) ->setHref($build->getURI()) ->setIcon('fa-wrench')); - return $view; + return $curtain; } } diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php index 342156c101..11837051c3 100644 --- a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php +++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php @@ -54,6 +54,10 @@ final class HarbormasterBuildPlanEditEngine return pht('Create Build Plan'); } + protected function getObjectName() { + return pht('Build Plan'); + } + protected function getEditorURI() { return '/harbormaster/plan/edit/'; } diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index 5eabd85034..75a078a9d7 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -458,8 +458,7 @@ final class HarbormasterBuildEngine extends Phobject { ->getPHID(); $daemon_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_DAEMON, - array()); + PhabricatorDaemonContentSource::SOURCECONST); $editor = $object->getApplicationTransactionEditor() ->setActor($viewer) diff --git a/src/applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php b/src/applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php new file mode 100644 index 0000000000..f02c4cfff0 --- /dev/null +++ b/src/applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php @@ -0,0 +1,12 @@ +getGenericDescription(); } + public function getEditInstructions() { + return null; + } + /** * Run the build target against the specified build. */ @@ -265,6 +269,37 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { } + protected function logHTTPResponse( + HarbormasterBuild $build, + HarbormasterBuildTarget $build_target, + BaseHTTPFuture $future, + $label) { + + list($status, $body, $headers) = $future->resolve(); + + $header_lines = array(); + + // TODO: We don't currently preserve the entire "HTTP" response header, but + // should. Once we do, reproduce it here faithfully. + $status_code = $status->getStatusCode(); + $header_lines[] = "HTTP {$status_code}"; + + foreach ($headers as $header) { + list($head, $tail) = $header; + $header_lines[] = "{$head}: {$tail}"; + } + $header_lines = implode("\n", $header_lines); + + $build_target + ->newLog($label, 'http.head') + ->append($header_lines); + + $build_target + ->newLog($label, 'http.body') + ->append($body); + } + + /* -( Automatic Targets )-------------------------------------------------- */ diff --git a/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php new file mode 100644 index 0000000000..365cf657de --- /dev/null +++ b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php @@ -0,0 +1,258 @@ +getDomain(); + + if (!strlen($domain)) { + $uri_object = new PhutilGitURI($uri); + $domain = $uri_object->getDomain(); + } + + $domain = phutil_utf8_strtolower($domain); + switch ($domain) { + case 'github.com': + case 'www.github.com': + return $uri_object->getPath(); + default: + return null; + } + } + + public function execute( + HarbormasterBuild $build, + HarbormasterBuildTarget $build_target) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $buildable = $build->getBuildable(); + + $object = $buildable->getBuildableObject(); + $object_phid = $object->getPHID(); + if (!($object instanceof HarbormasterCircleCIBuildableInterface)) { + throw new Exception( + pht( + 'Object ("%s") does not implement interface "%s". Only objects '. + 'which implement this interface can be built with CircleCI.', + $object_phid, + 'HarbormasterCircleCIBuildableInterface')); + } + + $github_uri = $object->getCircleCIGitHubRepositoryURI(); + $build_type = $object->getCircleCIBuildIdentifierType(); + $build_identifier = $object->getCircleCIBuildIdentifier(); + + $path = self::getGitHubPath($github_uri); + if ($path === null) { + throw new Exception( + pht( + 'Object ("%s") claims "%s" is a GitHub repository URI, but the '. + 'domain does not appear to be GitHub.', + $object_phid, + $github_uri)); + } + + $path_parts = trim($path, '/'); + $path_parts = explode('/', $path_parts); + if (count($path_parts) < 2) { + throw new Exception( + pht( + 'Object ("%s") claims "%s" is a GitHub repository URI, but the '. + 'path ("%s") does not have enough components (expected at least '. + 'two).', + $object_phid, + $github_uri, + $path)); + } + + list($github_namespace, $github_name) = $path_parts; + $github_name = preg_replace('(\\.git$)', '', $github_name); + + $credential_phid = $this->getSetting('token'); + $api_token = id(new PassphraseCredentialQuery()) + ->setViewer($viewer) + ->withPHIDs(array($credential_phid)) + ->needSecrets(true) + ->executeOne(); + if (!$api_token) { + throw new Exception( + pht( + 'Unable to load API token ("%s")!', + $credential_phid)); + } + + // When we pass "revision", the branch is ignored (and does not even need + // to exist), and only shows up in the UI. Use a cute string which will + // certainly never break anything or cause any kind of problem. + $ship = "\xF0\x9F\x9A\xA2"; + $branch = "{$ship}Harbormaster"; + + $token = $api_token->getSecret()->openEnvelope(); + $parts = array( + 'https://circleci.com/api/v1/project', + phutil_escape_uri($github_namespace), + phutil_escape_uri($github_name)."?circle-token={$token}", + ); + + $uri = implode('/', $parts); + + $data_structure = array(); + switch ($build_type) { + case 'tag': + $data_structure['tag'] = $build_identifier; + break; + case 'revision': + $data_structure['revision'] = $build_identifier; + break; + default: + throw new Exception( + pht( + 'Unknown CircleCI build type "%s". Expected "%s" or "%s".', + $build_type, + 'tag', + 'revision')); + } + + $data_structure['build_parameters'] = array( + 'HARBORMASTER_BUILD_TARGET_PHID' => $build_target->getPHID(), + ); + + $json_data = phutil_json_encode($data_structure); + + $future = id(new HTTPSFuture($uri, $json_data)) + ->setMethod('POST') + ->addHeader('Content-Type', 'application/json') + ->addHeader('Accept', 'application/json') + ->setTimeout(60); + + $this->resolveFutures( + $build, + $build_target, + array($future)); + + $this->logHTTPResponse($build, $build_target, $future, pht('CircleCI')); + + list($status, $body) = $future->resolve(); + if ($status->isError()) { + throw new HarbormasterBuildFailureException(); + } + + $response = phutil_json_decode($body); + $build_uri = idx($response, 'build_url'); + if (!$build_uri) { + throw new Exception( + pht( + 'CircleCI did not return a "%s"!', + 'build_url')); + } + + $target_phid = $build_target->getPHID(); + + // Write an artifact to create a link to the external build in CircleCI. + + $api_method = 'harbormaster.createartifact'; + $api_params = array( + 'buildTargetPHID' => $target_phid, + 'artifactType' => HarbormasterURIArtifact::ARTIFACTCONST, + 'artifactKey' => 'circleci.uri', + 'artifactData' => array( + 'uri' => $build_uri, + 'name' => pht('View in CircleCI'), + 'ui.external' => true, + ), + ); + + id(new ConduitCall($api_method, $api_params)) + ->setUser($viewer) + ->execute(); + } + + public function getFieldSpecifications() { + return array( + 'token' => array( + 'name' => pht('API Token'), + 'type' => 'credential', + 'credential.type' + => PassphraseTokenCredentialType::CREDENTIAL_TYPE, + 'credential.provides' + => PassphraseTokenCredentialType::PROVIDES_TYPE, + 'required' => true, + ), + ); + } + + public function supportsWaitForMessage() { + // NOTE: We always wait for a message, but don't need to show the UI + // control since "Wait" is the only valid choice. + return false; + } + + public function shouldWaitForMessage(HarbormasterBuildTarget $target) { + return true; + } + +} diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php index 10d2cb08b2..016f29642e 100644 --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -72,29 +72,9 @@ final class HarbormasterHTTPRequestBuildStepImplementation $build_target, array($future)); - list($status, $body, $headers) = $future->resolve(); - - $header_lines = array(); - - // TODO: We don't currently preserve the entire "HTTP" response header, but - // should. Once we do, reproduce it here faithfully. - $status_code = $status->getStatusCode(); - $header_lines[] = "HTTP {$status_code}"; - - foreach ($headers as $header) { - list($head, $tail) = $header; - $header_lines[] = "{$head}: {$tail}"; - } - $header_lines = implode("\n", $header_lines); - - $build_target - ->newLog($uri, 'http.head') - ->append($header_lines); - - $build_target - ->newLog($uri, 'http.body') - ->append($body); + $this->logHTTPResponse($build, $build_target, $future, $uri); + list($status) = $future->resolve(); if ($status->isError()) { throw new HarbormasterBuildFailureException(); } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildMessage.php b/src/applications/harbormaster/storage/HarbormasterBuildMessage.php index b212570e20..20d138ad02 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildMessage.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildMessage.php @@ -17,8 +17,13 @@ final class HarbormasterBuildMessage extends HarbormasterDAO private $buildTarget = self::ATTACHABLE; public static function initializeNewMessage(PhabricatorUser $actor) { + $actor_phid = $actor->getPHID(); + if (!$actor_phid) { + $actor_phid = id(new PhabricatorHarbormasterApplication())->getPHID(); + } + return id(new HarbormasterBuildMessage()) - ->setAuthorPHID($actor->getPHID()) + ->setAuthorPHID($actor_phid) ->setIsConsumed(0); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 40090d0ac3..290b288c6c 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -114,6 +114,8 @@ final class HarbormasterBuildLog $this->rope->append($content); $this->flush(); + + return $this; } private function flush() { diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php index c5b2142ac5..2a101ca355 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -127,10 +127,6 @@ final class HarbormasterBuildPlan extends HarbormasterDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -201,7 +197,7 @@ final class HarbormasterBuildPlan extends HarbormasterDAO } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php index 75de4b20df..28f79ee181 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -54,9 +54,14 @@ final class HarbormasterUnitSummaryView extends AphrontView { $tag_icon = 'fa-ban'; } + $tag = id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade($tag_color) + ->setIcon($tag_icon) + ->setName($tag_text); + $header = id(new PHUIHeaderView()) - ->setHeader(pht('Unit Tests')) - ->setStatus($tag_icon, $tag_color, $tag_text); + ->setHeader(array(pht('Unit Tests'), $tag)); if ($this->showViewAll) { $view_all = id(new PHUIButtonView()) @@ -68,7 +73,8 @@ final class HarbormasterUnitSummaryView extends AphrontView { } $box = id(new PHUIObjectBoxView()) - ->setHeader($header); + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $table = id(new HarbormasterUnitPropertyView()) ->setUnitMessages($messages); diff --git a/src/applications/help/controller/PhabricatorHelpDocumentationController.php b/src/applications/help/controller/PhabricatorHelpDocumentationController.php index ce4dc38f42..4983520bab 100644 --- a/src/applications/help/controller/PhabricatorHelpDocumentationController.php +++ b/src/applications/help/controller/PhabricatorHelpDocumentationController.php @@ -38,14 +38,10 @@ final class PhabricatorHelpDocumentationController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $list, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($list); } diff --git a/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php b/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php index 010e41ffcd..22ad0f8d2d 100644 --- a/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php +++ b/src/applications/help/controller/PhabricatorHelpEditorProtocolController.php @@ -10,8 +10,7 @@ final class PhabricatorHelpEditorProtocolController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setMethod('GET') ->setSubmitURI('/settings/panel/display/') ->setTitle(pht('Unsupported Editor Protocol')) @@ -24,9 +23,6 @@ final class PhabricatorHelpEditorProtocolController phutil_tag('tt', array(), 'uri.allowed-editor-protocols'))) ->addSubmitButton(pht('Change Settings')) ->addCancelButton('/'); - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } public static function hasAllowedProtocol($uri) { diff --git a/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php b/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php index b136265d7f..80bd259c48 100644 --- a/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php +++ b/src/applications/help/controller/PhabricatorHelpKeyboardShortcutController.php @@ -57,14 +57,11 @@ final class PhabricatorHelpKeyboardShortcutController array('class' => 'keyboard-shortcut-help'), $rows); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle(pht('Keyboard Shortcuts')) ->appendChild($table) ->addCancelButton('#', pht('Close')); - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } } diff --git a/src/applications/herald/contentsource/PhabricatorHeraldContentSource.php b/src/applications/herald/contentsource/PhabricatorHeraldContentSource.php new file mode 100644 index 0000000000..ad4adfce55 --- /dev/null +++ b/src/applications/herald/contentsource/PhabricatorHeraldContentSource.php @@ -0,0 +1,16 @@ +getApplicationURI(); + $title = pht('Create Herald Rule'); break; case 1: $rule_types = $this->renderRuleTypeControl( @@ -123,14 +124,6 @@ final class HeraldNewController extends HeraldController { $form ->addHiddenInput('content_type', $content_type) ->addHiddenInput('step', 2) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Rule for')) - ->setValue( - phutil_tag( - 'strong', - array(), - idx($content_type_map, $content_type)))) ->appendChild($rule_types); $cancel_text = pht('Back'); @@ -141,6 +134,8 @@ final class HeraldNewController extends HeraldController { 'step' => 0, )); $cancel_uri = $this->getApplicationURI($cancel_uri); + $title = pht('Create Herald Rule: %s', + idx($content_type_map, $content_type)); break; case 2: $adapter = HeraldAdapter::getAdapterForContentType($content_type); @@ -187,10 +182,11 @@ final class HeraldNewController extends HeraldController { 'step' => 1, )); $cancel_uri = $this->getApplicationURI($cancel_uri); + $title = pht('Create Herald Rule: %s', + idx($content_type_map, $content_type)); break; } - $form ->appendChild( id(new AphrontFormSubmitControl()) @@ -199,21 +195,28 @@ final class HeraldNewController extends HeraldController { $form_box = id(new PHUIObjectBoxView()) ->setFormErrors($errors) - ->setHeaderText(pht('Create Herald Rule')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this ->buildApplicationCrumbs() - ->addTextCrumb(pht('Create Rule')); + ->addTextCrumb(pht('Create Rule')) + ->setBorder(true); - $title = pht('Create Herald Rule'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $form_box, + $view, )); } diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index d8e5e42df3..74ce553f9c 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -235,42 +235,47 @@ final class HeraldRuleController extends HeraldController { $this->setupEditorBehavior($rule, $handles, $adapter); $title = $rule->getID() - ? pht('Edit Herald Rule') - : pht('Create Herald Rule'); + ? pht('Edit Herald Rule: %s', $rule->getName()) + : pht('Create Herald Rule: %s', idx($content_type_map, $content_type)); + + $icon = $rule->getID() ? 'fa-pencil' : 'fa-plus-square'; $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $crumbs = $this ->buildApplicationCrumbs() - ->addTextCrumb($title); + ->addTextCrumb($title) + ->setBorder(true); - $title = pht('Edit Rule'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $form_box, + $view, )); } private function saveRule(HeraldAdapter $adapter, $rule, $request) { - $rule->setName($request->getStr('name')); + $new_name = $request->getStr('name'); $match_all = ($request->getStr('must_match') == 'all'); - $rule->setMustMatchAll((int)$match_all); $repetition_policy_param = $request->getStr('repetition_policy'); - $rule->setRepetitionPolicy( - HeraldRepetitionPolicyConfig::toInt($repetition_policy_param)); $e_name = true; $errors = array(); - if (!strlen($rule->getName())) { + if (!strlen($new_name)) { $e_name = pht('Required'); $errors[] = pht('Rule must have a name.'); } @@ -343,19 +348,41 @@ final class HeraldRuleController extends HeraldController { $actions[] = $obj; } + if (!$errors) { + $new_state = id(new HeraldRuleSerializer())->serializeRuleComponents( + $match_all, + $conditions, + $actions, + $repetition_policy_param); + + $xactions = array(); + $xactions[] = id(new HeraldRuleTransaction()) + ->setTransactionType(HeraldRuleTransaction::TYPE_EDIT) + ->setNewValue($new_state); + $xactions[] = id(new HeraldRuleTransaction()) + ->setTransactionType(HeraldRuleTransaction::TYPE_NAME) + ->setNewValue($new_name); + + try { + id(new HeraldRuleEditor()) + ->setActor($this->getViewer()) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->applyTransactions($rule, $xactions); + return array(null, null); + } catch (Exception $ex) { + $errors[] = $ex->getMessage(); + } + } + + // mutate current rule, so it would be sent to the client in the right state + $rule->setMustMatchAll((int)$match_all); + $rule->setName($new_name); + $rule->setRepetitionPolicy( + HeraldRepetitionPolicyConfig::toInt($repetition_policy_param)); $rule->attachConditions($conditions); $rule->attachActions($actions); - if (!$errors) { - $edit_action = $rule->getID() ? 'edit' : 'create'; - - $rule->openTransaction(); - $rule->save(); - $rule->saveConditions($conditions); - $rule->saveActions($actions); - $rule->saveTransaction(); - } - return array($e_name, $errors); } @@ -364,43 +391,6 @@ final class HeraldRuleController extends HeraldController { array $handles, HeraldAdapter $adapter) { - $serial_conditions = array( - array('default', 'default', ''), - ); - - if ($rule->getConditions()) { - $serial_conditions = array(); - foreach ($rule->getConditions() as $condition) { - $value = $adapter->getEditorValueForCondition( - $this->getViewer(), - $condition); - - $serial_conditions[] = array( - $condition->getFieldName(), - $condition->getFieldCondition(), - $value, - ); - } - } - - $serial_actions = array( - array('default', ''), - ); - - if ($rule->getActions()) { - $serial_actions = array(); - foreach ($rule->getActions() as $action) { - $value = $adapter->getEditorValueForAction( - $this->getViewer(), - $action); - - $serial_actions[] = array( - $action->getAction(), - $value, - ); - } - } - $all_rules = $this->loadRulesThisRuleMayDependUpon($rule); $all_rules = mpull($all_rules, 'getName', 'getPHID'); asort($all_rules); @@ -492,10 +482,58 @@ final class HeraldRuleController extends HeraldController { $config_info['targets'][$action] = $value_key; } + $default_group = head($config_info['fields']); + $default_field = head_key($default_group['options']); + $default_condition = head($config_info['conditionMap'][$default_field]); + $default_actions = head($config_info['actions']); + $default_action = head_key($default_actions['options']); + + if ($rule->getConditions()) { + $serial_conditions = array(); + foreach ($rule->getConditions() as $condition) { + $value = $adapter->getEditorValueForCondition( + $this->getViewer(), + $condition); + + $serial_conditions[] = array( + $condition->getFieldName(), + $condition->getFieldCondition(), + $value, + ); + } + } else { + $serial_conditions = array( + array($default_field, $default_condition, null), + ); + } + + if ($rule->getActions()) { + $serial_actions = array(); + foreach ($rule->getActions() as $action) { + $value = $adapter->getEditorValueForAction( + $this->getViewer(), + $action); + + $serial_actions[] = array( + $action->getAction(), + $value, + ); + } + } else { + $serial_actions = array( + array($default_action, null), + ); + } + Javelin::initBehavior( 'herald-rule-editor', array( 'root' => 'herald-rule-edit-form', + 'default' => array( + 'field' => $default_field, + 'condition' => $default_condition, + 'action' => $default_action, + ), 'conditions' => (object)$serial_conditions, 'actions' => (object)$serial_actions, 'template' => $this->buildTokenizerTemplates() + array( diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index 4036be250b..f5e058d3f3 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -33,8 +33,7 @@ final class HeraldRuleViewController extends HeraldController { pht('Active')); } - $actions = $this->buildActionView($rule); - $properties = $this->buildPropertyView($rule); + $curtain = $this->buildCurtain($rule); $details = $this->buildPropertySectionView($rule); $description = $this->buildDescriptionView($rule); @@ -44,10 +43,6 @@ final class HeraldRuleViewController extends HeraldController { $crumbs->addTextCrumb("H{$id}"); $crumbs->setBorder(true); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - $timeline = $this->buildTransactionTimeline( $rule, new HeraldTransactionQuery()); @@ -57,35 +52,30 @@ final class HeraldRuleViewController extends HeraldController { $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn($timeline) ->addPropertySection(pht('DETAILS'), $details) - ->addPropertySection(pht('DESCRIPTION'), $description) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('DESCRIPTION'), $description); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } - private function buildActionView(HeraldRule $rule) { - $viewer = $this->getRequest()->getUser(); - $id = $rule->getID(); + private function buildCurtain(HeraldRule $rule) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($rule); + $id = $rule->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $rule, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain = $this->newCurtainView($rule); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Rule')) ->setHref($this->getApplicationURI("edit/{$id}/")) @@ -103,7 +93,7 @@ final class HeraldRuleViewController extends HeraldController { $disable_name = pht('Archive Rule'); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Disable Rule')) ->setHref($this->getApplicationURI($disable_uri)) @@ -112,23 +102,10 @@ final class HeraldRuleViewController extends HeraldController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $view; + return $curtain; } - private function buildPropertyView( - HeraldRule $rule) { - - $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($rule); - - $view->invokeWillRenderEvent(); - - return $view; - } - - private function buildPropertySectionView( + private function buildPropertySectionView( HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php index 1e12825291..1dd6034bb5 100644 --- a/src/applications/herald/controller/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/HeraldTestConsoleController.php @@ -98,21 +98,29 @@ final class HeraldTestConsoleController extends HeraldController { ->setValue(pht('Test Rules'))); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Herald Test Console')) ->setFormErrors($errors) ->setForm($form); $crumbs = id($this->buildApplicationCrumbs()) - ->addTextCrumb(pht('Test Console')); + ->addTextCrumb(pht('Test Console')) + ->setBorder(true); $title = pht('Test Console'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-desktop'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $box, + $view, )); } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index afb7b36558..e15c7ed52c 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -79,16 +79,25 @@ final class HeraldTranscriptController extends HeraldController { ->addTextCrumb( pht('Transcripts'), $this->getApplicationURI('/transcript/')) - ->addTextCrumb($xscript->getID()); + ->addTextCrumb($xscript->getID()) + ->setBorder(true); - $title = pht('Transcript'); + $title = pht('Transcript: %s', $xscript->getID()); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-file'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($content); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $content, + $view, )); } @@ -232,7 +241,8 @@ final class HeraldTranscriptController extends HeraldController { $action_map = mgroup($action_map, 'getRuleID'); $rule_list = id(new PHUIObjectItemListView()) - ->setNoDataString(pht('No Herald rules applied to this object.')); + ->setNoDataString(pht('No Herald rules applied to this object.')) + ->setFlush(true); $rule_xscripts = $xscript->getRuleTranscripts(); $rule_xscripts = msort($rule_xscripts, 'getRuleID'); diff --git a/src/applications/herald/editor/HeraldRuleEditor.php b/src/applications/herald/editor/HeraldRuleEditor.php index 635c24654c..de9bb01ef2 100644 --- a/src/applications/herald/editor/HeraldRuleEditor.php +++ b/src/applications/herald/editor/HeraldRuleEditor.php @@ -15,6 +15,8 @@ final class HeraldRuleEditor $types = parent::getTransactionTypes(); $types[] = PhabricatorTransactions::TYPE_COMMENT; + $types[] = HeraldRuleTransaction::TYPE_EDIT; + $types[] = HeraldRuleTransaction::TYPE_NAME; $types[] = HeraldRuleTransaction::TYPE_DISABLE; return $types; @@ -27,6 +29,11 @@ final class HeraldRuleEditor switch ($xaction->getTransactionType()) { case HeraldRuleTransaction::TYPE_DISABLE: return (int)$object->getIsDisabled(); + case HeraldRuleTransaction::TYPE_EDIT: + return id(new HeraldRuleSerializer()) + ->serializeRule($object); + case HeraldRuleTransaction::TYPE_NAME: + return $object->getName(); } } @@ -38,8 +45,10 @@ final class HeraldRuleEditor switch ($xaction->getTransactionType()) { case HeraldRuleTransaction::TYPE_DISABLE: return (int)$xaction->getNewValue(); + case HeraldRuleTransaction::TYPE_EDIT: + case HeraldRuleTransaction::TYPE_NAME: + return $xaction->getNewValue(); } - } protected function applyCustomInternalTransaction( @@ -49,6 +58,17 @@ final class HeraldRuleEditor switch ($xaction->getTransactionType()) { case HeraldRuleTransaction::TYPE_DISABLE: return $object->setIsDisabled($xaction->getNewValue()); + case HeraldRuleTransaction::TYPE_NAME: + return $object->setName($xaction->getNewValue()); + case HeraldRuleTransaction::TYPE_EDIT: + $new_state = id(new HeraldRuleSerializer()) + ->deserializeRuleComponents($xaction->getNewValue()); + $object->setMustMatchAll((int)$new_state['match_all']); + $object->attachConditions($new_state['conditions']); + $object->attachActions($new_state['actions']); + $object->setRepetitionPolicy( + HeraldRepetitionPolicyConfig::toInt($new_state['repetition_policy'])); + return $object; } } @@ -56,6 +76,12 @@ final class HeraldRuleEditor protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case HeraldRuleTransaction::TYPE_EDIT: + $object->saveConditions($object->getConditions()); + $object->saveActions($object->getActions()); + break; + } return; } diff --git a/src/applications/herald/editor/HeraldRuleSerializer.php b/src/applications/herald/editor/HeraldRuleSerializer.php new file mode 100644 index 0000000000..d1b2527aaa --- /dev/null +++ b/src/applications/herald/editor/HeraldRuleSerializer.php @@ -0,0 +1,73 @@ +serializeRuleComponents( + (bool)$rule->getMustMatchAll(), + $rule->getConditions(), + $rule->getActions(), + HeraldRepetitionPolicyConfig::toString($rule->getRepetitionPolicy())); + } + + public function serializeRuleComponents( + $match_all, + array $conditions, + array $actions, + $repetition_policy) { + + assert_instances_of($conditions, 'HeraldCondition'); + assert_instances_of($actions, 'HeraldActionRecord'); + + $conditions_array = array(); + foreach ($conditions as $condition) { + $conditions_array[] = array( + 'field' => $condition->getFieldName(), + 'condition' => $condition->getFieldCondition(), + 'value' => $condition->getValue(), + ); + } + + $actions_array = array(); + foreach ($actions as $action) { + $actions_array[] = array( + 'action' => $action->getAction(), + 'target' => $action->getTarget(), + ); + } + + return array( + 'match_all' => $match_all, + 'conditions' => $conditions_array, + 'actions' => $actions_array, + 'repetition_policy' => $repetition_policy, + ); + } + + public function deserializeRuleComponents(array $serialized) { + $deser_conditions = array(); + foreach ($serialized['conditions'] as $condition) { + $deser_conditions[] = id(new HeraldCondition()) + ->setFieldName($condition['field']) + ->setFieldCondition($condition['condition']) + ->setValue($condition['value']); + } + + $deser_actions = array(); + foreach ($serialized['actions'] as $action) { + $deser_actions[] = id(new HeraldActionRecord()) + ->setAction($action['action']) + ->setTarget($action['target']); + } + + return array( + 'match_all' => $serialized['match_all'], + 'conditions' => $deser_conditions, + 'actions' => $deser_actions, + 'repetition_policy' => $serialized['repetition_policy'], + ); + } + +} diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 00c063131a..df81117b80 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -274,7 +274,7 @@ final class HeraldEngine extends Phobject { } else { foreach ($conditions as $condition) { try { - $object->getHeraldField($condition->getFieldName()); + $this->getConditionObjectValue($condition, $object); } catch (Exception $ex) { $reason = pht( 'Field "%s" does not exist!', @@ -366,14 +366,11 @@ final class HeraldEngine extends Phobject { } public function getObjectFieldValue($field) { - if (isset($this->fieldCache[$field])) { - return $this->fieldCache[$field]; + if (!array_key_exists($field, $this->fieldCache)) { + $this->fieldCache[$field] = $this->object->getHeraldField($field); } - $result = $this->object->getHeraldField($field); - - $this->fieldCache[$field] = $result; - return $result; + return $this->fieldCache[$field]; } protected function getRuleEffects( diff --git a/src/applications/herald/field/HeraldContentSourceField.php b/src/applications/herald/field/HeraldContentSourceField.php index 1473f8f8d1..525f5133bf 100644 --- a/src/applications/herald/field/HeraldContentSourceField.php +++ b/src/applications/herald/field/HeraldContentSourceField.php @@ -24,12 +24,13 @@ final class HeraldContentSourceField extends HeraldField { } public function getHeraldFieldValueType($condition) { - $map = PhabricatorContentSource::getSourceNameMap(); + $map = PhabricatorContentSource::getAllContentSources(); + $map = mpull($map, 'getSourceName'); asort($map); return id(new HeraldSelectFieldValue()) ->setKey(self::FIELDCONST) - ->setDefault(PhabricatorContentSource::SOURCE_WEB) + ->setDefault(PhabricatorWebContentSource::SOURCECONST) ->setOptions($map); } diff --git a/src/applications/herald/storage/HeraldRule.php b/src/applications/herald/storage/HeraldRule.php index 8d585263c0..9902e952ef 100644 --- a/src/applications/herald/storage/HeraldRule.php +++ b/src/applications/herald/storage/HeraldRule.php @@ -328,10 +328,6 @@ final class HeraldRule extends HeraldDAO return $this->isPersonalRule() && $phid == $this->getAuthorPHID(); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/herald/storage/HeraldRuleTransaction.php b/src/applications/herald/storage/HeraldRuleTransaction.php index 8d4201faf6..b1bd563749 100644 --- a/src/applications/herald/storage/HeraldRuleTransaction.php +++ b/src/applications/herald/storage/HeraldRuleTransaction.php @@ -4,6 +4,7 @@ final class HeraldRuleTransaction extends PhabricatorApplicationTransaction { const TYPE_EDIT = 'herald:edit'; + const TYPE_NAME = 'herald:name'; const TYPE_DISABLE = 'herald:disable'; public function getApplicationName() { @@ -45,6 +46,8 @@ final class HeraldRuleTransaction } else { return pht('Enabled'); } + case self::TYPE_NAME: + return pht('Renamed'); } return parent::getActionName(); @@ -84,9 +87,49 @@ final class HeraldRuleTransaction '%s enabled this rule.', $this->renderHandleLink($author_phid)); } + case self::TYPE_NAME: + if ($old == null) { + return pht( + '%s created this rule.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this rule from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + case self::TYPE_EDIT: + return pht( + '%s edited this rule.', + $this->renderHandleLink($author_phid)); } return parent::getTitle(); } + public function hasChangeDetails() { + switch ($this->getTransactionType()) { + case self::TYPE_EDIT: + return true; + } + return parent::hasChangeDetails(); + } + + public function renderChangeDetails(PhabricatorUser $viewer) { + $json = new PhutilJSON(); + switch ($this->getTransactionType()) { + case self::TYPE_EDIT: + return $this->renderTextCorpusChangeDetails( + $viewer, + $json->encodeFormatted($this->getOldValue()), + $json->encodeFormatted($this->getNewValue())); + } + + return $this->renderTextCorpusChangeDetails( + $viewer, + $this->getOldValue(), + $this->getNewValue()); + } + } diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php index 15b09f14c7..01533620fd 100644 --- a/src/applications/home/controller/PhabricatorHomeMainController.php +++ b/src/applications/home/controller/PhabricatorHomeMainController.php @@ -55,12 +55,9 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController { $content = $nav; } - return $this->buildApplicationPage( - $content, - array( - 'title' => 'Blender Foundation: Welcome', - 'device' => true, - )); + return $this->newPage() + ->setTitle('Blender Foundation: Welcome') + ->appendChild($content); } private function buildMainResponse(array $projects) { diff --git a/src/applications/home/controller/PhabricatorHomeQuickCreateController.php b/src/applications/home/controller/PhabricatorHomeQuickCreateController.php index 40f9afeb8c..6429443ed2 100644 --- a/src/applications/home/controller/PhabricatorHomeQuickCreateController.php +++ b/src/applications/home/controller/PhabricatorHomeQuickCreateController.php @@ -19,17 +19,29 @@ final class PhabricatorHomeQuickCreateController ->setHref($item->getHref())); } + $title = pht('Quick Create'); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Quick Create')); + $crumbs->setBorder(true); + + $box = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); - return $this->buildApplicationPage( - array( - $crumbs, - $list, - ), - array( - 'title' => pht('Quick Create'), - )); } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php index 84a879a7a2..3804885edb 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php @@ -220,27 +220,30 @@ final class LegalpadDocumentEditController extends LegalpadController { $submit->addCancelButton($this->getApplicationURI()); $title = pht('Create Document'); $short = pht('Create'); + $header_icon = 'fa-plus-square'; } else { $submit->setValue(pht('Save Document')); $submit->addCancelButton( $this->getApplicationURI('view/'.$document->getID())); - $title = pht('Edit Document'); + $title = pht('Edit Document: %s', $document->getTitle()); $short = pht('Edit'); + $header_icon = 'fa-pencil'; $crumbs->addTextCrumb( $document->getMonogram(), $this->getApplicationURI('view/'.$document->getID())); } - $form - ->appendChild($submit); + $form->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Document')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs->addTextCrumb($short); + $crumbs->setBorder(true); $preview = id(new PHUIRemarkupPreviewPanel()) ->setHeader($document->getTitle()) @@ -248,15 +251,22 @@ final class LegalpadDocumentEditController extends LegalpadController { ->setControlID('document-text') ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $form_box, $preview, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentManageController.php b/src/applications/legalpad/controller/LegalpadDocumentManageController.php index baeefeaf4b..134dada128 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentManageController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentManageController.php @@ -43,10 +43,12 @@ final class LegalpadDocumentManageController extends LegalpadController { $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($document); + ->setPolicyObject($document) + ->setHeaderIcon('fa-gavel'); - $actions = $this->buildActionView($document); - $properties = $this->buildPropertyView($document, $engine, $actions); + $curtain = $this->buildCurtainView($document); + $properties = $this->buildPropertyView($document, $engine); + $document_view = $this->buildDocumentView($document, $engine); $comment_form_id = celerity_generate_unique_node_id(); @@ -57,48 +59,58 @@ final class LegalpadDocumentManageController extends LegalpadController { $document->getMonogram(), '/'.$document->getMonogram()); $crumbs->addTextCrumb(pht('Manage')); + $crumbs->setBorder(true); - $object_box = id(new PHUIObjectBoxView()) + + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties) - ->addPropertyList($this->buildDocument($engine, $document_body)); - - $content = array( - $crumbs, - $object_box, - $timeline, - $add_comment, - ); - - return $this->buildApplicationPage( - $content, - array( - 'title' => $title, - 'pageObjects' => array($document->getPHID()), + ->setCurtain($curtain) + ->setMainColumn(array( + $properties, + $document_view, + $timeline, + $add_comment, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($document->getPHID())) + ->appendChild($view); } - private function buildDocument( - PhabricatorMarkupEngine - $engine, LegalpadDocumentBody $body) { + private function buildDocumentView( + LegalpadDocument $document, + PhabricatorMarkupEngine $engine) { - $view = new PHUIPropertyListView(); - $view->addClass('legalpad'); - $view->addSectionHeader( - pht('Document'), 'fa-file-text-o'); - $view->addTextContent( - $engine->getOutput($body, LegalpadDocumentBody::MARKUP_FIELD_TEXT)); - - return $view; - - } - - private function buildActionView(LegalpadDocument $document) { $viewer = $this->getViewer(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($document); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + $document_body = $document->getDocumentBody(); + $document_text = $engine->getOutput( + $document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); + + $preamble_box = null; + if (strlen($document->getPreamble())) { + $preamble_text = new PHUIRemarkupView($viewer, $document->getPreamble()); + $view->addTextContent($preamble_text); + $view->addSectionHeader(''); + $view->addTextContent($document_text); + } else { + $view->addTextContent($document_text); + } + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DOCUMENT')) + ->addPropertyList($view) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + } + + private function buildCurtainView(LegalpadDocument $document) { + $viewer = $this->getViewer(); + + $curtain = $this->newCurtainView($document); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -107,13 +119,13 @@ final class LegalpadDocumentManageController extends LegalpadController { $doc_id = $document->getID(); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil-square') ->setName(pht('View/Sign Document')) ->setHref('/'.$document->getMonogram())); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Document')) @@ -121,26 +133,23 @@ final class LegalpadDocumentManageController extends LegalpadController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-terminal') ->setName(pht('View Signatures')) ->setHref($this->getApplicationURI('/signatures/'.$doc_id.'/'))); - return $actions; + return $curtain; } private function buildPropertyView( LegalpadDocument $document, - PhabricatorMarkupEngine $engine, - PhabricatorActionListView $actions) { + PhabricatorMarkupEngine $engine) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($document) - ->setActionList($actions); + ->setUser($viewer); $properties->addProperty( pht('Signature Type'), @@ -166,9 +175,10 @@ final class LegalpadDocumentManageController extends LegalpadController { ->setAsInline(true)); } - $properties->invokeWillRenderEvent(); - - return $properties; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('PROPERTIES')) + ->addPropertyList($properties) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); } private function buildAddCommentView( diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php index 00549c1adc..70b750c38a 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php @@ -301,12 +301,15 @@ final class LegalpadDocumentSignController extends LegalpadController { case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: $box = id(new PHUIObjectBoxView()) + ->addClass('document-sign-box') ->setHeaderText(pht('Agree and Sign Document')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($signature_form); if ($error_view) { $box->setInfoView($error_view); } - $signature_box = phutil_tag_div('phui-document-view-pro-box', $box); + $signature_box = phutil_tag_div( + 'phui-document-view-pro-box plt', $box); break; } @@ -317,15 +320,13 @@ final class LegalpadDocumentSignController extends LegalpadController { $crumbs->setBorder(true); $crumbs->addTextCrumb($document->getMonogram()); - return $this->buildApplicationPage( - array( - $crumbs, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($document->getPHID())) + ->appendChild(array( $content, $signature_box, - ), - array( - 'title' => $title, - 'pageObjects' => array($document->getPHID()), )); } diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php index 51f158a978..fcecac991f 100644 --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -163,10 +163,6 @@ final class LegalpadDocument extends LegalpadDAO return ($this->creatorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/lipsum/generator/PhabricatorTestDataGenerator.php b/src/applications/lipsum/generator/PhabricatorTestDataGenerator.php index aacef542b1..b44895ae87 100644 --- a/src/applications/lipsum/generator/PhabricatorTestDataGenerator.php +++ b/src/applications/lipsum/generator/PhabricatorTestDataGenerator.php @@ -56,8 +56,7 @@ abstract class PhabricatorTestDataGenerator extends Phobject { protected function getLipsumContentSource() { return PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_LIPSUM, - array()); + PhabricatorLipsumContentSource::SOURCECONST); } /** diff --git a/src/applications/macro/controller/PhabricatorMacroAudioController.php b/src/applications/macro/controller/PhabricatorMacroAudioController.php index 92b98e5564..7e2b924637 100644 --- a/src/applications/macro/controller/PhabricatorMacroAudioController.php +++ b/src/applications/macro/controller/PhabricatorMacroAudioController.php @@ -104,20 +104,19 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { 'Best for ambient sounds.')); $form->appendChild($options); - - $form - ->appendChild( + $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save Audio Behavior')) ->addCancelButton($view_uri)); $crumbs = $this->buildApplicationCrumbs(); - $title = pht('Edit Audio Behavior'); + $title = pht('Edit Audio: %s', $macro->getName()); $crumb = pht('Edit Audio'); $crumbs->addTextCrumb(pht('Macro "%s"', $macro->getName()), $view_uri); $crumbs->addTextCrumb($crumb, $request->getRequestURI()); + $crumbs->setBorder(true); $upload_form = id(new AphrontFormView()) ->setEncType('multipart/form-data') @@ -132,22 +131,30 @@ final class PhabricatorMacroAudioController extends PhabricatorMacroController { $upload = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Upload New Audio')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($upload_form); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Behavior')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $form_box, $upload, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/macro/controller/PhabricatorMacroEditController.php b/src/applications/macro/controller/PhabricatorMacroEditController.php index 9e988ef637..4e6fa314b0 100644 --- a/src/applications/macro/controller/PhabricatorMacroEditController.php +++ b/src/applications/macro/controller/PhabricatorMacroEditController.php @@ -233,16 +233,19 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { $crumbs = $this->buildApplicationCrumbs(); if ($macro->getID()) { - $title = pht('Edit Image Macro'); + $title = pht('Edit Macro: %s', $macro->getName()); $crumb = pht('Edit Macro'); + $header_icon = 'fa-pencil'; - $crumbs->addTextCrumb(pht('Macro "%s"', $macro->getName()), $view_uri); + $crumbs->addTextCrumb(pht('Macro: %s', $macro->getName()), $view_uri); } else { $title = pht('Create Image Macro'); $crumb = pht('Create Macro'); + $header_icon = 'fa-plus-square'; } $crumbs->addTextCrumb($crumb, $request->getRequestURI()); + $crumbs->setBorder(true); $upload = null; if ($macro->getID()) { @@ -267,23 +270,32 @@ final class PhabricatorMacroEditController extends PhabricatorMacroController { $upload = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Upload New File')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($upload_form); } $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Macro')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $form_box, $upload, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 4e43c796cf..8c12e5528a 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -23,9 +23,8 @@ final class PhabricatorMacroViewController $title_short = pht('Macro "%s"', $macro->getName()); $title_long = pht('Image Macro "%s"', $macro->getName()); - $actions = $this->buildActionView($macro); + $curtain = $this->buildCurtain($macro); $subheader = $this->buildSubheaderView($macro); - $properties = $this->buildPropertyView($macro); $file = $this->buildFileView($macro); $details = $this->buildPropertySectionView($macro); @@ -40,7 +39,8 @@ final class PhabricatorMacroViewController $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($macro) - ->setHeader($title_long); + ->setHeader($macro->getName()) + ->setHeaderIcon('fa-file-image-o'); if (!$macro->getIsDisabled()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); @@ -67,35 +67,29 @@ final class PhabricatorMacroViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) + ->setCurtain($curtain) ->setMainColumn(array( $timeline, $add_comment_form, )) ->addPropertySection(pht('MACRO'), $file) - ->addPropertySection(pht('DETAILS'), $details) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('DETAILS'), $details); return $this->newPage() ->setTitle($title_short) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($macro->getPHID())) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } - private function buildActionView( + private function buildCurtain( PhabricatorFileImageMacro $macro) { $can_manage = $this->hasApplicationCapability( PhabricatorMacroManageCapability::CAPABILITY); - $request = $this->getRequest(); - $view = id(new PhabricatorActionListView()) - ->setUser($request->getUser()) - ->setObject($macro) - ->addAction( + $curtain = $this->newCurtainView($macro); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Macro')) ->setHref($this->getApplicationURI('/edit/'.$macro->getID().'/')) @@ -103,7 +97,7 @@ final class PhabricatorMacroViewController ->setWorkflow(!$can_manage) ->setIcon('fa-pencil')); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Audio')) ->setHref($this->getApplicationURI('/audio/'.$macro->getID().'/')) @@ -112,7 +106,7 @@ final class PhabricatorMacroViewController ->setIcon('fa-music')); if ($macro->getIsDisabled()) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Macro')) ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/')) @@ -120,7 +114,7 @@ final class PhabricatorMacroViewController ->setDisabled(!$can_manage) ->setIcon('fa-check')); } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Macro')) ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/')) @@ -129,7 +123,7 @@ final class PhabricatorMacroViewController ->setIcon('fa-ban')); } - return $view; + return $curtain; } private function buildSubheaderView( @@ -177,7 +171,11 @@ final class PhabricatorMacroViewController $viewer->renderHandle($audio_phid)); } - return $view; + if ($view->hasAnyProperties()) { + return $view; + } + + return null; } private function buildFileView( @@ -201,17 +199,4 @@ final class PhabricatorMacroViewController return null; } - private function buildPropertyView( - PhabricatorFileImageMacro $macro) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($this->getRequest()->getUser()) - ->setObject($macro); - - $view->invokeWillRenderEvent(); - - return $view; - } - } diff --git a/src/applications/macro/storage/PhabricatorFileImageMacro.php b/src/applications/macro/storage/PhabricatorFileImageMacro.php index 5cf23133a3..37f0d5c06b 100644 --- a/src/applications/macro/storage/PhabricatorFileImageMacro.php +++ b/src/applications/macro/storage/PhabricatorFileImageMacro.php @@ -111,10 +111,6 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenRecevierInterface )---------------------------------- */ diff --git a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php index 18f433fe52..cf1da51bca 100644 --- a/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php +++ b/src/applications/maniphest/__tests__/ManiphestTaskTestCase.php @@ -207,7 +207,7 @@ final class ManiphestTaskTestCase extends PhabricatorTestCase { ManiphestTask $task, array $xactions) { - $content_source = PhabricatorContentSource::newConsoleSource(); + $content_source = $this->newContentSource(); $editor = id(new ManiphestTransactionEditor()) ->setActor($viewer) diff --git a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php index e72ffa4d46..d2a8de7d81 100644 --- a/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php @@ -192,9 +192,7 @@ abstract class ManiphestConduitAPIMethod extends ConduitAPIMethod { return; } - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_CONDUIT, - array()); + $content_source = $request->newContentSource(); $editor = id(new ManiphestTransactionEditor()) ->setActor($request->getUser()) diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php index d245817f20..90fcda28ec 100644 --- a/src/applications/maniphest/controller/ManiphestBatchEditController.php +++ b/src/applications/maniphest/controller/ManiphestBatchEditController.php @@ -194,24 +194,33 @@ final class ManiphestBatchEditController extends ManiphestController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Batch Editor')) + ->setHeaderIcon('fa-pencil-square-o'); $task_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Selected Tasks')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($list); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Batch Editor')) + ->setHeaderText(pht('Actions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $task_box, $form_box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index c7f0cf2186..f16281691b 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -45,17 +45,17 @@ final class ManiphestReportController extends ManiphestController { return new Aphront404Response(); } - $nav->appendChild($core); - $nav->setCrumbs( - $this->buildApplicationCrumbs() - ->addTextCrumb(pht('Reports'))); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb(pht('Reports')); + + $nav->appendChild($core); + $title = pht('Maniphest Reports'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($nav); - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Maniphest Reports'), - 'device' => false, - )); } public function renderBurn() { diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index cbb55413ad..285f919ce8 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -26,6 +26,10 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setViewer($viewer) ->readFieldsFromStorage($task); + $edit_engine = id(new ManiphestEditEngine()) + ->setViewer($viewer) + ->setTargetObject($task); + $e_commit = ManiphestTaskHasCommitEdgeType::EDGECONST; $e_dep_on = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; $e_dep_by = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; @@ -55,15 +59,9 @@ final class ManiphestTaskDetailController extends ManiphestController { $phids = array_keys($phids); $handles = $viewer->loadHandles($phids); - $engine = id(new PhabricatorMarkupEngine()) - ->setViewer($viewer) - ->setContextObject($task) - ->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION); - $timeline = $this->buildTransactionTimeline( $task, - new ManiphestTransactionQuery(), - $engine); + new ManiphestTransactionQuery()); $monogram = $task->getMonogram(); $crumbs = $this->buildApplicationCrumbs() @@ -72,14 +70,12 @@ final class ManiphestTaskDetailController extends ManiphestController { $header = $this->buildHeaderView($task); $details = $this->buildPropertyView($task, $field_list, $edges, $handles); - $description = $this->buildDescriptionView($task, $engine); - $actions = $this->buildActionView($task); - $properties = $this->buildPropertyListView($task, $handles); + $description = $this->buildDescriptionView($task); + $curtain = $this->buildCurtain($task, $edit_engine); $title = pht('%s %s', $monogram, $task->getTitle()); - $comment_view = id(new ManiphestEditEngine()) - ->setViewer($viewer) + $comment_view = $edit_engine ->buildEditEngineCommentView($task); $timeline->setQuoteRef($monogram); @@ -87,14 +83,13 @@ final class ManiphestTaskDetailController extends ManiphestController { $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $timeline, $comment_view, )) - ->addPropertySection(pht('DETAILS'), $details) ->addPropertySection(pht('DESCRIPTION'), $description) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('DETAILS'), $details); return $this->newPage() ->setTitle($title) @@ -148,8 +143,10 @@ final class ManiphestTaskDetailController extends ManiphestController { } - private function buildActionView(ManiphestTask $task) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtain( + ManiphestTask $task, + PhabricatorEditEngine $edit_engine) { + $viewer = $this->getViewer(); $id = $task->getID(); $phid = $task->getPHID(); @@ -159,11 +156,9 @@ final class ManiphestTaskDetailController extends ManiphestController { $task, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($task); + $curtain = $this->newCurtainView($task); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Task')) ->setIcon('fa-pencil') @@ -171,7 +166,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Merge Duplicates In')) ->setHref("/search/attach/{$phid}/TASK/merge/") @@ -180,11 +175,12 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - $edit_config = id(new ManiphestEditEngine()) - ->setViewer($viewer) - ->loadDefaultEditConfiguration(); - + $edit_config = $edit_engine->loadDefaultEditConfiguration(); $can_create = (bool)$edit_config; + + $can_reassign = $edit_engine->hasEditAccessToTransaction( + ManiphestTransaction::TYPE_OWNER); + if ($can_create) { $form_key = $edit_config->getIdentifier(); $edit_uri = id(new PhutilURI("/task/edit/form/{$form_key}/")) @@ -199,7 +195,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $edit_uri = $this->getApplicationURI($edit_uri); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Create Subtask')) ->setHref($edit_uri) @@ -207,7 +203,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_create) ->setWorkflow(!$can_create)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Blocking Tasks')) ->setHref("/search/attach/{$phid}/TASK/blocks/") @@ -216,7 +212,44 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $view; + + $owner_phid = $task->getOwnerPHID(); + $author_phid = $task->getAuthorPHID(); + $handles = $viewer->loadHandles(array($owner_phid, $author_phid)); + + if ($owner_phid) { + $image_uri = $handles[$owner_phid]->getImageURI(); + $image_href = $handles[$owner_phid]->getURI(); + $owner = $viewer->renderHandle($owner_phid)->render(); + $content = phutil_tag('strong', array(), $owner); + $assigned_to = id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } else { + $assigned_to = phutil_tag('em', array(), pht('None')); + } + + $curtain->newPanel() + ->setHeaderText(pht('Assigned To')) + ->appendChild($assigned_to); + + $author_uri = $handles[$author_phid]->getImageURI(); + $author_href = $handles[$author_phid]->getURI(); + $author = $viewer->renderHandle($author_phid)->render(); + $content = phutil_tag('strong', array(), $author); + $date = phabricator_date($task->getDateCreated(), $viewer); + $content = pht('%s, %s', $content, $date); + $authored_by = id(new PHUIHeadThingView()) + ->setImage($author_uri) + ->setImageHref($author_href) + ->setContent($content); + + $curtain->newPanel() + ->setHeaderText(pht('Authored By')) + ->appendChild($authored_by); + + return $curtain; } private function buildPropertyView( @@ -307,43 +340,13 @@ final class ManiphestTaskDetailController extends ManiphestController { return null; } - private function buildPropertyListView(ManiphestTask $task, $handles) { - $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($task); - - $view->invokeWillRenderEvent(); - - $owner_phid = $task->getOwnerPHID(); - if ($owner_phid) { - $assigned_to = $handles - ->renderHandle($owner_phid) - ->setShowHovercard(true); - } else { - $assigned_to = phutil_tag('em', array(), pht('None')); - } - - $view->addProperty(pht('Assigned To'), $assigned_to); - - $author_phid = $task->getAuthorPHID(); - $author = $handles - ->renderHandle($author_phid) - ->setShowHovercard(true); - - $date = phabricator_datetime($task->getDateCreated(), $viewer); - - $view->addProperty(pht('Author'), $author); - - return $view; - } - - private function buildDescriptionView( - ManiphestTask $task, - PhabricatorMarkupEngine $engine) { + private function buildDescriptionView(ManiphestTask $task) { + $viewer = $this->getViewer(); $section = null; - if (strlen($task->getDescription())) { + + $description = $task->getDescription(); + if (strlen($description)) { $section = new PHUIPropertyListView(); $section->addTextContent( phutil_tag( @@ -351,7 +354,8 @@ final class ManiphestTaskDetailController extends ManiphestController { array( 'class' => 'phabricator-remarkup', ), - $engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION))); + id(new PHUIRemarkupView($viewer, $description)) + ->setContextObject($task))); } return $section; diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index fe9b96f25b..273c0c5472 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -34,7 +34,7 @@ final class ManiphestEditEngine } protected function getObjectEditTitleText($object) { - return pht('Edit %s %s', $object->getMonogram(), $object->getTitle()); + return pht('Edit Task: %s', $object->getTitle()); } protected function getObjectEditShortText($object) { @@ -45,6 +45,10 @@ final class ManiphestEditEngine return pht('Create Task'); } + protected function getObjectName() { + return pht('Task'); + } + protected function getEditorURI() { return $this->getApplication()->getApplicationURI('task/edit/'); } diff --git a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php index e4147ad223..5b52aaa480 100644 --- a/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php +++ b/src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php @@ -15,9 +15,7 @@ final class PhabricatorManiphestTaskTestDataGenerator ->setSubPriority($this->generateTaskSubPriority()) ->setTitle($this->generateTitle()); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_UNKNOWN, - array()); + $content_source = $this->getLipsumContentSource(); $template = new ManiphestTransaction(); // Accumulate Transactions diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 8d9a920bcf..cfacb3f4dc 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -19,6 +19,7 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $dateModifiedBefore; private $subpriorityMin; private $subpriorityMax; + private $bridgedObjectPHIDs; private $fullTextSearch = ''; @@ -208,6 +209,11 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { return $this; } + public function withBridgedObjectPHIDs(array $phids) { + $this->bridgedObjectPHIDs = $phids; + return $this; + } + public function newResultObject() { return new ManiphestTask(); } @@ -449,6 +455,13 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { $this->subpriorityMax); } + if ($this->bridgedObjectPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'task.bridgedObjectPHID IN (%Ls)', + $this->bridgedObjectPHIDs); + } + return $where; } diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index 0c1a1bc787..9ac7190782 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -15,7 +15,8 @@ final class ManiphestTask extends ManiphestDAO PhabricatorProjectInterface, PhabricatorSpacesInterface, PhabricatorConduitResultInterface, - PhabricatorFulltextInterface { + PhabricatorFulltextInterface, + DoorkeeperBridgedObjectInterface { const MARKUP_FIELD_DESCRIPTION = 'markup:desc'; @@ -36,6 +37,7 @@ final class ManiphestTask extends ManiphestDAO protected $ownerOrdering; protected $spacePHID; + protected $bridgedObjectPHID; protected $properties = array(); protected $points; @@ -43,6 +45,7 @@ final class ManiphestTask extends ManiphestDAO private $groupByProjectPHID = self::ATTACHABLE; private $customFields = self::ATTACHABLE; private $edgeProjectPHIDs = self::ATTACHABLE; + private $bridgedObject = self::ATTACHABLE; public static function initializeNewTask(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -82,6 +85,7 @@ final class ManiphestTask extends ManiphestDAO 'originalEmailSource' => 'text255?', 'subpriority' => 'double', 'points' => 'double?', + 'bridgedObjectPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -116,6 +120,10 @@ final class ManiphestTask extends ManiphestDAO 'key_title' => array( 'columns' => array('title(64)'), ), + 'key_bridgedobject' => array( + 'columns' => array('bridgedObjectPHID'), + 'unique' => true, + ), ), ) + parent::getConfiguration(); } @@ -269,10 +277,6 @@ final class ManiphestTask extends ManiphestDAO return ($phid == $this->getOwnerPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( Markup Interface )--------------------------------------------------- */ @@ -508,4 +512,18 @@ final class ManiphestTask extends ManiphestDAO return new ManiphestTaskFulltextEngine(); } + +/* -( DoorkeeperBridgedObjectInterface )----------------------------------- */ + + + public function getBridgedObject() { + return $this->assertAttached($this->bridgedObject); + } + + public function attachBridgedObject( + DoorkeeperExternalObject $object = null) { + $this->bridgedObject = $object; + return $this; + } + } diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index 55d14083e6..caf57a3f71 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -301,7 +301,7 @@ final class ManiphestTransaction if ($this->getAuthorPHID() == $new) { return pht('Claimed'); } else if (!$new) { - return pht('Up For Grabs'); + return pht('Unassigned'); } else if (!$old) { return pht('Assigned'); } else { @@ -547,8 +547,9 @@ final class ManiphestTransaction $this->renderHandleLink($author_phid)); } else if (!$new) { return pht( - '%s placed this task up for grabs.', - $this->renderHandleLink($author_phid)); + '%s removed %s as the assignee of this task.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old)); } else if (!$old) { return pht( '%s assigned this task to %s.', diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index 19aad0e6a5..f86f9eb4d1 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -24,11 +24,13 @@ final class PhabricatorApplicationDetailViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($selected->getName()); + $crumbs->setBorder(true); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($selected); + ->setPolicyObject($selected) + ->setHeaderIcon($selected->getIcon()); if ($selected->isInstalled()) { $header->setStatus('fa-check', 'bluegrey', pht('Installed')); @@ -36,12 +38,9 @@ final class PhabricatorApplicationDetailViewController $header->setStatus('fa-ban', 'dark', pht('Uninstalled')); } - $actions = $this->buildActionView($viewer, $selected); - $properties = $this->buildPropertyView($selected, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $curtain = $this->buildCurtain($selected); + $details = $this->buildPropertySectionView($selected); + $policies = $this->buildPolicyView($selected); $configs = PhabricatorApplicationConfigurationPanel::loadAllPanelsForApplication( @@ -51,29 +50,35 @@ final class PhabricatorApplicationDetailViewController foreach ($configs as $config) { $config->setViewer($viewer); $config->setApplication($selected); + $panel = $config->buildConfigurationPagePanel(); + $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + $panels[] = $panel; - $panels[] = $config->buildConfigurationPagePanel(); } - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $panels, - ), - array( - 'title' => $title, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $policies, + $panels, + )) + ->addPropertySection(pht('DETAILS'), $details); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildPropertyView( - PhabricatorApplication $application, - PhabricatorActionListView $actions) { - - $viewer = $this->getRequest()->getUser(); + private function buildPropertySectionView( + PhabricatorApplication $application) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()); - $properties->setActionList($actions); $properties->addProperty( pht('Description'), @@ -111,37 +116,52 @@ final class PhabricatorApplicationDetailViewController $properties->addTextContent($overview); } + return $properties; + } + + private function buildPolicyView( + PhabricatorApplication $application) { + + $viewer = $this->getViewer(); + $properties = id(new PHUIPropertyListView()); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('POLICIES')) + ->setHeaderIcon('fa-lock'); + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $application); - $properties->addSectionHeader( - pht('Policies'), 'fa-lock'); - foreach ($application->getCapabilities() as $capability) { $properties->addProperty( $application->getCapabilityLabel($capability), idx($descriptions, $capability)); } - return $properties; + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + } - private function buildActionView( - PhabricatorUser $user, - PhabricatorApplication $selected) { - - $view = id(new PhabricatorActionListView()) - ->setUser($user); + private function buildCurtain(PhabricatorApplication $application) { + $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( - $user, - $selected, + $viewer, + $application, PhabricatorPolicyCapability::CAN_EDIT); - $edit_uri = $this->getApplicationURI('edit/'.get_class($selected).'/'); + $key = get_class($application); + $edit_uri = $this->getApplicationURI("edit/{$key}/"); + $install_uri = $this->getApplicationURI("{$key}/install/"); + $uninstall_uri = $this->getApplicationURI("{$key}/uninstall/"); - $view->addAction( + $curtain = $this->newCurtainView($application); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Policies')) ->setIcon('fa-pencil') @@ -149,45 +169,42 @@ final class PhabricatorApplicationDetailViewController ->setWorkflow(!$can_edit) ->setHref($edit_uri)); - if ($selected->canUninstall()) { - if ($selected->isInstalled()) { - $view->addAction( + if ($application->canUninstall()) { + if ($application->isInstalled()) { + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Uninstall')) ->setIcon('fa-times') ->setDisabled(!$can_edit) ->setWorkflow(true) - ->setHref( - $this->getApplicationURI(get_class($selected).'/uninstall/'))); + ->setHref($uninstall_uri)); } else { $action = id(new PhabricatorActionView()) ->setName(pht('Install')) ->setIcon('fa-plus') ->setDisabled(!$can_edit) ->setWorkflow(true) - ->setHref( - $this->getApplicationURI(get_class($selected).'/install/')); + ->setHref($install_uri); $prototypes_enabled = PhabricatorEnv::getEnvConfig( 'phabricator.show-prototypes'); - if ($selected->isPrototype() && !$prototypes_enabled) { + if ($application->isPrototype() && !$prototypes_enabled) { $action->setDisabled(true); } - $view->addAction($action); + $curtain->addAction($action); } } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Uninstall')) ->setIcon('fa-times') ->setWorkflow(true) ->setDisabled(true) - ->setHref( - $this->getApplicationURI(get_class($selected).'/uninstall/'))); + ->setHref($uninstall_uri)); } - return $view; + return $curtain; } } diff --git a/src/applications/meta/controller/PhabricatorApplicationEditController.php b/src/applications/meta/controller/PhabricatorApplicationEditController.php index d9d7b3c15c..ed51405db9 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEditController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEditController.php @@ -171,22 +171,27 @@ final class PhabricatorApplicationEditController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($application->getName(), $view_uri); $crumbs->addTextCrumb(pht('Edit Policies')); + $crumbs->setBorder(true); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Edit Policies: %s', $application->getName())); + ->setHeader(pht('Edit Policies: %s', $application->getName())) + ->setHeaderIcon('fa-pencil'); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Policies')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $object_box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php b/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php index 08f8e450b5..2979e92229 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php @@ -137,14 +137,10 @@ final class PhabricatorApplicationEmailCommandsController ->appendChild($info_view) ->appendChild($content_box); - return $this->buildApplicationPage( - array( - $crumbs, - $document, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($document); } diff --git a/src/applications/meta/controller/PhabricatorApplicationPanelController.php b/src/applications/meta/controller/PhabricatorApplicationPanelController.php index 48355d131f..4fab71c501 100644 --- a/src/applications/meta/controller/PhabricatorApplicationPanelController.php +++ b/src/applications/meta/controller/PhabricatorApplicationPanelController.php @@ -59,9 +59,14 @@ final class PhabricatorApplicationPanelController public function buildPanelPage( PhabricatorApplicationConfigurationPanel $panel, - $content, - array $options) { - return $this->buildApplicationPage($content, $options); + $title, + $crumbs, + $content) { + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($content); } } diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php index 59c7b08933..5b03cd86ac 100644 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php @@ -23,12 +23,13 @@ final class PhabricatorMailImplementationAmazonSESAdapter public function executeSend($body) { $key = PhabricatorEnv::getEnvConfig('amazon-ses.access-key'); $secret = PhabricatorEnv::getEnvConfig('amazon-ses.secret-key'); + $endpoint = PhabricatorEnv::getEnvConfig('amazon-ses.endpoint'); $root = phutil_get_library_root('phabricator'); $root = dirname($root); require_once $root.'/externals/amazon-ses/ses.php'; - $service = new SimpleEmailService($key, $secret); + $service = new SimpleEmailService($key, $secret, $endpoint); $service->enableUseExceptions(true); return $service->sendRawEmail($body); } diff --git a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php index 1899ac1be1..a2d32e3360 100644 --- a/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php +++ b/src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php @@ -37,6 +37,7 @@ final class PhabricatorMetaMTAApplicationEmailPanel $box = id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); return $box; @@ -81,10 +82,12 @@ final class PhabricatorMetaMTAApplicationEmailPanel $crumbs = $controller->buildPanelCrumbs($this); $crumbs->addTextCrumb(pht('Edit Application Emails')); + $crumbs->setBorder(true); $header = id(new PHUIHeaderView()) ->setHeader(pht('Edit Application Emails: %s', $application->getName())) - ->setSubheader($application->getAppEmailBlurb()); + ->setSubheader($application->getAppEmailBlurb()) + ->setHeaderIcon('fa-pencil'); $icon = id(new PHUIIconView()) ->setIcon('fa-plus'); @@ -97,20 +100,20 @@ final class PhabricatorMetaMTAApplicationEmailPanel $header->addActionLink($button); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Emails')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $title = $application->getName(); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($object_box); return $controller->buildPanelPage( $this, - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + $title, + $crumbs, + $view); } private function returnNewAddressResponse( diff --git a/src/applications/metamta/contentsource/PhabricatorContentSource.php b/src/applications/metamta/contentsource/PhabricatorContentSource.php deleted file mode 100644 index e2320eb85f..0000000000 --- a/src/applications/metamta/contentsource/PhabricatorContentSource.php +++ /dev/null @@ -1,104 +0,0 @@ - - } - - public static function newForSource($source, array $params) { - $obj = new PhabricatorContentSource(); - $obj->source = $source; - $obj->params = $params; - - return $obj; - } - - public static function newFromSerialized($serialized) { - $dict = json_decode($serialized, true); - if (!is_array($dict)) { - $dict = array(); - } - - $obj = new PhabricatorContentSource(); - $obj->source = idx($dict, 'source', self::SOURCE_UNKNOWN); - $obj->params = idx($dict, 'params', array()); - - return $obj; - } - - public static function newConsoleSource() { - return self::newForSource( - self::SOURCE_CONSOLE, - array()); - } - - public static function newFromRequest(AphrontRequest $request) { - return self::newForSource( - self::SOURCE_WEB, - array()); - } - - public static function newFromConduitRequest(ConduitAPIRequest $request) { - return self::newForSource( - self::SOURCE_CONDUIT, - array()); - } - - public static function getSourceNameMap() { - return array( - self::SOURCE_WEB => pht('Web'), - self::SOURCE_EMAIL => pht('Email'), - self::SOURCE_CONDUIT => pht('Conduit'), - self::SOURCE_MOBILE => pht('Mobile'), - self::SOURCE_TABLET => pht('Tablet'), - self::SOURCE_FAX => pht('Fax'), - self::SOURCE_CONSOLE => pht('Console'), - self::SOURCE_LEGACY => pht('Legacy'), - self::SOURCE_HERALD => pht('Herald'), - self::SOURCE_DAEMON => pht('Daemons'), - self::SOURCE_LIPSUM => pht('Lipsum'), - self::SOURCE_UNKNOWN => pht('Old World'), - self::SOURCE_PHORTUNE => pht('Phortune'), - self::SOURCE_BULK => pht('Bulk Edit'), - ); - } - - public function serialize() { - return json_encode(array( - 'source' => $this->getSource(), - 'params' => $this->getParams(), - )); - } - - public function getSource() { - return $this->source; - } - - public function getParams() { - return $this->params; - } - - public function getParam($key, $default = null) { - return idx($this->params, $key, $default); - } - -} diff --git a/src/applications/metamta/contentsource/PhabricatorEmailContentSource.php b/src/applications/metamta/contentsource/PhabricatorEmailContentSource.php new file mode 100644 index 0000000000..78a66b4150 --- /dev/null +++ b/src/applications/metamta/contentsource/PhabricatorEmailContentSource.php @@ -0,0 +1,16 @@ +setHeader($title) ->setUser($viewer) - ->setPolicyObject($mail); + ->setPolicyObject($mail) + ->setHeaderIcon('fa-envelope'); $status = $mail->getStatus(); $name = PhabricatorMailOutboundStatus::getStatusName($status); @@ -32,24 +33,26 @@ final class PhabricatorMetaMTAMailViewController $header->setStatus($icon, $color, $name); $crumbs = $this->buildApplicationCrumbs() - ->addTextCrumb(pht('Mail %d', $mail->getID())); + ->addTextCrumb(pht('Mail %d', $mail->getID())) + ->setBorder(true); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Mail')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($this->buildMessageProperties($mail), pht('Message')) ->addPropertyList($this->buildHeaderProperties($mail), pht('Headers')) ->addPropertyList($this->buildDeliveryProperties($mail), pht('Delivery')) ->addPropertyList($this->buildMetadataProperties($mail), pht('Metadata')); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - 'pageObjects' => array($mail->getPHID()), - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($object_box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($mail->getPHID())) + ->appendChild($view); } private function buildMessageProperties(PhabricatorMetaMTAMail $mail) { diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php index af5ca34712..467995a186 100644 --- a/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php +++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php @@ -28,14 +28,14 @@ final class PhabricatorMetaMTAMailgunReceiveController pht('Mail signature is not valid. Check your Mailgun API key.')); } - $user = $request->getUser(); - - $raw_headers = $request->getStr('headers'); - $raw_headers = explode("\n", rtrim($raw_headers)); + $raw_headers = $request->getStr('message-headers'); $raw_dict = array(); - foreach (array_filter($raw_headers) as $header) { - list($name, $value) = explode(':', $header, 2); - $raw_dict[$name] = ltrim($value); + if (strlen($raw_headers)) { + $raw_headers = phutil_json_decode($raw_headers); + foreach ($raw_headers as $raw_header) { + list($name, $value) = $raw_header; + $raw_dict[$name] = $value; + } } $headers = array( @@ -65,9 +65,25 @@ final class PhabricatorMetaMTAMailgunReceiveController } } $received->setAttachments($file_phids); - $received->save(); - $received->processReceivedMail(); + try { + $received->save(); + $received->processReceivedMail(); + } catch (Exception $ex) { + // We can get exceptions here in two cases. + + // First, saving the message may throw if we have already received a + // message with the same Message ID. In this case, we're declining to + // process a duplicate message, so failing silently is correct. + + // Second, processing the message may throw (for example, if it contains + // an invalid !command). This will generate an email as a side effect, + // so we don't need to explicitly handle the exception here. + + // In these cases, we want to return HTTP 200. If we do not, MailGun will + // re-transmit the message later. + phlog($ex); + } $response = new AphrontWebpageResponse(); $response->setContent(pht("Got it! Thanks, Mailgun!\n")); diff --git a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php index efae153a0f..e4da2a04d6 100644 --- a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php +++ b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php @@ -254,10 +254,19 @@ abstract class PhabricatorMailReplyHandler extends Phobject { $map = $to + $cc; foreach ($map as $phid => $user) { + // Preserve the original To/Cc information on the target. + if (isset($to[$phid])) { + $target_to = array($phid => $user); + $target_cc = array(); + } else { + $target_to = array(); + $target_cc = array($phid => $user); + } + $target = id(clone $template) ->setViewer($user) - ->setToMap(array($phid => $user)) - ->setCCMap(array()); + ->setToMap($target_to) + ->setCCMap($target_cc); if ($supports_private_replies) { $reply_to = $this->getPrivateReplyHandlerEmailAddress($user); diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php index fd6e6da639..18fa7dd2ba 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php @@ -371,4 +371,12 @@ EOBODY ->saveAndSend(); } + public function newContentSource() { + return PhabricatorContentSource::newForSource( + PhabricatorEmailContentSource::SOURCECONST, + array( + 'id' => $this->getID(), + )); + } + } diff --git a/src/applications/multimeter/controller/MultimeterSampleController.php b/src/applications/multimeter/controller/MultimeterSampleController.php index f9a36b37d1..0023038185 100644 --- a/src/applications/multimeter/controller/MultimeterSampleController.php +++ b/src/applications/multimeter/controller/MultimeterSampleController.php @@ -230,17 +230,15 @@ final class MultimeterSampleController extends MultimeterController { )); $box = id(new PHUIObjectBoxView()) - ->setHeaderText( - pht( - 'Samples (%s - %s)', - phabricator_datetime($ago, $viewer), - phabricator_datetime($now, $viewer))) + ->setHeaderText(pht('Samples')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Samples'), $this->getGroupURI(array(), true)); + $crumbs->setBorder(true); $crumb_map = array( 'host' => pht('By Host'), @@ -262,14 +260,23 @@ final class MultimeterSampleController extends MultimeterController { $this->getGroupURI($parts, true)); } - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Samples'), - )); + $header = id(new PHUIHeaderView()) + ->setHeader( + pht( + 'Samples (%s - %s)', + phabricator_datetime($ago, $viewer), + phabricator_datetime($now, $viewer))) + ->setHeaderIcon('fa-motorcycle'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle(pht('Samples')) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function renderGroupingLink(array $group, $key, $name = null) { diff --git a/src/applications/notification/controller/PhabricatorNotificationStatusController.php b/src/applications/notification/controller/PhabricatorNotificationStatusController.php index eb79a3a8c5..49d3fb9d9b 100644 --- a/src/applications/notification/controller/PhabricatorNotificationStatusController.php +++ b/src/applications/notification/controller/PhabricatorNotificationStatusController.php @@ -24,15 +24,12 @@ final class PhabricatorNotificationStatusController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Status')); - return $this->buildApplicationPage( - array( - $crumbs, - $status, - ), - array( - 'title' => pht('Notification Server Status'), - 'device' => false, - )); + $title = pht('Notification Server Status'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($status); } private function renderServerStatus(array $status) { diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index e3d5825d4e..dede1175ab 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -40,27 +40,21 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { '/nuance/' => array( '' => 'NuanceConsoleController', 'item/' => array( + $this->getQueryRoutePattern() => 'NuanceItemListController', 'view/(?P[1-9]\d*)/' => 'NuanceItemViewController', - 'edit/(?P[1-9]\d*)/' => 'NuanceItemEditController', - 'new/' => 'NuanceItemEditController', + 'manage/(?P[1-9]\d*)/' => 'NuanceItemManageController', + 'action/(?P[1-9]\d*)/(?P[^/]+)/' + => 'NuanceItemActionController', ), 'source/' => array( - '(?:query/(?P[^/]+)/)?' => 'NuanceSourceListController', + $this->getQueryRoutePattern() => 'NuanceSourceListController', + $this->getEditRoutePattern('edit/') => 'NuanceSourceEditController', 'view/(?P[1-9]\d*)/' => 'NuanceSourceViewController', - 'edit/(?P[1-9]\d*)/' => 'NuanceSourceEditController', - 'new/(?P[^/]+)/' => 'NuanceSourceEditController', - 'create/' => 'NuanceSourceCreateController', ), 'queue/' => array( - '(?:query/(?P[^/]+)/)?' => 'NuanceQueueListController', + $this->getQueryRoutePattern() => 'NuanceQueueListController', + $this->getEditRoutePattern('edit/') => 'NuanceQueueEditController', 'view/(?P[1-9]\d*)/' => 'NuanceQueueViewController', - 'edit/(?P[1-9]\d*)/' => 'NuanceQueueEditController', - 'new/' => 'NuanceQueueEditController', - ), - 'requestor/' => array( - 'view/(?P[1-9]\d*)/' => 'NuanceRequestorViewController', - 'edit/(?P[1-9]\d*)/' => 'NuanceRequestorEditController', - 'new/' => 'NuanceRequestorEditController', ), ), '/action/' => array( diff --git a/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php b/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php deleted file mode 100644 index b05c09fec4..0000000000 --- a/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php +++ /dev/null @@ -1,73 +0,0 @@ - 'required string', - 'sourcePHID' => 'required string', - 'ownerPHID' => 'optional string', - ); - } - - protected function defineReturnType() { - return 'nonempty dict'; - } - - protected function defineErrorTypes() { - return array( - 'ERR-NO-REQUESTOR-PHID' => pht('Items must have a requestor.'), - 'ERR-NO-SOURCE-PHID' => pht('Items must have a source.'), - ); - } - - protected function execute(ConduitAPIRequest $request) { - $source_phid = $request->getValue('sourcePHID'); - $owner_phid = $request->getValue('ownerPHID'); - $requestor_phid = $request->getValue('requestorPHID'); - - $user = $request->getUser(); - - $item = NuanceItem::initializeNewItem(); - $xactions = array(); - - if ($source_phid) { - $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_SOURCE) - ->setNewValue($source_phid); - } else { - throw new ConduitException('ERR-NO-SOURCE-PHID'); - } - - if ($owner_phid) { - $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_OWNER) - ->setNewValue($owner_phid); - } - - if ($requestor_phid) { - $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_REQUESTOR) - ->setNewValue($requestor_phid); - } else { - throw new ConduitException('ERR-NO-REQUESTOR-PHID'); - } - - $source = PhabricatorContentSource::newFromConduitRequest($request); - $editor = id(new NuanceItemEditor()) - ->setActor($user) - ->setContentSource($source) - ->applyTransactions($item, $xactions); - - return $item->toDictionary(); - } - -} diff --git a/src/applications/nuance/contentsource/NuanceContentSource.php b/src/applications/nuance/contentsource/NuanceContentSource.php new file mode 100644 index 0000000000..030db940a6 --- /dev/null +++ b/src/applications/nuance/contentsource/NuanceContentSource.php @@ -0,0 +1,16 @@ +setIcon('fa-filter') ->addAttribute(pht('Manage Nuance sources.'))); + $menu->addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Items')) + ->setHref($this->getApplicationURI('item/')) + ->setIcon('fa-clone') + ->addAttribute(pht('Manage Nuance items.'))); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Console')) ->setObjectList($menu); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Nuance Console')) + ->setHeaderIcon('fa-fax'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => pht('Nuance Console'), )); + + return $this->newPage() + ->setTitle(pht('Nuance Console')) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/nuance/controller/NuanceItemActionController.php b/src/applications/nuance/controller/NuanceItemActionController.php new file mode 100644 index 0000000000..c64ac5f6ac --- /dev/null +++ b/src/applications/nuance/controller/NuanceItemActionController.php @@ -0,0 +1,26 @@ +getViewer(); + $id = $request->getURIData('id'); + + $item = id(new NuanceItemQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$item) { + return new Aphront404Response(); + } + + $action = $request->getURIData('action'); + + $impl = $item->getImplementation(); + $impl->setViewer($viewer); + $impl->setController($this); + + return $impl->buildActionResponse($item, $action); + } + +} diff --git a/src/applications/nuance/controller/NuanceItemController.php b/src/applications/nuance/controller/NuanceItemController.php new file mode 100644 index 0000000000..fa5b01ddb9 --- /dev/null +++ b/src/applications/nuance/controller/NuanceItemController.php @@ -0,0 +1,11 @@ +newApplicationMenu() + ->setSearchEngine(new NuanceItemSearchEngine()); + } + +} diff --git a/src/applications/nuance/controller/NuanceItemEditController.php b/src/applications/nuance/controller/NuanceItemEditController.php deleted file mode 100644 index afb84e6471..0000000000 --- a/src/applications/nuance/controller/NuanceItemEditController.php +++ /dev/null @@ -1,104 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $item = id(new NuanceItemQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$item) { - return new Aphront404Response(); - } - - $title = pht('Item %d', $item->getID()); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title); - $crumbs->addTextCrumb(pht('Edit')); - - $properties = $this->buildPropertyView($item); - $actions = $this->buildActionView($item); - $properties->setActionList($actions); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->addPropertyList($properties); - - $timeline = $this->buildTransactionTimeline( - $item, - new NuanceItemTransactionQuery()); - - $timeline->setShouldTerminate(true); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, - )); - } - - private function buildPropertyView(NuanceItem $item) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($item); - - $properties->addProperty( - pht('Date Created'), - phabricator_datetime($item->getDateCreated(), $viewer)); - - $properties->addProperty( - pht('Requestor'), - $viewer->renderHandle($item->getRequestorPHID())); - - $properties->addProperty( - pht('Source'), - $viewer->renderHandle($item->getSourcePHID())); - - $properties->addProperty( - pht('Queue'), - $viewer->renderHandle($item->getQueuePHID())); - - $source = $item->getSource(); - $definition = $source->requireDefinition(); - - $definition->renderItemEditProperties( - $viewer, - $item, - $properties); - - return $properties; - } - - private function buildActionView(NuanceItem $item) { - $viewer = $this->getViewer(); - $id = $item->getID(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Item')) - ->setIcon('fa-eye') - ->setHref($this->getApplicationURI("item/view/{$id}/"))); - - return $actions; - } - - -} diff --git a/src/applications/nuance/controller/NuanceItemListController.php b/src/applications/nuance/controller/NuanceItemListController.php new file mode 100644 index 0000000000..2135076fee --- /dev/null +++ b/src/applications/nuance/controller/NuanceItemListController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/nuance/controller/NuanceItemManageController.php b/src/applications/nuance/controller/NuanceItemManageController.php new file mode 100644 index 0000000000..c86d2cd985 --- /dev/null +++ b/src/applications/nuance/controller/NuanceItemManageController.php @@ -0,0 +1,109 @@ +getViewer(); + $id = $request->getURIData('id'); + + $item = id(new NuanceItemQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$item) { + return new Aphront404Response(); + } + + $title = pht('Item %d', $item->getID()); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Items'), + $this->getApplicationURI('item/')); + $crumbs->addTextCrumb( + $title, + $item->getURI()); + $crumbs->addTextCrumb(pht('Manage')); + $crumbs->setBorder(true); + + $properties = $this->buildPropertyView($item); + $curtain = $this->buildCurtain($item); + + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + $timeline = $this->buildTransactionTimeline( + $item, + new NuanceItemTransactionQuery()); + $timeline->setShouldTerminate(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties) + ->setMainColumn($timeline); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildPropertyView(NuanceItem $item) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $properties->addProperty( + pht('Date Created'), + phabricator_datetime($item->getDateCreated(), $viewer)); + + $requestor_phid = $item->getRequestorPHID(); + if ($requestor_phid) { + $requestor_view = $viewer->renderHandle($requestor_phid); + } else { + $requestor_view = phutil_tag('em', array(), pht('None')); + } + $properties->addProperty(pht('Requestor'), $requestor_view); + + $properties->addProperty( + pht('Source'), + $viewer->renderHandle($item->getSourcePHID())); + + $queue_phid = $item->getQueuePHID(); + if ($queue_phid) { + $queue_view = $viewer->renderHandle($queue_phid); + } else { + $queue_view = phutil_tag('em', array(), pht('None')); + } + $properties->addProperty(pht('Queue'), $queue_view); + + $source = $item->getSource(); + $definition = $source->getDefinition(); + + $definition->renderItemEditProperties( + $viewer, + $item, + $properties); + + return $properties; + } + + private function buildCurtain(NuanceItem $item) { + $viewer = $this->getViewer(); + $id = $item->getID(); + + $curtain = $this->newCurtainView($item); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Item')) + ->setIcon('fa-eye') + ->setHref($item->getURI())); + + return $curtain; + } + + +} diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php index d325afbd29..7ef5d06682 100644 --- a/src/applications/nuance/controller/NuanceItemViewController.php +++ b/src/applications/nuance/controller/NuanceItemViewController.php @@ -15,72 +15,112 @@ final class NuanceItemViewController extends NuanceController { } $title = pht('Item %d', $item->getID()); + $name = $item->getDisplayName(); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Items'), + $this->getApplicationURI('item/')); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - $properties = $this->buildPropertyView($item); - $actions = $this->buildActionView($item); - $properties->setActionList($actions); + $curtain = $this->buildCurtain($item); + $content = $this->buildContent($item); + $commands = $this->buildCommands($item); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->addPropertyList($properties); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); - } - - private function buildPropertyView(NuanceItem $item) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($item); - - $properties->addProperty( - pht('Date Created'), - phabricator_datetime($item->getDateCreated(), $viewer)); - - $source = $item->getSource(); - $definition = $source->requireDefinition(); - - $definition->renderItemViewProperties( - $viewer, + $timeline = $this->buildTransactionTimeline( $item, - $properties); + new NuanceItemTransactionQuery()); - return $properties; + $main = array( + $commands, + $content, + $timeline, + ); + + $header = id(new PHUIHeaderView()) + ->setHeader($name); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn($main); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } - private function buildActionView(NuanceItem $item) { + private function buildCurtain(NuanceItem $item) { $viewer = $this->getViewer(); $id = $item->getID(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $item, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Item')) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI("item/edit/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); + $curtain = $this->newCurtainView($item); - return $actions; + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manage Item')) + ->setIcon('fa-cogs') + ->setHref($this->getApplicationURI("item/manage/{$id}/"))); + + $impl = $item->getImplementation(); + $impl->setViewer($viewer); + + foreach ($impl->getItemActions($item) as $action) { + $curtain->addAction($action); + } + + foreach ($impl->getItemCurtainPanels($item) as $panel) { + $curtain->addPanel($panel); + } + + return $curtain; } + private function buildContent(NuanceItem $item) { + $viewer = $this->getViewer(); + $impl = $item->getImplementation(); + + $impl->setViewer($viewer); + return $impl->buildItemView($item); + } + + private function buildCommands(NuanceItem $item) { + $viewer = $this->getViewer(); + + $commands = id(new NuanceItemCommandQuery()) + ->setViewer($viewer) + ->withItemPHIDs(array($item->getPHID())) + ->execute(); + $commands = msort($commands, 'getID'); + + if (!$commands) { + return null; + } + + $rows = array(); + foreach ($commands as $command) { + $rows[] = array( + $command->getCommand(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Command'), + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Pending Commands')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($table); + } } diff --git a/src/applications/nuance/controller/NuanceQueueController.php b/src/applications/nuance/controller/NuanceQueueController.php new file mode 100644 index 0000000000..5736c9a234 --- /dev/null +++ b/src/applications/nuance/controller/NuanceQueueController.php @@ -0,0 +1,11 @@ +newApplicationMenu() + ->setSearchEngine(new NuanceQueueSearchEngine()); + } + +} diff --git a/src/applications/nuance/controller/NuanceQueueEditController.php b/src/applications/nuance/controller/NuanceQueueEditController.php index cfb657615d..5f220e1ee4 100644 --- a/src/applications/nuance/controller/NuanceQueueEditController.php +++ b/src/applications/nuance/controller/NuanceQueueEditController.php @@ -1,135 +1,12 @@ getViewer(); - $queues_uri = $this->getApplicationURI('queue/'); - - $queue_id = $request->getURIData('id'); - $is_new = !$queue_id; - if ($is_new) { - $queue = NuanceQueue::initializeNewQueue(); - $cancel_uri = $queues_uri; - } else { - $queue = id(new NuanceQueueQuery()) - ->setViewer($viewer) - ->withIDs(array($queue_id)) - ->executeOne(); - if (!$queue) { - return new Aphront404Response(); - } - $cancel_uri = $queue->getURI(); - } - - $v_name = $queue->getName(); - $e_name = true; - $v_edit = $queue->getEditPolicy(); - $v_view = $queue->getViewPolicy(); - - $validation_exception = null; - if ($request->isFormPost()) { - $e_name = null; - - $v_name = $request->getStr('name'); - $v_edit = $request->getStr('editPolicy'); - $v_view = $request->getStr('viewPolicy'); - - $type_name = NuanceQueueTransaction::TYPE_NAME; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new NuanceQueueTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new NuanceQueueTransaction()) - ->setTransactionType($type_view) - ->setNewValue($v_view); - - $xactions[] = id(new NuanceQueueTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $editor = id(new NuanceQueueEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - - $editor->applyTransactions($queue, $xactions); - - $uri = $queue->getURI(); - return id(new AphrontRedirectResponse())->setURI($uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - } - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Queues'), $queues_uri); - - if ($is_new) { - $title = pht('Create Queue'); - $crumbs->addTextCrumb(pht('Create')); - } else { - $title = pht('Edit %s', $queue->getName()); - $crumbs->addTextCrumb($queue->getName(), $queue->getURI()); - $crumbs->addTextCrumb(pht('Edit')); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($queue) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setError($e_name) - ->setValue($v_name)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($queue) - ->setPolicies($policies) - ->setValue($v_view) - ->setName('viewPolicy')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($queue) - ->setPolicies($policies) - ->setValue($v_edit) - ->setName('editPolicy')) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue(pht('Save'))); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setValidationException($validation_exception) - ->appendChild($form); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + return id(new NuanceQueueEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/nuance/controller/NuanceQueueListController.php b/src/applications/nuance/controller/NuanceQueueListController.php index e139386bdf..bf104da2a8 100644 --- a/src/applications/nuance/controller/NuanceQueueListController.php +++ b/src/applications/nuance/controller/NuanceQueueListController.php @@ -1,46 +1,20 @@ getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new NuanceQueueSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView($for_app = false) { - $user = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new NuanceQueueSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + return id(new NuanceQueueSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - // TODO: Maybe use SourceManage capability? - $can_create = true; - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Queue')) - ->setHref($this->getApplicationURI('queue/new/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new NuanceQueueEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/nuance/controller/NuanceQueueViewController.php b/src/applications/nuance/controller/NuanceQueueViewController.php index 71e5e04ce7..8f4e85565a 100644 --- a/src/applications/nuance/controller/NuanceQueueViewController.php +++ b/src/applications/nuance/controller/NuanceQueueViewController.php @@ -1,6 +1,7 @@ getViewer(); @@ -18,29 +19,25 @@ final class NuanceQueueViewController extends NuanceController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Queues'), $this->getApplicationURI('queue/')); $crumbs->addTextCrumb($queue->getName()); + $crumbs->setBorder(true); $header = $this->buildHeaderView($queue); - $actions = $this->buildActionView($queue); - $properties = $this->buildPropertyView($queue, $actions); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $curtain = $this->buildCurtain($queue); $timeline = $this->buildTransactionTimeline( $queue, new NuanceQueueTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn($timeline); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildHeaderView(NuanceQueue $queue) { @@ -54,19 +51,18 @@ final class NuanceQueueViewController extends NuanceController { return $header; } - private function buildActionView(NuanceQueue $queue) { + private function buildCurtain(NuanceQueue $queue) { $viewer = $this->getViewer(); $id = $queue->getID(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $queue, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain = $this->newCurtainView($queue); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Queue')) ->setIcon('fa-pencil') @@ -74,19 +70,7 @@ final class NuanceQueueViewController extends NuanceController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - return $actions; + return $curtain; } - private function buildPropertyView( - NuanceQueue $queue, - PhabricatorActionListView $actions) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($queue) - ->setActionList($actions); - - return $properties; - } } diff --git a/src/applications/nuance/controller/NuanceRequestorEditController.php b/src/applications/nuance/controller/NuanceRequestorEditController.php deleted file mode 100644 index 2efc49f711..0000000000 --- a/src/applications/nuance/controller/NuanceRequestorEditController.php +++ /dev/null @@ -1,33 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$id) { - $requestor = new NuanceRequestor(); - - } else { - $requestor = id(new NuanceRequestorQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - } - - if (!$requestor) { - return new Aphront404Response(); - } - - $crumbs = $this->buildApplicationCrumbs(); - $title = pht('TODO'); - - return $this->buildApplicationPage( - $crumbs, - array( - 'title' => $title, - )); - } - -} diff --git a/src/applications/nuance/controller/NuanceRequestorViewController.php b/src/applications/nuance/controller/NuanceRequestorViewController.php deleted file mode 100644 index 8f6912c4d7..0000000000 --- a/src/applications/nuance/controller/NuanceRequestorViewController.php +++ /dev/null @@ -1,27 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $requestor = id(new NuanceRequestorQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - - if (!$requestor) { - return new Aphront404Response(); - } - - $crumbs = $this->buildApplicationCrumbs(); - $title = 'TODO'; - - return $this->buildApplicationPage( - $crumbs, - array( - 'title' => $title, - )); - } -} diff --git a/src/applications/nuance/controller/NuanceSourceActionController.php b/src/applications/nuance/controller/NuanceSourceActionController.php index 06739cea1e..a9fb41bccd 100644 --- a/src/applications/nuance/controller/NuanceSourceActionController.php +++ b/src/applications/nuance/controller/NuanceSourceActionController.php @@ -13,8 +13,11 @@ final class NuanceSourceActionController extends NuanceController { return new Aphront404Response(); } - $def = $source->requireDefinition(); - $def->setActor($viewer); + $def = $source->getDefinition(); + + $def + ->setViewer($viewer) + ->setSource($source); $response = $def->handleActionRequest($request); if ($response instanceof AphrontResponse) { @@ -25,14 +28,10 @@ final class NuanceSourceActionController extends NuanceController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $response, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($response); } } diff --git a/src/applications/nuance/controller/NuanceSourceController.php b/src/applications/nuance/controller/NuanceSourceController.php new file mode 100644 index 0000000000..c3b73376b5 --- /dev/null +++ b/src/applications/nuance/controller/NuanceSourceController.php @@ -0,0 +1,11 @@ +newApplicationMenu() + ->setSearchEngine(new NuanceSourceSearchEngine()); + } + +} diff --git a/src/applications/nuance/controller/NuanceSourceCreateController.php b/src/applications/nuance/controller/NuanceSourceCreateController.php deleted file mode 100644 index 22dd41024c..0000000000 --- a/src/applications/nuance/controller/NuanceSourceCreateController.php +++ /dev/null @@ -1,57 +0,0 @@ -requireApplicationCapability( - NuanceSourceManageCapability::CAPABILITY); - - $viewer = $this->getViewer(); - $map = NuanceSourceDefinition::getAllDefinitions(); - $cancel_uri = $this->getApplicationURI('source/'); - - if ($request->isFormPost()) { - $type = $request->getStr('type'); - if (isset($map[$type])) { - $uri = $this->getApplicationURI('source/new/'.$type.'/'); - return id(new AphrontRedirectResponse())->setURI($uri); - } - } - - $source_types = id(new AphrontFormRadioButtonControl()) - ->setName('type') - ->setLabel(pht('Source Type')); - - foreach ($map as $type => $definition) { - $source_types->addButton( - $type, - $definition->getName(), - $definition->getSourceDescription()); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild($source_types) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Continue')) - ->addCancelButton($cancel_uri)); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Choose Source Type')) - ->appendChild($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Sources'), $cancel_uri); - $crumbs->addTextCrumb(pht('New')); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Choose Source Type'), - )); - } -} diff --git a/src/applications/nuance/controller/NuanceSourceEditController.php b/src/applications/nuance/controller/NuanceSourceEditController.php index 18234bc778..76025b2eb9 100644 --- a/src/applications/nuance/controller/NuanceSourceEditController.php +++ b/src/applications/nuance/controller/NuanceSourceEditController.php @@ -1,72 +1,76 @@ requireApplicationCapability( - NuanceSourceManageCapability::CAPABILITY); + $engine = id(new NuanceSourceEditEngine()) + ->setController($this); - $viewer = $this->getViewer(); + $id = $request->getURIData('id'); + if (!$id) { + $this->requireApplicationCapability( + NuanceSourceManageCapability::CAPABILITY); - $sources_uri = $this->getApplicationURI('source/'); - - $source_id = $request->getURIData('id'); - $is_new = !$source_id; - - if ($is_new) { - $source = NuanceSource::initializeNewSource($viewer); - - $type = $request->getURIData('type'); + $cancel_uri = $this->getApplicationURI('source/'); $map = NuanceSourceDefinition::getAllDefinitions(); - - if (empty($map[$type])) { - return new Aphront404Response(); + $source_type = $request->getStr('sourceType'); + if (!isset($map[$source_type])) { + return $this->buildSourceTypeResponse($cancel_uri); } - $source->setType($type); - $cancel_uri = $sources_uri; - } else { - $source = id(new NuanceSourceQuery()) - ->setViewer($viewer) - ->withIDs(array($source_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$source) { - return new Aphront404Response(); - } - $cancel_uri = $source->getURI(); + $engine + ->setSourceDefinition($map[$source_type]) + ->addContextParameter('sourceType', $source_type); } - $definition = $source->requireDefinition(); - $definition->setActor($viewer); + return $engine->buildResponse(); + } - $response = $definition->buildEditLayout($request); - if ($response instanceof AphrontResponse) { - return $response; + private function buildSourceTypeResponse($cancel_uri) { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + $map = NuanceSourceDefinition::getAllDefinitions(); + + $errors = array(); + $e_source = null; + if ($request->isFormPost()) { + $errors[] = pht('You must choose a source type.'); + $e_source = pht('Required'); } - $layout = $response; + + $source_types = id(new AphrontFormRadioButtonControl()) + ->setName('sourceType') + ->setLabel(pht('Source Type')); + + foreach ($map as $type => $definition) { + $source_types->addButton( + $type, + $definition->getName(), + $definition->getSourceDescription()); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild($source_types) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Continue')) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setFormErrors($errors) + ->setHeaderText(pht('Choose Source Type')) + ->appendChild($form); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Sources'), $sources_uri); + $crumbs->addTextCrumb(pht('Sources'), $cancel_uri); + $crumbs->addTextCrumb(pht('New')); - if ($is_new) { - $crumbs->addTextCrumb(pht('New')); - } else { - $crumbs->addTextCrumb($source->getName(), $cancel_uri); - $crumbs->addTextCrumb(pht('Edit')); - } - - return $this->buildApplicationPage( - array( - $crumbs, - $layout, - ), - array( - 'title' => $definition->getEditTitle(), - )); + return $this->newPage() + ->setTitle(pht('Choose Source Type')) + ->setCrumbs($crumbs) + ->appendChild($box); } + } diff --git a/src/applications/nuance/controller/NuanceSourceListController.php b/src/applications/nuance/controller/NuanceSourceListController.php index 1d906dc3d3..a38c992d9a 100644 --- a/src/applications/nuance/controller/NuanceSourceListController.php +++ b/src/applications/nuance/controller/NuanceSourceListController.php @@ -1,46 +1,20 @@ getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new NuanceSourceSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView($for_app = false) { - $user = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new NuanceSourceSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + return id(new NuanceSourceSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - NuanceSourceManageCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Source')) - ->setHref($this->getApplicationURI('source/create/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new NuanceSourceEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/nuance/controller/NuanceSourceViewController.php b/src/applications/nuance/controller/NuanceSourceViewController.php index 78d6949455..af602bfd7e 100644 --- a/src/applications/nuance/controller/NuanceSourceViewController.php +++ b/src/applications/nuance/controller/NuanceSourceViewController.php @@ -1,6 +1,7 @@ getViewer(); @@ -15,53 +16,38 @@ final class NuanceSourceViewController extends NuanceController { $source_id = $source->getID(); - $timeline = $this->buildTransactionTimeline( - $source, - new NuanceSourceTransactionQuery()); - $timeline->setShouldTerminate(true); - $header = $this->buildHeaderView($source); - $actions = $this->buildActionView($source); - $properties = $this->buildPropertyView($source, $actions); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $curtain = $this->buildCurtain($source); + $properties = $this->buildPropertyView($source); $title = $source->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Sources'), $this->getApplicationURI('source/')); - - $crumbs->addTextCrumb($title); - - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $source, - PhabricatorPolicyCapability::CAN_EDIT); $routing_list = id(new PHUIPropertyListView()) ->addProperty( pht('Default Queue'), $viewer->renderHandle($source->getDefaultQueuePHID())); - $routing_header = id(new PHUIHeaderView()) - ->setHeader(pht('Routing Rules')); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Sources'), $this->getApplicationURI('source/')); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - $routing = id(new PHUIObjectBoxView()) - ->setHeader($routing_header) - ->addPropertyList($routing_list); + $timeline = $this->buildTransactionTimeline( + $source, + new NuanceSourceTransactionQuery()); + $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $routing, - $timeline, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties) + ->addPropertySection(pht('ROUTING'), $routing_list) + ->setMainColumn($timeline); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildHeaderView(NuanceSource $source) { @@ -75,7 +61,7 @@ final class NuanceSourceViewController extends NuanceController { return $header; } - private function buildActionView(NuanceSource $source) { + private function buildCurtain(NuanceSource $source) { $viewer = $this->getViewer(); $id = $source->getID(); @@ -87,7 +73,9 @@ final class NuanceSourceViewController extends NuanceController { $source, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain = $this->newCurtainView($source); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Source')) ->setIcon('fa-pencil') @@ -96,38 +84,33 @@ final class NuanceSourceViewController extends NuanceController { ->setWorkflow(!$can_edit)); $request = $this->getRequest(); - $definition = $source->requireDefinition(); + $definition = $source->getDefinition(); + + $definition + ->setViewer($viewer) + ->setSource($source); + $source_actions = $definition->getSourceViewActions($request); foreach ($source_actions as $source_action) { - $actions->addAction($source_action); + $curtain->addAction($source_action); } - return $actions; + return $curtain; } private function buildPropertyView( - NuanceSource $source, - PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + NuanceSource $source) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($source) - ->setActionList($actions); + ->setViewer($viewer); + + $definition = $source->getDefinition(); - $definition = $source->requireDefinition(); $properties->addProperty( pht('Source Type'), $definition->getName()); - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $source); - - $properties->addProperty( - pht('Editable By'), - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); - return $properties; } } diff --git a/src/applications/nuance/cursor/NuanceGitHubImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubImportCursor.php new file mode 100644 index 0000000000..fc21e2f1d6 --- /dev/null +++ b/src/applications/nuance/cursor/NuanceGitHubImportCursor.php @@ -0,0 +1,258 @@ +getCursorProperty('github.poll.ttl'); + if ($ttl && ($ttl >= $now)) { + $this->logInfo( + pht( + 'Respecting "%s" or minimum poll delay: waiting for %s second(s) '. + 'to poll GitHub.', + 'X-Poll-Interval', + new PhutilNumber(1 + ($ttl - $now)))); + + return false; + } + + // Respect GitHub's API rate limiting. If we've exceeded the rate limit, + // wait until it resets to try again. + $limit = $this->getCursorProperty('github.limit.ttl'); + if ($limit && ($limit >= $now)) { + $this->logInfo( + pht( + 'Respecting "%s": waiting for %s second(s) to poll GitHub.', + 'X-RateLimit-Reset', + new PhutilNumber(1 + ($limit - $now)))); + return false; + } + + return true; + } + + final protected function pullDataFromSource() { + $viewer = $this->getViewer(); + $now = PhabricatorTime::getNow(); + + $source = $this->getSource(); + + $user = $source->getSourceProperty('github.user'); + $repository = $source->getSourceProperty('github.repository'); + $api_token = $source->getSourceProperty('github.token'); + + // This API only supports fetching 10 pages of 30 events each, for a total + // of 300 events. + $etag = null; + $new_items = array(); + $hit_known_items = false; + + $max_page = $this->getMaximumPage(); + $page_size = $this->getPageSize(); + + for ($page = 1; $page <= $max_page; $page++) { + $uri = $this->getGitHubAPIEndpointURI($user, $repository); + + $data = array( + 'page' => $page, + 'per_page' => $page_size, + ); + + $future = id(new PhutilGitHubFuture()) + ->setAccessToken($api_token) + ->setRawGitHubQuery($uri, $data); + + if ($page == 1) { + $cursor_etag = $this->getCursorProperty('github.poll.etag'); + if ($cursor_etag) { + $future->addHeader('If-None-Match', $cursor_etag); + } + } + + $this->logInfo( + pht( + 'Polling GitHub Repository API endpoint "%s".', + $uri)); + $response = $future->resolve(); + + // Do this first: if we hit the rate limit, we get a response but the + // body isn't valid. + $this->updateRateLimits($response); + + if ($response->getStatus()->getStatusCode() == 304) { + $this->logInfo( + pht( + 'Received a 304 Not Modified from GitHub, no new events.')); + } + + // This means we hit a rate limit or a "Not Modified" because of the + // "ETag" header. In either case, we should bail out. + if ($response->getStatus()->isError()) { + $this->updatePolling($response, $now, false); + $this->getCursorData()->save(); + return false; + } + + if ($page == 1) { + $etag = $response->getHeaderValue('ETag'); + } + + $records = $response->getBody(); + foreach ($records as $record) { + $item = $this->newNuanceItemFromGitHubRecord($record); + $item_key = $item->getItemKey(); + + $this->logInfo( + pht( + 'Fetched event "%s".', + $item_key)); + + $new_items[$item->getItemKey()] = $item; + } + + if ($new_items) { + $existing = id(new NuanceItemQuery()) + ->setViewer($viewer) + ->withSourcePHIDs(array($source->getPHID())) + ->withItemKeys(array_keys($new_items)) + ->execute(); + $existing = mpull($existing, null, 'getItemKey'); + foreach ($new_items as $key => $new_item) { + if (isset($existing[$key])) { + unset($new_items[$key]); + $hit_known_items = true; + + $this->logInfo( + pht( + 'Event "%s" is previously known.', + $key)); + } + } + } + + if ($hit_known_items) { + break; + } + + if (count($records) < $page_size) { + break; + } + } + + // TODO: When we go through the whole queue without hitting anything we + // have seen before, we should record some sort of global event so we + // can tell the user when the bridging started or was interrupted? + if (!$hit_known_items) { + $already_polled = $this->getCursorProperty('github.polled'); + if ($already_polled) { + // TODO: This is bad: we missed some items, maybe because too much + // stuff happened too fast or the daemons were broken for a long + // time. + } else { + // TODO: This is OK, we're doing the initial import. + } + } + + if ($etag !== null) { + $this->updateETag($etag); + } + + $this->updatePolling($response, $now, true); + + // Reverse the new items so we insert them in chronological order. + $new_items = array_reverse($new_items); + + $source->openTransaction(); + foreach ($new_items as $new_item) { + $new_item->save(); + } + $this->getCursorData()->save(); + $source->saveTransaction(); + + foreach ($new_items as $new_item) { + $new_item->scheduleUpdate(); + } + + return false; + } + + private function updateRateLimits(PhutilGitHubResponse $response) { + $remaining = $response->getHeaderValue('X-RateLimit-Remaining'); + $limit_reset = $response->getHeaderValue('X-RateLimit-Reset'); + $now = PhabricatorTime::getNow(); + + $limit_ttl = null; + if (strlen($remaining)) { + $remaining = (int)$remaining; + if (!$remaining) { + $limit_ttl = (int)$limit_reset; + } + } + + $this->setCursorProperty('github.limit.ttl', $limit_ttl); + + $this->logInfo( + pht( + 'This key has %s remaining API request(s), '. + 'limit resets in %s second(s).', + new PhutilNumber($remaining), + new PhutilNumber($limit_reset - $now))); + } + + private function updateETag($etag) { + + $this->setCursorProperty('github.poll.etag', $etag); + + $this->logInfo( + pht( + 'ETag for this request was "%s".', + $etag)); + } + + private function updatePolling( + PhutilGitHubResponse $response, + $start, + $success) { + + if ($success) { + $this->setCursorProperty('github.polled', true); + } + + $poll_interval = (int)$response->getHeaderValue('X-Poll-Interval'); + $poll_interval = max($this->getMinimumDelayBetweenPolls(), $poll_interval); + + $poll_ttl = $start + $poll_interval; + $this->setCursorProperty('github.poll.ttl', $poll_ttl); + + $now = PhabricatorTime::getNow(); + + $this->logInfo( + pht( + 'Set API poll TTL to +%s second(s) (%s second(s) from now).', + new PhutilNumber($poll_interval), + new PhutilNumber($poll_ttl - $now))); + } + +} diff --git a/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php new file mode 100644 index 0000000000..d250b1d02d --- /dev/null +++ b/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php @@ -0,0 +1,30 @@ +getSource(); + + $id = $record['id']; + $item_key = "github.issueevent.{$id}"; + + $container_key = null; + + return NuanceItem::initializeNewItem() + ->setStatus(NuanceItem::STATUS_IMPORTING) + ->setSourcePHID($source->getPHID()) + ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) + ->setItemKey($item_key) + ->setItemContainerKey($container_key) + ->setItemProperty('api.type', 'issue') + ->setItemProperty('api.raw', $record); + } + +} diff --git a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php new file mode 100644 index 0000000000..72aca276d2 --- /dev/null +++ b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php @@ -0,0 +1,49 @@ +getSource(); + + $id = $record['id']; + $item_key = "github.event.{$id}"; + + $container_key = null; + + $issue_id = idxv( + $record, + array( + 'payload', + 'issue', + 'id', + )); + if ($issue_id) { + $container_key = "github.issue.{$issue_id}"; + } + + return NuanceItem::initializeNewItem() + ->setStatus(NuanceItem::STATUS_IMPORTING) + ->setSourcePHID($source->getPHID()) + ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) + ->setItemKey($item_key) + ->setItemContainerKey($container_key) + ->setItemProperty('api.type', 'repository') + ->setItemProperty('api.raw', $record); + } + +} diff --git a/src/applications/nuance/cursor/NuanceImportCursor.php b/src/applications/nuance/cursor/NuanceImportCursor.php new file mode 100644 index 0000000000..03dad4d7e4 --- /dev/null +++ b/src/applications/nuance/cursor/NuanceImportCursor.php @@ -0,0 +1,108 @@ +getPhobjectClassConstant('CURSORTYPE', 32); + } + + public function setCursorData(NuanceImportCursorData $cursor_data) { + $this->cursorData = $cursor_data; + return $this; + } + + public function getCursorData() { + return $this->cursorData; + } + + public function setSource($source) { + $this->source = $source; + return $this; + } + + public function getSource() { + return $this->source; + } + + public function setCursorKey($cursor_key) { + $this->cursorKey = $cursor_key; + return $this; + } + + public function getCursorKey() { + return $this->cursorKey; + } + + public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + final public function importFromSource() { + if (!$this->shouldPullDataFromSource()) { + return false; + } + + $source = $this->getSource(); + $key = $this->getCursorKey(); + + $parts = array( + 'nsc', + $source->getID(), + PhabricatorHash::digestToLength($key, 20), + ); + $lock_name = implode('.', $parts); + + $lock = PhabricatorGlobalLock::newLock($lock_name); + $lock->lock(1); + + try { + $more_data = $this->pullDataFromSource(); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + + return $more_data; + } + + final public function newEmptyCursorData(NuanceSource $source) { + return id(new NuanceImportCursorData()) + ->setCursorKey($this->getCursorKey()) + ->setCursorType($this->getCursorType()) + ->setSourcePHID($source->getPHID()); + } + + final protected function logInfo($message) { + echo tsprintf( + " %s\n", + $this->getCursorKey(), + $message); + + return $this; + } + + final protected function getCursorProperty($key, $default = null) { + return $this->getCursorData()->getCursorProperty($key, $default); + } + + final protected function setCursorProperty($key, $value) { + $this->getCursorData()->setCursorProperty($key, $value); + return $this; + } + +} diff --git a/src/applications/nuance/editor/NuanceItemEditor.php b/src/applications/nuance/editor/NuanceItemEditor.php index a331a65196..45288052c2 100644 --- a/src/applications/nuance/editor/NuanceItemEditor.php +++ b/src/applications/nuance/editor/NuanceItemEditor.php @@ -19,6 +19,7 @@ final class NuanceItemEditor $types[] = NuanceItemTransaction::TYPE_REQUESTOR; $types[] = NuanceItemTransaction::TYPE_PROPERTY; $types[] = NuanceItemTransaction::TYPE_QUEUE; + $types[] = NuanceItemTransaction::TYPE_COMMAND; $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_COMMENT; @@ -45,6 +46,8 @@ final class NuanceItemEditor $key = $xaction->getMetadataValue( NuanceItemTransaction::PROPERTY_KEY); return $object->getNuanceProperty($key); + case NuanceItemTransaction::TYPE_COMMAND: + return null; } return parent::getCustomTransactionOldValue($object, $xaction); @@ -60,6 +63,7 @@ final class NuanceItemEditor case NuanceItemTransaction::TYPE_OWNER: case NuanceItemTransaction::TYPE_PROPERTY: case NuanceItemTransaction::TYPE_QUEUE: + case NuanceItemTransaction::TYPE_COMMAND: return $xaction->getNewValue(); } @@ -88,6 +92,8 @@ final class NuanceItemEditor NuanceItemTransaction::PROPERTY_KEY); $object->setNuanceProperty($key, $xaction->getNewValue()); break; + case NuanceItemTransaction::TYPE_COMMAND: + break; } } @@ -101,6 +107,7 @@ final class NuanceItemEditor case NuanceItemTransaction::TYPE_OWNER: case NuanceItemTransaction::TYPE_PROPERTY: case NuanceItemTransaction::TYPE_QUEUE: + case NuanceItemTransaction::TYPE_COMMAND: return; } diff --git a/src/applications/nuance/editor/NuanceQueueEditEngine.php b/src/applications/nuance/editor/NuanceQueueEditEngine.php new file mode 100644 index 0000000000..049916adbe --- /dev/null +++ b/src/applications/nuance/editor/NuanceQueueEditEngine.php @@ -0,0 +1,84 @@ +getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Queue'); + } + + protected function getObjectCreateShortText() { + return pht('Create Queue'); + } + + protected function getObjectName() { + return pht('Queue'); + } + + protected function getEditorURI() { + return '/nuance/queue/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/nuance/queue/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the queue.')) + ->setTransactionType(NuanceQueueTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + ); + } + +} diff --git a/src/applications/nuance/editor/NuanceQueueEditor.php b/src/applications/nuance/editor/NuanceQueueEditor.php index 589ab5da5c..cb3ead2417 100644 --- a/src/applications/nuance/editor/NuanceQueueEditor.php +++ b/src/applications/nuance/editor/NuanceQueueEditor.php @@ -68,6 +68,7 @@ final class NuanceQueueEditor return parent::applyCustomExternalTransaction($object, $xaction); } + protected function validateTransaction( PhabricatorLiskDAO $object, $type, diff --git a/src/applications/nuance/editor/NuanceRequestorEditor.php b/src/applications/nuance/editor/NuanceRequestorEditor.php deleted file mode 100644 index 4ca4c875ff..0000000000 --- a/src/applications/nuance/editor/NuanceRequestorEditor.php +++ /dev/null @@ -1,75 +0,0 @@ -getTransactionType()) { - case NuanceRequestorTransaction::TYPE_PROPERTY: - $key = $xaction->getMetadataValue( - NuanceRequestorTransaction::PROPERTY_KEY); - return $object->getNuanceProperty($key); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceRequestorTransaction::TYPE_PROPERTY: - return $xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceRequestorTransaction::TYPE_PROPERTY: - $key = $xaction->getMetadataValue( - NuanceRequestorTransaction::PROPERTY_KEY); - $object->setNuanceProperty($key, $xaction->getNewValue()); - break; - } - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case NuanceRequestorTransaction::TYPE_PROPERTY: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } -} diff --git a/src/applications/nuance/editor/NuanceSourceEditEngine.php b/src/applications/nuance/editor/NuanceSourceEditEngine.php new file mode 100644 index 0000000000..18d27863ac --- /dev/null +++ b/src/applications/nuance/editor/NuanceSourceEditEngine.php @@ -0,0 +1,112 @@ +sourceDefinition = $source_definition; + return $this; + } + + public function getSourceDefinition() { + return $this->sourceDefinition; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Nuance Sources'); + } + + public function getSummaryHeader() { + return pht('Edit Nuance Source Configurations'); + } + + public function getSummaryText() { + return pht('This engine is used to edit Nuance sources.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorNuanceApplication'; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + + $definition = $this->getSourceDefinition(); + if (!$definition) { + throw new PhutilInvalidStateException('setSourceDefinition'); + } + + return NuanceSource::initializeNewSource( + $viewer, + $definition); + } + + protected function newObjectQuery() { + return new NuanceSourceQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Source'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Source'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Source: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Source'); + } + + protected function getObjectCreateShortText() { + return pht('Create Source'); + } + + protected function getObjectName() { + return pht('Source'); + } + + protected function getEditorURI() { + return '/nuance/source/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/nuance/source/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the source.')) + ->setTransactionType(NuanceSourceTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + id(new PhabricatorDatasourceEditField()) + ->setKey('defaultQueue') + ->setLabel(pht('Default Queue')) + ->setDescription(pht('Default queue.')) + ->setTransactionType(NuanceSourceTransaction::TYPE_DEFAULT_QUEUE) + ->setDatasource(new NuanceQueueDatasource()) + ->setSingleValue($object->getDefaultQueuePHID()), + ); + } + +} diff --git a/src/applications/nuance/editor/NuanceSourceEditor.php b/src/applications/nuance/editor/NuanceSourceEditor.php index 233b2ae163..5fbc02b962 100644 --- a/src/applications/nuance/editor/NuanceSourceEditor.php +++ b/src/applications/nuance/editor/NuanceSourceEditor.php @@ -11,6 +11,10 @@ final class NuanceSourceEditor return pht('Nuance Sources'); } + protected function supportsSearch() { + return true; + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); diff --git a/src/applications/nuance/github/NuanceGitHubRawEvent.php b/src/applications/nuance/github/NuanceGitHubRawEvent.php new file mode 100644 index 0000000000..b28a9222dc --- /dev/null +++ b/src/applications/nuance/github/NuanceGitHubRawEvent.php @@ -0,0 +1,391 @@ +type = $type; + $event->raw = $raw; + return $event; + } + + public function getRepositoryFullName() { + return $this->getRepositoryFullRawName(); + } + + public function isIssueEvent() { + if ($this->isPullRequestEvent()) { + return false; + } + + if ($this->type == self::TYPE_ISSUE) { + return true; + } + + switch ($this->getIssueRawKind()) { + case 'IssuesEvent': + return true; + case 'IssueCommentEvent': + if (!$this->getRawPullRequestData()) { + return true; + } + break; + } + + return false; + } + + public function isPullRequestEvent() { + if ($this->type == self::TYPE_ISSUE) { + // TODO: This is wrong, some of these are pull events. + return false; + } + + $raw = $this->raw; + + switch ($this->getIssueRawKind()) { + case 'PullRequestEvent': + return true; + case 'IssueCommentEvent': + if ($this->getRawPullRequestData()) { + return true; + } + break; + } + + return false; + } + + public function getIssueNumber() { + if (!$this->isIssueEvent()) { + return null; + } + + return $this->getRawIssueNumber(); + } + + public function getPullRequestNumber() { + if (!$this->isPullRequestEvent()) { + return null; + } + + return $this->getRawIssueNumber(); + } + + + public function getID() { + $raw = $this->raw; + + $id = idx($raw, 'id'); + if ($id) { + return (int)$id; + } + + return null; + } + + public function getComment() { + if (!$this->isIssueEvent() && !$this->isPullRequestEvent()) { + return null; + } + + $raw = $this->raw; + + return idxv($raw, array('payload', 'comment', 'body')); + } + + public function getURI() { + $raw = $this->raw; + + if ($this->isIssueEvent() || $this->isPullRequestEvent()) { + if ($this->type == self::TYPE_ISSUE) { + $uri = idxv($raw, array('issue', 'html_url')); + $uri = $uri.'#event-'.$this->getID(); + } else { + // The format of pull request events varies so we need to fish around + // a bit to find the correct URI. + $uri = idxv($raw, array('payload', 'pull_request', 'html_url')); + $need_anchor = true; + + // For comments, we get a different anchor to link to the comment. In + // this case, the URI comes with an anchor already. + if (!$uri) { + $uri = idxv($raw, array('payload', 'comment', 'html_url')); + $need_anchor = false; + } + + if (!$uri) { + $uri = idxv($raw, array('payload', 'issue', 'html_url')); + $need_anchor = true; + } + + if ($need_anchor) { + $uri = $uri.'#event-'.$this->getID(); + } + } + } else { + switch ($this->getIssueRawKind()) { + case 'CreateEvent': + $ref = idxv($raw, array('payload', 'ref')); + + $repo = $this->getRepositoryFullRawName(); + return "https://github.com/{$repo}/commits/{$ref}"; + case 'PushEvent': + // These don't really have a URI since there may be multiple commits + // involved and GitHub doesn't bundle the push as an object on its + // own. Just try to find the URI for the log. The API also does + // not return any HTML URI for these events. + + $head = idxv($raw, array('payload', 'head')); + if ($head === null) { + return null; + } + + $repo = $this->getRepositoryFullRawName(); + return "https://github.com/{$repo}/commits/{$head}"; + case 'WatchEvent': + // These have no reasonable URI. + return null; + default: + return null; + } + } + + return $uri; + } + + private function getRepositoryFullRawName() { + $raw = $this->raw; + + $full = idxv($raw, array('repo', 'name')); + if (strlen($full)) { + return $full; + } + + // For issue events, the repository is not identified explicitly in the + // response body. Parse it out of the URI. + + $matches = null; + $ok = preg_match( + '(/repos/((?:[^/]+)/(?:[^/]+))/issues/events/)', + idx($raw, 'url'), + $matches); + + if ($ok) { + return $matches[1]; + } + + return null; + } + + private function getIssueRawKind() { + $raw = $this->raw; + return idxv($raw, array('type')); + } + + private function getRawIssueNumber() { + $raw = $this->raw; + + if ($this->type == self::TYPE_ISSUE) { + return idxv($raw, array('issue', 'number')); + } + + if ($this->type == self::TYPE_REPOSITORY) { + $issue_number = idxv($raw, array('payload', 'issue', 'number')); + if ($issue_number) { + return $issue_number; + } + + $pull_number = idxv($raw, array('payload', 'number')); + if ($pull_number) { + return $pull_number; + } + } + + return null; + } + + private function getRawPullRequestData() { + $raw = $this->raw; + return idxv($raw, array('payload', 'issue', 'pull_request')); + } + + public function getEventFullTitle() { + switch ($this->type) { + case self::TYPE_ISSUE: + $title = $this->getRawIssueEventTitle(); + break; + case self::TYPE_REPOSITORY: + $title = $this->getRawRepositoryEventTitle(); + break; + default: + $title = pht('Unknown Event Type ("%s")', $this->type); + break; + } + + return pht( + 'GitHub %s %s (%s)', + $this->getRepositoryFullRawName(), + $this->getTargetObjectName(), + $title); + } + + public function getActorGitHubUserID() { + $raw = $this->raw; + return (int)idxv($raw, array('actor', 'id')); + } + + private function getTargetObjectName() { + if ($this->isPullRequestEvent()) { + $number = $this->getRawIssueNumber(); + return pht('Pull Request #%d', $number); + } else if ($this->isIssueEvent()) { + $number = $this->getRawIssueNumber(); + return pht('Issue #%d', $number); + } else if ($this->type == self::TYPE_REPOSITORY) { + $raw = $this->raw; + + + $type = idx($raw, 'type'); + switch ($type) { + case 'CreateEvent': + $ref = idxv($raw, array('payload', 'ref')); + $ref_type = idxv($raw, array('payload', 'ref_type')); + + switch ($ref_type) { + case 'branch': + return pht('Branch %s', $ref); + case 'tag': + return pht('Tag %s', $ref); + default: + return pht('Ref %s', $ref); + } + break; + case 'PushEvent': + $ref = idxv($raw, array('payload', 'ref')); + if (preg_match('(^refs/heads/)', $ref)) { + return pht('Branch %s', substr($ref, strlen('refs/heads/'))); + } else { + return pht('Ref %s', $ref); + } + break; + case 'WatchEvent': + $actor = idxv($raw, array('actor', 'login')); + return pht('User %s', $actor); + } + + return pht('Unknown Object'); + } else { + return pht('Unknown Object'); + } + } + + private function getRawIssueEventTitle() { + $raw = $this->raw; + + $action = idxv($raw, array('event')); + switch ($action) { + case 'assigned': + $assignee = idxv($raw, array('assignee', 'login')); + $title = pht('Assigned: %s', $assignee); + break; + case 'closed': + $title = pht('Closed'); + break; + case 'demilestoned': + $milestone = idxv($raw, array('milestone', 'title')); + $title = pht('Removed Milestone: %s', $milestone); + break; + case 'labeled': + $label = idxv($raw, array('label', 'name')); + $title = pht('Added Label: %s', $label); + break; + case 'locked': + $title = pht('Locked'); + break; + case 'milestoned': + $milestone = idxv($raw, array('milestone', 'title')); + $title = pht('Added Milestone: %s', $milestone); + break; + case 'renamed': + $title = pht('Renamed'); + break; + case 'reopened': + $title = pht('Reopened'); + break; + case 'unassigned': + $assignee = idxv($raw, array('assignee', 'login')); + $title = pht('Unassigned: %s', $assignee); + break; + case 'unlabeled': + $label = idxv($raw, array('label', 'name')); + $title = pht('Removed Label: %s', $label); + break; + case 'unlocked': + $title = pht('Unlocked'); + break; + default: + $title = pht('"%s"', $action); + break; + } + + + return $title; + } + + private function getRawRepositoryEventTitle() { + $raw = $this->raw; + + $type = idx($raw, 'type'); + switch ($type) { + case 'CreateEvent': + return pht('Created'); + case 'PushEvent': + $head = idxv($raw, array('payload', 'head')); + $head = substr($head, 0, 12); + return pht('Pushed: %s', $head); + case 'IssuesEvent': + $action = idxv($raw, array('payload', 'action')); + switch ($action) { + case 'closed': + return pht('Closed'); + case 'opened': + return pht('Created'); + case 'reopened': + return pht('Reopened'); + default: + return pht('"%s"', $action); + } + break; + case 'IssueCommentEvent': + $action = idxv($raw, array('payload', 'action')); + switch ($action) { + case 'created': + return pht('Comment'); + default: + return pht('"%s"', $action); + } + break; + case 'PullRequestEvent': + $action = idxv($raw, array('payload', 'action')); + switch ($action) { + case 'opened': + return pht('Created'); + default: + return pht('"%s"', $action); + } + break; + case 'WatchEvent': + return pht('Watched'); + } + + return pht('"%s"', $type); + } + +} diff --git a/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php new file mode 100644 index 0000000000..29b9e3c0a9 --- /dev/null +++ b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php @@ -0,0 +1,113 @@ +readTestCases($path); + + foreach ($cases as $name => $info) { + $input = $info['input']; + $expect = $info['expect']; + + $event = NuanceGitHubRawEvent::newEvent( + NuanceGitHubRawEvent::TYPE_ISSUE, + $input); + + $this->assertGitHubRawEventParse($expect, $event, $name); + } + } + + public function testRepositoryEvents() { + $path = dirname(__FILE__).'/repositoryevents/'; + + $cases = $this->readTestCases($path); + + foreach ($cases as $name => $info) { + $input = $info['input']; + $expect = $info['expect']; + + $event = NuanceGitHubRawEvent::newEvent( + NuanceGitHubRawEvent::TYPE_REPOSITORY, + $input); + + $this->assertGitHubRawEventParse($expect, $event, $name); + } + } + + private function assertGitHubRawEventParse( + array $expect, + NuanceGitHubRawEvent $event, + $name) { + + $actual = array( + 'repository.name.full' => $event->getRepositoryFullName(), + 'is.issue' => $event->isIssueEvent(), + 'is.pull' => $event->isPullRequestEvent(), + 'issue.number' => $event->getIssueNumber(), + 'pull.number' => $event->getPullRequestNumber(), + 'id' => $event->getID(), + 'uri' => $event->getURI(), + 'title.full' => $event->getEventFullTitle(), + 'comment' => $event->getComment(), + 'actor.id' => $event->getActorGitHubUserID(), + ); + + // Only verify the keys which are actually present in the test. This + // allows tests to specify only relevant keys. + $actual = array_select_keys($actual, array_keys($expect)); + + ksort($expect); + ksort($actual); + + $this->assertEqual($expect, $actual, $name); + } + + private function readTestCases($path) { + $files = Filesystem::listDirectory($path, $include_hidden = false); + + $tests = array(); + foreach ($files as $file) { + $data = Filesystem::readFile($path.$file); + + $parts = preg_split('/^~{5,}$/m', $data); + if (count($parts) < 2) { + throw new Exception( + pht( + 'Expected test file "%s" to contain an input section in JSON, '. + 'then an expected result section in JSON, with the two sections '. + 'separated by a line of "~~~~~", but the divider is not present '. + 'in the file.', + $file)); + } else if (count($parts) > 2) { + throw new Exception( + pht( + 'Expected test file "%s" to contain exactly two sections, '. + 'but it has more than two sections.')); + } + + list($input, $expect) = $parts; + + try { + $input = phutil_json_decode($input); + $expect = phutil_json_decode($expect); + } catch (Exception $ex) { + throw new PhutilProxyException( + pht( + 'Exception while decoding test data for test "%s".', + $file), + $ex); + } + + $tests[$file] = array( + 'input' => $input, + 'expect' => $expect, + ); + } + + return $tests; + } + +} diff --git a/src/applications/nuance/github/__tests__/issueevents/assigned.txt b/src/applications/nuance/github/__tests__/issueevents/assigned.txt new file mode 100644 index 0000000000..8847a5d860 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/assigned.txt @@ -0,0 +1,118 @@ +{ + "id": 583217900, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583217900", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "assigned", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:42:53Z", + "assignee": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "assigner": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583217900, + "uri": "https://github.com/epriestley/poems/issues/1#event-583217900", + "title.full": "GitHub epriestley/poems Issue #1 (Assigned: epriestley)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/closed.txt b/src/applications/nuance/github/__tests__/issueevents/closed.txt new file mode 100644 index 0000000000..db66ff25bd --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/closed.txt @@ -0,0 +1,80 @@ +{ + "id": 583218864, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218864", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "closed", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:53Z", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583218864, + "uri": "https://github.com/epriestley/poems/issues/1#event-583218864", + "title.full": "GitHub epriestley/poems Issue #1 (Closed)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/demilestoned.txt b/src/applications/nuance/github/__tests__/issueevents/demilestoned.txt new file mode 100644 index 0000000000..b144a321de --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/demilestoned.txt @@ -0,0 +1,83 @@ +{ + "id": 583218613, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218613", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "demilestoned", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:36Z", + "milestone": { + "title": "b" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583218613, + "uri": "https://github.com/epriestley/poems/issues/1#event-583218613", + "title.full": "GitHub epriestley/poems Issue #1 (Removed Milestone: b)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/labeled.txt b/src/applications/nuance/github/__tests__/issueevents/labeled.txt new file mode 100644 index 0000000000..b2458ce0a4 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/labeled.txt @@ -0,0 +1,84 @@ +{ + "id": 583217784, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583217784", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "labeled", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:42:44Z", + "label": { + "name": "bug", + "color": "fc2929" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583217784, + "uri": "https://github.com/epriestley/poems/issues/1#event-583217784", + "title.full": "GitHub epriestley/poems Issue #1 (Added Label: bug)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/locked.txt b/src/applications/nuance/github/__tests__/issueevents/locked.txt new file mode 100644 index 0000000000..7eafd9e906 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/locked.txt @@ -0,0 +1,79 @@ +{ + "id": 583218006, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218006", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "locked", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:42:58Z", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583218006, + "uri": "https://github.com/epriestley/poems/issues/1#event-583218006", + "title.full": "GitHub epriestley/poems Issue #1 (Locked)" +} diff --git a/src/applications/nuance/github/__tests__/issueevents/milestoned.txt b/src/applications/nuance/github/__tests__/issueevents/milestoned.txt new file mode 100644 index 0000000000..ad56c4020a --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/milestoned.txt @@ -0,0 +1,83 @@ +{ + "id": 583217866, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583217866", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "milestoned", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:42:50Z", + "milestone": { + "title": "b" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583217866, + "uri": "https://github.com/epriestley/poems/issues/1#event-583217866", + "title.full": "GitHub epriestley/poems Issue #1 (Added Milestone: b)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/renamed.txt b/src/applications/nuance/github/__tests__/issueevents/renamed.txt new file mode 100644 index 0000000000..ecf6e4f465 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/renamed.txt @@ -0,0 +1,84 @@ +{ + "id": 583218162, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218162", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "renamed", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:07Z", + "rename": { + "from": "Enforce haiku in commit messages", + "to": "Enforce haiku in commit messages edit" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583218162, + "uri": "https://github.com/epriestley/poems/issues/1#event-583218162", + "title.full": "GitHub epriestley/poems Issue #1 (Renamed)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/reopened.txt b/src/applications/nuance/github/__tests__/issueevents/reopened.txt new file mode 100644 index 0000000000..be66183d17 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/reopened.txt @@ -0,0 +1,80 @@ +{ + "id": 583218814, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218814", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "reopened", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:50Z", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583218814, + "uri": "https://github.com/epriestley/poems/issues/1#event-583218814", + "title.full": "GitHub epriestley/poems Issue #1 (Reopened)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/unassigned.txt b/src/applications/nuance/github/__tests__/issueevents/unassigned.txt new file mode 100644 index 0000000000..2086e277ec --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/unassigned.txt @@ -0,0 +1,118 @@ +{ + "id": 583218511, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218511", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "unassigned", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:29Z", + "assignee": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "assigner": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583218511, + "uri": "https://github.com/epriestley/poems/issues/1#event-583218511", + "title.full": "GitHub epriestley/poems Issue #1 (Unassigned: epriestley)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/unlabeled.txt b/src/applications/nuance/github/__tests__/issueevents/unlabeled.txt new file mode 100644 index 0000000000..8323d661cd --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/unlabeled.txt @@ -0,0 +1,84 @@ +{ + "id": 583218703, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218703", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "unlabeled", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:42Z", + "label": { + "name": "bug", + "color": "fc2929" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583218703, + "uri": "https://github.com/epriestley/poems/issues/1#event-583218703", + "title.full": "GitHub epriestley/poems Issue #1 (Removed Label: bug)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/unlocked.txt b/src/applications/nuance/github/__tests__/issueevents/unlocked.txt new file mode 100644 index 0000000000..31c87296cf --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/unlocked.txt @@ -0,0 +1,80 @@ +{ + "id": 583218062, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218062", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "unlocked", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:01Z", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 583218062, + "uri": "https://github.com/epriestley/poems/issues/1#event-583218062", + "title.full": "GitHub epriestley/poems Issue #1 (Unlocked)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/CreateEvent.tag.txt b/src/applications/nuance/github/__tests__/repositoryevents/CreateEvent.tag.txt new file mode 100644 index 0000000000..1e73df40ff --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/CreateEvent.tag.txt @@ -0,0 +1,38 @@ +{ + "id": "3784548642", + "type": "CreateEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "ref": "phabricator/diff/400", + "ref_type": "tag", + "master_branch": "master", + "description": "Poems (Mirror)", + "pusher_type": "user" + }, + "public": true, + "created_at": "2016-03-19T22:07:56Z" +} + +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": false, + "issue.number": null, + "pull.number": null, + "id": 3784548642, + "uri": "https://github.com/epriestley/poems/commits/phabricator/diff/400", + "title.full": "GitHub epriestley/poems Tag phabricator/diff/400 (Created)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt new file mode 100644 index 0000000000..9f25d5fdd0 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt @@ -0,0 +1,166 @@ +{ + "id": "3740938746", + "type": "IssueCommentEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "created", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/2", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/2/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/2/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/2/events", + "html_url": "https://github.com/epriestley/poems/pull/2", + "id": 139568860, + "number": 2, + "title": "Please Merge Quack2 into Feature", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "url": "https://api.github.com/repos/epriestley/poems/labels/bug", + "name": "bug", + "color": "fc2929" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "milestone": { + "url": "https://api.github.com/repos/epriestley/poems/milestones/1", + "html_url": "https://github.com/epriestley/poems/milestones/b", + "labels_url": "https://api.github.com/repos/epriestley/poems/milestones/1/labels", + "id": 1633589, + "number": 1, + "title": "b", + "description": null, + "creator": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 1, + "closed_issues": 0, + "state": "open", + "created_at": "2016-03-09T12:42:50Z", + "updated_at": "2016-03-09T12:52:41Z", + "due_on": null, + "closed_at": null + }, + "comments": 1, + "created_at": "2016-03-09T12:52:31Z", + "updated_at": "2016-03-09T12:53:06Z", + "closed_at": null, + "pull_request": { + "url": "https://api.github.com/repos/epriestley/poems/pulls/2", + "html_url": "https://github.com/epriestley/poems/pull/2", + "diff_url": "https://github.com/epriestley/poems/pull/2.diff", + "patch_url": "https://github.com/epriestley/poems/pull/2.patch" + }, + "body": "" + }, + "comment": { + "url": "https://api.github.com/repos/epriestley/poems/issues/comments/194282800", + "html_url": "https://github.com/epriestley/poems/pull/2#issuecomment-194282800", + "issue_url": "https://api.github.com/repos/epriestley/poems/issues/2", + "id": 194282800, + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2016-03-09T12:53:06Z", + "updated_at": "2016-03-09T12:53:06Z", + "body": "wub wub" + } + }, + "public": true, + "created_at": "2016-03-09T12:53:06Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": true, + "issue.number": null, + "pull.number": 2, + "id": 3740938746, + "uri": "https://github.com/epriestley/poems/pull/2#issuecomment-194282800", + "title.full": "GitHub epriestley/poems Pull Request #2 (Comment)", + "comment": "wub wub", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt new file mode 100644 index 0000000000..112b15bb9c --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt @@ -0,0 +1,103 @@ +{ + "id": "3733510485", + "type": "IssueCommentEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "created", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 1, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-08T00:41:22Z", + "closed_at": null, + "body": "OK" + }, + "comment": { + "url": "https://api.github.com/repos/epriestley/poems/issues/comments/193528669", + "html_url": "https://github.com/epriestley/poems/issues/1#issuecomment-193528669", + "issue_url": "https://api.github.com/repos/epriestley/poems/issues/1", + "id": 193528669, + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2016-03-08T00:41:22Z", + "updated_at": "2016-03-08T00:41:22Z", + "body": "comment on issue" + } + }, + "public": true, + "created_at": "2016-03-08T00:41:22Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 3733510485, + "uri": "https://github.com/epriestley/poems/issues/1#issuecomment-193528669", + "title.full": "GitHub epriestley/poems Issue #1 (Comment)", + "comment": "comment on issue", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.closed.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.closed.txt new file mode 100644 index 0000000000..b02827202b --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.closed.txt @@ -0,0 +1,74 @@ +{ + "id": "3740905151", + "type": "IssuesEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "closed", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "closed", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 2, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T12:43:48Z", + "closed_at": "2016-03-09T12:43:48Z", + "body": "OK" + } + }, + "public": true, + "created_at": "2016-03-09T12:43:48Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 3740905151, + "uri": "https://github.com/epriestley/poems/issues/1#event-3740905151", + "title.full": "GitHub epriestley/poems Issue #1 (Closed)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.opened.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.opened.txt new file mode 100644 index 0000000000..59ab8978bc --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.opened.txt @@ -0,0 +1,74 @@ +{ + "id": "3733509737", + "type": "IssuesEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "opened", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 0, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-08T00:41:08Z", + "closed_at": null, + "body": "OK" + } + }, + "public": true, + "created_at": "2016-03-08T00:41:08Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 3733509737, + "uri": "https://github.com/epriestley/poems/issues/1#event-3733509737", + "title.full": "GitHub epriestley/poems Issue #1 (Created)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.reopened.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.reopened.txt new file mode 100644 index 0000000000..aa27834884 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.reopened.txt @@ -0,0 +1,74 @@ +{ + "id": "3740908680", + "type": "IssuesEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "reopened", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 3, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T12:44:49Z", + "closed_at": null, + "body": "OK" + } + }, + "public": true, + "created_at": "2016-03-09T12:44:49Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1, + "id": 3740908680, + "uri": "https://github.com/epriestley/poems/issues/1#event-3740908680", + "title.full": "GitHub epriestley/poems Issue #1 (Reopened)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/PullRequestEvent.opened.txt b/src/applications/nuance/github/__tests__/repositoryevents/PullRequestEvent.opened.txt new file mode 100644 index 0000000000..1c863b2554 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/PullRequestEvent.opened.txt @@ -0,0 +1,338 @@ +{ + "id": "3740936638", + "type": "PullRequestEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "opened", + "number": 2, + "pull_request": { + "url": "https://api.github.com/repos/epriestley/poems/pulls/2", + "id": 62223852, + "html_url": "https://github.com/epriestley/poems/pull/2", + "diff_url": "https://github.com/epriestley/poems/pull/2.diff", + "patch_url": "https://github.com/epriestley/poems/pull/2.patch", + "issue_url": "https://api.github.com/repos/epriestley/poems/issues/2", + "number": 2, + "state": "open", + "locked": false, + "title": "Please Merge Quack2 into Feature", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "body": "", + "created_at": "2016-03-09T12:52:31Z", + "updated_at": "2016-03-09T12:52:31Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "milestone": null, + "commits_url": "https://api.github.com/repos/epriestley/poems/pulls/2/commits", + "review_comments_url": "https://api.github.com/repos/epriestley/poems/pulls/2/comments", + "review_comment_url": "https://api.github.com/repos/epriestley/poems/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/2/comments", + "statuses_url": "https://api.github.com/repos/epriestley/poems/statuses/6cf5f6d0c8c06c4c73b8783666d9b3ecce138244", + "head": { + "label": "epriestley:feature", + "ref": "feature", + "sha": "6cf5f6d0c8c06c4c73b8783666d9b3ecce138244", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 14627834, + "name": "poems", + "full_name": "epriestley/poems", + "owner": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/epriestley/poems", + "description": "Poems (Mirror)", + "fork": false, + "url": "https://api.github.com/repos/epriestley/poems", + "forks_url": "https://api.github.com/repos/epriestley/poems/forks", + "keys_url": "https://api.github.com/repos/epriestley/poems/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/epriestley/poems/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/epriestley/poems/teams", + "hooks_url": "https://api.github.com/repos/epriestley/poems/hooks", + "issue_events_url": "https://api.github.com/repos/epriestley/poems/issues/events{/number}", + "events_url": "https://api.github.com/repos/epriestley/poems/events", + "assignees_url": "https://api.github.com/repos/epriestley/poems/assignees{/user}", + "branches_url": "https://api.github.com/repos/epriestley/poems/branches{/branch}", + "tags_url": "https://api.github.com/repos/epriestley/poems/tags", + "blobs_url": "https://api.github.com/repos/epriestley/poems/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/epriestley/poems/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/epriestley/poems/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/epriestley/poems/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/epriestley/poems/statuses/{sha}", + "languages_url": "https://api.github.com/repos/epriestley/poems/languages", + "stargazers_url": "https://api.github.com/repos/epriestley/poems/stargazers", + "contributors_url": "https://api.github.com/repos/epriestley/poems/contributors", + "subscribers_url": "https://api.github.com/repos/epriestley/poems/subscribers", + "subscription_url": "https://api.github.com/repos/epriestley/poems/subscription", + "commits_url": "https://api.github.com/repos/epriestley/poems/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/epriestley/poems/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/epriestley/poems/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/epriestley/poems/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/epriestley/poems/contents/{+path}", + "compare_url": "https://api.github.com/repos/epriestley/poems/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/epriestley/poems/merges", + "archive_url": "https://api.github.com/repos/epriestley/poems/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/epriestley/poems/downloads", + "issues_url": "https://api.github.com/repos/epriestley/poems/issues{/number}", + "pulls_url": "https://api.github.com/repos/epriestley/poems/pulls{/number}", + "milestones_url": "https://api.github.com/repos/epriestley/poems/milestones{/number}", + "notifications_url": "https://api.github.com/repos/epriestley/poems/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/epriestley/poems/labels{/name}", + "releases_url": "https://api.github.com/repos/epriestley/poems/releases{/id}", + "deployments_url": "https://api.github.com/repos/epriestley/poems/deployments", + "created_at": "2013-11-22T19:47:42Z", + "updated_at": "2016-01-21T17:10:27Z", + "pushed_at": "2016-01-21T17:10:21Z", + "git_url": "git://github.com/epriestley/poems.git", + "ssh_url": "git@github.com:epriestley/poems.git", + "clone_url": "https://github.com/epriestley/poems.git", + "svn_url": "https://github.com/epriestley/poems", + "homepage": null, + "size": 715, + "stargazers_count": 9, + "watchers_count": 9, + "language": "PHP", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 9, + "default_branch": "master" + } + }, + "base": { + "label": "epriestley:quack2", + "ref": "quack2", + "sha": "5a9c51e86615f6e1097b2a4a73ef0fe75981c1dd", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 14627834, + "name": "poems", + "full_name": "epriestley/poems", + "owner": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/epriestley/poems", + "description": "Poems (Mirror)", + "fork": false, + "url": "https://api.github.com/repos/epriestley/poems", + "forks_url": "https://api.github.com/repos/epriestley/poems/forks", + "keys_url": "https://api.github.com/repos/epriestley/poems/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/epriestley/poems/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/epriestley/poems/teams", + "hooks_url": "https://api.github.com/repos/epriestley/poems/hooks", + "issue_events_url": "https://api.github.com/repos/epriestley/poems/issues/events{/number}", + "events_url": "https://api.github.com/repos/epriestley/poems/events", + "assignees_url": "https://api.github.com/repos/epriestley/poems/assignees{/user}", + "branches_url": "https://api.github.com/repos/epriestley/poems/branches{/branch}", + "tags_url": "https://api.github.com/repos/epriestley/poems/tags", + "blobs_url": "https://api.github.com/repos/epriestley/poems/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/epriestley/poems/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/epriestley/poems/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/epriestley/poems/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/epriestley/poems/statuses/{sha}", + "languages_url": "https://api.github.com/repos/epriestley/poems/languages", + "stargazers_url": "https://api.github.com/repos/epriestley/poems/stargazers", + "contributors_url": "https://api.github.com/repos/epriestley/poems/contributors", + "subscribers_url": "https://api.github.com/repos/epriestley/poems/subscribers", + "subscription_url": "https://api.github.com/repos/epriestley/poems/subscription", + "commits_url": "https://api.github.com/repos/epriestley/poems/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/epriestley/poems/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/epriestley/poems/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/epriestley/poems/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/epriestley/poems/contents/{+path}", + "compare_url": "https://api.github.com/repos/epriestley/poems/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/epriestley/poems/merges", + "archive_url": "https://api.github.com/repos/epriestley/poems/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/epriestley/poems/downloads", + "issues_url": "https://api.github.com/repos/epriestley/poems/issues{/number}", + "pulls_url": "https://api.github.com/repos/epriestley/poems/pulls{/number}", + "milestones_url": "https://api.github.com/repos/epriestley/poems/milestones{/number}", + "notifications_url": "https://api.github.com/repos/epriestley/poems/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/epriestley/poems/labels{/name}", + "releases_url": "https://api.github.com/repos/epriestley/poems/releases{/id}", + "deployments_url": "https://api.github.com/repos/epriestley/poems/deployments", + "created_at": "2013-11-22T19:47:42Z", + "updated_at": "2016-01-21T17:10:27Z", + "pushed_at": "2016-01-21T17:10:21Z", + "git_url": "git://github.com/epriestley/poems.git", + "ssh_url": "git@github.com:epriestley/poems.git", + "clone_url": "https://github.com/epriestley/poems.git", + "svn_url": "https://github.com/epriestley/poems", + "homepage": null, + "size": 715, + "stargazers_count": 9, + "watchers_count": 9, + "language": "PHP", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 9, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/epriestley/poems/pulls/2" + }, + "html": { + "href": "https://github.com/epriestley/poems/pull/2" + }, + "issue": { + "href": "https://api.github.com/repos/epriestley/poems/issues/2" + }, + "comments": { + "href": "https://api.github.com/repos/epriestley/poems/issues/2/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/epriestley/poems/pulls/2/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/epriestley/poems/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/epriestley/poems/pulls/2/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/epriestley/poems/statuses/6cf5f6d0c8c06c4c73b8783666d9b3ecce138244" + } + }, + "merged": false, + "mergeable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "commits": 26, + "additions": 26, + "deletions": 0, + "changed_files": 1 + } + }, + "public": true, + "created_at": "2016-03-09T12:52:31Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": true, + "issue.number": null, + "pull.number": 2, + "id": 3740936638, + "uri": "https://github.com/epriestley/poems/pull/2#event-3740936638", + "title.full": "GitHub epriestley/poems Pull Request #2 (Created)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/PushEvent.txt b/src/applications/nuance/github/__tests__/repositoryevents/PushEvent.txt new file mode 100644 index 0000000000..67133676e7 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/PushEvent.txt @@ -0,0 +1,49 @@ +{ + "id": "3498724127", + "type": "PushEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "push_id": 924333172, + "size": 1, + "distinct_size": 1, + "ref": "refs/heads/master", + "head": "c829132d37c4c1da80d319942a5a1e500632b52f", + "before": "d8262dc45f0bd79c06571c6851d47efaeb6b599b", + "commits": [ + { + "sha": "c829132d37c4c1da80d319942a5a1e500632b52f", + "author": { + "email": "git@epriestley.com", + "name": "epriestley" + }, + "message": "Put 16K files in a single directory", + "distinct": true, + "url": "https://api.github.com/repos/epriestley/poems/commits/c829132d37c4c1da80d319942a5a1e500632b52f" + } + ] + }, + "public": true, + "created_at": "2016-01-06T11:21:59Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": false, + "issue.number": null, + "id": 3498724127, + "uri": "https://github.com/epriestley/poems/commits/c829132d37c4c1da80d319942a5a1e500632b52f", + "title.full": "GitHub epriestley/poems Branch master (Pushed: c829132d37c4)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/WatchEvent.started.txt b/src/applications/nuance/github/__tests__/repositoryevents/WatchEvent.started.txt new file mode 100644 index 0000000000..51c0550ccc --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/WatchEvent.started.txt @@ -0,0 +1,33 @@ +{ + "id": "3740950917", + "type": "WatchEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "started" + }, + "public": true, + "created_at": "2016-03-09T12:56:28Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": false, + "issue.number": null, + "pull.number": null, + "id": 3740950917, + "uri": null, + "title.full": "GitHub epriestley/poems User epriestley (Watched)", + "actor.id": 102631 +} diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php new file mode 100644 index 0000000000..3fef8b3da7 --- /dev/null +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -0,0 +1,445 @@ +newRawEvent($item)->getEventFullTitle(); + } + + public function canUpdateItems() { + return true; + } + + protected function updateItemFromSource(NuanceItem $item) { + $viewer = $this->getViewer(); + $is_dirty = false; + + $xobj = $this->reloadExternalObject($item); + if ($xobj) { + $item->setItemProperty('doorkeeper.xobj.phid', $xobj->getPHID()); + $is_dirty = true; + } + + $actor = $this->reloadExternalActor($item); + if ($actor) { + $item->setRequestorPHID($actor->getPHID()); + $is_dirty = true; + } + + if ($item->getStatus() == NuanceItem::STATUS_IMPORTING) { + $item->setStatus(NuanceItem::STATUS_ROUTING); + $is_dirty = true; + } + + if ($is_dirty) { + $item->save(); + } + } + + private function getDoorkeeperActorRef(NuanceItem $item) { + $raw = $this->newRawEvent($item); + + $user_id = $raw->getActorGitHubUserID(); + if (!$user_id) { + return null; + } + + $ref_type = DoorkeeperBridgeGitHubUser::OBJTYPE_GITHUB_USER; + + return $this->newDoorkeeperRef() + ->setObjectType($ref_type) + ->setObjectID($user_id); + } + + private function getDoorkeeperRef(NuanceItem $item) { + $raw = $this->newRawEvent($item); + + $full_repository = $raw->getRepositoryFullName(); + if (!strlen($full_repository)) { + return null; + } + + if ($raw->isIssueEvent()) { + $ref_type = DoorkeeperBridgeGitHubIssue::OBJTYPE_GITHUB_ISSUE; + $issue_number = $raw->getIssueNumber(); + $full_ref = "{$full_repository}#{$issue_number}"; + } else { + return null; + } + + return $this->newDoorkeeperRef() + ->setObjectType($ref_type) + ->setObjectID($full_ref); + } + + private function newDoorkeeperRef() { + return id(new DoorkeeperObjectRef()) + ->setApplicationType(DoorkeeperBridgeGitHub::APPTYPE_GITHUB) + ->setApplicationDomain(DoorkeeperBridgeGitHub::APPDOMAIN_GITHUB); + } + + private function reloadExternalObject(NuanceItem $item, $local = false) { + $ref = $this->getDoorkeeperRef($item); + if (!$ref) { + return null; + } + + $xobj = $this->reloadExternalRef($item, $ref, $local); + + if ($xobj) { + $this->externalObject = $xobj; + } + + return $xobj; + } + + private function reloadExternalActor(NuanceItem $item, $local = false) { + $ref = $this->getDoorkeeperActorRef($item); + if (!$ref) { + return null; + } + + $xobj = $this->reloadExternalRef($item, $ref, $local); + + if ($xobj) { + $this->externalActor = $xobj; + } + + return $xobj; + } + + private function reloadExternalRef( + NuanceItem $item, + DoorkeeperObjectRef $ref, + $local) { + + $source = $item->getSource(); + $token = $source->getSourceProperty('github.token'); + $token = new PhutilOpaqueEnvelope($token); + + $viewer = $this->getViewer(); + + $ref = id(new DoorkeeperImportEngine()) + ->setViewer($viewer) + ->setRefs(array($ref)) + ->setThrowOnMissingLink(true) + ->setContextProperty('github.token', $token) + ->needLocalOnly($local) + ->executeOne(); + + if ($ref->getSyncFailed()) { + $xobj = null; + } else { + $xobj = $ref->getExternalObject(); + } + + return $xobj; + } + + private function getExternalObject(NuanceItem $item) { + if ($this->externalObject === null) { + $xobj = $this->reloadExternalObject($item, $local = true); + if ($xobj) { + $this->externalObject = $xobj; + } else { + $this->externalObject = false; + } + } + + if ($this->externalObject) { + return $this->externalObject; + } + + return null; + } + + private function getExternalActor(NuanceItem $item) { + if ($this->externalActor === null) { + $xobj = $this->reloadExternalActor($item, $local = true); + if ($xobj) { + $this->externalActor = $xobj; + } else { + $this->externalActor = false; + } + } + + if ($this->externalActor) { + return $this->externalActor; + } + + return null; + } + + private function newRawEvent(NuanceItem $item) { + $type = $item->getItemProperty('api.type'); + $raw = $item->getItemProperty('api.raw', array()); + + return NuanceGitHubRawEvent::newEvent($type, $raw); + } + + public function getItemActions(NuanceItem $item) { + $actions = array(); + + $xobj = $this->getExternalObject($item); + if ($xobj) { + $actions[] = $this->newItemAction($item, 'reload') + ->setName(pht('Reload from GitHub')) + ->setIcon('fa-refresh') + ->setWorkflow(true) + ->setRenderAsForm(true); + } + + $actions[] = $this->newItemAction($item, 'sync') + ->setName(pht('Import to Maniphest')) + ->setIcon('fa-anchor') + ->setWorkflow(true) + ->setRenderAsForm(true); + + $actions[] = $this->newItemAction($item, 'raw') + ->setName(pht('View Raw Event')) + ->setWorkflow(true) + ->setIcon('fa-code'); + + return $actions; + } + + public function getItemCurtainPanels(NuanceItem $item) { + $viewer = $this->getViewer(); + + $panels = array(); + + $xobj = $this->getExternalObject($item); + if ($xobj) { + $xobj_phid = $xobj->getPHID(); + + $task = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withBridgedObjectPHIDs(array($xobj_phid)) + ->executeOne(); + if ($task) { + $panels[] = $this->newCurtainPanel($item) + ->setHeaderText(pht('Imported As')) + ->appendChild($viewer->renderHandle($task->getPHID())); + } + } + + $xactor = $this->getExternalActor($item); + if ($xactor) { + $panels[] = $this->newCurtainPanel($item) + ->setHeaderText(pht('GitHub Actor')) + ->appendChild($viewer->renderHandle($xactor->getPHID())); + } + + return $panels; + } + + protected function handleAction(NuanceItem $item, $action) { + $viewer = $this->getViewer(); + $controller = $this->getController(); + + switch ($action) { + case 'raw': + $raw = array( + 'api.type' => $item->getItemProperty('api.type'), + 'api.raw' => $item->getItemProperty('api.raw'), + ); + + $raw_output = id(new PhutilJSON())->encodeFormatted($raw); + + $raw_box = id(new AphrontFormTextAreaControl()) + ->setCustomClass('PhabricatorMonospaced') + ->setLabel(pht('Raw Event')) + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) + ->setValue($raw_output); + + $form = id(new AphrontFormView()) + ->appendChild($raw_box); + + return $controller->newDialog() + ->setWidth(AphrontDialogView::WIDTH_FULL) + ->setTitle(pht('GitHub Raw Event')) + ->appendForm($form) + ->addCancelButton($item->getURI(), pht('Done')); + case 'sync': + case 'reload': + $item->issueCommand($viewer->getPHID(), $action); + return id(new AphrontRedirectResponse())->setURI($item->getURI()); + } + + return null; + } + + protected function newItemView(NuanceItem $item) { + $content = array(); + + $content[] = $this->newGitHubEventItemPropertyBox($item); + + return $content; + } + + private function newGitHubEventItemPropertyBox($item) { + $viewer = $this->getViewer(); + + $property_list = id(new PHUIPropertyListView()) + ->setViewer($viewer); + + $event = $this->newRawEvent($item); + + $property_list->addProperty( + pht('GitHub Event ID'), + $event->getID()); + + $event_uri = $event->getURI(); + if ($event_uri && PhabricatorEnv::isValidRemoteURIForLink($event_uri)) { + $event_uri = phutil_tag( + 'a', + array( + 'href' => $event_uri, + ), + $event_uri); + } + + if ($event_uri) { + $property_list->addProperty( + pht('GitHub Event URI'), + $event_uri); + } + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Event Properties')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($property_list); + } + + protected function handleCommand( + NuanceItem $item, + NuanceItemCommand $command) { + + $action = $command->getCommand(); + switch ($action) { + case 'sync': + return $this->syncItem($item, $command); + case 'reload': + $this->reloadExternalObject($item); + return true; + } + + return null; + } + + private function syncItem( + NuanceItem $item, + NuanceItemCommand $command) { + + $xobj_phid = $item->getItemProperty('doorkeeper.xobj.phid'); + if (!$xobj_phid) { + throw new Exception( + pht( + 'Unable to sync: no external object PHID.')); + } + + // TODO: Write some kind of marker to prevent double-synchronization. + + $viewer = $this->getViewer(); + + $xobj = id(new DoorkeeperExternalObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($xobj_phid)) + ->executeOne(); + if (!$xobj) { + throw new Exception( + pht( + 'Unable to sync: failed to load object "%s".', + $xobj_phid)); + } + + $acting_as_phid = $this->getActingAsPHID($item); + + $xactions = array(); + + $task = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withBridgedObjectPHIDs(array($xobj_phid)) + ->executeOne(); + if (!$task) { + $task = ManiphestTask::initializeNewTask($viewer) + ->setAuthorPHID($acting_as_phid) + ->setBridgedObjectPHID($xobj_phid); + + $title = $xobj->getProperty('task.title'); + if (!strlen($title)) { + $title = pht('Nuance Item %d Task', $item->getID()); + } + + $description = $xobj->getProperty('task.description'); + $created = $xobj->getProperty('task.created'); + $state = $xobj->getProperty('task.state'); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setNewValue($title) + ->setDateCreated($created); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setNewValue($description) + ->setDateCreated($created); + + $task->setDateCreated($created); + + // TODO: Synchronize state. + } + + $event = $this->newRawEvent($item); + $comment = $event->getComment(); + if (strlen($comment)) { + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) + ->attachComment( + id(new ManiphestTransactionComment()) + ->setContent($comment)); + } + + $agent_phid = $command->getAuthorPHID(); + $source = $this->newContentSource($item, $agent_phid); + + $editor = id(new ManiphestTransactionEditor()) + ->setActor($viewer) + ->setActingAsPHID($acting_as_phid) + ->setContentSource($source) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $xactions = $editor->applyTransactions($task, $xactions); + + return array( + 'objectPHID' => $task->getPHID(), + 'xactionPHIDs' => mpull($xactions, 'getPHID'), + ); + } + + protected function getActingAsPHID(NuanceItem $item) { + $actor_phid = $item->getRequestorPHID(); + + if ($actor_phid) { + return $actor_phid; + } + + return parent::getActingAsPHID($item); + } + +} diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php new file mode 100644 index 0000000000..74555494d0 --- /dev/null +++ b/src/applications/nuance/item/NuanceItemType.php @@ -0,0 +1,162 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setController(PhabricatorController $controller) { + $this->controller = $controller; + return $this; + } + + public function getController() { + return $this->controller; + } + + public function canUpdateItems() { + return false; + } + + final public function buildItemView(NuanceItem $item) { + return $this->newItemView($item); + } + + protected function newItemView(NuanceItem $item) { + return null; + } + + public function getItemTypeDisplayIcon() { + return null; + } + + public function getItemActions(NuanceItem $item) { + return array(); + } + + public function getItemCurtainPanels(NuanceItem $item) { + return array(); + } + + abstract public function getItemTypeDisplayName(); + abstract public function getItemDisplayName(NuanceItem $item); + + final public function updateItem(NuanceItem $item) { + if (!$this->canUpdateItems()) { + throw new Exception( + pht( + 'This item type ("%s", of class "%s") can not update items.', + $this->getItemTypeConstant(), + get_class($this))); + } + + $this->updateItemFromSource($item); + } + + protected function updateItemFromSource(NuanceItem $item) { + throw new PhutilMethodNotImplementedException(); + } + + final public function getItemTypeConstant() { + return $this->getPhobjectClassConstant('ITEMTYPE', 64); + } + + final public static function getAllItemTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getItemTypeConstant') + ->execute(); + } + + final protected function newItemAction(NuanceItem $item, $key) { + $id = $item->getID(); + $action_uri = "/nuance/item/action/{$id}/{$key}/"; + + return id(new PhabricatorActionView()) + ->setHref($action_uri); + } + + final protected function newCurtainPanel(NuanceItem $item) { + return id(new PHUICurtainPanelView()); + } + + final public function buildActionResponse(NuanceItem $item, $action) { + $response = $this->handleAction($item, $action); + + if ($response === null) { + return new Aphront404Response(); + } + + return $response; + } + + protected function handleAction(NuanceItem $item, $action) { + return null; + } + + final public function applyCommand( + NuanceItem $item, + NuanceItemCommand $command) { + + $result = $this->handleCommand($item, $command); + + if ($result === null) { + return; + } + + $xaction = id(new NuanceItemTransaction()) + ->setTransactionType(NuanceItemTransaction::TYPE_COMMAND) + ->setNewValue( + array( + 'command' => $command->getCommand(), + 'parameters' => $command->getParameters(), + 'result' => $result, + )); + + $viewer = $this->getViewer(); + + // TODO: Maybe preserve the actor's original content source? + $source = PhabricatorContentSource::newForSource( + PhabricatorDaemonContentSource::SOURCECONST); + + $editor = id(new NuanceItemEditor()) + ->setActor($viewer) + ->setActingAsPHID($command->getAuthorPHID()) + ->setContentSource($source) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->applyTransactions($item, array($xaction)); + } + + protected function handleCommand( + NuanceItem $item, + NuanceItemCommand $command) { + return null; + } + + final protected function newContentSource( + NuanceItem $item, + $agent_phid) { + return PhabricatorContentSource::newForSource( + NuanceContentSource::SOURCECONST, + array( + 'itemPHID' => $item->getPHID(), + 'agentPHID' => $agent_phid, + )); + } + + protected function getActingAsPHID(NuanceItem $item) { + return id(new PhabricatorNuanceApplication())->getPHID(); + } + +} diff --git a/src/applications/nuance/management/NuanceManagementImportWorkflow.php b/src/applications/nuance/management/NuanceManagementImportWorkflow.php new file mode 100644 index 0000000000..b14537f885 --- /dev/null +++ b/src/applications/nuance/management/NuanceManagementImportWorkflow.php @@ -0,0 +1,85 @@ +setName('import') + ->setExamples('**import** --source __source__ [__options__]') + ->setSynopsis(pht('Import data from a source.')) + ->setArguments( + array( + array( + 'name' => 'source', + 'param' => 'source', + 'help' => pht('Choose which source to import.'), + ), + array( + 'name' => 'cursor', + 'param' => 'cursor', + 'help' => pht('Import only a particular cursor.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $source = $this->loadSource($args, 'source'); + + $definition = $source->getDefinition() + ->setViewer($this->getViewer()) + ->setSource($source); + + if (!$definition->hasImportCursors()) { + throw new PhutilArgumentUsageException( + pht( + 'This source ("%s") does not expose import cursors.', + $source->getName())); + } + + $cursors = $definition->getImportCursors(); + if (!$cursors) { + throw new PhutilArgumentUsageException( + pht( + 'This source ("%s") does not have any import cursors.', + $source->getName())); + } + + $select = $args->getArg('cursor'); + if (strlen($select)) { + if (empty($cursors[$select])) { + throw new PhutilArgumentUsageException( + pht( + 'This source ("%s") does not have a "%s" cursor. Available '. + 'cursors: %s.', + $source->getName(), + $select, + implode(', ', array_keys($cursors)))); + } else { + echo tsprintf( + "%s\n", + pht( + 'Importing cursor "%s" only.', + $select)); + $cursors = array_select_keys($cursors, array($select)); + } + } else { + echo tsprintf( + "%s\n", + pht( + 'Importing all cursors: %s.', + implode(', ', array_keys($cursors)))); + + echo tsprintf( + "%s\n", + pht('(Use --cursor to import only a particular cursor.)')); + } + + foreach ($cursors as $cursor) { + $cursor->importFromSource(); + } + + return 0; + } + +} diff --git a/src/applications/nuance/management/NuanceManagementUpdateWorkflow.php b/src/applications/nuance/management/NuanceManagementUpdateWorkflow.php new file mode 100644 index 0000000000..10878770f6 --- /dev/null +++ b/src/applications/nuance/management/NuanceManagementUpdateWorkflow.php @@ -0,0 +1,30 @@ +setName('update') + ->setExamples('**update** --item __item__ [__options__]') + ->setSynopsis(pht('Update or route an item.')) + ->setArguments( + array( + array( + 'name' => 'item', + 'param' => 'item', + 'help' => pht('Choose which item to route.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $item = $this->loadItem($args, 'item'); + + PhabricatorWorker::setRunAllTasksInProcess(true); + $item->scheduleUpdate(); + + return 0; + } + +} diff --git a/src/applications/nuance/management/NuanceManagementWorkflow.php b/src/applications/nuance/management/NuanceManagementWorkflow.php new file mode 100644 index 0000000000..2c7eb6b662 --- /dev/null +++ b/src/applications/nuance/management/NuanceManagementWorkflow.php @@ -0,0 +1,117 @@ +getArg($key); + if (!strlen($source)) { + throw new PhutilArgumentUsageException( + pht( + 'Specify a source with %s.', + '--'.$key)); + } + + $query = id(new NuanceSourceQuery()) + ->setViewer($this->getViewer()) + ->setRaisePolicyExceptions(true); + + $type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN; + + if (ctype_digit($source)) { + $kind = 'id'; + $query->withIDs(array($source)); + } else if (phid_get_type($source) !== $type_unknown) { + $kind = 'phid'; + $query->withPHIDs($source); + } else { + $kind = 'name'; + $query->withNameNgrams($source); + } + + $sources = $query->execute(); + + if (!$sources) { + switch ($kind) { + case 'id': + $message = pht( + 'No source exists with ID "%s".', + $source); + break; + case 'phid': + $message = pht( + 'No source exists with PHID "%s".', + $source); + break; + default: + $message = pht( + 'No source exists with a name matching "%s".', + $source); + break; + } + + throw new PhutilArgumentUsageException($message); + } else if (count($sources) > 1) { + $message = pht( + 'More than one source matches "%s". Choose a narrower query, or '. + 'use an ID or PHID to select a source. Matching sources: %s.', + $source, + implode(', ', mpull($sources, 'getName'))); + + throw new PhutilArgumentUsageException($message); + } + + return head($sources); + } + + protected function loadITem(PhutilArgumentParser $argv, $key) { + $item = $argv->getArg($key); + if (!strlen($item)) { + throw new PhutilArgumentUsageException( + pht( + 'Specify a item with %s.', + '--'.$key)); + } + + $query = id(new NuanceItemQuery()) + ->setViewer($this->getViewer()) + ->setRaisePolicyExceptions(true); + + $type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN; + + if (ctype_digit($item)) { + $kind = 'id'; + $query->withIDs(array($item)); + } else if (phid_get_type($item) !== $type_unknown) { + $kind = 'phid'; + $query->withPHIDs($item); + } else { + throw new PhutilArgumentUsageException( + pht( + 'Specify the ID or PHID of an item to update. Parameter "%s" '. + 'is not an ID or PHID.', + $item)); + } + + $items = $query->execute(); + + if (!$items) { + switch ($kind) { + case 'id': + $message = pht( + 'No item exists with ID "%s".', + $item); + break; + case 'phid': + $message = pht( + 'No item exists with PHID "%s".', + $item); + break; + } + + throw new PhutilArgumentUsageException($message); + } + + return head($items); + } +} diff --git a/src/applications/nuance/phid/NuanceRequestorPHIDType.php b/src/applications/nuance/phid/NuanceImportCursorPHIDType.php similarity index 54% rename from src/applications/nuance/phid/NuanceRequestorPHIDType.php rename to src/applications/nuance/phid/NuanceImportCursorPHIDType.php index a2442d3b48..9d1f816a71 100644 --- a/src/applications/nuance/phid/NuanceRequestorPHIDType.php +++ b/src/applications/nuance/phid/NuanceImportCursorPHIDType.php @@ -1,15 +1,15 @@ withPHIDs($phids); } @@ -31,12 +31,7 @@ final class NuanceRequestorPHIDType extends PhabricatorPHIDType { $viewer = $query->getViewer(); foreach ($handles as $phid => $handle) { - $requestor = $objects[$phid]; - - // TODO: This is currently useless and should be far more informative. - $handle->setName(pht('Requestor %d', $requestor->getID())); - - $handle->setURI($requestor->getURI()); + $item = $objects[$phid]; } } diff --git a/src/applications/nuance/phid/NuanceItemPHIDType.php b/src/applications/nuance/phid/NuanceItemPHIDType.php index f401c63594..ee51633ea9 100644 --- a/src/applications/nuance/phid/NuanceItemPHIDType.php +++ b/src/applications/nuance/phid/NuanceItemPHIDType.php @@ -33,7 +33,7 @@ final class NuanceItemPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $item = $objects[$phid]; - $handle->setName($item->getLabel($viewer)); + $handle->setName($item->getItemDisplayName()); $handle->setURI($item->getURI()); } } diff --git a/src/applications/nuance/query/NuanceRequestorQuery.php b/src/applications/nuance/query/NuanceImportCursorDataQuery.php similarity index 59% rename from src/applications/nuance/query/NuanceRequestorQuery.php rename to src/applications/nuance/query/NuanceImportCursorDataQuery.php index 49e4e61e94..ae451abfb9 100644 --- a/src/applications/nuance/query/NuanceRequestorQuery.php +++ b/src/applications/nuance/query/NuanceImportCursorDataQuery.php @@ -1,10 +1,11 @@ ids = $ids; @@ -16,17 +17,29 @@ final class NuanceRequestorQuery return $this; } - public function newObject() { - return new NuanceRequestor(); + public function withSourcePHIDs(array $source_phids) { + $this->sourcePHIDs = $source_phids; + return $this; + } + + public function newResultObject() { + return new NuanceImportCursorData(); } protected function loadPage() { - return $this->loadStandardPage($this->newObject()); + return $this->loadStandardPage($this->newResultObject()); } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); + if ($this->sourcePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'sourcePHID IN (%Ls)', + $this->sourcePHIDs); + } + if ($this->ids !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/nuance/query/NuanceItemCommandQuery.php b/src/applications/nuance/query/NuanceItemCommandQuery.php new file mode 100644 index 0000000000..cb20610187 --- /dev/null +++ b/src/applications/nuance/query/NuanceItemCommandQuery.php @@ -0,0 +1,47 @@ +ids = $ids; + return $this; + } + + public function withItemPHIDs(array $item_phids) { + $this->itemPHIDs = $item_phids; + return $this; + } + + public function newResultObject() { + return new NuanceItemCommand(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->itemPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'itemPHID IN (%Ls)', + $this->itemPHIDs); + } + + return $where; + } + +} diff --git a/src/applications/nuance/query/NuanceItemQuery.php b/src/applications/nuance/query/NuanceItemQuery.php index fbcac6e5b7..5bb0ec70ec 100644 --- a/src/applications/nuance/query/NuanceItemQuery.php +++ b/src/applications/nuance/query/NuanceItemQuery.php @@ -6,6 +6,9 @@ final class NuanceItemQuery private $ids; private $phids; private $sourcePHIDs; + private $itemTypes; + private $itemKeys; + private $containerKeys; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +25,21 @@ final class NuanceItemQuery return $this; } + public function withItemTypes(array $item_types) { + $this->itemTypes = $item_types; + return $this; + } + + public function withItemKeys(array $item_keys) { + $this->itemKeys = $item_keys; + return $this; + } + + public function withItemContainerKeys(array $container_keys) { + $this->containerKeys = $container_keys; + return $this; + } + public function newResultObject() { return new NuanceItem(); } @@ -52,6 +70,17 @@ final class NuanceItemQuery $item->attachSource($source); } + $type_map = NuanceItemType::getAllItemTypes(); + foreach ($items as $key => $item) { + $type = idx($type_map, $item->getItemType()); + if (!$type) { + $this->didRejectResult($items[$key]); + unset($items[$key]); + continue; + } + $item->attachImplementation($type); + } + return $items; } @@ -79,6 +108,27 @@ final class NuanceItemQuery $this->phids); } + if ($this->itemTypes !== null) { + $where[] = qsprintf( + $conn, + 'itemType IN (%Ls)', + $this->itemTypes); + } + + if ($this->itemKeys !== null) { + $where[] = qsprintf( + $conn, + 'itemKey IN (%Ls)', + $this->itemKeys); + } + + if ($this->containerKeys !== null) { + $where[] = qsprintf( + $conn, + 'itemContainerKey IN (%Ls)', + $this->containerKeys); + } + return $where; } diff --git a/src/applications/nuance/query/NuanceItemSearchEngine.php b/src/applications/nuance/query/NuanceItemSearchEngine.php new file mode 100644 index 0000000000..2f0951b4e0 --- /dev/null +++ b/src/applications/nuance/query/NuanceItemSearchEngine.php @@ -0,0 +1,85 @@ +newQuery(); + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + ); + } + + protected function getURI($path) { + return '/nuance/item/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Items'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $items, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($items, 'NuanceItem'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($items as $item) { + $impl = $item->getImplementation(); + + $view = id(new PHUIObjectItemView()) + ->setObjectName(pht('Item %d', $item->getID())) + ->setHeader($item->getDisplayName()) + ->setHref($item->getURI()); + + $view->addIcon( + $impl->getItemTypeDisplayIcon(), + $impl->getItemTypeDisplayName()); + + $list->addItem($view); + } + + $result = new PhabricatorApplicationSearchResultView(); + $result->setObjectList($list); + $result->setNoDataString(pht('No items found.')); + + return $result; + } + +} diff --git a/src/applications/nuance/query/NuanceQueueQuery.php b/src/applications/nuance/query/NuanceQueueQuery.php index d50e393667..10f761d189 100644 --- a/src/applications/nuance/query/NuanceQueueQuery.php +++ b/src/applications/nuance/query/NuanceQueueQuery.php @@ -16,20 +16,12 @@ final class NuanceQueueQuery return $this; } + public function newResultObject() { + return new NuanceQueue(); + } + protected function loadPage() { - $table = new NuanceQueue(); - $conn = $table->establishConnection('r'); - - $data = queryfx_all( - $conn, - '%Q FROM %T %Q %Q %Q', - $this->buildSelectClause($conn), - $table->getTableName(), - $this->buildWhereClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { diff --git a/src/applications/nuance/query/NuanceQueueSearchEngine.php b/src/applications/nuance/query/NuanceQueueSearchEngine.php index 12259982f1..2f794c2a9c 100644 --- a/src/applications/nuance/query/NuanceQueueSearchEngine.php +++ b/src/applications/nuance/query/NuanceQueueSearchEngine.php @@ -11,21 +11,19 @@ final class NuanceQueueSearchEngine return pht('Nuance Queues'); } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - return $saved; + public function newQuery() { + return new NuanceQueueQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new NuanceQueueQuery()); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) {} + protected function buildCustomSearchFields() { + return array(); + } protected function getURI($path) { return '/nuance/queue/'.$path; diff --git a/src/applications/nuance/query/NuanceSourceQuery.php b/src/applications/nuance/query/NuanceSourceQuery.php index ee4b964ee3..907d9c314f 100644 --- a/src/applications/nuance/query/NuanceSourceQuery.php +++ b/src/applications/nuance/query/NuanceSourceQuery.php @@ -6,6 +6,8 @@ final class NuanceSourceQuery private $ids; private $phids; private $types; + private $isDisabled; + private $hasCursors; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,46 +24,113 @@ final class NuanceSourceQuery return $this; } - protected function loadPage() { - $table = new NuanceSource(); - $conn = $table->establishConnection('r'); - - $data = queryfx_all( - $conn, - '%Q FROM %T %Q %Q %Q', - $this->buildSelectClause($conn), - $table->getTableName(), - $this->buildWhereClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $table->loadAllFromArray($data); + public function withIsDisabled($disabled) { + $this->isDisabled = $disabled; + return $this; } + public function withHasImportCursors($has_cursors) { + $this->hasCursors = $has_cursors; + return $this; + } + + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new NuanceSourceNameNgrams(), + $ngrams); + } + + public function newResultObject() { + return new NuanceSource(); + } + + protected function getPrimaryTableAlias() { + return 'source'; + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function willFilterPage(array $sources) { + $all_types = NuanceSourceDefinition::getAllDefinitions(); + + foreach ($sources as $key => $source) { + $definition = idx($all_types, $source->getType()); + if (!$definition) { + $this->didRejectResult($source); + unset($sources[$key]); + continue; + } + $source->attachDefinition($definition); + } + + return $sources; + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->types !== null) { $where[] = qsprintf( $conn, - 'type IN (%Ls)', + 'source.type IN (%Ls)', $this->types); } if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'source.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'source.phid IN (%Ls)', $this->phids); } + if ($this->isDisabled !== null) { + $where[] = qsprintf( + $conn, + 'source.isDisabled = %d', + (int)$this->isDisabled); + } + + if ($this->hasCursors !== null) { + $cursor_types = array(); + + $definitions = NuanceSourceDefinition::getAllDefinitions(); + foreach ($definitions as $key => $definition) { + if ($definition->hasImportCursors()) { + $cursor_types[] = $key; + } + } + + if ($this->hasCursors) { + if (!$cursor_types) { + throw new PhabricatorEmptyQueryException(); + } else { + $where[] = qsprintf( + $conn, + 'source.type IN (%Ls)', + $cursor_types); + } + } else { + if (!$cursor_types) { + // Apply no constraint. + } else { + $where[] = qsprintf( + $conn, + 'source.type NOT IN (%Ls)', + $cursor_types); + } + } + } + return $where; } diff --git a/src/applications/nuance/query/NuanceSourceSearchEngine.php b/src/applications/nuance/query/NuanceSourceSearchEngine.php index 02a8b502fb..44f131aa1b 100644 --- a/src/applications/nuance/query/NuanceSourceSearchEngine.php +++ b/src/applications/nuance/query/NuanceSourceSearchEngine.php @@ -11,21 +11,28 @@ final class NuanceSourceSearchEngine return pht('Nuance Sources'); } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - return $saved; + public function newQuery() { + return new NuanceSourceQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new NuanceSourceQuery()); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) {} + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for sources by name substring.')), + ); + } protected function getURI($path) { return '/nuance/source/'.$path; diff --git a/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php b/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php new file mode 100644 index 0000000000..8768e35864 --- /dev/null +++ b/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php @@ -0,0 +1,31 @@ +setCursorKey('events.repository'), + id(new NuanceGitHubIssuesImportCursor()) + ->setCursorKey('events.issues'), + ); + } + +} diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php index cca5a19cd9..e7c7212590 100644 --- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php +++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php @@ -26,34 +26,6 @@ final class NuancePhabricatorFormSourceDefinition return $actions; } - public function updateItems() { - return null; - } - - protected function augmentEditForm( - AphrontFormView $form, - PhabricatorApplicationTransactionValidationException $ex = null) { - - /* TODO - add a box to allow for custom fields to be defined here, so that - * these NuanceSource objects made from this definition can be used to - * capture arbitrary data */ - - return $form; - } - - protected function buildTransactions(AphrontRequest $request) { - $transactions = parent::buildTransactions($request); - - // TODO -- as above - - return $transactions; - } - - public function renderView() {} - - public function renderListView() {} - - public function handleActionRequest(AphrontRequest $request) { $viewer = $request->getViewer(); @@ -66,12 +38,7 @@ final class NuancePhabricatorFormSourceDefinition $content_source = PhabricatorContentSource::newFromRequest($request); - $requestor = NuanceRequestor::newFromPhabricatorUser( - $viewer, - $content_source); - $item = $this->newItemFromProperties( - $requestor, $properties, $content_source); @@ -100,13 +67,6 @@ final class NuancePhabricatorFormSourceDefinition return $box; } - public function renderItemViewProperties( - PhabricatorUser $viewer, - NuanceItem $item, - PHUIPropertyListView $view) { - $this->renderItemCommonProperties($viewer, $item, $view); - } - public function renderItemEditProperties( PhabricatorUser $viewer, NuanceItem $item, diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index 4b37e39845..99d79f8f31 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -5,42 +5,31 @@ */ abstract class NuanceSourceDefinition extends Phobject { - private $actor; - private $sourceObject; + private $viewer; + private $source; - public function setActor(PhabricatorUser $actor) { - $this->actor = $actor; + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; return $this; } - public function getActor() { - return $this->actor; - } - - public function requireActor() { - $actor = $this->getActor(); - if (!$actor) { - throw new PhutilInvalidStateException('setActor'); + public function getViewer() { + if (!$this->viewer) { + throw new PhutilInvalidStateException('setViewer'); } - return $actor; + return $this->viewer; } - public function setSourceObject(NuanceSource $source) { - $source->setType($this->getSourceTypeConstant()); - $this->sourceObject = $source; + public function setSource(NuanceSource $source) { + $this->source = $source; return $this; } - public function getSourceObject() { - return $this->sourceObject; - } - - public function requireSourceObject() { - $source = $this->getSourceObject(); - if (!$source) { - throw new PhutilInvalidStateException('setSourceObject'); + public function getSource() { + if (!$this->source) { + throw new PhutilInvalidStateException('setSource'); } - return $source; + return $this->source; } public function getSourceViewActions(AphrontRequest $request) { @@ -54,6 +43,84 @@ abstract class NuanceSourceDefinition extends Phobject { ->execute(); } + public function hasImportCursors() { + return false; + } + + final public function getImportCursors() { + if (!$this->hasImportCursors()) { + throw new Exception( + pht('This source has no input cursors.')); + } + + $viewer = PhabricatorUser::getOmnipotentUser(); + $source = $this->getSource(); + $cursors = $this->newImportCursors(); + + $data = id(new NuanceImportCursorDataQuery()) + ->setViewer($viewer) + ->withSourcePHIDs(array($source->getPHID())) + ->execute(); + $data = mpull($data, null, 'getCursorKey'); + + $map = array(); + foreach ($cursors as $cursor) { + if (!($cursor instanceof NuanceImportCursor)) { + throw new Exception( + pht( + 'Source "%s" (of class "%s") returned an invalid value from '. + 'method "%s": all values must be objects of class "%s".', + $this->getName(), + get_class($this), + 'newImportCursors()', + 'NuanceImportCursor')); + } + + $key = $cursor->getCursorKey(); + if (!strlen($key)) { + throw new Exception( + pht( + 'Source "%s" (of class "%s") returned an import cursor with '. + 'a missing key from "%s". Each cursor must have a unique, '. + 'nonempty key.', + $this->getName(), + get_class($this), + 'newImportCursors()')); + } + + $other = idx($map, $key); + if ($other) { + throw new Exception( + pht( + 'Source "%s" (of class "%s") returned two cursors from method '. + '"%s" with the same key ("%s"). Each cursor must have a unique '. + 'key.', + $this->getName(), + get_class($this), + 'newImportCursors()', + $key)); + } + + $map[$key] = $cursor; + + $cursor_data = idx($data, $key); + if (!$cursor_data) { + $cursor_data = $cursor->newEmptyCursorData($source); + } + + $cursor + ->setViewer($viewer) + ->setSource($source) + ->setCursorData($cursor_data); + } + + return $map; + } + + protected function newImportCursors() { + throw new PhutilMethodNotImplementedException(); + } + /** * A human readable string like "Twitter" or "Phabricator Form". */ @@ -73,191 +140,22 @@ abstract class NuanceSourceDefinition extends Phobject { */ abstract public function getSourceTypeConstant(); - /** - * Code to create and update @{class:NuanceItem}s and - * @{class:NuanceRequestor}s via daemons goes here. - * - * If that does not make sense for the @{class:NuanceSource} you are - * defining, simply return null. For example, - * @{class:NuancePhabricatorFormSourceDefinition} since these are one-way - * contact forms. - */ - abstract public function updateItems(); - - private function loadSourceObjectPolicies( - PhabricatorUser $user, - NuanceSource $source) { - - $user = $this->requireActor(); - $source = $this->requireSourceObject(); - return id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($source) - ->execute(); + public function renderView() { + return null; } - final public function getEditTitle() { - $source = $this->requireSourceObject(); - if ($source->getPHID()) { - $title = pht('Edit "%s" source.', $source->getName()); - } else { - $title = pht('Create a new "%s" source.', $this->getName()); - } - - return $title; + public function renderListView() { + return null; } - final public function buildEditLayout(AphrontRequest $request) { - $actor = $this->requireActor(); - $source = $this->requireSourceObject(); - - $form_errors = array(); - $error_messages = array(); - $transactions = array(); - $validation_exception = null; - if ($request->isFormPost()) { - $transactions = $this->buildTransactions($request); - try { - $editor = id(new NuanceSourceEditor()) - ->setActor($actor) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->applyTransactions($source, $transactions); - - return id(new AphrontRedirectResponse()) - ->setURI($source->getURI()); - - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - } - - } - - $form = $this->renderEditForm($validation_exception); - $layout = id(new PHUIObjectBoxView()) - ->setHeaderText($this->getEditTitle()) - ->setValidationException($validation_exception) - ->setFormErrors($error_messages) - ->setForm($form); - - return $layout; - } - - /** - * Code to create a form to edit the @{class:NuanceItem} you are defining. - * - * return @{class:AphrontFormView} - */ - private function renderEditForm( - PhabricatorApplicationTransactionValidationException $ex = null) { - $user = $this->requireActor(); - $source = $this->requireSourceObject(); - $policies = $this->loadSourceObjectPolicies($user, $source); - $e_name = null; - if ($ex) { - $e_name = $ex->getShortMessage(NuanceSourceTransaction::TYPE_NAME); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setError($e_name) - ->setValue($source->getName())); - - $form = $this->augmentEditForm($form, $ex); - - $default_phid = $source->getDefaultQueuePHID(); - if ($default_phid) { - $default_queues = array($default_phid); - } else { - $default_queues = array(); - } - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Default Queue')) - ->setName('defaultQueuePHIDs') - ->setLimit(1) - ->setDatasource(new NuanceQueueDatasource()) - ->setValue($default_queues)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($user) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($source) - ->setPolicies($policies) - ->setName('viewPolicy')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($user) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($source) - ->setPolicies($policies) - ->setName('editPolicy')) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($source->getURI()) - ->setValue(pht('Save'))); - - return $form; - } - - /** - * return @{class:AphrontFormView} - */ - protected function augmentEditForm( - AphrontFormView $form, - PhabricatorApplicationTransactionValidationException $ex = null) { - - return $form; - } - - /** - * Hook to build up @{class:PhabricatorTransactions}. - * - * return array $transactions - */ - protected function buildTransactions(AphrontRequest $request) { - $transactions = array(); - - $transactions[] = id(new NuanceSourceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($request->getStr('editPolicy')); - - $transactions[] = id(new NuanceSourceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($request->getStr('viewPolicy')); - - $transactions[] = id(new NuanceSourceTransaction()) - ->setTransactionType(NuanceSourceTransaction::TYPE_NAME) - ->setNewvalue($request->getStr('name')); - - $transactions[] = id(new NuanceSourceTransaction()) - ->setTransactionType(NuanceSourceTransaction::TYPE_DEFAULT_QUEUE) - ->setNewvalue(head($request->getArr('defaultQueuePHIDs'))); - - return $transactions; - } - - abstract public function renderView(); - - abstract public function renderListView(); - - protected function newItemFromProperties( - NuanceRequestor $requestor, array $properties, PhabricatorContentSource $content_source) { // TODO: Should we have a tighter actor/viewer model? Requestors will // often have no real user associated with them... $actor = PhabricatorUser::getOmnipotentUser(); - - $source = $this->requireSourceObject(); + $source = $this->getSource(); $item = NuanceItem::initializeNewItem(); @@ -267,10 +165,6 @@ abstract class NuanceSourceDefinition extends Phobject { ->setTransactionType(NuanceItemTransaction::TYPE_SOURCE) ->setNewValue($source->getPHID()); - $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_REQUESTOR) - ->setNewValue($requestor->getPHID()); - // TODO: Eventually, apply real routing rules. For now, just put everything // in the default queue for the source. $xactions[] = id(new NuanceItemTransaction()) @@ -286,7 +180,6 @@ abstract class NuanceSourceDefinition extends Phobject { $editor = id(new NuanceItemEditor()) ->setActor($actor) - ->setActingAsPHID($requestor->getActingAsPHID()) ->setContentSource($content_source); $editor->applyTransactions($item, $xactions); @@ -294,13 +187,6 @@ abstract class NuanceSourceDefinition extends Phobject { return $item; } - public function renderItemViewProperties( - PhabricatorUser $viewer, - NuanceItem $item, - PHUIPropertyListView $view) { - return; - } - public function renderItemEditProperties( PhabricatorUser $viewer, NuanceItem $item, @@ -317,7 +203,7 @@ abstract class NuanceSourceDefinition extends Phobject { } public function getActionURI($path = null) { - $source_id = $this->getSourceObject()->getID(); + $source_id = $this->getSource()->getID(); return '/action/'.$source_id.'/'.ltrim($path, '/'); } diff --git a/src/applications/nuance/storage/NuanceImportCursorData.php b/src/applications/nuance/storage/NuanceImportCursorData.php new file mode 100644 index 0000000000..01f3d56c6d --- /dev/null +++ b/src/applications/nuance/storage/NuanceImportCursorData.php @@ -0,0 +1,70 @@ + true, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'cursorType' => 'text32', + 'cursorKey' => 'text32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_source' => array( + 'columns' => array('sourcePHID', 'cursorKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + NuanceImportCursorPHIDType::TYPECONST); + } + + public function getCursorProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setCursorProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::POLICY_USER; + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index 7ce517333f..2eadcdb2d9 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -6,20 +6,25 @@ final class NuanceItem PhabricatorPolicyInterface, PhabricatorApplicationTransactionInterface { - const STATUS_OPEN = 0; - const STATUS_ASSIGNED = 10; - const STATUS_CLOSED = 20; + const STATUS_IMPORTING = 'importing'; + const STATUS_ROUTING = 'routing'; + const STATUS_OPEN = 'open'; + const STATUS_ASSIGNED = 'assigned'; + const STATUS_CLOSED = 'closed'; protected $status; protected $ownerPHID; protected $requestorPHID; protected $sourcePHID; - protected $sourceLabel; + protected $queuePHID; + protected $itemType; + protected $itemKey; + protected $itemContainerKey; protected $data = array(); protected $mailKey; - protected $queuePHID; private $source = self::ATTACHABLE; + private $implementation = self::ATTACHABLE; public static function initializeNewItem() { return id(new NuanceItem()) @@ -34,8 +39,12 @@ final class NuanceItem ), self::CONFIG_COLUMN_SCHEMA => array( 'ownerPHID' => 'phid?', - 'sourceLabel' => 'text255?', - 'status' => 'uint32', + 'requestorPHID' => 'phid?', + 'queuePHID' => 'phid?', + 'itemType' => 'text64', + 'itemKey' => 'text64', + 'itemContainerKey' => 'text64?', + 'status' => 'text32', 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( @@ -51,6 +60,13 @@ final class NuanceItem 'key_queue' => array( 'columns' => array('queuePHID', 'status'), ), + 'key_container' => array( + 'columns' => array('sourcePHID', 'itemContainerKey'), + ), + 'key_item' => array( + 'columns' => array('sourcePHID', 'itemKey'), + 'unique' => true, + ), ), ) + parent::getConfiguration(); } @@ -71,26 +87,6 @@ final class NuanceItem return '/nuance/item/view/'.$this->getID().'/'; } - public function getLabel(PhabricatorUser $viewer) { - // this is generated at the time the item is created based on - // the configuration from the item source. It is typically - // something like 'Twitter'. - $source_label = $this->getSourceLabel(); - - return pht( - 'Item via %s @ %s.', - $source_label, - phabricator_datetime($this->getDateCreated(), $viewer)); - } - - public function getRequestor() { - return $this->assertAttached($this->requestor); - } - - public function attachRequestor(NuanceRequestor $requestor) { - return $this->requestor = $requestor; - } - public function getSource() { return $this->assertAttached($this->source); } @@ -99,11 +95,11 @@ final class NuanceItem $this->source = $source; } - public function getNuanceProperty($key, $default = null) { + public function getItemProperty($key, $default = null) { return idx($this->data, $key, $default); } - public function setNuanceProperty($key, $value) { + public function setItemProperty($key, $value) { $this->data[$key] = $value; return $this; } @@ -135,17 +131,45 @@ final class NuanceItem return null; } - public function toDictionary() { - return array( - 'id' => $this->getID(), - 'phid' => $this->getPHID(), - 'ownerPHID' => $this->getOwnerPHID(), - 'requestorPHID' => $this->getRequestorPHID(), - 'sourcePHID' => $this->getSourcePHID(), - 'sourceLabel' => $this->getSourceLabel(), - 'dateCreated' => $this->getDateCreated(), - 'dateModified' => $this->getDateModified(), - ); + public function getDisplayName() { + return $this->getImplementation()->getItemDisplayName($this); + } + + public function scheduleUpdate() { + PhabricatorWorker::scheduleTask( + 'NuanceItemUpdateWorker', + array( + 'itemPHID' => $this->getPHID(), + ), + array( + 'objectPHID' => $this->getPHID(), + )); + } + + public function issueCommand( + $author_phid, + $command, + array $parameters = array()) { + + $command = id(NuanceItemCommand::initializeNewCommand()) + ->setItemPHID($this->getPHID()) + ->setAuthorPHID($author_phid) + ->setCommand($command) + ->setParameters($parameters) + ->save(); + + $this->scheduleUpdate(); + + return $this; + } + + public function getImplementation() { + return $this->assertAttached($this->implementation); + } + + public function attachImplementation(NuanceItemType $type) { + $this->implementation = $type; + return $this; } diff --git a/src/applications/nuance/storage/NuanceItemCommand.php b/src/applications/nuance/storage/NuanceItemCommand.php new file mode 100644 index 0000000000..94409dd89a --- /dev/null +++ b/src/applications/nuance/storage/NuanceItemCommand.php @@ -0,0 +1,55 @@ + false, + self::CONFIG_SERIALIZATION => array( + 'parameters' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'command' => 'text64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_item' => array( + 'columns' => array('itemPHID'), + ), + ), + ) + parent::getConfiguration(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + +} diff --git a/src/applications/nuance/storage/NuanceItemTransaction.php b/src/applications/nuance/storage/NuanceItemTransaction.php index 183596402f..77471fcdb9 100644 --- a/src/applications/nuance/storage/NuanceItemTransaction.php +++ b/src/applications/nuance/storage/NuanceItemTransaction.php @@ -10,6 +10,7 @@ final class NuanceItemTransaction const TYPE_SOURCE = 'nuance.item.source'; const TYPE_PROPERTY = 'nuance.item.property'; const TYPE_QUEUE = 'nuance.item.queue'; + const TYPE_COMMAND = 'nuance.item.command'; public function getApplicationTransactionType() { return NuanceItemPHIDType::TYPECONST; @@ -65,6 +66,12 @@ final class NuanceItemTransaction '%s routed this item to the %s queue.', $this->renderHandleLink($author_phid), $this->renderHandleLink($new)); + case self::TYPE_COMMAND: + // TODO: Give item types a chance to render this properly. + return pht( + '%s applied command "%s" to this item.', + $this->renderHandleLink($author_phid), + idx($new, 'command')); } return parent::getTitle(); diff --git a/src/applications/nuance/storage/NuanceQueue.php b/src/applications/nuance/storage/NuanceQueue.php index 093b437e24..15197d9bd8 100644 --- a/src/applications/nuance/storage/NuanceQueue.php +++ b/src/applications/nuance/storage/NuanceQueue.php @@ -27,7 +27,9 @@ final class NuanceQueue } public static function initializeNewQueue() { - return new NuanceQueue(); + return id(new self()) + ->setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->setEditPolicy(PhabricatorPolicies::POLICY_USER); } public function save() { diff --git a/src/applications/nuance/storage/NuanceQueueTransaction.php b/src/applications/nuance/storage/NuanceQueueTransaction.php index c1630cdbd8..44309c2ae6 100644 --- a/src/applications/nuance/storage/NuanceQueueTransaction.php +++ b/src/applications/nuance/storage/NuanceQueueTransaction.php @@ -12,18 +12,6 @@ final class NuanceQueueTransaction extends NuanceTransaction { return new NuanceQueueTransactionComment(); } - public function shouldHide() { - $old = $this->getOldValue(); - $type = $this->getTransactionType(); - - switch ($type) { - case self::TYPE_NAME: - return ($old === null); - } - - return parent::shouldHide(); - } - public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); @@ -32,6 +20,10 @@ final class NuanceQueueTransaction extends NuanceTransaction { $author_phid = $this->getAuthorPHID(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this queue.', + $this->renderHandleLink($author_phid)); case self::TYPE_NAME: return pht( '%s renamed this queue from "%s" to "%s".', diff --git a/src/applications/nuance/storage/NuanceRequestor.php b/src/applications/nuance/storage/NuanceRequestor.php deleted file mode 100644 index d4341d0b24..0000000000 --- a/src/applications/nuance/storage/NuanceRequestor.php +++ /dev/null @@ -1,137 +0,0 @@ - true, - self::CONFIG_SERIALIZATION => array( - 'data' => self::SERIALIZATION_JSON, - ), - ) + parent::getConfiguration(); - } - - public function generatePHID() { - return PhabricatorPHID::generateNewPHID( - NuanceRequestorPHIDType::TYPECONST); - } - - public static function initializeNewRequestor() { - return new NuanceRequestor(); - } - - public function getURI() { - return '/nuance/requestor/view/'.$this->getID().'/'; - } - - public function getPhabricatorUserPHID() { - return idx($this->getData(), 'phabricatorUserPHID'); - } - - public function getActingAsPHID() { - $user_phid = $this->getPhabricatorUserPHID(); - if ($user_phid) { - return $user_phid; - } - - return id(new PhabricatorNuanceApplication())->getPHID(); - } - - public static function newFromPhabricatorUser( - PhabricatorUser $viewer, - PhabricatorContentSource $content_source) { - - // TODO: This is real sketchy and creates a new requestor every time. It - // shouldn't do that. - - $requestor = self::initializeNewRequestor(); - - $xactions = array(); - - $properties = array( - 'phabricatorUserPHID' => $viewer->getPHID(), - ); - - foreach ($properties as $key => $value) { - $xactions[] = id(new NuanceRequestorTransaction()) - ->setTransactionType(NuanceRequestorTransaction::TYPE_PROPERTY) - ->setMetadataValue(NuanceRequestorTransaction::PROPERTY_KEY, $key) - ->setNewValue($value); - } - - $editor = id(new NuanceRequestorEditor()) - ->setActor($viewer) - ->setContentSource($content_source); - - $editor->applyTransactions($requestor, $xactions); - - return $requestor; - } - - public function getNuanceProperty($key, $default = null) { - return idx($this->data, $key, $default); - } - - public function setNuanceProperty($key, $value) { - $this->data[$key] = $value; - return $this; - } - - -/* -( PhabricatorPolicyInterface )----------------------------------------- */ - - - public function getCapabilities() { - return array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - ); - } - - public function getPolicy($capability) { - switch ($capability) { - case PhabricatorPolicyCapability::CAN_VIEW: - return PhabricatorPolicies::POLICY_USER; - case PhabricatorPolicyCapability::CAN_EDIT: - return PhabricatorPolicies::POLICY_USER; - } - } - - public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return false; - } - - public function describeAutomaticCapability($capability) { - return null; - } - - -/* -( PhabricatorApplicationTransactionInterface )------------------------- */ - - - public function getApplicationTransactionEditor() { - return new NuanceRequestorEditor(); - } - - public function getApplicationTransactionObject() { - return $this; - } - - public function getApplicationTransactionTemplate() { - return new NuanceRequestorTransaction(); - } - - public function willRenderTimeline( - PhabricatorApplicationTransactionView $timeline, - AphrontRequest $request) { - - return $timeline; - } - -} diff --git a/src/applications/nuance/storage/NuanceRequestorSource.php b/src/applications/nuance/storage/NuanceRequestorSource.php deleted file mode 100644 index bc31fdb6a1..0000000000 --- a/src/applications/nuance/storage/NuanceRequestorSource.php +++ /dev/null @@ -1,34 +0,0 @@ - array( - 'data' => self::SERIALIZATION_JSON, - ), - self::CONFIG_COLUMN_SCHEMA => array( - 'sourceKey' => 'text128', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_source_key' => array( - 'columns' => array('sourcePHID', 'sourceKey'), - 'unique' => true, - ), - 'key_requestor' => array( - 'columns' => array('requestorPHID', 'id'), - ), - 'key_source' => array( - 'columns' => array('sourcePHID', 'id'), - ), - ), - ) + parent::getConfiguration(); - } - -} diff --git a/src/applications/nuance/storage/NuanceRequestorTransaction.php b/src/applications/nuance/storage/NuanceRequestorTransaction.php deleted file mode 100644 index ae2e0a119f..0000000000 --- a/src/applications/nuance/storage/NuanceRequestorTransaction.php +++ /dev/null @@ -1,17 +0,0 @@ - self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text255?', + 'name' => 'sort255', 'type' => 'text32', 'mailKey' => 'bytes20', + 'isDisabled' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_type' => array( @@ -49,7 +52,9 @@ final class NuanceSource extends NuanceDAO return '/nuance/source/view/'.$this->getID().'/'; } - public static function initializeNewSource(PhabricatorUser $actor) { + public static function initializeNewSource( + PhabricatorUser $actor, + NuanceSourceDefinition $definition) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorNuanceApplication')) @@ -62,32 +67,28 @@ final class NuanceSource extends NuanceDAO return id(new NuanceSource()) ->setViewPolicy($view_policy) - ->setEditPolicy($edit_policy); + ->setEditPolicy($edit_policy) + ->setType($definition->getSourceTypeConstant()) + ->attachDefinition($definition) + ->setIsDisabled(0); } public function getDefinition() { - if ($this->definition === null) { - $definitions = NuanceSourceDefinition::getAllDefinitions(); - if (isset($definitions[$this->getType()])) { - $definition = clone $definitions[$this->getType()]; - $definition->setSourceObject($this); - $this->definition = $definition; - } - } - - return $this->definition; + return $this->assertAttached($this->definition); } - public function requireDefinition() { - $definition = $this->getDefinition(); - if (!$definition) { - throw new Exception( - pht( - 'Unable to load source definition implementation for source '. - 'type "%s".', - $this->getType())); - } - return $definition; + public function attachDefinition(NuanceSourceDefinition $definition) { + $this->definition = $definition; + return $this; + } + + public function getSourceProperty($key, $default = null) { + return idx($this->data, $key, $default); + } + + public function setSourceProperty($key, $value) { + $this->data[$key] = $value; + return $this; } @@ -141,4 +142,15 @@ final class NuanceSource extends NuanceDAO return null; } + +/* -( PhabricatorNgramsInterface )----------------------------------------- */ + + + public function newNgrams() { + return array( + id(new NuanceSourceNameNgrams()) + ->setValue($this->getName()), + ); + } + } diff --git a/src/applications/nuance/storage/NuanceSourceNameNgrams.php b/src/applications/nuance/storage/NuanceSourceNameNgrams.php new file mode 100644 index 0000000000..8bff4b8a2a --- /dev/null +++ b/src/applications/nuance/storage/NuanceSourceNameNgrams.php @@ -0,0 +1,18 @@ +getTaskDataValue('itemPHID'); + + $hash = PhabricatorHash::digestForIndex($item_phid); + $lock_key = "nuance.item.{$hash}"; + $lock = PhabricatorGlobalLock::newLock($lock_key); + + $lock->lock(1); + try { + $item = $this->loadItem($item_phid); + $this->updateItem($item); + $this->routeItem($item); + $this->applyCommands($item); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + } + + private function updateItem(NuanceItem $item) { + $impl = $item->getImplementation(); + if (!$impl->canUpdateItems()) { + return null; + } + + $viewer = $this->getViewer(); + + $impl->setViewer($viewer); + $impl->updateItem($item); + } + + private function routeItem(NuanceItem $item) { + $status = $item->getStatus(); + if ($status != NuanceItem::STATUS_ROUTING) { + return; + } + + $source = $item->getSource(); + + // For now, always route items into the source's default queue. + + $item + ->setQueuePHID($source->getDefaultQueuePHID()) + ->setStatus(NuanceItem::STATUS_OPEN) + ->save(); + } + + private function applyCommands(NuanceItem $item) { + $viewer = $this->getViewer(); + + $impl = $item->getImplementation(); + $impl->setViewer($viewer); + + $commands = id(new NuanceItemCommandQuery()) + ->setViewer($viewer) + ->withItemPHIDs(array($item->getPHID())) + ->execute(); + $commands = msort($commands, 'getID'); + + foreach ($commands as $command) { + $impl->applyCommand($item, $command); + $command->delete(); + } + } + +} diff --git a/src/applications/nuance/worker/NuanceWorker.php b/src/applications/nuance/worker/NuanceWorker.php new file mode 100644 index 0000000000..5d09de9e69 --- /dev/null +++ b/src/applications/nuance/worker/NuanceWorker.php @@ -0,0 +1,25 @@ +setViewer($this->getViewer()) + ->withPHIDs(array($item_phid)) + ->executeOne(); + + if (!$item) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'There is no Nuance item with PHID "%s".', + $item_phid)); + } + + return $item; + } + +} diff --git a/src/applications/oauthserver/PhabricatorOAuthServer.php b/src/applications/oauthserver/PhabricatorOAuthServer.php index 38b2e34623..f5c074f4eb 100644 --- a/src/applications/oauthserver/PhabricatorOAuthServer.php +++ b/src/applications/oauthserver/PhabricatorOAuthServer.php @@ -29,7 +29,6 @@ final class PhabricatorOAuthServer extends Phobject { const AUTHORIZATION_CODE_TIMEOUT = 300; - const ACCESS_TOKEN_TIMEOUT = 3600; private $user; private $client; @@ -158,59 +157,72 @@ final class PhabricatorOAuthServer extends Phobject { /** * @task token */ - public function validateAccessToken( - PhabricatorOAuthServerAccessToken $token, - $required_scope) { + public function authorizeToken( + PhabricatorOAuthServerAccessToken $token) { - $created_time = $token->getDateCreated(); - $must_be_used_by = $created_time + self::ACCESS_TOKEN_TIMEOUT; - $expired = time() > $must_be_used_by; - $authorization = id(new PhabricatorOAuthClientAuthorization()) - ->loadOneWhere( - 'userPHID = %s AND clientPHID = %s', - $token->getUserPHID(), - $token->getClientPHID()); + $user_phid = $token->getUserPHID(); + $client_phid = $token->getClientPHID(); + $authorization = id(new PhabricatorOAuthClientAuthorizationQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withUserPHIDs(array($user_phid)) + ->withClientPHIDs(array($client_phid)) + ->executeOne(); if (!$authorization) { - return false; - } - $token_scope = $authorization->getScope(); - if (!isset($token_scope[$required_scope])) { - return false; + return null; } - $valid = true; - if ($expired) { - $valid = false; - // check if the scope includes "offline_access", which makes the - // token valid despite being expired - if (isset( - $token_scope[PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS])) { - $valid = true; - } + $application = $authorization->getClient(); + if ($application->getIsDisabled()) { + return null; } - return $valid; + return $authorization; + } + + public function validateRedirectURI($uri) { + try { + $this->assertValidRedirectURI($uri); + return true; + } catch (Exception $ex) { + return false; + } } /** * See http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2 * for details on what makes a given redirect URI "valid". */ - public function validateRedirectURI(PhutilURI $uri) { - if (!PhabricatorEnv::isValidRemoteURIForLink($uri)) { - return false; + public function assertValidRedirectURI($raw_uri) { + // This covers basics like reasonable formatting and the existence of a + // protocol. + PhabricatorEnv::requireValidRemoteURIForLink($raw_uri); + + $uri = new PhutilURI($raw_uri); + + $fragment = $uri->getFragment(); + if (strlen($fragment)) { + throw new Exception( + pht( + 'OAuth application redirect URIs must not contain URI '. + 'fragments, but the URI "%s" has a fragment ("%s").', + $raw_uri, + $fragment)); } - if ($uri->getFragment()) { - return false; + $protocol = $uri->getProtocol(); + switch ($protocol) { + case 'http': + case 'https': + break; + default: + throw new Exception( + pht( + 'OAuth application redirect URIs must only use the "http" or '. + '"https" protocols, but the URI "%s" uses the "%s" protocol.', + $raw_uri, + $protocol)); } - - if (!$uri->getDomain()) { - return false; - } - - return true; } /** diff --git a/src/applications/oauthserver/PhabricatorOAuthServerScope.php b/src/applications/oauthserver/PhabricatorOAuthServerScope.php index 4ab6c455d8..68aec848f6 100644 --- a/src/applications/oauthserver/PhabricatorOAuthServerScope.php +++ b/src/applications/oauthserver/PhabricatorOAuthServerScope.php @@ -2,126 +2,20 @@ final class PhabricatorOAuthServerScope extends Phobject { - const SCOPE_OFFLINE_ACCESS = 'offline_access'; - const SCOPE_WHOAMI = 'whoami'; - const SCOPE_NOT_ACCESSIBLE = 'not_accessible'; - - /* - * Note this does not contain SCOPE_NOT_ACCESSIBLE which is magic - * used to simplify code for data that is not currently accessible - * via OAuth. - */ - public static function getScopesDict() { - return array( - self::SCOPE_OFFLINE_ACCESS => 1, - self::SCOPE_WHOAMI => 1, - ); + public static function getScopeMap() { + return array(); } - public static function getDefaultScope() { - return self::SCOPE_WHOAMI; - } + public static function filterScope(array $scope) { + $valid_scopes = self::getScopeMap(); - public static function getCheckboxControl( - array $current_scopes) { - - $have_options = false; - $scopes = self::getScopesDict(); - $scope_keys = array_keys($scopes); - sort($scope_keys); - $default_scope = self::getDefaultScope(); - - $checkboxes = new AphrontFormCheckboxControl(); - foreach ($scope_keys as $scope) { - if ($scope == $default_scope) { - continue; - } - if (!isset($current_scopes[$scope])) { - continue; - } - - $checkboxes->addCheckbox( - $name = $scope, - $value = 1, - $label = self::getCheckboxLabel($scope), - $checked = isset($current_scopes[$scope])); - $have_options = true; - } - - if ($have_options) { - $checkboxes->setLabel(pht('Scope')); - return $checkboxes; - } - - return null; - } - - private static function getCheckboxLabel($scope) { - $label = null; - switch ($scope) { - case self::SCOPE_OFFLINE_ACCESS: - $label = pht('Make access tokens granted to this client never expire.'); - break; - case self::SCOPE_WHOAMI: - $label = pht('Read access to Conduit method %s.', 'user.whoami'); - break; - } - - return $label; - } - - public static function getScopesFromRequest(AphrontRequest $request) { - $scopes = self::getScopesDict(); - $requested_scopes = array(); - foreach ($scopes as $scope => $bit) { - if ($request->getBool($scope)) { - $requested_scopes[$scope] = 1; + foreach ($scope as $key => $scope_item) { + if (!isset($valid_scopes[$scope_item])) { + unset($scope[$key]); } } - $requested_scopes[self::getDefaultScope()] = 1; - return $requested_scopes; - } - /** - * A scopes list is considered valid if each scope is a known scope - * and each scope is seen only once. Otherwise, the list is invalid. - */ - public static function validateScopesList($scope_list) { - $scopes = explode(' ', $scope_list); - $known_scopes = self::getScopesDict(); - $seen_scopes = array(); - foreach ($scopes as $scope) { - if (!isset($known_scopes[$scope])) { - return false; - } - if (isset($seen_scopes[$scope])) { - return false; - } - $seen_scopes[$scope] = 1; - } - - return true; - } - - /** - * A scopes dictionary is considered valid if each key is a known scope. - * Otherwise, the dictionary is invalid. - */ - public static function validateScopesDict($scope_dict) { - $known_scopes = self::getScopesDict(); - $unknown_scopes = array_diff_key($scope_dict, - $known_scopes); - return empty($unknown_scopes); - } - - /** - * Transforms a space-delimited scopes list into a scopes dict. The list - * should be validated by @{method:validateScopesList} before - * transformation. - */ - public static function scopesListToDict($scope_list) { - $scopes = explode(' ', $scope_list); - return array_fill_keys($scopes, 1); + return $scope; } } diff --git a/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php b/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php index 7bd7eed9ac..024d9101dd 100644 --- a/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php +++ b/src/applications/oauthserver/application/PhabricatorOAuthServerApplication.php @@ -50,14 +50,14 @@ final class PhabricatorOAuthServerApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'PhabricatorOAuthClientListController', 'auth/' => 'PhabricatorOAuthServerAuthController', - 'test/(?P\d+)/' => 'PhabricatorOAuthServerTestController', 'token/' => 'PhabricatorOAuthServerTokenController', - 'client/' => array( - 'create/' => 'PhabricatorOAuthClientEditController', - 'delete/(?P[^/]+)/' => 'PhabricatorOAuthClientDeleteController', - 'edit/(?P[^/]+)/' => 'PhabricatorOAuthClientEditController', - 'view/(?P[^/]+)/' => 'PhabricatorOAuthClientViewController', - 'secret/(?P[^/]+)/' => 'PhabricatorOAuthClientSecretController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorOAuthClientEditController', + 'client/' => array( + 'disable/(?P\d+)/' => 'PhabricatorOAuthClientDisableController', + 'view/(?P\d+)/' => 'PhabricatorOAuthClientViewController', + 'secret/(?P\d+)/' => 'PhabricatorOAuthClientSecretController', + 'test/(?P\d+)/' => 'PhabricatorOAuthClientTestController', ), ), ); diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php index d473e1557a..b9d916e82b 100644 --- a/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php +++ b/src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php @@ -3,12 +3,17 @@ final class PhabricatorOAuthServerAuthController extends PhabricatorOAuthServerController { + protected function buildApplicationCrumbs() { + // We're specifically not putting an "OAuth Server" application crumb + // on the auth pages because it doesn't make sense to send users there. + return new PHUICrumbsView(); + } + public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $server = new PhabricatorOAuthServer(); $client_phid = $request->getStr('client_id'); - $scope = $request->getStr('scope'); $redirect_uri = $request->getStr('redirect_uri'); $response_type = $request->getStr('response_type'); @@ -56,6 +61,15 @@ final class PhabricatorOAuthServerAuthController phutil_tag('strong', array(), 'client_id'))); } + if ($client->getIsDisabled()) { + return $this->buildErrorResponse( + 'invalid_request', + pht('Application Disabled'), + pht( + 'The %s OAuth application has been disabled.', + phutil_tag('strong', array(), 'client_id'))); + } + $name = $client->getName(); $server->setClient($client); if ($redirect_uri) { @@ -99,24 +113,11 @@ final class PhabricatorOAuthServerAuthController implode(', ', array('code')))); } - if ($scope) { - if (!PhabricatorOAuthServerScope::validateScopesList($scope)) { - return $this->buildErrorResponse( - 'invalid_scope', - pht('Invalid Scope'), - pht( - 'Request parameter %s specifies an unsupported scope.', - phutil_tag('strong', array(), 'scope'))); - } - $scope = PhabricatorOAuthServerScope::scopesListToDict($scope); - } else { - return $this->buildErrorResponse( - 'invalid_request', - pht('Malformed Request'), - pht( - 'Required parameter %s was not present in the request.', - phutil_tag('strong', array(), 'scope'))); - } + + $requested_scope = $request->getStrList('scope'); + $requested_scope = array_fuse($requested_scope); + + $scope = PhabricatorOAuthServerScope::filterScope($requested_scope); // NOTE: We're always requiring a confirmation dialog to redirect. // Partly this is a general defense against redirect attacks, and @@ -127,8 +128,6 @@ final class PhabricatorOAuthServerAuthController list($is_authorized, $authorization) = $auth_info; if ($request->isFormPost()) { - $scope = PhabricatorOAuthServerScope::getScopesFromRequest($request); - if ($authorization) { $authorization->setScope($scope)->save(); } else { @@ -197,16 +196,9 @@ final class PhabricatorOAuthServerAuthController // Here, we're confirming authorization for the application. if ($authorization) { - $desired_scopes = array_merge($scope, $authorization->getScope()); + $missing_scope = array_diff_key($scope, $authorization->getScope()); } else { - $desired_scopes = $scope; - } - - if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) { - return $this->buildErrorResponse( - 'invalid_scope', - pht('Invalid Scope'), - pht('The requested scope is invalid, unknown, or malformed.')); + $missing_scope = $scope; } $form = id(new AphrontFormView()) @@ -215,9 +207,7 @@ final class PhabricatorOAuthServerAuthController ->addHiddenInput('response_type', $response_type) ->addHiddenInput('state', $state) ->addHiddenInput('scope', $request->getStr('scope')) - ->setUser($viewer) - ->appendChild( - PhabricatorOAuthServerScope::getCheckboxControl($desired_scopes)); + ->setUser($viewer); $cancel_msg = pht('The user declined to authorize this application.'); $cancel_uri = $this->addQueryParams( @@ -227,7 +217,7 @@ final class PhabricatorOAuthServerAuthController 'error_description' => $cancel_msg, )); - return $this->newDialog() + $dialog = $this->newDialog() ->setShortTitle(pht('Authorize Access')) ->setTitle(pht('Authorize "%s"?', $name)) ->setSubmitURI($request->getRequestURI()->getPath()) @@ -238,9 +228,41 @@ final class PhabricatorOAuthServerAuthController 'access your Phabricator account data, including your primary '. 'email address?', phutil_tag('strong', array(), $name))) - ->appendChild($form->buildLayoutView()) + ->appendForm($form) ->addSubmitButton(pht('Authorize Access')) ->addCancelButton((string)$cancel_uri, pht('Do Not Authorize')); + + if ($missing_scope) { + $dialog->appendParagraph( + pht( + 'This application has requested these additional permissions. '. + 'Authorizing it will grant it the permissions it requests:')); + foreach ($missing_scope as $scope_key => $ignored) { + // TODO: Once we introduce more scopes, explain them here. + } + } + + $unknown_scope = array_diff_key($requested_scope, $scope); + if ($unknown_scope) { + $dialog->appendParagraph( + pht( + 'This application also requested additional unrecognized '. + 'permissions. These permissions may have existed in an older '. + 'version of Phabricator, or may be from a future version of '. + 'Phabricator. They will not be granted.')); + + $unknown_form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Unknown Scope')) + ->setValue(implode(', ', array_keys($unknown_scope))) + ->setDisabled(true)); + + $dialog->appendForm($unknown_form); + } + + return $dialog; } diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerController.php index 00be8a1e8d..b71faae89e 100644 --- a/src/applications/oauthserver/controller/PhabricatorOAuthServerController.php +++ b/src/applications/oauthserver/controller/PhabricatorOAuthServerController.php @@ -5,11 +5,4 @@ abstract class PhabricatorOAuthServerController const CONTEXT_AUTHORIZE = 'oauthserver.authorize'; - protected function buildApplicationCrumbs() { - // We're specifically not putting an "OAuth Server" application crumb - // on these pages because it doesn't make sense to send users there on - // the auth workflows. - return new PHUICrumbsView(); - } - } diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php deleted file mode 100644 index e5966518f5..0000000000 --- a/src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php +++ /dev/null @@ -1,62 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $client = id(new PhabricatorOAuthServerClientQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$client) { - return new Aphront404Response(); - } - - $view_uri = $client->getViewURI(); - - // Look for an existing authorization. - $authorization = id(new PhabricatorOAuthClientAuthorizationQuery()) - ->setViewer($viewer) - ->withUserPHIDs(array($viewer->getPHID())) - ->withClientPHIDs(array($client->getPHID())) - ->executeOne(); - if ($authorization) { - return $this->newDialog() - ->setTitle(pht('Already Authorized')) - ->appendParagraph( - pht( - 'You have already authorized this application to access your '. - 'account.')) - ->addCancelButton($view_uri, pht('Close')); - } - - if ($request->isFormPost()) { - $server = id(new PhabricatorOAuthServer()) - ->setUser($viewer) - ->setClient($client); - - $scope = array(); - $authorization = $server->authorizeClient($scope); - - $id = $authorization->getID(); - $panel_uri = '/settings/panel/oauthorizations/?id='.$id; - - return id(new AphrontRedirectResponse())->setURI($panel_uri); - } - - // TODO: It would be nice to put scope options in this dialog, maybe? - - return $this->newDialog() - ->setTitle(pht('Authorize Application?')) - ->appendParagraph( - pht( - 'This will create an authorization, permitting %s to access '. - 'your account.', - phutil_tag('strong', array(), $client->getName()))) - ->addCancelButton($view_uri) - ->addSubmitButton(pht('Authorize Application')); - } -} diff --git a/src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php b/src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php index 7e35574f61..3b5ff72575 100644 --- a/src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php +++ b/src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php @@ -67,7 +67,7 @@ final class PhabricatorOAuthServerTokenController $response->setError('invalid_grant'); $response->setErrorDescription( pht( - 'Authorization code %d not found.', + 'Authorization code %s not found.', $code)); return $response; } @@ -102,11 +102,22 @@ final class PhabricatorOAuthServerTokenController $response->setError('invalid_client'); $response->setErrorDescription( pht( - 'Client with %s %d not found.', + 'Client with %s %s not found.', 'client_id', $client_phid)); return $response; } + + if ($client->getIsDisabled()) { + $response->setError('invalid_client'); + $response->setErrorDescription( + pht( + 'OAuth application "%s" has been disabled.', + $client->getName())); + + return $response; + } + $server->setClient($client); $user_phid = $auth_code->getUserPHID(); @@ -116,7 +127,7 @@ final class PhabricatorOAuthServerTokenController $response->setError('invalid_grant'); $response->setErrorDescription( pht( - 'User with PHID %d not found.', + 'User with PHID %s not found.', $user_phid)); return $response; } @@ -132,7 +143,7 @@ final class PhabricatorOAuthServerTokenController $response->setError('invalid_grant'); $response->setErrorDescription( pht( - 'Invalid authorization code %d.', + 'Invalid authorization code %s.', $code)); return $response; } @@ -143,8 +154,7 @@ final class PhabricatorOAuthServerTokenController unset($unguarded); $result = array( 'access_token' => $access_token->getToken(), - 'token_type' => 'Bearer', - 'expires_in' => PhabricatorOAuthServer::ACCESS_TOKEN_TIMEOUT, + 'token_type' => 'Bearer', ); return $response->setContent($result); } catch (Exception $e) { diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php deleted file mode 100644 index 409063d9e9..0000000000 --- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php +++ /dev/null @@ -1,42 +0,0 @@ -getRequest(); - $viewer = $request->getUser(); - - $client = id(new PhabricatorOAuthServerClientQuery()) - ->setViewer($viewer) - ->withPHIDs(array($this->getClientPHID())) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$client) { - return new Aphront404Response(); - } - - if ($request->isFormPost()) { - $client->delete(); - $app_uri = $this->getApplicationURI(); - return id(new AphrontRedirectResponse())->setURI($app_uri); - } - - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setTitle(pht('Delete OAuth Application?')) - ->appendParagraph( - pht( - 'Really delete the OAuth application %s?', - phutil_tag('strong', array(), $client->getName()))) - ->addCancelButton($client->getViewURI()) - ->addSubmitButton(pht('Delete Application')); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php new file mode 100644 index 0000000000..2ea9955365 --- /dev/null +++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientDisableController.php @@ -0,0 +1,67 @@ +getViewer(); + + $client = id(new PhabricatorOAuthServerClientQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$client) { + return new Aphront404Response(); + } + + $done_uri = $client->getViewURI(); + $is_disable = !$client->getIsDisabled(); + + if ($request->isFormPost()) { + $xactions = array(); + + $xactions[] = id(new PhabricatorOAuthServerTransaction()) + ->setTransactionType(PhabricatorOAuthServerTransaction::TYPE_DISABLED) + ->setNewValue((int)$is_disable); + + $editor = id(new PhabricatorOAuthServerEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($client, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + + if ($is_disable) { + $title = pht('Disable OAuth Application'); + $body = pht( + 'Really disable the %s OAuth application? Users will no longer be '. + 'able to authenticate against it, nor access Phabricator using '. + 'tokens generated by this application.', + phutil_tag('strong', array(), $client->getName())); + $button = pht('Disable Application'); + } else { + $title = pht('Enable OAuth Application'); + $body = pht( + 'Really enable the %s OAuth application? Users will be able to '. + 'authenticate against it, and existing tokens will become usable '. + 'again.', + phutil_tag('strong', array(), $client->getName())); + $button = pht('Enable Application'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addCancelButton($done_uri) + ->addSubmitButton($button); + } + +} diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php index cd194aa74b..178a2df35b 100644 --- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php +++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php @@ -3,135 +3,10 @@ final class PhabricatorOAuthClientEditController extends PhabricatorOAuthClientController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $phid = $this->getClientPHID(); - if ($phid) { - $client = id(new PhabricatorOAuthServerClientQuery()) - ->setViewer($viewer) - ->withPHIDs(array($phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$client) { - return new Aphront404Response(); - } - - $title = pht('Edit OAuth Application: %s', $client->getName()); - $submit_button = pht('Save Application'); - $crumb_text = pht('Edit'); - $cancel_uri = $client->getViewURI(); - $is_new = false; - } else { - $this->requireApplicationCapability( - PhabricatorOAuthServerCreateClientsCapability::CAPABILITY); - - $client = PhabricatorOAuthServerClient::initializeNewClient($viewer); - - $title = pht('Create OAuth Application'); - $submit_button = pht('Create Application'); - $crumb_text = pht('Create Application'); - $cancel_uri = $this->getApplicationURI(); - $is_new = true; - } - - $errors = array(); - $e_redirect = true; - $e_name = true; - if ($request->isFormPost()) { - $redirect_uri = $request->getStr('redirect_uri'); - $client->setName($request->getStr('name')); - $client->setRedirectURI($redirect_uri); - - if (!strlen($client->getName())) { - $errors[] = pht('You must choose a name for this OAuth application.'); - $e_name = pht('Required'); - } - - $server = new PhabricatorOAuthServer(); - $uri = new PhutilURI($redirect_uri); - if (!$server->validateRedirectURI($uri)) { - $errors[] = pht( - 'Redirect URI must be a fully qualified domain name '. - 'with no fragments. See %s for more information on the correct '. - 'format.', - 'http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2'); - $e_redirect = pht('Invalid'); - } - - $client->setViewPolicy($request->getStr('viewPolicy')); - $client->setEditPolicy($request->getStr('editPolicy')); - if (!$errors) { - $client->save(); - $view_uri = $client->getViewURI(); - return id(new AphrontRedirectResponse())->setURI($view_uri); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($client) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($client->getName()) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Redirect URI')) - ->setName('redirect_uri') - ->setValue($client->getRedirectURI()) - ->setError($e_redirect)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($client) - ->setPolicies($policies) - ->setName('viewPolicy')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($client) - ->setPolicies($policies) - ->setName('editPolicy')) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($submit_button)); - - $crumbs = $this->buildApplicationCrumbs(); - if (!$is_new) { - $crumbs->addTextCrumb( - $client->getName(), - $client->getViewURI()); - } - $crumbs->addTextCrumb($crumb_text); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setFormErrors($errors) - ->setForm($form); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + public function handleRequest(AphrontRequest $request) { + return id(new PhabricatorOAuthServerEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php index 217fa1d7b1..513bed7bc8 100644 --- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php +++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php @@ -3,38 +3,22 @@ final class PhabricatorOAuthClientListController extends PhabricatorOAuthClientController { - private $queryKey; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->queryKey = idx($data, 'queryKey'); - } - - public function processRequest() { - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($this->queryKey) - ->setSearchEngine(new PhabricatorOAuthServerClientSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + public function handleRequest(AphrontRequest $request) { + return id(new PhabricatorOAuthServerClientSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - PhabricatorOAuthServerCreateClientsCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setHref($this->getApplicationURI('client/create/')) - ->setName(pht('Create Application')) - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create) - ->setIcon('fa-plus-square')); + id(new PhabricatorOAuthServerEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php index ceb1379b7a..9f4e0d0683 100644 --- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php +++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php @@ -8,7 +8,7 @@ final class PhabricatorOAuthClientSecretController $client = id(new PhabricatorOAuthServerClientQuery()) ->setViewer($viewer) - ->withPHIDs(array($this->getClientPHID())) + ->withIDs(array($request->getURIData('id'))) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -27,22 +27,20 @@ final class PhabricatorOAuthClientSecretController if ($request->isFormPost()) { $secret = $client->getSecret(); + $body = id(new PHUIFormLayoutView()) ->appendChild( id(new AphrontFormTextAreaControl()) - ->setLabel(pht('Plaintext')) - ->setReadOnly(true) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) - ->setValue($secret)); + ->setLabel(pht('Plaintext')) + ->setReadOnly(true) + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) + ->setValue($secret)); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FORM) ->setTitle(pht('Application Secret')) ->appendChild($body) ->addCancelButton($view_uri, pht('Done')); - - return id(new AphrontDialogResponse())->setDialog($dialog); } @@ -59,8 +57,8 @@ final class PhabricatorOAuthClientSecretController 'your monitor to create a human shield, keeping it safe from prying '. 'eyes. Protect company secrets!'); } + return $this->newDialog() - ->setUser($viewer) ->setTitle(pht('Really show application secret?')) ->appendChild($body) ->addSubmitButton(pht('Show Application Secret')) diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php new file mode 100644 index 0000000000..427c1be2a1 --- /dev/null +++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientTestController.php @@ -0,0 +1,67 @@ +getViewer(); + $id = $request->getURIData('id'); + + $client = id(new PhabricatorOAuthServerClientQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$client) { + return new Aphront404Response(); + } + + $done_uri = $client->getViewURI(); + + if ($request->isFormPost()) { + $server = id(new PhabricatorOAuthServer()) + ->setUser($viewer) + ->setClient($client); + + // Create an authorization if we don't already have one. + $authorization = id(new PhabricatorOAuthClientAuthorizationQuery()) + ->setViewer($viewer) + ->withUserPHIDs(array($viewer->getPHID())) + ->withClientPHIDs(array($client->getPHID())) + ->executeOne(); + if (!$authorization) { + $scope = array(); + $authorization = $server->authorizeClient($scope); + } + + $access_token = $server->generateAccessToken(); + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendInstructions( + pht( + 'Keep this token private, it allows any bearer to access '. + 'your account on behalf of this application.')) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Token')) + ->setValue($access_token->getToken())); + + return $this->newDialog() + ->setTitle(pht('OAuth Access Token')) + ->appendForm($form) + ->addCancelButton($done_uri, pht('Close')); + } + + // TODO: It would be nice to put scope options in this dialog, maybe? + + return $this->newDialog() + ->setTitle(pht('Authorize Application?')) + ->appendParagraph( + pht( + 'This will create an authorization and OAuth token, permitting %s '. + 'to access your account.', + phutil_tag('strong', array(), $client->getName()))) + ->addCancelButton($done_uri) + ->addSubmitButton(pht('Authorize Application')); + } +} diff --git a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php index 368de86f10..394ace52a4 100644 --- a/src/applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php +++ b/src/applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php @@ -3,13 +3,12 @@ final class PhabricatorOAuthClientViewController extends PhabricatorOAuthClientController { - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $client = id(new PhabricatorOAuthServerClientQuery()) ->setViewer($viewer) - ->withPHIDs(array($this->getClientPHID())) + ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$client) { return new Aphront404Response(); @@ -23,47 +22,53 @@ final class PhabricatorOAuthClientViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($client->getName()); + $timeline = $this->buildTransactionTimeline( + $client, + new PhabricatorOAuthServerTransactionQuery()); + $timeline->setShouldTerminate(true); + $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('OAuth Application: %s', $client->getName()), - )); + $title = pht('OAuth Application: %s', $client->getName()); + + return $this->newPage() + ->setCrumbs($crumbs) + ->setTitle($title) + ->appendChild( + array( + $box, + $timeline, + )); } private function buildHeaderView(PhabricatorOAuthServerClient $client) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader(pht('OAuth Application: %s', $client->getName())) ->setPolicyObject($client); + if ($client->getIsDisabled()) { + $header->setStatus('fa-ban', 'indigo', pht('Disabled')); + } else { + $header->setStatus('fa-check', 'green', pht('Enabled')); + } + return $header; } private function buildActionView(PhabricatorOAuthServerClient $client) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $client, PhabricatorPolicyCapability::CAN_EDIT); - $authorization = id(new PhabricatorOAuthClientAuthorizationQuery()) - ->setViewer($viewer) - ->withUserPHIDs(array($viewer->getPHID())) - ->withClientPHIDs(array($client->getPHID())) - ->executeOne(); - $is_authorized = (bool)$authorization; $id = $client->getID(); - $phid = $client->getPHID(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); @@ -80,25 +85,35 @@ final class PhabricatorOAuthClientViewController id(new PhabricatorActionView()) ->setName(pht('Show Application Secret')) ->setIcon('fa-eye') - ->setHref($this->getApplicationURI("client/secret/{$phid}/")) + ->setHref($this->getApplicationURI("client/secret/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Delete Application')) - ->setIcon('fa-times') - ->setWorkflow(true) - ->setDisabled(!$can_edit) - ->setHref($client->getDeleteURI())); + $is_disabled = $client->getIsDisabled(); + if ($is_disabled) { + $disable_text = pht('Enable Application'); + $disable_icon = 'fa-check'; + } else { + $disable_text = pht('Disable Application'); + $disable_icon = 'fa-ban'; + } + + $disable_uri = $this->getApplicationURI("client/disable/{$id}/"); $view->addAction( id(new PhabricatorActionView()) - ->setName(pht('Create Test Authorization')) - ->setIcon('fa-wrench') + ->setName($disable_text) + ->setIcon($disable_icon) ->setWorkflow(true) - ->setDisabled($is_authorized) - ->setHref($this->getApplicationURI('test/'.$id.'/'))); + ->setDisabled(!$can_edit) + ->setHref($disable_uri)); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Generate Test Token')) + ->setIcon('fa-plus') + ->setWorkflow(true) + ->setHref($this->getApplicationURI("client/test/{$id}/"))); return $view; } @@ -110,7 +125,7 @@ final class PhabricatorOAuthClientViewController ->setUser($viewer); $view->addProperty( - pht('Client ID'), + pht('Client PHID'), $client->getPHID()); $view->addProperty( diff --git a/src/applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php b/src/applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php new file mode 100644 index 0000000000..ad47552c19 --- /dev/null +++ b/src/applications/oauthserver/editor/PhabricatorOAuthServerEditEngine.php @@ -0,0 +1,100 @@ +getViewer()); + } + + protected function newObjectQuery() { + return id(new PhabricatorOAuthServerClientQuery()); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create OAuth Server'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create OAuth Server'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit OAuth Server: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit OAuth Server'); + } + + protected function getObjectCreateShortText() { + return pht('Create OAuth Server'); + } + + protected function getObjectName() { + return pht('OAuth Server'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhabricatorOAuthServerCreateClientsCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setIsRequired(true) + ->setTransactionType(PhabricatorOAuthServerTransaction::TYPE_NAME) + ->setDescription(pht('The name of the OAuth application.')) + ->setConduitDescription(pht('Rename the application.')) + ->setConduitTypeDescription(pht('New application name.')) + ->setValue($object->getName()), + id(new PhabricatorTextEditField()) + ->setKey('redirectURI') + ->setLabel(pht('Redirect URI')) + ->setIsRequired(true) + ->setTransactionType( + PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI) + ->setDescription( + pht('The redirect URI for OAuth handshakes.')) + ->setConduitDescription( + pht( + 'Change where this application redirects users to during OAuth '. + 'handshakes.')) + ->setConduitTypeDescription( + pht( + 'New OAuth application redirect URI.')) + ->setValue($object->getRedirectURI()), + ); + } + +} diff --git a/src/applications/oauthserver/editor/PhabricatorOAuthServerEditor.php b/src/applications/oauthserver/editor/PhabricatorOAuthServerEditor.php new file mode 100644 index 0000000000..32b9d45054 --- /dev/null +++ b/src/applications/oauthserver/editor/PhabricatorOAuthServerEditor.php @@ -0,0 +1,146 @@ +getTransactionType()) { + case PhabricatorOAuthServerTransaction::TYPE_NAME: + return $object->getName(); + case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI: + return $object->getRedirectURI(); + case PhabricatorOAuthServerTransaction::TYPE_DISABLED: + return $object->getIsDisabled(); + } + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOAuthServerTransaction::TYPE_NAME: + case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI: + return $xaction->getNewValue(); + case PhabricatorOAuthServerTransaction::TYPE_DISABLED: + return (int)$xaction->getNewValue(); + } + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOAuthServerTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI: + $object->setRedirectURI($xaction->getNewValue()); + return; + case PhabricatorOAuthServerTransaction::TYPE_DISABLED: + $object->setIsDisabled($xaction->getNewValue()); + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOAuthServerTransaction::TYPE_NAME: + case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI: + case PhabricatorOAuthServerTransaction::TYPE_DISABLED: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case PhabricatorOAuthServerTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('OAuth applications must have a name.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + case PhabricatorOAuthServerTransaction::TYPE_REDIRECT_URI: + $missing = $this->validateIsEmptyTextField( + $object->getRedirectURI(), + $xactions); + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('OAuth applications must have a valid redirect URI.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } else { + foreach ($xactions as $xaction) { + $redirect_uri = $xaction->getNewValue(); + + try { + $server = new PhabricatorOAuthServer(); + $server->assertValidRedirectURI($redirect_uri); + } catch (Exception $ex) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + $ex->getMessage(), + $xaction); + } + } + } + break; + } + + return $errors; + } + +} diff --git a/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php b/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php index f19be14434..a746008f55 100644 --- a/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php +++ b/src/applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php @@ -7,7 +7,7 @@ final class PhabricatorOAuthClientAuthorizationQuery private $userPHIDs; private $clientPHIDs; - public function witHPHIDs(array $phids) { + public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } @@ -22,19 +22,12 @@ final class PhabricatorOAuthClientAuthorizationQuery return $this; } + public function newResultObject() { + return new PhabricatorOAuthClientAuthorization(); + } + protected function loadPage() { - $table = new PhabricatorOAuthClientAuthorization(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T auth %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $authorizations) { @@ -49,43 +42,44 @@ final class PhabricatorOAuthClientAuthorizationQuery foreach ($authorizations as $key => $authorization) { $client = idx($clients, $authorization->getClientPHID()); + if (!$client) { + $this->didRejectResult($authorization); unset($authorizations[$key]); continue; } + $authorization->attachClient($client); } return $authorizations; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->userPHIDs) { + if ($this->userPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'userPHID IN (%Ls)', $this->userPHIDs); } - if ($this->clientPHIDs) { + if ($this->clientPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'clientPHID IN (%Ls)', $this->clientPHIDs); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php b/src/applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php index 8168527f4d..3cb027fe4f 100644 --- a/src/applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php +++ b/src/applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php @@ -11,40 +11,30 @@ final class PhabricatorOAuthServerClientSearchEngine return 'PhabricatorOAuthServerApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'creatorPHIDs', - $this->readUsersFromRequest($request, 'creators')); - - return $saved; + public function newQuery() { + return id(new PhabricatorOAuthServerClientQuery()); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new PhabricatorOAuthServerClientQuery()); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $creator_phids = $saved->getParameter('creatorPHIDs', array()); - if ($creator_phids) { - $query->withCreatorPHIDs($saved->getParameter('creatorPHIDs', array())); + if ($map['creatorPHIDs']) { + $query->withCreatorPHIDs($map['creatorPHIDs']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $creator_phids = $saved_query->getParameter('creatorPHIDs', array()); - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('creators') - ->setLabel(pht('Creators')) - ->setValue($creator_phids)); + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setAliases(array('creators')) + ->setKey('creatorPHIDs') + ->setConduitKey('creators') + ->setLabel(pht('Creators')) + ->setDescription( + pht('Search for applications created by particular users.')), + ); } protected function getURI($path) { @@ -79,31 +69,26 @@ final class PhabricatorOAuthServerClientSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $clients, - PhabricatorSavedQuery $query) { - return mpull($clients, 'getCreatorPHID'); - } - protected function renderResultList( array $clients, PhabricatorSavedQuery $query, array $handles) { - assert_instances_of($clients, 'PhabricatorOauthServerClient'); + assert_instances_of($clients, 'PhabricatorOAuthServerClient'); $viewer = $this->requireViewer(); $list = id(new PHUIObjectItemListView()) ->setUser($viewer); foreach ($clients as $client) { - $creator = $handles[$client->getCreatorPHID()]; - $item = id(new PHUIObjectItemView()) ->setObjectName(pht('Application %d', $client->getID())) ->setHeader($client->getName()) ->setHref($client->getViewURI()) - ->setObject($client) - ->addByline(pht('Creator: %s', $creator->renderLink())); + ->setObject($client); + + if ($client->getIsDisabled()) { + $item->setDisabled(true); + } $list->addItem($item); } diff --git a/src/applications/nuance/query/NuanceRequestorTransactionQuery.php b/src/applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php similarity index 54% rename from src/applications/nuance/query/NuanceRequestorTransactionQuery.php rename to src/applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php index 5ecb41c47d..4dd21e2609 100644 --- a/src/applications/nuance/query/NuanceRequestorTransactionQuery.php +++ b/src/applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php @@ -1,10 +1,10 @@ getPHID().'/'; + $id = $this->getID(); + return "/oauthserver/edit/{$id}/"; } public function getViewURI() { - return '/oauthserver/client/view/'.$this->getPHID().'/'; - } - - public function getDeleteURI() { - return '/oauthserver/client/delete/'.$this->getPHID().'/'; + $id = $this->getID(); + return "/oauthserver/client/view/{$id}/"; } public static function initializeNewClient(PhabricatorUser $actor) { @@ -31,7 +31,9 @@ final class PhabricatorOAuthServerClient ->setCreatorPHID($actor->getPHID()) ->setSecret(Filesystem::readRandomCharacters(32)) ->setViewPolicy(PhabricatorPolicies::POLICY_USER) - ->setEditPolicy($actor->getPHID()); + ->setEditPolicy($actor->getPHID()) + ->setIsDisabled(0) + ->setIsTrusted(0); } protected function getConfiguration() { @@ -42,13 +44,9 @@ final class PhabricatorOAuthServerClient 'secret' => 'text32', 'redirectURI' => 'text255', 'isTrusted' => 'bool', + 'isDisabled' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, - ), 'creatorPHID' => array( 'columns' => array('creatorPHID'), ), @@ -89,8 +87,32 @@ final class PhabricatorOAuthServerClient return null; } + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorOAuthServerEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorOAuthServerTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + /* -( PhabricatorDestructibleInterface )----------------------------------- */ + public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { diff --git a/src/applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php b/src/applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php new file mode 100644 index 0000000000..b2624dd9a4 --- /dev/null +++ b/src/applications/oauthserver/storage/PhabricatorOAuthServerTransaction.php @@ -0,0 +1,63 @@ +getAuthorPHID(); + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this OAuth application.', + $this->renderHandleLink($author_phid)); + case self::TYPE_NAME: + return pht( + '%s renamed this application from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + case self::TYPE_REDIRECT_URI: + return pht( + '%s changed the application redirect URI from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + case self::TYPE_DISABLED: + if ($new) { + return pht( + '%s disabled this application.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s enabled this application.', + $this->renderHandleLink($author_phid)); + } + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index fc09508bfe..5da36ad473 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -43,9 +43,8 @@ final class PhabricatorOwnersDetailController ->setViewer($viewer) ->readFieldsFromStorage($package); - $actions = $this->buildPackageActionView($package); - $properties = $this->buildPackagePropertyView($package, $field_list); - $properties->setActionList($actions); + $curtain = $this->buildCurtain($package); + $details = $this->buildPackageDetailView($package, $field_list); if ($package->isArchived()) { $header_icon = 'fa-ban'; @@ -61,11 +60,8 @@ final class PhabricatorOwnersDetailController ->setUser($viewer) ->setHeader($package->getName()) ->setStatus($header_icon, $header_color, $header_name) - ->setPolicyObject($package); - - $panel = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + ->setPolicyObject($package) + ->setHeaderIcon('fa-gift'); $commit_views = array(); @@ -91,11 +87,13 @@ final class PhabricatorOwnersDetailController $commit_views[] = array( 'view' => $view, - 'header' => pht('Commits in this Package that Need Attention'), + 'header' => pht('Needs Attention'), + 'icon' => 'fa-warning', 'button' => id(new PHUIButtonView()) ->setTag('a') ->setHref($commit_uri->alter('status', $status_concern)) - ->setText(pht('View All Problem Commits')), + ->setIcon('fa-list-ul') + ->setText(pht('View All')), ); $all_commits = id(new DiffusionCommitQuery()) @@ -112,11 +110,13 @@ final class PhabricatorOwnersDetailController $commit_views[] = array( 'view' => $view, - 'header' => pht('Recent Commits in Package'), + 'header' => pht('Recent Commits'), + 'icon' => 'fa-code', 'button' => id(new PHUIButtonView()) ->setTag('a') ->setHref($commit_uri) - ->setText(pht('View All Package Commits')), + ->setIcon('fa-list-ul') + ->setText(pht('View All')), ); $phids = array(); @@ -128,14 +128,16 @@ final class PhabricatorOwnersDetailController $commit_panels = array(); foreach ($commit_views as $commit_view) { - $commit_panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader($commit_view['header']); + $commit_panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + $commit_header = id(new PHUIHeaderView()) + ->setHeader($commit_view['header']) + ->setHeaderIcon($commit_view['icon']); if (isset($commit_view['button'])) { - $header->addActionLink($commit_view['button']); + $commit_header->addActionLink($commit_view['button']); } $commit_view['view']->setHandles($handles); - $commit_panel->setHeader($header); + $commit_panel->setHeader($commit_header); $commit_panel->appendChild($commit_view['view']); $commit_panels[] = $commit_panel; @@ -143,32 +145,34 @@ final class PhabricatorOwnersDetailController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($package->getName()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $package, new PhabricatorOwnersPackageTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $panel, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $this->renderPathsTable($paths, $repositories), $commit_panels, $timeline, - ), - array( - 'title' => $package->getName(), - )); + )) + ->addPropertySection(pht('Details'), $details); + + return $this->newPage() + ->setTitle($package->getName()) + ->setCrumbs($crumbs) + ->appendChild($view); } - - private function buildPackagePropertyView( + private function buildPackageDetailView( PhabricatorOwnersPackage $package, PhabricatorCustomFieldList $field_list) { $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) ->setUser($viewer); @@ -190,13 +194,10 @@ final class PhabricatorOwnersDetailController $description = $package->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); - $view->addSectionHeader( - pht('Description'), PHUIPropertyListView::ICON_SUMMARY); + $view->addSectionHeader(pht('Description')); $view->addTextContent($description); } - $view->invokeWillRenderEvent(); - $field_list->appendFieldsToPropertyList( $package, $viewer, @@ -205,7 +206,7 @@ final class PhabricatorOwnersDetailController return $view; } - private function buildPackageActionView(PhabricatorOwnersPackage $package) { + private function buildCurtain(PhabricatorOwnersPackage $package) { $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -217,45 +218,43 @@ final class PhabricatorOwnersDetailController $edit_uri = $this->getApplicationURI("/edit/{$id}/"); $paths_uri = $this->getApplicationURI("/paths/{$id}/"); - $action_list = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($package); + $curtain = $this->newCurtainView($package); - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Package')) - ->setIcon('fa-pencil') - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit) - ->setHref($edit_uri)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Package')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)); if ($package->isArchived()) { - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Activate Package')) - ->setIcon('fa-check') - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("/archive/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Activate Package')) + ->setIcon('fa-check') + ->setDisabled(!$can_edit) + ->setWorkflow($can_edit) + ->setHref($this->getApplicationURI("/archive/{$id}/"))); } else { - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Archive Package')) - ->setIcon('fa-ban') - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("/archive/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Archive Package')) + ->setIcon('fa-ban') + ->setDisabled(!$can_edit) + ->setWorkflow($can_edit) + ->setHref($this->getApplicationURI("/archive/{$id}/"))); } - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Paths')) - ->setIcon('fa-folder-open') - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit) - ->setHref($paths_uri)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Paths')) + ->setIcon('fa-folder-open') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($paths_uri)); - return $action_list; + return $curtain; } private function renderPathsTable(array $paths, array $repositories) { @@ -314,8 +313,13 @@ final class PhabricatorOwnersDetailController 'wide', )); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Paths')) + ->setHeaderIcon('fa-folder-open'); + $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Paths')) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); if ($info) { diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index ccca55b6c5..ad7db5cc6e 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -139,8 +139,9 @@ final class PhabricatorOwnersPathsController ->addCancelButton($cancel_uri) ->setValue(pht('Save Paths'))); - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Edit Paths')) + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Paths')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); @@ -148,18 +149,23 @@ final class PhabricatorOwnersPathsController $package->getName(), $this->getApplicationURI('package/'.$package->getID().'/')); $crumbs->addTextCrumb(pht('Edit Paths')); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => array( - $package->getName(), - pht('Edit Paths'), - ), - )); - } + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Paths: %s', $package->getName())) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + $title = array($package->getName(), pht('Edit Paths')); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + + } } diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php index 62119e6dd6..fc97be6fce 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php @@ -35,7 +35,7 @@ final class PhabricatorOwnersPackageEditEngine } protected function getObjectEditTitleText($object) { - return pht('Edit Package %s', $object->getName()); + return pht('Edit Package: %s', $object->getName()); } protected function getObjectEditShortText($object) { @@ -46,6 +46,10 @@ final class PhabricatorOwnersPackageEditEngine return pht('Create Package'); } + protected function getObjectName() { + return pht('Package'); + } + protected function getObjectViewURI($object) { $id = $object->getID(); return "/owners/package/{$id}/"; diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index e007b18c00..adfc4d94d8 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -451,7 +451,7 @@ final class PhabricatorOwnersPackage } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php index cfddcbcc4a..87afe22cc6 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php @@ -49,20 +49,26 @@ final class PassphraseCredentialCreateController extends PassphraseController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Create')); + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Create New Credential')) + ->setHeaderText(pht('Credential')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index a749e82af3..4aa687ddc1 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -36,7 +36,8 @@ final class PassphraseCredentialEditController extends PassphraseController { $credential = PassphraseCredential::initializeNewCredential($viewer) ->setCredentialType($type->getCredentialType()) - ->setProvidesType($type->getProvidesType()); + ->setProvidesType($type->getProvidesType()) + ->attachImplementation($type); $is_new = true; @@ -310,20 +311,21 @@ final class PassphraseCredentialEditController extends PassphraseController { } $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); if ($is_new) { - $title = pht('Create Credential'); - $header = pht('Create New Credential'); + $title = pht('Create New Credential'); $crumbs->addTextCrumb(pht('Create')); $cancel_uri = $this->getApplicationURI(); + $header_icon = 'fa-plus-square'; } else { - $title = pht('Edit Credential'); - $header = pht('Edit Credential %s', 'K'.$credential->getID()); + $title = pht('Edit Credential: %s', $credential->getName()); $crumbs->addTextCrumb( 'K'.$credential->getID(), '/K'.$credential->getID()); $crumbs->addTextCrumb(pht('Edit')); $cancel_uri = '/K'.$credential->getID(); + $header_icon = 'fa-pencil'; } if ($request->isAjax()) { @@ -331,16 +333,13 @@ final class PassphraseCredentialEditController extends PassphraseController { $errors = id(new PHUIInfoView())->setErrors($errors); } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FORM) ->setTitle($title) ->appendChild($errors) ->appendChild($form->buildLayoutView()) ->addSubmitButton(pht('Create Credential')) ->addCancelButton($cancel_uri); - - return id(new AphrontDialogResponse())->setDialog($dialog); } $form->appendChild( @@ -349,19 +348,26 @@ final class PassphraseCredentialEditController extends PassphraseController { ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($header) + ->setHeaderText(pht('Credential')) ->setFormErrors($errors) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function getCredentialType($type_const) { diff --git a/src/applications/passphrase/controller/PassphraseCredentialPublicController.php b/src/applications/passphrase/controller/PassphraseCredentialPublicController.php index 56fc6ac4ae..481d111fd9 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialPublicController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialPublicController.php @@ -40,14 +40,12 @@ final class PassphraseCredentialPublicController ->setReadOnly(true) ->setValue($public_key)); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FORM) ->setTitle(pht('Public Key (%s)', $credential->getMonogram())) ->appendChild($body) ->addCancelButton($view_uri, pht('Done')); - return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index 5ec2fe7c07..db31964773 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -14,43 +14,34 @@ final class PassphraseCredentialViewController extends PassphraseController { return new Aphront404Response(); } - $type = PassphraseCredentialType::getTypeByConstant( - $credential->getCredentialType()); - if (!$type) { - throw new Exception(pht('Credential has invalid type "%s"!', $type)); - } + $type = $credential->getImplementation(); $timeline = $this->buildTransactionTimeline( $credential, new PassphraseCredentialTransactionQuery()); $timeline->setShouldTerminate(true); - $title = pht('%s %s', 'K'.$credential->getID(), $credential->getName()); + $title = pht('%s %s', $credential->getMonogram(), $credential->getName()); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb('K'.$credential->getID()); + $crumbs->addTextCrumb($credential->getMonogram()); $crumbs->setBorder(true); $header = $this->buildHeaderView($credential); - $actions = $this->buildActionView($credential, $type); - $properties = $this->buildPropertyView($credential, $type); + $curtain = $this->buildCurtain($credential, $type); $subheader = $this->buildSubheaderView($credential); $content = $this->buildPropertySectionView($credential, $type); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) + ->setCurtain($curtain) ->setMainColumn($timeline) - ->addPropertySection(pht('PROPERTIES'), $content) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('PROPERTIES'), $content); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } private function buildHeaderView(PassphraseCredential $credential) { @@ -59,7 +50,8 @@ final class PassphraseCredentialViewController extends PassphraseController { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($credential->getName()) - ->setPolicyObject($credential); + ->setPolicyObject($credential) + ->setHeaderIcon('fa-user-secret'); if ($credential->getIsDestroyed()) { $header->setStatus('fa-ban', 'red', pht('Destroyed')); @@ -97,10 +89,10 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setContent($content); } - private function buildActionView( + private function buildCurtain( PassphraseCredential $credential, PassphraseCredentialType $type) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $id = $credential->getID(); @@ -122,16 +114,14 @@ final class PassphraseCredentialViewController extends PassphraseController { $credential_conduit_icon = 'fa-wrench'; } - $actions = id(new PhabricatorActionListView()) - ->setObject($credential) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $credential, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain = $this->newCurtainView($credential); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Credential')) ->setIcon('fa-pencil') @@ -140,7 +130,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setWorkflow(!$can_edit)); if (!$credential->getIsDestroyed()) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Destroy Credential')) ->setIcon('fa-times') @@ -148,7 +138,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Secret')) ->setIcon('fa-eye') @@ -157,7 +147,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setWorkflow(true)); if ($type->hasPublicKey()) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Public Key')) ->setIcon('fa-download') @@ -166,7 +156,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setWorkflow(true)); } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($credential_conduit_text) ->setIcon($credential_conduit_icon) @@ -174,7 +164,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($credential_lock_text) ->setIcon($credential_lock_icon) @@ -183,8 +173,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setWorkflow(true)); } - - return $actions; + return $curtain; } private function buildPropertySectionView( @@ -235,17 +224,4 @@ final class PassphraseCredentialViewController extends PassphraseController { return $properties; } - private function buildPropertyView( - PassphraseCredential $credential, - PassphraseCredentialType $type) { - $viewer = $this->getRequest()->getUser(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($credential); - - $properties->invokeWillRenderEvent(); - return $properties; - } - } diff --git a/src/applications/passphrase/credentialtype/PassphraseTokenCredentialType.php b/src/applications/passphrase/credentialtype/PassphraseTokenCredentialType.php new file mode 100644 index 0000000000..d057d61eba --- /dev/null +++ b/src/applications/passphrase/credentialtype/PassphraseTokenCredentialType.php @@ -0,0 +1,37 @@ +getCredentialTypeImplementation(); + $credential_type = $object->getImplementation(); if (!$credential_type->shouldRequireUsername()) { break; } diff --git a/src/applications/passphrase/keys/PassphraseAbstractKey.php b/src/applications/passphrase/keys/PassphraseAbstractKey.php index da56de15af..e3d902236d 100644 --- a/src/applications/passphrase/keys/PassphraseAbstractKey.php +++ b/src/applications/passphrase/keys/PassphraseAbstractKey.php @@ -32,13 +32,13 @@ abstract class PassphraseAbstractKey extends Phobject { PassphraseCredential $credential, $provides_type) { - $type = $credential->getCredentialTypeImplementation(); + $type = $credential->getImplementation(); if (!$type) { throw new Exception( pht( 'Credential "%s" is of unknown type "%s"!', - 'K'.$credential->getID(), + $credential->getMonogram(), $credential->getCredentialType())); } @@ -46,7 +46,7 @@ abstract class PassphraseAbstractKey extends Phobject { throw new Exception( pht( 'Credential "%s" must provide "%s", but provides "%s"!', - 'K'.$credential->getID(), + $credential->getMonogram(), $provides_type, $type->getProvidesType())); } diff --git a/src/applications/passphrase/query/PassphraseCredentialQuery.php b/src/applications/passphrase/query/PassphraseCredentialQuery.php index 9411fa8a77..3a78ac0d13 100644 --- a/src/applications/passphrase/query/PassphraseCredentialQuery.php +++ b/src/applications/passphrase/query/PassphraseCredentialQuery.php @@ -89,6 +89,17 @@ final class PassphraseCredentialQuery } } + foreach ($page as $key => $credential) { + $type = PassphraseCredentialType::getTypeByConstant( + $credential->getCredentialType()); + if (!$type) { + unset($page[$key]); + continue; + } + + $credential->attachImplementation(clone $type); + } + return $page; } diff --git a/src/applications/passphrase/storage/PassphraseCredential.php b/src/applications/passphrase/storage/PassphraseCredential.php index f263523b49..88bc4595bd 100644 --- a/src/applications/passphrase/storage/PassphraseCredential.php +++ b/src/applications/passphrase/storage/PassphraseCredential.php @@ -25,6 +25,7 @@ final class PassphraseCredential extends PassphraseDAO protected $spacePHID; private $secret = self::ATTACHABLE; + private $implementation = self::ATTACHABLE; public static function initializeNewCredential(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -98,6 +99,15 @@ final class PassphraseCredential extends PassphraseDAO return PassphraseCredentialType::getTypeByConstant($type); } + public function attachImplementation(PassphraseCredentialType $impl) { + $this->implementation = $impl; + return $this; + } + + public function getImplementation() { + return $this->assertAttached($this->implementation); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ @@ -157,10 +167,6 @@ final class PassphraseCredential extends PassphraseDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php index 2dae0132d4..5cdf6afdab 100644 --- a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php +++ b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php @@ -61,7 +61,7 @@ final class PasteCreateConduitAPIMethod extends PasteConduitAPIMethod { $editor = id(new PhabricatorPasteEditor()) ->setActor($viewer) ->setContinueOnNoEffect(true) - ->setContentSourceFromConduitRequest($request); + ->setContentSource($request->newContentSource()); $xactions = $editor->applyTransactions($paste, $xactions); diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index f259cdc6af..f29c93a162 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -40,15 +40,9 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { return new Aphront404Response(); } - $forks = id(new PhabricatorPasteQuery()) - ->setViewer($viewer) - ->withParentPHIDs(array($paste->getPHID())) - ->execute(); - $fork_phids = mpull($forks, 'getPHID'); - $header = $this->buildHeaderView($paste); - $actions = $this->buildActionView($viewer, $paste); - $properties = $this->buildPropertyView($paste, $fork_phids); + $curtain = $this->buildCurtain($paste); + $subheader = $this->buildSubheaderView($paste); $source_code = $this->buildSourceCodeView($paste, $this->highlightMap); @@ -78,8 +72,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $timeline, $comment_view, )) - ->setPropertyList($properties) - ->setActionList($actions) + ->setCurtain($curtain) ->addClass('ponder-question-view'); return $this->newPage() @@ -110,14 +103,15 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { ->setHeader($title) ->setUser($this->getRequest()->getUser()) ->setStatus($header_icon, $header_color, $header_name) - ->setPolicyObject($paste); + ->setPolicyObject($paste) + ->setHeaderIcon('fa-clipboard'); return $header; } - private function buildActionView( - PhabricatorUser $viewer, - PhabricatorPaste $paste) { + private function buildCurtain(PhabricatorPaste $paste) { + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($paste); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -125,43 +119,42 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { PhabricatorPolicyCapability::CAN_EDIT); $id = $paste->getID(); + $edit_uri = $this->getApplicationURI("edit/{$id}/"); + $archive_uri = $this->getApplicationURI("archive/{$id}/"); + $raw_uri = $this->getApplicationURI("raw/{$id}/"); - $action_list = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($paste); - - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Paste')) - ->setIcon('fa-pencil') - ->setDisabled(!$can_edit) - ->setHref($this->getApplicationURI("edit/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Paste')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setHref($edit_uri)); if ($paste->isArchived()) { - $action_list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht('Activate Paste')) - ->setIcon('fa-check') - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("archive/{$id}/"))); + ->setName(pht('Activate Paste')) + ->setIcon('fa-check') + ->setDisabled(!$can_edit) + ->setWorkflow($can_edit) + ->setHref($archive_uri)); } else { - $action_list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht('Archive Paste')) - ->setIcon('fa-ban') - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("archive/{$id}/"))); + ->setName(pht('Archive Paste')) + ->setIcon('fa-ban') + ->setDisabled(!$can_edit) + ->setWorkflow($can_edit) + ->setHref($archive_uri)); } - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Raw File')) - ->setIcon('fa-file-text-o') - ->setHref($this->getApplicationURI("raw/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Raw File')) + ->setIcon('fa-file-text-o') + ->setHref($raw_uri)); - return $action_list; + return $curtain; } @@ -190,32 +183,4 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { ->setContent($content); } - private function buildPropertyView( - PhabricatorPaste $paste, - array $child_phids) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($paste); - - if ($paste->getParentPHID()) { - $properties->addProperty( - pht('Forked From'), - $viewer->renderHandle($paste->getParentPHID())); - } - - if ($child_phids) { - $properties->addProperty( - pht('Forks'), - $viewer->renderHandleList($child_phids)); - } - - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $paste); - - return $properties; - } - } diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php index 4cc35ad6e3..f88ab76697 100644 --- a/src/applications/paste/editor/PhabricatorPasteEditEngine.php +++ b/src/applications/paste/editor/PhabricatorPasteEditEngine.php @@ -35,7 +35,7 @@ final class PhabricatorPasteEditEngine } protected function getObjectEditTitleText($object) { - return pht('Edit %s %s', $object->getMonogram(), $object->getTitle()); + return pht('Edit Paste: %s', $object->getTitle()); } protected function getObjectEditShortText($object) { @@ -46,6 +46,10 @@ final class PhabricatorPasteEditEngine return pht('Create Paste'); } + protected function getObjectName() { + return pht('Paste'); + } + protected function getCommentViewHeaderText($object) { return pht('Eat Paste'); } diff --git a/src/applications/paste/mail/PasteCreateMailReceiver.php b/src/applications/paste/mail/PasteCreateMailReceiver.php index 672667cd65..85acf7f45d 100644 --- a/src/applications/paste/mail/PasteCreateMailReceiver.php +++ b/src/applications/paste/mail/PasteCreateMailReceiver.php @@ -37,11 +37,7 @@ final class PasteCreateMailReceiver extends PhabricatorMailReceiver { $paste = PhabricatorPaste::initializeNewPaste($sender); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_EMAIL, - array( - 'id' => $mail->getID(), - )); + $content_source = $mail->newContentSource(); $editor = id(new PhabricatorPasteEditor()) ->setActor($sender) @@ -69,4 +65,5 @@ final class PasteCreateMailReceiver extends PhabricatorMailReceiver { ->saveAndSend(); } + } diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php index 624c70c410..0e8f497936 100644 --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -155,10 +155,6 @@ final class PhabricatorPaste extends PhabricatorPasteDAO return ($this->authorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ @@ -268,7 +264,7 @@ final class PhabricatorPaste extends PhabricatorPasteDAO id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('status') ->setType('string') - ->setDescription(pht('Active or arhived status of the paste.')), + ->setDescription(pht('Active or archived status of the paste.')), ); } diff --git a/src/applications/people/conduit/UserWhoAmIConduitAPIMethod.php b/src/applications/people/conduit/UserWhoAmIConduitAPIMethod.php index 13884e1e83..2911056f5e 100644 --- a/src/applications/people/conduit/UserWhoAmIConduitAPIMethod.php +++ b/src/applications/people/conduit/UserWhoAmIConduitAPIMethod.php @@ -19,7 +19,7 @@ final class UserWhoAmIConduitAPIMethod extends UserConduitAPIMethod { } public function getRequiredScope() { - return PhabricatorOAuthServerScope::SCOPE_WHOAMI; + return self::SCOPE_ALWAYS; } protected function execute(ConduitAPIRequest $request) { diff --git a/src/applications/people/controller/PhabricatorPeopleApproveController.php b/src/applications/people/controller/PhabricatorPeopleApproveController.php index a906b15655..a63682239f 100644 --- a/src/applications/people/controller/PhabricatorPeopleApproveController.php +++ b/src/applications/people/controller/PhabricatorPeopleApproveController.php @@ -53,8 +53,7 @@ final class PhabricatorPeopleApproveController return id(new AphrontRedirectResponse())->setURI($done_uri); } - $dialog = id(new AphrontDialogView()) - ->setUser($admin) + return $this->newDialog() ->setTitle(pht('Confirm Approval')) ->appendChild( pht( @@ -62,7 +61,5 @@ final class PhabricatorPeopleApproveController phutil_tag('strong', array(), $user->getUsername()))) ->addCancelButton($done_uri) ->addSubmitButton(pht('Approve Account')); - - return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/people/controller/PhabricatorPeopleCreateController.php b/src/applications/people/controller/PhabricatorPeopleCreateController.php index 15a34d0063..82a27e2f9e 100644 --- a/src/applications/people/controller/PhabricatorPeopleCreateController.php +++ b/src/applications/people/controller/PhabricatorPeopleCreateController.php @@ -90,19 +90,25 @@ final class PhabricatorPeopleCreateController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-user'); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('User')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/people/controller/PhabricatorPeopleInviteSendController.php b/src/applications/people/controller/PhabricatorPeopleInviteSendController.php index 6a7049215d..e611606a79 100644 --- a/src/applications/people/controller/PhabricatorPeopleInviteSendController.php +++ b/src/applications/people/controller/PhabricatorPeopleInviteSendController.php @@ -125,8 +125,10 @@ final class PhabricatorPeopleInviteSendController } else { $crumbs->addTextCrumb(pht('Invite Users')); } + $crumbs->setBorder(true); $confirm_box = null; + $info_view = null; if ($is_confirm) { $handles = array(); @@ -157,14 +159,15 @@ final class PhabricatorPeopleInviteSendController ->setValue(pht('Send Invitations'))); } + $info_view = id(new PHUIInfoView()) + ->setErrors($confirm_errors) + ->setSeverity($severity); + $confirm_box = id(new PHUIObjectBoxView()) - ->setInfoView( - id(new PHUIInfoView()) - ->setErrors($confirm_errors) - ->setSeverity($severity)) ->setHeaderText(pht('Confirm Invites')) ->setTable($invite_table) - ->appendChild($confirm_form); + ->appendChild($confirm_form) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); } $form = id(new AphrontFormView()) @@ -197,23 +200,32 @@ final class PhabricatorPeopleInviteSendController : pht('Continue')) ->addCancelButton($this->getApplicationURI('invite/'))); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-group'); + $box = id(new PHUIObjectBoxView()) ->setHeaderText( $is_confirm ? pht('Revise Invites') : pht('Invite Users')) ->setFormErrors($errors) - ->setForm($form); + ->setForm($form) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $info_view, $confirm_box, $box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/people/controller/PhabricatorPeopleLdapController.php b/src/applications/people/controller/PhabricatorPeopleLdapController.php index 2e030ad580..1a6530ab31 100644 --- a/src/applications/people/controller/PhabricatorPeopleLdapController.php +++ b/src/applications/people/controller/PhabricatorPeopleLdapController.php @@ -42,7 +42,6 @@ final class PhabricatorPeopleLdapController $this->getApplicationURI('/ldap/')); $nav = $this->buildSideNavView(); - $nav->setCrumbs($crumbs); $nav->selectFilter('ldap'); $nav->appendChild($content); @@ -56,11 +55,10 @@ final class PhabricatorPeopleLdapController $nav->appendChild($this->processSearchRequest($request)); } - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Import Ldap Users'), - )); + return $this->newPage() + ->setTitle(pht('Import Ldap Users')) + ->setCrumbs($crumbs) + ->setNavigation($nav); } private function processImportRequest($request) { diff --git a/src/applications/people/controller/PhabricatorPeopleNewController.php b/src/applications/people/controller/PhabricatorPeopleNewController.php index 2590129c09..60f9f47b5d 100644 --- a/src/applications/people/controller/PhabricatorPeopleNewController.php +++ b/src/applications/people/controller/PhabricatorPeopleNewController.php @@ -208,22 +208,28 @@ final class PhabricatorPeopleNewController $title = pht('Create New User'); - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('User')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-user'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php index cb258d17ac..9f132a18d8 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php @@ -75,24 +75,33 @@ final class PhabricatorPeopleProfileEditController } $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Edit Profile')) + ->setHeaderText(pht('Profile')) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - if ($note) { - $form_box->setInfoView($note); - } - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Profile')); + $crumbs->setBorder(true); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_MANAGE); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Profile: %s', $user->getFullName())) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $note, + $form_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setNavigation($nav) - ->appendChild($form_box); + ->appendChild($view); } } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php index 204fc7abda..51cf79ecff 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php @@ -35,17 +35,13 @@ final class PhabricatorPeopleProfileManageController $header = id(new PHUIHeaderView()) ->setHeader($user->getFullName()) ->setSubheader(array($profile_icon, $profile_title)) - ->setImage($picture); + ->setImage($picture) + ->setProfileHeader(true); - $actions = $this->buildActionList($user); + $curtain = $this->buildCurtain($user); $properties = $this->buildPropertyView($user); - $properties->setActionList($actions); $name = $user->getUsername(); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_MANAGE); @@ -56,6 +52,16 @@ final class PhabricatorPeopleProfileManageController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Manage')); + $crumbs->setBorder(true); + + $manage = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties) + ->setMainColumn( + array( + $timeline, + )); return $this->newPage() ->setTitle( @@ -67,8 +73,7 @@ final class PhabricatorPeopleProfileManageController ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $timeline, + $manage, )); } @@ -87,18 +92,17 @@ final class PhabricatorPeopleProfileManageController return $view; } - private function buildActionList(PhabricatorUser $user) { + private function buildCurtain(PhabricatorUser $user) { $viewer = $this->getViewer(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $user, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain = $this->newCurtainView($user); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Profile')) @@ -106,7 +110,7 @@ final class PhabricatorPeopleProfileManageController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-picture-o') ->setName(pht('Edit Profile Picture')) @@ -114,7 +118,7 @@ final class PhabricatorPeopleProfileManageController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-wrench') ->setName(pht('Edit Settings')) @@ -134,7 +138,7 @@ final class PhabricatorPeopleProfileManageController $is_self = ($user->getPHID() === $viewer->getPHID()); $can_admin = ($is_admin && !$is_self); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon($empower_icon) ->setName($empower_name) @@ -142,7 +146,7 @@ final class PhabricatorPeopleProfileManageController ->setWorkflow(true) ->setHref($this->getApplicationURI('empower/'.$user->getID().'/'))); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-tag') ->setName(pht('Change Username')) @@ -158,7 +162,7 @@ final class PhabricatorPeopleProfileManageController $disable_name = pht('Disable User'); } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon($disable_icon) ->setName($disable_name) @@ -166,7 +170,7 @@ final class PhabricatorPeopleProfileManageController ->setWorkflow(true) ->setHref($this->getApplicationURI('disable/'.$user->getID().'/'))); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-times') ->setName(pht('Delete User')) @@ -176,7 +180,7 @@ final class PhabricatorPeopleProfileManageController $can_welcome = ($is_admin && $user->canEstablishWebSessions()); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-envelope') ->setName(pht('Send Welcome Email')) @@ -184,7 +188,7 @@ final class PhabricatorPeopleProfileManageController ->setDisabled(!$can_welcome) ->setHref($this->getApplicationURI('welcome/'.$user->getID().'/'))); - return $actions; + return $curtain; } diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index cf78dd9c03..29b2290153 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -228,6 +228,7 @@ final class PhabricatorPeopleProfilePictureController $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $upload_form = id(new AphrontFormView()) @@ -247,22 +248,31 @@ final class PhabricatorPeopleProfilePictureController $upload_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Upload New Picture')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($upload_form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Profile Picture')); + $crumbs->setBorder(true); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_MANAGE); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Profile Picture')) + ->setHeaderIcon('fa-camera'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, + $upload_box, + )); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setNavigation($nav) - ->appendChild( - array( - $form_box, - $upload_box, - )); + ->appendChild($view); } } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 60b02b01f4..f77933df90 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -63,7 +63,6 @@ final class PhabricatorPeopleProfileViewController $home = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFluid(true) ->addClass('project-view-home') ->setMainColumn( array( @@ -182,24 +181,49 @@ final class PhabricatorPeopleProfileViewController return null; } - $badge_phids = $user->getBadgePHIDs(); - if ($badge_phids) { - $badges = id(new PhabricatorBadgesQuery()) + $awards = array(); + $badges = array(); + if ($user->getBadgePHIDs()) { + $awards = id(new PhabricatorBadgesAwardQuery()) ->setViewer($viewer) - ->withPHIDs($badge_phids) - ->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE)) + ->withRecipientPHIDs(array($user->getPHID())) ->execute(); + $awards = mpull($awards, null, 'getBadgePHID'); - $flex = new PHUIBadgeBoxView(); - foreach ($badges as $badge) { - $item = id(new PHUIBadgeView()) - ->setIcon($badge->getIcon()) - ->setHeader($badge->getName()) - ->setSubhead($badge->getFlavor()) - ->setQuality($badge->getQuality()); - $flex->addItem($item); + $badges = array(); + foreach ($awards as $award) { + $badge = $award->getBadge(); + if ($badge->getStatus() == PhabricatorBadgesBadge::STATUS_ACTIVE) { + $badges[$award->getBadgePHID()] = $badge; + } } + } + if (count($badges)) { + $flex = new PHUIBadgeBoxView(); + + foreach ($badges as $badge) { + if ($badge) { + $awarder_info = array(); + + $award = idx($awards, $badge->getPHID(), null); + $awarder_phid = $award->getAwarderPHID(); + $awarder_handle = $viewer->renderHandle($awarder_phid); + + $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()) + ->addByLine($awarder_info); + + $flex->addItem($item); + } + } } else { $error = id(new PHUIBoxView()) ->addClass('mlb') @@ -209,8 +233,40 @@ final class PhabricatorPeopleProfileViewController ->appendChild($error); } + // Best option? + $badges = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withStatuses(array( + PhabricatorBadgesBadge::STATUS_ACTIVE, + )) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-plus') + ->setText(pht('Award')) + ->setWorkflow(true) + ->setHref('/badges/award/'.$user->getID().'/'); + + $can_award = false; + if (count($badges)) { + $can_award = true; + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Badges')); + + if (count($badges)) { + $header->addActionLink($button); + } + $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Badges')) + ->setHeader($header) ->addClass('project-view-badges') ->appendChild($flex) ->setBackground(PHUIObjectBoxView::GREY); diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index d7885ef101..3370fb428b 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -700,7 +700,7 @@ final class PhabricatorUserEditor extends PhabricatorEditor { } } - private function revokePasswordResetLinks(PhabricatorUser $user) { + public function revokePasswordResetLinks(PhabricatorUser $user) { // Revoke any outstanding password reset links. If an attacker compromises // an account, changes the email address, and sends themselves a password // reset link, it could otherwise remain live for a short period of time @@ -710,8 +710,8 @@ final class PhabricatorUserEditor extends PhabricatorEditor { $user, array($user->getPHID()), array( - PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE, - PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE, + PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE, + PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE, )); } diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 2932d8c853..0bdb9fab2d 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -155,20 +155,17 @@ final class PhabricatorPeopleQuery } if ($this->needBadges) { - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs(mpull($users, 'getPHID')) - ->withEdgeTypes( - array( - PhabricatorRecipientHasBadgeEdgeType::EDGECONST, - )); - $edge_query->execute(); + $awards = id(new PhabricatorBadgesAwardQuery()) + ->setViewer($this->getViewer()) + ->withRecipientPHIDs(mpull($users, 'getPHID')) + ->execute(); + + $awards = mgroup($awards, 'getRecipientPHID'); foreach ($users as $user) { - $phids = $edge_query->getDestinationPHIDs( - array( - $user->getPHID(), - )); - $user->attachBadgePHIDs($phids); + $user_awards = idx($awards, $user->getPHID(), array()); + $badge_phids = mpull($user_awards, 'getBadgePHID'); + $user->attachBadgePHIDs($badge_phids); } } diff --git a/src/applications/people/storage/PhabricatorExternalAccount.php b/src/applications/people/storage/PhabricatorExternalAccount.php index 12d5f545a7..4bc0fcae98 100644 --- a/src/applications/people/storage/PhabricatorExternalAccount.php +++ b/src/applications/people/storage/PhabricatorExternalAccount.php @@ -54,15 +54,13 @@ final class PhabricatorExternalAccount extends PhabricatorUserDAO 'accountURI' => 'text255?', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, - ), 'account_details' => array( 'columns' => array('accountType', 'accountDomain', 'accountID'), 'unique' => true, ), + 'key_user' => array( + 'columns' => array('userPHID'), + ), ), ) + parent::getConfiguration(); } diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index c9d282dc6b..7af632ee28 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -814,6 +814,38 @@ final class PhabricatorUser return new DateTimeZone($this->getTimezoneIdentifier()); } + public function formatShortDateTime($when, $now = null) { + if ($now === null) { + $now = PhabricatorTime::getNow(); + } + + try { + $when = new DateTime('@'.$when); + $now = new DateTime('@'.$now); + } catch (Exception $ex) { + return null; + } + + $zone = $this->getTimeZone(); + + $when->setTimeZone($zone); + $now->setTimeZone($zone); + + if ($when->format('Y') !== $now->format('Y')) { + // Different year, so show "Feb 31 2075". + $format = 'M j Y'; + } else if ($when->format('Ymd') !== $now->format('Ymd')) { + // Same year but different month and day, so show "Feb 31". + $format = 'M j'; + } else { + // Same year, month and day so show a time of day. + $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; + $format = $this->getPreference($pref_time); + } + + return $when->format($format); + } + public function getPreference($key) { $preferences = $this->loadPreferences(); diff --git a/src/applications/phame/editor/PhameBlogEditEngine.php b/src/applications/phame/editor/PhameBlogEditEngine.php index 503a6bbd27..b9ee23a442 100644 --- a/src/applications/phame/editor/PhameBlogEditEngine.php +++ b/src/applications/phame/editor/PhameBlogEditEngine.php @@ -46,6 +46,10 @@ final class PhameBlogEditEngine return pht('Create Blog'); } + protected function getObjectName() { + return pht('Blog'); + } + protected function getObjectCreateCancelURI($object) { return $this->getApplication()->getApplicationURI('blog/'); } diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php index 652922ea56..1c58722e2e 100644 --- a/src/applications/phame/editor/PhamePostEditEngine.php +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -60,6 +60,10 @@ final class PhamePostEditEngine return pht('Create Post'); } + protected function getObjectName() { + return pht('Post'); + } + protected function getObjectViewURI($object) { return $object->getViewURI(); } diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index d9b9bdb41d..861def127b 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -336,10 +336,6 @@ final class PhameBlog extends PhameDAO return ($this->creatorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorConduitResultInterface )---------------------------------- */ diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 01dd71e88d..fb2e8058dc 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -282,10 +282,6 @@ final class PhamePost extends PhameDAO return ($this->bloggerPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorConduitResultInterface )---------------------------------- */ diff --git a/src/applications/phame/view/PhameBlogListView.php b/src/applications/phame/view/PhameBlogListView.php index 0d897b730a..5aa58ee8f9 100644 --- a/src/applications/phame/view/PhameBlogListView.php +++ b/src/applications/phame/view/PhameBlogListView.php @@ -3,7 +3,6 @@ final class PhameBlogListView extends AphrontTagView { private $blogs; - private $viewer; public function setBlogs($blogs) { assert_instances_of($blogs, 'PhameBlog'); @@ -11,11 +10,6 @@ final class PhameBlogListView extends AphrontTagView { return $this; } - public function setViewer($viewer) { - $this->viewer = $viewer; - return $this; - } - protected function getTagAttributes() { $classes = array(); $classes[] = 'phame-blog-list'; diff --git a/src/applications/phame/view/PhameDraftListView.php b/src/applications/phame/view/PhameDraftListView.php index 87c5a6d7b5..294eeb11bc 100644 --- a/src/applications/phame/view/PhameDraftListView.php +++ b/src/applications/phame/view/PhameDraftListView.php @@ -4,7 +4,6 @@ final class PhameDraftListView extends AphrontTagView { private $posts; private $blogs; - private $viewer; public function setPosts($posts) { assert_instances_of($posts, 'PhamePost'); @@ -18,11 +17,6 @@ final class PhameDraftListView extends AphrontTagView { return $this; } - public function setViewer($viewer) { - $this->viewer = $viewer; - return $this; - } - protected function getTagAttributes() { $classes = array(); $classes[] = 'phame-blog-list'; diff --git a/src/applications/phame/view/PhamePostListView.php b/src/applications/phame/view/PhamePostListView.php index dbe77d4aa6..fe6da36541 100644 --- a/src/applications/phame/view/PhamePostListView.php +++ b/src/applications/phame/view/PhamePostListView.php @@ -4,7 +4,6 @@ final class PhamePostListView extends AphrontTagView { private $posts; private $nodata; - private $viewer; private $showBlog = false; private $isExternal; private $isLive; @@ -25,11 +24,6 @@ final class PhamePostListView extends AphrontTagView { return $this; } - public function setViewer($viewer) { - $this->viewer = $viewer; - return $this; - } - public function setIsExternal($is_external) { $this->isExternal = $is_external; return $this; @@ -53,7 +47,7 @@ final class PhamePostListView extends AphrontTagView { } protected function getTagContent() { - $viewer = $this->viewer; + $viewer = $this->getViewer(); $posts = $this->posts; $nodata = $this->nodata; diff --git a/src/applications/phid/PhabricatorPHIDConstants.php b/src/applications/phid/PhabricatorPHIDConstants.php index 6e6dfca6dc..91330bf499 100644 --- a/src/applications/phid/PhabricatorPHIDConstants.php +++ b/src/applications/phid/PhabricatorPHIDConstants.php @@ -10,8 +10,6 @@ final class PhabricatorPHIDConstants extends Phobject { const PHID_TYPE_XCMT = 'XCMT'; - const PHID_TYPE_XOBJ = 'XOBJ'; - const PHID_TYPE_VOID = 'VOID'; const PHID_VOID = 'PHID-VOID-00000000000000000000'; diff --git a/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php b/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php new file mode 100644 index 0000000000..35920e16e0 --- /dev/null +++ b/src/applications/phid/remarkup/PhabricatorHandleRemarkupRule.php @@ -0,0 +1,109 @@ +getEngine(); + $viewer = $engine->getConfig('viewer'); + + if (!$this->isFlatText($matches[0])) { + return $matches[0]; + } + + $phid_type = phid_get_type($matches[1]); + if ($phid_type == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { + return $matches[0]; + } + + $token = $engine->storeText($matches[0]); + if ($engine->isTextMode()) { + return $token; + } + + $original_key = self::KEY_RULE_HANDLE_ORIGINAL; + $original = $engine->getTextMetadata($original_key, array()); + $original[$token] = $matches[0]; + $engine->setTextMetadata($original_key, $original); + + $metadata_key = self::KEY_RULE_HANDLE; + $metadata = $engine->getTextMetadata($metadata_key, array()); + $phid = $matches[1]; + if (empty($metadata[$phid])) { + $metadata[$phid] = array(); + } + $metadata[$phid][] = $token; + $engine->setTextMetadata($metadata_key, $metadata); + + return $token; + } + + public function didMarkupText() { + $engine = $this->getEngine(); + + $metadata_key = self::KEY_RULE_HANDLE; + + $metadata = $engine->getTextMetadata($metadata_key, array()); + if (empty($metadata)) { + // No mentions, or we already processed them. + return; + } + + $original_key = self::KEY_RULE_HANDLE_ORIGINAL; + $original = $engine->getTextMetadata($original_key, array()); + + $phids = array_keys($metadata); + + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->getEngine()->getConfig('viewer')) + ->withPHIDs($phids) + ->execute(); + + foreach ($metadata as $phid => $tokens) { + $handle = idx($handles, $phid); + + if ($handle->isComplete()) { + if ($engine->isHTMLMailMode()) { + $href = $handle->getURI(); + $href = PhabricatorEnv::getProductionURI($href); + + $link = phutil_tag( + 'a', + array( + 'href' => $href, + 'style' => ' + border-color: #f1f7ff; + color: #19558d; + background-color: #f1f7ff; + border: 1px solid transparent; + border-radius: 3px; + font-weight: bold; + padding: 0 4px;', + ), + $handle->getLinkName()); + } else { + $link = $handle->renderTag(); + $link->setPHID($phid); + } + foreach ($tokens as $token) { + $engine->overwriteStoredText($token, $link); + } + } else { + foreach ($tokens as $token) { + $engine->overwriteStoredText($token, idx($original, $token)); + } + } + } + + $engine->setTextMetadata($metadata_key, array()); + } +} diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php index b789fa1bf1..eb909d4b86 100644 --- a/src/applications/phid/view/PHUIHandleTagListView.php +++ b/src/applications/phid/view/PHUIHandleTagListView.php @@ -61,7 +61,7 @@ final class PHUIHandleTagListView extends AphrontTagView { } } - if ($this->limit && ($this->limit > count($handles))) { + if ($this->limit && (count($handles) > $this->limit)) { if (!is_array($handles)) { $handles = iterator_to_array($handles); } @@ -85,7 +85,7 @@ final class PHUIHandleTagListView extends AphrontTagView { } if ($this->limit) { - if ($this->limit < count($this->handles)) { + if (count($this->handles) > $this->limit) { $tip_text = implode(', ', mpull($this->handles, 'getName')); $more = $this->newPlaceholderTag() diff --git a/src/applications/phlux/controller/PhluxEditController.php b/src/applications/phlux/controller/PhluxEditController.php index 775737c9b7..f37795a754 100644 --- a/src/applications/phlux/controller/PhluxEditController.php +++ b/src/applications/phlux/controller/PhluxEditController.php @@ -154,24 +154,34 @@ final class PhluxEditController extends PhluxController { if ($is_new) { $title = pht('Create Variable'); $crumbs->addTextCrumb($title, $request->getRequestURI()); + $header_icon = 'fa-plus-square'; } else { - $title = pht('Edit %s', $key); + $title = pht('Edit Variable: %s', $key); + $header_icon = 'fa-pencil'; $crumbs->addTextCrumb($title, $request->getRequestURI()); } + $crumbs->setBorder(true); - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Variable')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/phlux/controller/PhluxListController.php b/src/applications/phlux/controller/PhluxListController.php index 550f571a6a..e75fb09999 100644 --- a/src/applications/phlux/controller/PhluxListController.php +++ b/src/applications/phlux/controller/PhluxListController.php @@ -13,6 +13,7 @@ final class PhluxListController extends PhluxController { $vars = $query->executeWithCursorPager($pager); $view = new PHUIObjectItemListView(); + $view->setFlush(true); foreach ($vars as $var) { $key = $var->getVariableKey(); @@ -28,19 +29,31 @@ final class PhluxListController extends PhluxController { $crumbs = $this->buildApplicationCrumbs(); + $box = id(new PHUIObjectBoxView()) + ->setHeaderText('Variables') + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); + $title = pht('Variable List'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-copy'); $crumbs->addTextCrumb($title, $this->getApplicationURI()); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $view, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, $pager, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phlux/controller/PhluxViewController.php b/src/applications/phlux/controller/PhluxViewController.php index 384e15b57e..b3019443be 100644 --- a/src/applications/phlux/controller/PhluxViewController.php +++ b/src/applications/phlux/controller/PhluxViewController.php @@ -15,40 +15,24 @@ final class PhluxViewController extends PhluxController { return new Aphront404Response(); } - $crumbs = $this->buildApplicationCrumbs(); - $title = $var->getVariableKey(); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, $request->getRequestURI()); + $crumbs->setBorder(true); + + $curtain = $this->buildCurtainView($var); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($var); - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($var); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $var, - PhabricatorPolicyCapability::CAN_EDIT); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Edit Variable')) - ->setHref($this->getApplicationURI('/edit/'.$var->getVariableKey().'/')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); + ->setPolicyObject($var) + ->setHeaderIcon('fa-copy'); $display_value = json_encode($var->getVariableValue()); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($var) - ->setActionList($actions) ->addProperty(pht('Value'), $display_value); $timeline = $this->buildTransactionTimeline( @@ -57,18 +41,43 @@ final class PhluxViewController extends PhluxController { $timeline->setShouldTerminate(true); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( $object_box, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildCurtainView(PhluxVariable $var) { + $viewer = $this->getViewer(); + + $curtain = $this->newCurtainView($var); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $var, + PhabricatorPolicyCapability::CAN_EDIT); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Variable')) + ->setHref($this->getApplicationURI('/edit/'.$var->getVariableKey().'/')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + return $curtain; } } diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index 60c4202444..f3dedc9a33 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -22,7 +22,8 @@ final class PholioMockEditController extends PholioController { return new Aphront404Response(); } - $title = pht('Edit Mock'); + $title = pht('Edit Mock: %s', $mock->getName()); + $header_icon = 'fa-pencil'; $is_new = false; $mock_images = $mock->getImages(); @@ -32,6 +33,7 @@ final class PholioMockEditController extends PholioController { $mock = PholioMock::initializeNewMock($viewer); $title = pht('Create Mock'); + $header_icon = 'fa-plus-square'; $is_new = true; $files = array(); @@ -350,8 +352,9 @@ final class PholioMockEditController extends PholioController { ->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Mock')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); @@ -359,21 +362,22 @@ final class PholioMockEditController extends PholioController { $crumbs->addTextCrumb($mock->getMonogram(), '/'.$mock->getMonogram()); } $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - $content = array( - $crumbs, - $form_box, - ); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->addQuicksandConfig( array('mockEditConfig' => true)) - ->appendChild( - array( - $form_box, - )); + ->appendChild($view); } } diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index 78c1d4bfe9..565d2d6cb4 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -57,7 +57,8 @@ final class PholioMockViewController extends PholioController { ->setHeader($title) ->setUser($viewer) ->setStatus($header_icon, $header_color, $header_name) - ->setPolicyObject($mock); + ->setPolicyObject($mock) + ->setHeaderIcon('fa-camera-retro'); $timeline = $this->buildTransactionTimeline( $mock, @@ -65,8 +66,8 @@ final class PholioMockViewController extends PholioController { $engine); $timeline->setMock($mock); - $actions = $this->buildActionView($mock); - $properties = $this->buildPropertyView($mock, $engine, $actions); + $curtain = $this->buildCurtainView($mock); + $details = $this->buildDescriptionView($mock, $engine); require_celerity_resource('pholio-css'); require_celerity_resource('pholio-inline-comments-css'); @@ -80,51 +81,50 @@ final class PholioMockViewController extends PholioController { ->setImageID($image_id); $output = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Image')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($mock_view); $add_comment = $this->buildAddCommentView($mock, $comment_form_id); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('M'.$mock->getID(), '/M'.$mock->getID()); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $thumb_grid = id(new PholioMockThumbGridView()) ->setUser($viewer) ->setMock($mock); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $output, + $thumb_grid, + $details, + $timeline, + $add_comment, + )); + return $this->newPage() ->setTitle('M'.$mock->getID().' '.$title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($mock->getPHID())) ->addQuicksandConfig( array('mockViewConfig' => $mock_view->getBehaviorConfig())) - ->appendChild( - array( - $object_box, - $output, - $thumb_grid, - $timeline, - $add_comment, - )); + ->appendChild($view); } - private function buildActionView(PholioMock $mock) { + private function buildCurtainView(PholioMock $mock) { $viewer = $this->getViewer(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($mock); + $curtain = $this->newCurtainView($mock); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $mock, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Mock')) @@ -133,7 +133,7 @@ final class PholioMockViewController extends PholioController { ->setWorkflow(!$can_edit)); if ($mock->isClosed()) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-check') ->setName(pht('Open Mock')) @@ -141,7 +141,7 @@ final class PholioMockViewController extends PholioController { ->setDisabled(!$can_edit) ->setWorkflow(true)); } else { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-ban') ->setName(pht('Close Mock')) @@ -150,7 +150,7 @@ final class PholioMockViewController extends PholioController { ->setWorkflow(true)); } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-anchor') ->setName(pht('Edit Maniphest Tasks')) @@ -158,45 +158,56 @@ final class PholioMockViewController extends PholioController { ->setDisabled(!$viewer->isLoggedIn()) ->setWorkflow(true)); - return $actions; - } - - private function buildPropertyView( - PholioMock $mock, - PhabricatorMarkupEngine $engine, - PhabricatorActionListView $actions) { - - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($mock) - ->setActionList($actions); - - $properties->addProperty( - pht('Author'), - $viewer->renderHandle($mock->getAuthorPHID())); - - $properties->addProperty( - pht('Created'), - phabricator_datetime($mock->getDateCreated(), $viewer)); - if ($this->getManiphestTaskPHIDs()) { - $properties->addProperty( - pht('Maniphest Tasks'), - $viewer->renderHandleList($this->getManiphestTaskPHIDs())); + $curtain->newPanel() + ->setHeaderText(pht('Maniphest Tasks')) + ->appendChild( + $viewer->renderHandleList($this->getManiphestTaskPHIDs())); } - $properties->invokeWillRenderEvent(); + $curtain->newPanel() + ->setHeaderText(pht('Authored By')) + ->appendChild($this->buildAuthorPanel($mock)); - $properties->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); + return $curtain; + } - $properties->addImageContent( - $engine->getOutput($mock, PholioMock::MARKUP_FIELD_DESCRIPTION)); + private function buildDescriptionView(PholioMock $mock) { - return $properties; + $viewer = $this->getViewer(); + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + $description = $mock->getDescription(); + + if (strlen($description)) { + $properties->addImageContent($description); + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Mock Description')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + } + + return null; + } + + private function buildAuthorPanel(PholioMock $mock) { + $viewer = $this->getViewer(); + $author_phid = $mock->getAuthorPHID(); + $handles = $viewer->loadHandles(array($author_phid)); + + $author_uri = $handles[$author_phid]->getImageURI(); + $author_href = $handles[$author_phid]->getURI(); + $author = $viewer->renderHandle($author_phid)->render(); + + $content = phutil_tag('strong', array(), $author); + $date = phabricator_date($mock->getDateCreated(), $viewer); + $content = pht('%s, %s', $content, $date); + $authored_by = id(new PHUIHeadThingView()) + ->setImage($author_uri) + ->setImageHref($author_href) + ->setContent($content); + + return $authored_by; } private function buildAddCommentView(PholioMock $mock, $comment_form_id) { diff --git a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php index ce620a0d3a..ac61327b39 100644 --- a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php +++ b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php @@ -13,9 +13,8 @@ final class PhabricatorPholioMockTestDataGenerator ->loadOneWhere('phid = %s', $author_phid); $mock = PholioMock::initializeNewMock($author); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_UNKNOWN, - array()); + $content_source = $this->getLipsumContentSource(); + $template = id(new PholioTransaction()) ->setContentSource($content_source); diff --git a/src/applications/pholio/storage/PholioMock.php b/src/applications/pholio/storage/PholioMock.php index c817b7f32b..00e8efd981 100644 --- a/src/applications/pholio/storage/PholioMock.php +++ b/src/applications/pholio/storage/PholioMock.php @@ -184,10 +184,6 @@ final class PholioMock extends PholioDAO return ($this->authorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ @@ -231,12 +227,10 @@ final class PholioMock extends PholioDAO public function getMarkupText($field) { if ($this->getDescription()) { - $description = $this->getDescription(); - } else { - $description = pht('No Description Given'); + return $this->getDescription(); } - return $description; + return null; } public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index 17b0730dcb..d98c6b451b 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -81,12 +81,21 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { } public function getIcon() { + + $new = $this->getNewValue(); + $old = $this->getOldValue(); + switch ($this->getTransactionType()) { case self::TYPE_INLINE: return 'fa-comment'; case self::TYPE_NAME: case self::TYPE_DESCRIPTION: case self::TYPE_STATUS: + if ($new == PholioMock::STATUS_CLOSED) { + return 'fa-ban'; + } else { + return 'fa-check'; + } case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_SEQUENCE: @@ -153,9 +162,15 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $this->renderHandleLink($author_phid)); break; case self::TYPE_STATUS: - return pht( - "%s updated the mock's status.", - $this->renderHandleLink($author_phid)); + if ($new == PholioMock::STATUS_CLOSED) { + return pht( + '%s closed this mock.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s opened this mock.', + $this->renderHandleLink($author_phid)); + } break; case self::TYPE_INLINE: $count = 1; @@ -260,10 +275,17 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $this->renderHandleLink($object_phid)); break; case self::TYPE_STATUS: - return pht( - '%s updated the status for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); + if ($new == PholioMock::STATUS_CLOSED) { + return pht( + '%s closed a mock %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } else { + return pht( + '%s opened a mock %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + } break; case self::TYPE_INLINE: return pht( @@ -347,16 +369,16 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $new = $this->getNewValue(); switch ($this->getTransactionType()) { + case self::TYPE_STATUS: + if ($new == PholioMock::STATUS_CLOSED) { + return PhabricatorTransactions::COLOR_INDIGO; + } else { + return PhabricatorTransactions::COLOR_GREEN; + } case self::TYPE_NAME: if ($old === null) { return PhabricatorTransactions::COLOR_GREEN; } - case self::TYPE_DESCRIPTION: - case self::TYPE_STATUS: - case self::TYPE_IMAGE_NAME: - case self::TYPE_IMAGE_DESCRIPTION: - case self::TYPE_IMAGE_SEQUENCE: - return PhabricatorTransactions::COLOR_BLUE; case self::TYPE_IMAGE_REPLACE: return PhabricatorTransactions::COLOR_YELLOW; case self::TYPE_IMAGE_FILE: diff --git a/src/applications/pholio/view/PholioMockThumbGridView.php b/src/applications/pholio/view/PholioMockThumbGridView.php index 6467106c14..457d700f1f 100644 --- a/src/applications/pholio/view/PholioMockThumbGridView.php +++ b/src/applications/pholio/view/PholioMockThumbGridView.php @@ -107,6 +107,7 @@ final class PholioMockThumbGridView extends AphrontView { return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Mock History')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($grid); } diff --git a/src/applications/phortune/contentsource/PhabricatorPhortuneContentSource.php b/src/applications/phortune/contentsource/PhabricatorPhortuneContentSource.php new file mode 100644 index 0000000000..f2f3247376 --- /dev/null +++ b/src/applications/phortune/contentsource/PhabricatorPhortuneContentSource.php @@ -0,0 +1,16 @@ +buildApplicationCrumbs(); + $crumbs->setBorder(true); if ($is_new) { $cancel_uri = $this->getApplicationURI('account/'); @@ -112,18 +113,25 @@ final class PhortuneAccountEditController extends PhortuneController { ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Account')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setValidationException($validation_exception) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/phortune/controller/PhortuneAccountListController.php b/src/applications/phortune/controller/PhortuneAccountListController.php index 9b095186cc..4b7521ceaa 100644 --- a/src/applications/phortune/controller/PhortuneAccountListController.php +++ b/src/applications/phortune/controller/PhortuneAccountListController.php @@ -24,6 +24,7 @@ final class PhortuneAccountListController extends PhortuneController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Accounts')); + $crumbs->setBorder(true); $payment_list = id(new PHUIObjectItemListView()) ->setUser($viewer) @@ -34,10 +35,11 @@ final class PhortuneAccountListController extends PhortuneController { foreach ($accounts as $account) { $item = id(new PHUIObjectItemView()) - ->setObjectName(pht('Account %d', $account->getID())) + ->setSubhead(pht('Account %d', $account->getID())) ->setHeader($account->getName()) ->setHref($this->getApplicationURI($account->getID().'/')) - ->setObject($account); + ->setObject($account) + ->setIcon('fa-credit-card'); $payment_list->addItem($item); } @@ -53,6 +55,7 @@ final class PhortuneAccountListController extends PhortuneController { $payment_box = id(new PHUIObjectBoxView()) ->setHeader($payment_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($payment_list); $merchant_list = id(new PHUIObjectItemListView()) @@ -64,10 +67,11 @@ final class PhortuneAccountListController extends PhortuneController { foreach ($merchants as $merchant) { $item = id(new PHUIObjectItemView()) - ->setObjectName(pht('Merchant %d', $merchant->getID())) + ->setSubhead(pht('Merchant %d', $merchant->getID())) ->setHeader($merchant->getName()) ->setHref($this->getApplicationURI('/merchant/'.$merchant->getID().'/')) - ->setObject($merchant); + ->setObject($merchant) + ->setIcon('fa-bank'); $merchant_list->addItem($item); } @@ -83,17 +87,24 @@ final class PhortuneAccountListController extends PhortuneController { $merchant_box = id(new PHUIObjectBoxView()) ->setHeader($merchant_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($merchant_list); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Accounts')); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $payment_box, $merchant_box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php index 001db3bebc..97ea1beea8 100644 --- a/src/applications/phortune/controller/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -36,29 +36,61 @@ final class PhortuneAccountViewController extends PhortuneController { $crumbs = $this->buildApplicationCrumbs(); $this->addAccountCrumb($crumbs, $account, $link = false); + $crumbs->setBorder(true); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($title) + ->setHeaderIcon('fa-credit-card'); + + $curtain = $this->buildCurtainView($account, $invoices); + $invoices = $this->buildInvoicesSection($account, $invoices); + $purchase_history = $this->buildPurchaseHistorySection($account); + $charge_history = $this->buildChargeHistorySection($account); + $subscriptions = $this->buildSubscriptionsSection($account); + $payment_methods = $this->buildPaymentMethodsSection($account); + + $timeline = $this->buildTransactionTimeline( + $account, + new PhortuneAccountTransactionQuery()); + $timeline->setShouldTerminate(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $invoices, + $purchase_history, + $charge_history, + $subscriptions, + $payment_methods, + $timeline, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + + } + + private function buildCurtainView(PhortuneAccount $account, $invoices) { + $viewer = $this->getViewer(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); $edit_uri = $this->getApplicationURI('account/edit/'.$account->getID().'/'); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Account')) - ->setIcon('fa-pencil') - ->setHref($edit_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - $properties = id(new PHUIPropertyListView()) - ->setObject($account) - ->setUser($viewer); - - $properties->addProperty( - pht('Members'), - $viewer->renderHandleList($account->getMemberPHIDs())); + $curtain = $this->newCurtainView($account); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Account')) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); $status_items = $this->getStatusItemsForAccount($account, $invoices); $status_view = new PHUIStatusListView(); @@ -72,46 +104,39 @@ final class PhortuneAccountViewController extends PhortuneController { ->setTarget(idx($item, 'target')) ->setNote(idx($item, 'note'))); } - $properties->addProperty( - pht('Status'), - $status_view); - $properties->setActionList($actions); + $member_phids = $account->getMemberPHIDs(); + $handles = $viewer->loadHandles($member_phids); - $invoices = $this->buildInvoicesSection($account, $invoices); - $purchase_history = $this->buildPurchaseHistorySection($account); - $charge_history = $this->buildChargeHistorySection($account); - $subscriptions = $this->buildSubscriptionsSection($account); - $payment_methods = $this->buildPaymentMethodsSection($account); + $member_list = id(new PHUIObjectItemListView()) + ->setSimple(true); - $timeline = $this->buildTransactionTimeline( - $account, - new PhortuneAccountTransactionQuery()); - $timeline->setShouldTerminate(true); + foreach ($member_phids as $member_phid) { + $image_uri = $handles[$member_phid]->getImageURI(); + $image_href = $handles[$member_phid]->getURI(); + $person = $handles[$member_phid]; - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $member = id(new PHUIObjectItemView()) + ->setImageURI($image_uri) + ->setHref($image_href) + ->setHeader($person->getFullName()); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $invoices, - $purchase_history, - $charge_history, - $subscriptions, - $payment_methods, - $timeline, - ), - array( - 'title' => $title, - )); + $member_list->addItem($member); + } + + $curtain->newPanel() + ->setHeaderText(pht('Status')) + ->appendChild($status_view); + + $curtain->newPanel() + ->setHeaderText(pht('Members')) + ->appendChild($member_list); + + return $curtain; } private function buildPaymentMethodsSection(PhortuneAccount $account) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -179,6 +204,7 @@ final class PhortuneAccountViewController extends PhortuneController { return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($list); } @@ -186,8 +212,7 @@ final class PhortuneAccountViewController extends PhortuneController { PhortuneAccount $account, array $carts) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $phids = array(); foreach ($carts as $cart) { @@ -211,12 +236,12 @@ final class PhortuneAccountViewController extends PhortuneController { return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } private function buildPurchaseHistorySection(PhortuneAccount $account) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $carts = id(new PhortuneCartQuery()) ->setViewer($viewer) @@ -260,12 +285,12 @@ final class PhortuneAccountViewController extends PhortuneController { return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } private function buildChargeHistorySection(PhortuneAccount $account) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $charges = id(new PhortuneChargeQuery()) ->setViewer($viewer) @@ -302,12 +327,12 @@ final class PhortuneAccountViewController extends PhortuneController { return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } private function buildSubscriptionsSection(PhortuneAccount $account) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $subscriptions = id(new PhortuneSubscriptionQuery()) ->setViewer($viewer) @@ -338,6 +363,7 @@ final class PhortuneAccountViewController extends PhortuneController { return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } diff --git a/src/applications/phortune/controller/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/PhortuneCartCheckoutController.php index e22a9521fd..fdc19dae44 100644 --- a/src/applications/phortune/controller/PhortuneCartCheckoutController.php +++ b/src/applications/phortune/controller/PhortuneCartCheckoutController.php @@ -107,6 +107,7 @@ final class PhortuneCartCheckoutController $cart_box = id(new PHUIObjectBoxView()) ->setFormErrors($errors) ->setHeaderText(pht('Cart Contents')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($cart_table); $title = $cart->getName(); @@ -200,6 +201,7 @@ final class PhortuneCartCheckoutController $payment_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Choose Payment Method')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form) ->appendChild($provider_form); @@ -208,17 +210,24 @@ final class PhortuneCartCheckoutController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Checkout')); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-shopping-cart'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $cart_box, $description_box, $payment_box, - ), - array( - 'title' => $title, )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phortune/controller/PhortuneCartController.php b/src/applications/phortune/controller/PhortuneCartController.php index 9fefc7db6e..b8f926d3b2 100644 --- a/src/applications/phortune/controller/PhortuneCartController.php +++ b/src/applications/phortune/controller/PhortuneCartController.php @@ -48,7 +48,7 @@ abstract class PhortuneCartController return null; } - $output = new PHUIRemarkupView($this->getUser(), $description); + $output = new PHUIRemarkupView($this->getViewer(), $description); $box = id(new PHUIBoxView()) ->addMargin(PHUI::MARGIN_LARGE) @@ -56,6 +56,7 @@ abstract class PhortuneCartController return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Description')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($box); } diff --git a/src/applications/phortune/controller/PhortuneCartViewController.php b/src/applications/phortune/controller/PhortuneCartViewController.php index 3beaa657c6..8d85ec70fe 100644 --- a/src/applications/phortune/controller/PhortuneCartViewController.php +++ b/src/applications/phortune/controller/PhortuneCartViewController.php @@ -108,22 +108,28 @@ final class PhortuneCartViewController break; case PhortuneCart::STATUS_PURCHASED: $error_view = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->setSeverity(PHUIInfoView::SEVERITY_SUCCESS) ->appendChild(pht('This purchase has been completed.')); break; } - $properties = $this->buildPropertyListView($cart); - $actions = $this->buildActionListView( + if ($errors) { + $error_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->appendChild($errors); + } + + $details = $this->buildDetailsView($cart); + $curtain = $this->buildCurtainView( $cart, $can_edit, $authority, $resume_uri); - $properties->setActionList($actions); $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setHeader(pht('Order Detail')); + ->setHeader(pht('Order Detail')) + ->setHeaderIcon('fa-shopping-cart'); if ($cart->getStatus() == PhortuneCart::STATUS_PURCHASED) { $done_uri = $cart->getDoneURI(); @@ -138,16 +144,10 @@ final class PhortuneCartViewController } $cart_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties) + ->setHeaderText(pht('Cart Items')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($cart_table); - if ($errors) { - $cart_box->setFormErrors($errors); - } else if ($error_view) { - $cart_box->setInfoView($error_view); - } - $description = $this->renderCartDescription($cart); $charges = id(new PhortuneChargeQuery()) @@ -173,6 +173,7 @@ final class PhortuneCartViewController $charges = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Charges')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($charges_table); $account = $cart->getAccount(); @@ -184,6 +185,7 @@ final class PhortuneCartViewController $this->addAccountCrumb($crumbs, $cart->getAccount()); } $crumbs->addTextCrumb(pht('Cart %d', $cart->getID())); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $cart, @@ -191,23 +193,28 @@ final class PhortuneCartViewController $timeline ->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $error_view, + $details, $cart_box, $description, $charges, $timeline, - ), - array( - 'title' => pht('Cart'), )); + return $this->newPage() + ->setTitle(pht('Cart %d', $cart->getID())) + ->setCrumbs($crumbs) + ->appendChild($view); + } - private function buildPropertyListView(PhortuneCart $cart) { + private function buildDetailsView(PhortuneCart $cart) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -239,21 +246,21 @@ final class PhortuneCartViewController pht('Updated'), phabricator_datetime($cart->getDateModified(), $viewer)); - return $view; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); } - private function buildActionListView( + private function buildCurtainView( PhortuneCart $cart, $can_edit, $authority, $resume_uri) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $id = $cart->getID(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($cart); + $curtain = $this->newCurtainView($cart); $can_cancel = ($can_edit && $cart->canCancelOrder()); @@ -269,7 +276,7 @@ final class PhortuneCartViewController $accept_uri = $this->getApplicationURI("{$prefix}cart/{$id}/accept/"); $print_uri = $this->getApplicationURI("{$prefix}cart/{$id}/?__print__=1"); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Cancel Order')) ->setIcon('fa-times') @@ -279,7 +286,7 @@ final class PhortuneCartViewController if ($authority) { if ($cart->getStatus() == PhortuneCart::STATUS_REVIEW) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Accept Order')) ->setIcon('fa-check') @@ -287,7 +294,7 @@ final class PhortuneCartViewController ->setHref($accept_uri)); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Refund Order')) ->setIcon('fa-reply') @@ -295,28 +302,28 @@ final class PhortuneCartViewController ->setHref($refund_uri)); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Update Status')) ->setIcon('fa-refresh') ->setHref($update_uri)); if ($can_edit && $resume_uri) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Continue Checkout')) ->setIcon('fa-shopping-cart') ->setHref($resume_uri)); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Printable Version')) ->setHref($print_uri) ->setOpenInNewWindow(true) ->setIcon('fa-print')); - return $view; + return $curtain; } } diff --git a/src/applications/phortune/controller/PhortuneMerchantEditController.php b/src/applications/phortune/controller/PhortuneMerchantEditController.php index d3b396ddb0..3a6aad215f 100644 --- a/src/applications/phortune/controller/PhortuneMerchantEditController.php +++ b/src/applications/phortune/controller/PhortuneMerchantEditController.php @@ -145,29 +145,39 @@ final class PhortuneMerchantEditController ->setValue($button_text) ->addCancelButton($cancel_uri)); + $header = id(new PHUIHeaderView()) + ->setHeader($title); + $crumbs = $this->buildApplicationCrumbs(); if ($is_new) { $crumbs->addTextCrumb(pht('Create Merchant')); + $header->setHeaderIcon('fa-plus-square'); } else { $crumbs->addTextCrumb( pht('Merchant %d', $merchant->getID()), $this->getApplicationURI('/merchant/'.$merchant->getID().'/')); $crumbs->addTextCrumb(pht('Edit')); + $header->setHeaderIcon('fa-pencil'); } + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Merchant')) ->setValidationException($validation_exception) - ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => $title, )); - } + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + + } } diff --git a/src/applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php b/src/applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php index acb0d1be08..ccdc4f7ac1 100644 --- a/src/applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php +++ b/src/applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php @@ -89,6 +89,7 @@ final class PhortuneMerchantInvoiceCreateController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($merchant->getName()); + $crumbs->setBorder(true); $v_title = $request->getStr('title'); $e_title = true; @@ -229,18 +230,25 @@ final class PhortuneMerchantInvoiceCreateController ->setValue(pht('Send Invoice'))); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('New Invoice')) + ->setHeaderText(pht('Details')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setFormErrors($errors) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php index 1921f0c5a5..59d9273eaa 100644 --- a/src/applications/phortune/controller/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php @@ -17,6 +17,7 @@ final class PhortuneMerchantViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($merchant->getName()); + $crumbs->setBorder(true); $title = pht( 'Merchant %d %s', @@ -26,43 +27,44 @@ final class PhortuneMerchantViewController $header = id(new PHUIHeaderView()) ->setHeader($merchant->getName()) ->setUser($viewer) - ->setPolicyObject($merchant); + ->setPolicyObject($merchant) + ->setHeaderIcon('fa-bank'); $providers = id(new PhortunePaymentProviderConfigQuery()) ->setViewer($viewer) ->withMerchantPHIDs(array($merchant->getPHID())) ->execute(); - $properties = $this->buildPropertyListView($merchant, $providers); - $actions = $this->buildActionListView($merchant); - $properties->setActionList($actions); + $details = $this->buildDetailsView($merchant, $providers); + $description = $this->buildDescriptionView($merchant); + $curtain = $this->buildCurtainView($merchant); $provider_list = $this->buildProviderList( $merchant, $providers); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - $timeline = $this->buildTransactionTimeline( $merchant, new PhortuneMerchantTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $details, + $description, $provider_list, $timeline, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } - private function buildPropertyListView( + private function buildDetailsView( PhortuneMerchant $merchant, array $providers) { @@ -128,24 +130,31 @@ final class PhortuneMerchantViewController $view->addProperty(pht('Status'), $status_view); - $view->addProperty( - pht('Members'), - $viewer->renderHandleList($merchant->getMemberPHIDs())); + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); + } - $view->invokeWillRenderEvent(); + private function buildDescriptionView(PhortuneMerchant $merchant) { + $viewer = $this->getViewer(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); $description = $merchant->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); - $view->addSectionHeader( - pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent($description); + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DESCRIPTION')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); } - return $view; + return null; } - private function buildActionListView(PhortuneMerchant $merchant) { + private function buildCurtainView(PhortuneMerchant $merchant) { $viewer = $this->getRequest()->getUser(); $id = $merchant->getID(); @@ -154,11 +163,9 @@ final class PhortuneMerchantViewController $merchant, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($merchant); + $curtain = $this->newCurtainView($merchant); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Merchant')) ->setIcon('fa-pencil') @@ -166,7 +173,7 @@ final class PhortuneMerchantViewController ->setWorkflow(!$can_edit) ->setHref($this->getApplicationURI("merchant/edit/{$id}/"))); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Orders')) ->setIcon('fa-shopping-cart') @@ -174,7 +181,7 @@ final class PhortuneMerchantViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Subscriptions')) ->setIcon('fa-moon-o') @@ -182,8 +189,7 @@ final class PhortuneMerchantViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('New Invoice')) ->setIcon('fa-fax') @@ -191,7 +197,30 @@ final class PhortuneMerchantViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - return $view; + $member_phids = $merchant->getMemberPHIDs(); + $handles = $viewer->loadHandles($member_phids); + + $member_list = id(new PHUIObjectItemListView()) + ->setSimple(true); + + foreach ($member_phids as $member_phid) { + $image_uri = $handles[$member_phid]->getImageURI(); + $image_href = $handles[$member_phid]->getURI(); + $person = $handles[$member_phid]; + + $member = id(new PHUIObjectItemView()) + ->setImageURI($image_uri) + ->setHref($image_href) + ->setHeader($person->getFullName()); + + $member_list->addItem($member); + } + + $curtain->newPanel() + ->setHeaderText(pht('Members')) + ->appendChild($member_list); + + return $curtain; } private function buildProviderList( @@ -283,6 +312,7 @@ final class PhortuneMerchantViewController return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($provider_list); } diff --git a/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php index dd783d4c51..794f97aa8e 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php @@ -158,20 +158,29 @@ final class PhortunePaymentMethodCreateController ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($provider->getPaymentMethodDescription()) + ->setHeaderText(pht('Method')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Add Payment Method')); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Add Payment Method')) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => $provider->getPaymentMethodDescription(), )); + + return $this->newPage() + ->setTitle($provider->getPaymentMethodDescription()) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function renderSelectProvider( diff --git a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php index 27edd72a59..dc23b81ad0 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php @@ -58,22 +58,31 @@ final class PhortunePaymentMethodEditController ->setValue(pht('Save Changes'))); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Edit Payment Method')) + ->setHeaderText(pht('Payment Method')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($account->getName(), $account_uri); $crumbs->addTextCrumb($method->getDisplayName()); $crumbs->addTextCrumb(pht('Edit')); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Payment Method')) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => pht('Edit Payment Method'), )); + + return $this->newPage() + ->setTitle(pht('Edit Payment Method')) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phortune/controller/PhortuneProductListController.php b/src/applications/phortune/controller/PhortuneProductListController.php index a82effa6d4..eeb594d650 100644 --- a/src/applications/phortune/controller/PhortuneProductListController.php +++ b/src/applications/phortune/controller/PhortuneProductListController.php @@ -24,6 +24,7 @@ final class PhortuneProductListController extends PhabricatorController { ->setName(pht('Create Product')) ->setHref($this->getApplicationURI('product/edit/')) ->setIcon('fa-plus-square')); + $crumbs->setBorder(true); $product_list = id(new PHUIObjectItemListView()) ->setUser($viewer) @@ -39,20 +40,33 @@ final class PhortuneProductListController extends PhabricatorController { ->setObjectName($product->getID()) ->setHeader($product->getProductName()) ->setHref($view_uri) - ->addAttribute($price->formatForDisplay()); + ->addAttribute($price->formatForDisplay()) + ->setIcon('fa-gift'); $product_list->addItem($item); } - return $this->buildApplicationPage( - array( - $crumbs, - $product_list, + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Products')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($product_list); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Products')) + ->setHeaderIcon('fa-gift'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, $pager, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php index 56bf0736d3..0bf022e373 100644 --- a/src/applications/phortune/controller/PhortuneProductViewController.php +++ b/src/applications/phortune/controller/PhortuneProductViewController.php @@ -17,13 +17,11 @@ final class PhortuneProductViewController extends PhortuneController { $title = pht('Product: %s', $product->getProductName()); $header = id(new PHUIHeaderView()) - ->setHeader($product->getProductName()); + ->setHeader($product->getProductName()) + ->setHeaderIcon('fa-gift'); $edit_uri = $this->getApplicationURI('product/edit/'.$product->getID().'/'); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Products'), @@ -31,26 +29,30 @@ final class PhortuneProductViewController extends PhortuneController { $crumbs->addTextCrumb( pht('#%d', $product->getID()), $request->getRequestURI()); + $crumbs->setBorder(true); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setActionList($actions) ->addProperty( pht('Price'), $product->getPriceAsCurrency()->formatForDisplay()); $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $object_box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phortune/controller/PhortuneProviderActionController.php b/src/applications/phortune/controller/PhortuneProviderActionController.php index 52d5453f42..4d9b9f8386 100644 --- a/src/applications/phortune/controller/PhortuneProviderActionController.php +++ b/src/applications/phortune/controller/PhortuneProviderActionController.php @@ -39,11 +39,12 @@ final class PhortuneProviderActionController return $response; } - return $this->buildApplicationPage( - $response, - array( - 'title' => pht('Phortune'), - )); + $title = pht('Phortune'); + + return $this->newPage() + ->setTitle($title) + ->appendChild($response); + } diff --git a/src/applications/phortune/controller/PhortuneProviderEditController.php b/src/applications/phortune/controller/PhortuneProviderEditController.php index f956c41d68..f7ad2486c4 100644 --- a/src/applications/phortune/controller/PhortuneProviderEditController.php +++ b/src/applications/phortune/controller/PhortuneProviderEditController.php @@ -177,6 +177,7 @@ final class PhortuneProviderEditController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($merchant->getName(), $cancel_uri); + $crumbs->setBorder(true); if ($is_new) { $crumbs->addTextCrumb(pht('Add Provider')); @@ -185,19 +186,27 @@ final class PhortuneProviderEditController pht('Edit Provider %d', $provider_config->getID())); } + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + $box = id(new PHUIObjectBoxView()) ->setFormErrors($errors) - ->setHeaderText($title) + ->setHeaderText(pht('Properties')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); - return $this->buildApplicationPage( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function processChooseClassRequest( @@ -266,20 +275,28 @@ final class PhortuneProviderEditController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($merchant->getName(), $cancel_uri); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Provider')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/phortune/controller/PhortuneSubscriptionEditController.php b/src/applications/phortune/controller/PhortuneSubscriptionEditController.php index a6500cdf91..8ba0592be4 100644 --- a/src/applications/phortune/controller/PhortuneSubscriptionEditController.php +++ b/src/applications/phortune/controller/PhortuneSubscriptionEditController.php @@ -21,7 +21,7 @@ final class PhortuneSubscriptionEditController extends PhortuneController { id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( $viewer, $request, - $this->getApplicationURI($subscription->getEditURI())); + $subscription->getURI()); $merchant = $subscription->getMerchant(); $account = $subscription->getAccount(); @@ -140,18 +140,26 @@ final class PhortuneSubscriptionEditController extends PhortuneController { $box = id(new PHUIObjectBoxView()) ->setUser($viewer) - ->setHeaderText(pht('Edit %s', $subscription->getSubscriptionName())) + ->setHeaderText(pht('Subscription')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setFormErrors($errors) ->appendChild($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit %s', $subscription->getSubscriptionName())) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } diff --git a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php index 8ecc97c301..0e1bc55b62 100644 --- a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php +++ b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php @@ -35,14 +35,13 @@ final class PhortuneSubscriptionViewController extends PhortuneController { $title = $subscription->getSubscriptionFullName(); $header = id(new PHUIHeaderView()) - ->setHeader($title); - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + ->setHeader($title) + ->setHeaderIcon('fa-calendar-o'); + $curtain = $this->newCurtainView($subscription); $edit_uri = $subscription->getEditURI(); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Subscription')) @@ -50,7 +49,6 @@ final class PhortuneSubscriptionViewController extends PhortuneController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $crumbs = $this->buildApplicationCrumbs(); if ($authority) { $this->addMerchantCrumb($crumbs, $merchant); @@ -58,10 +56,10 @@ final class PhortuneSubscriptionViewController extends PhortuneController { $this->addAccountCrumb($crumbs, $account); } $crumbs->addTextCrumb($subscription->getSubscriptionCrumbName()); + $crumbs->setBorder(true); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); + ->setUser($viewer); $next_invoice = $subscription->getTrigger()->getNextEventPrediction(); $properties->addProperty( @@ -83,23 +81,27 @@ final class PhortuneSubscriptionViewController extends PhortuneController { pht('Autopay With'), $autopay_method); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + $details = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); $due_box = $this->buildDueInvoices($subscription, $authority); $invoice_box = $this->buildPastInvoices($subscription, $authority); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $details, $due_box, $invoice_box, - ), - array( - 'title' => $title, - )); + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildDueInvoices( @@ -136,6 +138,7 @@ final class PhortuneSubscriptionViewController extends PhortuneController { return id(new PHUIObjectBoxView()) ->setHeader($invoice_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($invoice_table); } @@ -199,6 +202,7 @@ final class PhortuneSubscriptionViewController extends PhortuneController { return id(new PHUIObjectBoxView()) ->setHeader($invoice_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($invoice_table); } diff --git a/src/applications/phortune/product/PhortuneProductImplementation.php b/src/applications/phortune/product/PhortuneProductImplementation.php index 84e6c6ee80..bfaf55b32e 100644 --- a/src/applications/phortune/product/PhortuneProductImplementation.php +++ b/src/applications/phortune/product/PhortuneProductImplementation.php @@ -12,8 +12,7 @@ abstract class PhortuneProductImplementation extends Phobject { protected function getContentSource() { return PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_PHORTUNE, - array()); + PhabricatorPhortuneContentSource::SOURCECONST); } public function getPurchaseName( diff --git a/src/applications/phortune/query/PhortuneMerchantSearchEngine.php b/src/applications/phortune/query/PhortuneMerchantSearchEngine.php index 257e74484d..1806af371e 100644 --- a/src/applications/phortune/query/PhortuneMerchantSearchEngine.php +++ b/src/applications/phortune/query/PhortuneMerchantSearchEngine.php @@ -70,10 +70,11 @@ final class PhortuneMerchantSearchEngine $list->setUser($viewer); foreach ($merchants as $merchant) { $item = id(new PHUIObjectItemView()) - ->setObjectName(pht('Merchant %d', $merchant->getID())) + ->setSubhead(pht('Merchant %d', $merchant->getID())) ->setHeader($merchant->getName()) ->setHref('/phortune/merchant/'.$merchant->getID().'/') - ->setObject($merchant); + ->setObject($merchant) + ->setIcon('fa-bank'); $list->addItem($item); } diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php index 22dc27a9ed..af2b386dfe 100644 --- a/src/applications/phortune/storage/PhortuneCart.php +++ b/src/applications/phortune/storage/PhortuneCart.php @@ -431,8 +431,7 @@ final class PhortuneCart extends PhortuneDAO ->setNewValue(true); $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_PHORTUNE, - array()); + PhabricatorPhortuneContentSource::SOURCECONST); $editor = id(new PhortuneCartEditor()) ->setActor($omnipotent_user) diff --git a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php index 96f1948528..19399150e8 100644 --- a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php +++ b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php @@ -96,8 +96,7 @@ final class PhortuneSubscriptionWorker extends PhabricatorWorker { ->setNewValue(true); $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_PHORTUNE, - array()); + PhabricatorPhortuneContentSource::SOURCECONST); $acting_phid = id(new PhabricatorPhortuneApplication())->getPHID(); $editor = id(new PhortuneCartEditor()) diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php index 290666e375..8a530075ae 100644 --- a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php +++ b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php @@ -47,13 +47,24 @@ final class PhabricatorXHPASTViewRunController $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Generate XHP AST')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - $form_box, - array( - 'title' => pht('XHPAST View'), + $title = pht('XHPAST View'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-ambulance'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $form_box, )); + + return $this->newPage() + ->setTitle($title) + ->appendChild($view); + } } diff --git a/src/applications/phragment/controller/PhragmentBrowseController.php b/src/applications/phragment/controller/PhragmentBrowseController.php index 7b0a16cc52..d511835ce9 100644 --- a/src/applications/phragment/controller/PhragmentBrowseController.php +++ b/src/applications/phragment/controller/PhragmentBrowseController.php @@ -2,21 +2,15 @@ final class PhragmentBrowseController extends PhragmentController { - private $dblob; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->dblob = idx($data, 'dblob', ''); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $dblob = $request->getURIData('dblob'); - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $parents = $this->loadParentFragments($this->dblob); + $parents = $this->loadParentFragments($dblob); if ($parents === null) { return new Aphront404Response(); } @@ -83,16 +77,19 @@ final class PhragmentBrowseController extends PhragmentController { $list->addItem($item); } - return $this->buildApplicationPage( - array( - $crumbs, - $this->renderConfigurationWarningIfRequired(), - $current_box, - $list, - ), - array( - 'title' => pht('Browse Fragments'), - )); + $title = pht('Browse Fragments'); + + $view = array( + $this->renderConfigurationWarningIfRequired(), + $current_box, + $list, + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phragment/controller/PhragmentCreateController.php b/src/applications/phragment/controller/PhragmentCreateController.php index 2a5b54538c..946d7cc332 100644 --- a/src/applications/phragment/controller/PhragmentCreateController.php +++ b/src/applications/phragment/controller/PhragmentCreateController.php @@ -2,18 +2,12 @@ final class PhragmentCreateController extends PhragmentController { - private $dblob; - - public function willProcessRequest(array $data) { - $this->dblob = idx($data, 'dblob', ''); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $dblob = $request->getURIData('dblob'); $parent = null; - $parents = $this->loadParentFragments($this->dblob); + $parents = $this->loadParentFragments($dblob); if ($parents === null) { return new Aphront404Response(); } @@ -124,15 +118,18 @@ final class PhragmentCreateController extends PhragmentController { $box->setInfoView($error_view); } - return $this->buildApplicationPage( - array( - $crumbs, - $this->renderConfigurationWarningIfRequired(), - $box, - ), - array( - 'title' => pht('Create Fragment'), - )); + $title = pht('Create Fragments'); + + $view = array( + $this->renderConfigurationWarningIfRequired(), + $box, + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phragment/controller/PhragmentHistoryController.php b/src/applications/phragment/controller/PhragmentHistoryController.php index 72e0000b80..4e16deb43d 100644 --- a/src/applications/phragment/controller/PhragmentHistoryController.php +++ b/src/applications/phragment/controller/PhragmentHistoryController.php @@ -2,21 +2,15 @@ final class PhragmentHistoryController extends PhragmentController { - private $dblob; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->dblob = idx($data, 'dblob', ''); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $dblob = $request->getURIData('dblob'); - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $parents = $this->loadParentFragments($this->dblob); + $parents = $this->loadParentFragments($dblob); if ($parents === null) { return new Aphront404Response(); } @@ -97,16 +91,19 @@ final class PhragmentHistoryController extends PhragmentController { $first = false; } - return $this->buildApplicationPage( - array( - $crumbs, - $this->renderConfigurationWarningIfRequired(), - $current_box, - $list, - ), - array( - 'title' => pht('Fragment History'), - )); + $title = pht('Fragment History'); + + $view = array( + $this->renderConfigurationWarningIfRequired(), + $current_box, + $list, + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phragment/controller/PhragmentPatchController.php b/src/applications/phragment/controller/PhragmentPatchController.php index 5a04403898..5ad4b6dfec 100644 --- a/src/applications/phragment/controller/PhragmentPatchController.php +++ b/src/applications/phragment/controller/PhragmentPatchController.php @@ -2,28 +2,21 @@ final class PhragmentPatchController extends PhragmentController { - private $aid; - private $bid; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->aid = idx($data, 'aid', 0); - $this->bid = idx($data, 'bid', 0); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $aid = $request->getURIData('aid'); + $bid = $request->getURIData('bid'); // If "aid" is "x", then it means the user wants to generate // a patch of an empty file to the version specified by "bid". - $ids = array($this->aid, $this->bid); - if ($this->aid === 'x') { - $ids = array($this->bid); + $ids = array($aid, $bid); + if ($aid === 'x') { + $ids = array($bid); } $versions = id(new PhragmentFragmentVersionQuery()) @@ -32,14 +25,14 @@ final class PhragmentPatchController extends PhragmentController { ->execute(); $version_a = null; - if ($this->aid !== 'x') { - $version_a = idx($versions, $this->aid, null); + if ($aid !== 'x') { + $version_a = idx($versions, $aid, null); if ($version_a === null) { return new Aphront404Response(); } } - $version_b = idx($versions, $this->bid, null); + $version_b = idx($versions, $bid, null); if ($version_b === null) { return new Aphront404Response(); } diff --git a/src/applications/phragment/controller/PhragmentPolicyController.php b/src/applications/phragment/controller/PhragmentPolicyController.php index 700c3edb5b..edcde80990 100644 --- a/src/applications/phragment/controller/PhragmentPolicyController.php +++ b/src/applications/phragment/controller/PhragmentPolicyController.php @@ -2,17 +2,11 @@ final class PhragmentPolicyController extends PhragmentController { - private $dblob; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $dblob = $request->getURIData('dblob'); - public function willProcessRequest(array $data) { - $this->dblob = idx($data, 'dblob', ''); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $parents = $this->loadParentFragments($this->dblob); + $parents = $this->loadParentFragments($dblob); if ($parents === null) { return new Aphront404Response(); } @@ -95,15 +89,18 @@ final class PhragmentPolicyController extends PhragmentController { ->setValidationException(null) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $this->renderConfigurationWarningIfRequired(), - $box, - ), - array( - 'title' => pht('Edit Fragment Policies'), - )); + $title = pht('Edit Fragment Policies'); + + $view = array( + $this->renderConfigurationWarningIfRequired(), + $box, + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phragment/controller/PhragmentRevertController.php b/src/applications/phragment/controller/PhragmentRevertController.php index 92da1f27a3..e9d56eb112 100644 --- a/src/applications/phragment/controller/PhragmentRevertController.php +++ b/src/applications/phragment/controller/PhragmentRevertController.php @@ -2,21 +2,14 @@ final class PhragmentRevertController extends PhragmentController { - private $dblob; - private $id; - - public function willProcessRequest(array $data) { - $this->dblob = $data['dblob']; - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $dblob = $request->getURIData('dblob'); $fragment = id(new PhragmentFragmentQuery()) ->setViewer($viewer) - ->withPaths(array($this->dblob)) + ->withPaths(array($dblob)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -30,7 +23,7 @@ final class PhragmentRevertController extends PhragmentController { $version = id(new PhragmentFragmentVersionQuery()) ->setViewer($viewer) ->withFragmentPHIDs(array($fragment->getPHID())) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if ($version === null) { return new Aphront404Response(); @@ -58,7 +51,7 @@ final class PhragmentRevertController extends PhragmentController { } return id(new AphrontRedirectResponse()) - ->setURI($this->getApplicationURI('/history/'.$this->dblob)); + ->setURI($this->getApplicationURI('/history/'.$dblob)); } return $this->createDialog($fragment, $version); @@ -68,12 +61,11 @@ final class PhragmentRevertController extends PhragmentController { PhragmentFragment $fragment, PhragmentFragmentVersion $version) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $dialog = id(new AphrontDialogView()) ->setTitle(pht('Really revert this fragment?')) - ->setUser($request->getUser()) + ->setUser($this->getViewer()) ->addSubmitButton(pht('Revert')) ->addCancelButton(pht('Cancel')) ->appendParagraph(pht( diff --git a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php index e135819280..c32be32cd7 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php @@ -2,17 +2,11 @@ final class PhragmentSnapshotCreateController extends PhragmentController { - private $dblob; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $dblob = $request->getURIData('dblob'); - public function willProcessRequest(array $data) { - $this->dblob = idx($data, 'dblob', ''); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $parents = $this->loadParentFragments($this->dblob); + $parents = $this->loadParentFragments($dblob); if ($parents === null) { return new Aphront404Response(); } @@ -159,15 +153,18 @@ final class PhragmentSnapshotCreateController extends PhragmentController { ->setFormErrors($errors) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $this->renderConfigurationWarningIfRequired(), - $box, - ), - array( - 'title' => pht('Create Fragment'), - )); + $title = pht('Create Snapshot'); + + $view = array( + $this->renderConfigurationWarningIfRequired(), + $box, + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php index 715280a2c2..8f112585d0 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotDeleteController.php @@ -2,15 +2,9 @@ final class PhragmentSnapshotDeleteController extends PhragmentController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $snapshot = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) @@ -18,7 +12,7 @@ final class PhragmentSnapshotDeleteController extends PhragmentController { PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if ($snapshot === null) { return new Aphront404Response(); @@ -37,12 +31,11 @@ final class PhragmentSnapshotDeleteController extends PhragmentController { } public function createDialog() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $dialog = id(new AphrontDialogView()) ->setTitle(pht('Really delete this snapshot?')) - ->setUser($request->getUser()) + ->setUser($this->getViewer()) ->addSubmitButton(pht('Delete')) ->addCancelButton(pht('Cancel')) ->appendParagraph(pht( diff --git a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php index 18d7df6a5a..981d139742 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotPromoteController.php @@ -2,32 +2,26 @@ final class PhragmentSnapshotPromoteController extends PhragmentController { - private $dblob; - private $id; private $targetSnapshot; private $targetFragment; private $snapshots; private $options; - public function willProcessRequest(array $data) { - $this->dblob = idx($data, 'dblob', null); - $this->id = idx($data, 'id', null); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); + $dblob = $request->getURIData('dblob'); // When the user is promoting a snapshot to the latest version, the // identifier is a fragment path. - if ($this->dblob !== null) { + if ($dblob !== null) { $this->targetFragment = id(new PhragmentFragmentQuery()) ->setViewer($viewer) ->requireCapabilities(array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withPaths(array($this->dblob)) + ->withPaths(array($dblob)) ->executeOne(); if ($this->targetFragment === null) { return new Aphront404Response(); @@ -41,14 +35,14 @@ final class PhragmentSnapshotPromoteController extends PhragmentController { // When the user is promoting a snapshot to another snapshot, the // identifier is another snapshot ID. - if ($this->id !== null) { + if ($id !== null) { $this->targetSnapshot = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->requireCapabilities(array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if ($this->targetSnapshot === null) { return new Aphront404Response(); @@ -72,8 +66,8 @@ final class PhragmentSnapshotPromoteController extends PhragmentController { $this->snapshots, 'getName', 'getID'); - if ($this->id !== null) { - unset($this->options[$this->id]); + if ($id !== null) { + unset($this->options[$id]); } // If there's no options, show a dialog telling the @@ -84,7 +78,7 @@ final class PhragmentSnapshotPromoteController extends PhragmentController { ->setTitle(pht('No snapshots to promote')) ->appendParagraph(pht( 'There are no snapshots available to promote.')) - ->setUser($request->getUser()) + ->setUser($this->getViewer()) ->addCancelButton(pht('Cancel'))); } @@ -108,7 +102,7 @@ final class PhragmentSnapshotPromoteController extends PhragmentController { $child->delete(); } - if ($this->id === null) { + if ($id === null) { // The user is promoting the snapshot to the latest version. $children = id(new PhragmentFragmentQuery()) ->setViewer($viewer) @@ -150,7 +144,7 @@ final class PhragmentSnapshotPromoteController extends PhragmentController { } $snapshot->saveTransaction(); - if ($this->id === null) { + if ($id === null) { return id(new AphrontRedirectResponse()) ->setURI($this->targetFragment->getURI()); } else { @@ -159,20 +153,19 @@ final class PhragmentSnapshotPromoteController extends PhragmentController { } } - return $this->createDialog(); + return $this->createDialog($id); } - public function createDialog() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function createDialog($id) { + $viewer = $this->getViewer(); $dialog = id(new AphrontDialogView()) ->setTitle(pht('Promote which snapshot?')) - ->setUser($request->getUser()) + ->setUser($this->getViewer()) ->addSubmitButton(pht('Promote')) ->addCancelButton(pht('Cancel')); - if ($this->id === null) { + if ($id === null) { // The user is promoting a snapshot to the latest version. $dialog->appendParagraph(pht( 'Select the snapshot you want to promote to the latest version:')); diff --git a/src/applications/phragment/controller/PhragmentSnapshotViewController.php b/src/applications/phragment/controller/PhragmentSnapshotViewController.php index fe499fffa1..052b5036fb 100644 --- a/src/applications/phragment/controller/PhragmentSnapshotViewController.php +++ b/src/applications/phragment/controller/PhragmentSnapshotViewController.php @@ -2,23 +2,17 @@ final class PhragmentSnapshotViewController extends PhragmentController { - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id', ''); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $snapshot = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if ($snapshot === null) { return new Aphront404Response(); @@ -71,16 +65,18 @@ final class PhragmentSnapshotViewController extends PhragmentController { $list->addItem($item); } - return $this->buildApplicationPage( - array( - $crumbs, - $this->renderConfigurationWarningIfRequired(), - $box, - $list, - ), - array( - 'title' => pht('View Snapshot'), - )); + $title = pht('View Snapshot'); + + $view = array( + $this->renderConfigurationWarningIfRequired(), + $box, + $list, + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } protected function createSnapshotView($snapshot) { diff --git a/src/applications/phragment/controller/PhragmentUpdateController.php b/src/applications/phragment/controller/PhragmentUpdateController.php index 1b8d10e91f..7fb91ccd4e 100644 --- a/src/applications/phragment/controller/PhragmentUpdateController.php +++ b/src/applications/phragment/controller/PhragmentUpdateController.php @@ -2,17 +2,11 @@ final class PhragmentUpdateController extends PhragmentController { - private $dblob; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $dblob = $request->getURIData('dblob'); - public function willProcessRequest(array $data) { - $this->dblob = idx($data, 'dblob', ''); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $parents = $this->loadParentFragments($this->dblob); + $parents = $this->loadParentFragments($dblob); if ($parents === null) { return new Aphront404Response(); } @@ -69,15 +63,18 @@ final class PhragmentUpdateController extends PhragmentController { ->setValidationException(null) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $this->renderConfigurationWarningIfRequired(), - $box, - ), - array( - 'title' => pht('Update Fragment'), - )); + $title = pht('Update Fragment'); + + $view = array( + $this->renderConfigurationWarningIfRequired(), + $box, + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phragment/controller/PhragmentVersionController.php b/src/applications/phragment/controller/PhragmentVersionController.php index 29b920a4e5..2b1ae2bdf0 100644 --- a/src/applications/phragment/controller/PhragmentVersionController.php +++ b/src/applications/phragment/controller/PhragmentVersionController.php @@ -2,23 +2,17 @@ final class PhragmentVersionController extends PhragmentController { - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id', 0); - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); $version = id(new PhragmentFragmentVersionQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if ($version === null) { return new Aphront404Response(); @@ -71,23 +65,23 @@ final class PhragmentVersionController extends PhragmentController { ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, - $this->renderConfigurationWarningIfRequired(), - $box, - $this->renderPreviousVersionList($version), - ), - array( - 'title' => pht('View Version'), - )); + $title = pht('View Version'); + + $view = array( + $this->renderConfigurationWarningIfRequired(), + $box, + $this->renderPreviousVersionList($version), + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function renderPreviousVersionList( PhragmentFragmentVersion $version) { - - $request = $this->getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $previous_versions = id(new PhragmentFragmentVersionQuery()) ->setViewer($viewer) diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php index 0de9f4d344..75d464d9c8 100644 --- a/src/applications/phragment/controller/PhragmentZIPController.php +++ b/src/applications/phragment/controller/PhragmentZIPController.php @@ -2,35 +2,28 @@ final class PhragmentZIPController extends PhragmentController { - private $dblob; - private $snapshot; - private $snapshotCache; public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->dblob = idx($data, 'dblob', ''); - $this->snapshot = idx($data, 'snapshot', null); - } + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $dblob = $request->getURIData('dblob'); + $snapshot = $request->getURIData('snapshot'); - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $parents = $this->loadParentFragments($this->dblob); + $parents = $this->loadParentFragments($dblob); if ($parents === null) { return new Aphront404Response(); } $fragment = idx($parents, count($parents) - 1, null); - if ($this->snapshot !== null) { + if ($snapshot !== null) { $snapshot = id(new PhragmentSnapshotQuery()) ->setViewer($viewer) ->withPrimaryFragmentPHIDs(array($fragment->getPHID())) - ->withNames(array($this->snapshot)) + ->withNames(array($snapshot)) ->executeOne(); if ($snapshot === null) { return new Aphront404Response(); @@ -63,7 +56,7 @@ final class PhragmentZIPController extends PhragmentController { $dialog->setTitle(pht('ZIP Extension Not Installed')); $dialog->appendParagraph($inst); - $dialog->addCancelButton('/phragment/browse/'.$this->dblob); + $dialog->addCancelButton('/phragment/browse/'.$dblob); return id(new AphrontDialogResponse())->setDialog($dialog); } @@ -71,7 +64,8 @@ final class PhragmentZIPController extends PhragmentController { throw new Exception(pht('Unable to create ZIP archive!')); } - $mappings = $this->getFragmentMappings($fragment, $fragment->getPath()); + $mappings = $this->getFragmentMappings( + $fragment, $fragment->getPath(), $snapshot); $phids = array(); foreach ($mappings as $path => $file_phid) { @@ -130,14 +124,17 @@ final class PhragmentZIPController extends PhragmentController { /** * Returns a list of mappings like array('some/path.txt' => 'file PHID'); */ - private function getFragmentMappings(PhragmentFragment $current, $base_path) { + private function getFragmentMappings( + PhragmentFragment $current, + $base_path, + $snapshot) { $mappings = $current->getFragmentMappings( $this->getRequest()->getUser(), $base_path); $result = array(); foreach ($mappings as $path => $fragment) { - $version = $this->getVersion($fragment); + $version = $this->getVersion($fragment, $snapshot); if ($version !== null) { $result[$path] = $version->getFilePHID(); } @@ -145,8 +142,8 @@ final class PhragmentZIPController extends PhragmentController { return $result; } - private function getVersion($fragment) { - if ($this->snapshot === null) { + private function getVersion($fragment, $snapshot) { + if ($snapshot === null) { return $fragment->getLatestVersion(); } else { return idx($this->snapshotCache, $fragment->getPHID(), null); diff --git a/src/applications/phrequent/engineextension/PhrequentCurtainExtension.php b/src/applications/phrequent/engineextension/PhrequentCurtainExtension.php new file mode 100644 index 0000000000..25d0e424a6 --- /dev/null +++ b/src/applications/phrequent/engineextension/PhrequentCurtainExtension.php @@ -0,0 +1,87 @@ +getViewer(); + + $events = id(new PhrequentUserTimeQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->needPreemptingEvents(true) + ->execute(); + $event_groups = mgroup($events, 'getUserPHID'); + + if (!$events) { + return; + } + + $handles = $viewer->loadHandles(array_keys($event_groups)); + $status_view = new PHUIStatusListView(); + + foreach ($event_groups as $user_phid => $event_group) { + $item = new PHUIStatusItemView(); + $item->setTarget($handles[$user_phid]->renderLink()); + + $state = 'stopped'; + foreach ($event_group as $event) { + if ($event->getDateEnded() === null) { + if ($event->isPreempted()) { + $state = 'suspended'; + } else { + $state = 'active'; + break; + } + } + } + + switch ($state) { + case 'active': + $item->setIcon( + PHUIStatusItemView::ICON_CLOCK, + 'green', + pht('Working Now')); + break; + case 'suspended': + $item->setIcon( + PHUIStatusItemView::ICON_CLOCK, + 'yellow', + pht('Interrupted')); + break; + case 'stopped': + $item->setIcon( + PHUIStatusItemView::ICON_CLOCK, + 'bluegrey', + pht('Not Working Now')); + break; + } + + $block = new PhrequentTimeBlock($event_group); + $item->setNote( + phutil_format_relative_time( + $block->getTimeSpentOnObject( + $object->getPHID(), + time()))); + + $status_view->addItem($item); + } + + + return $this->newPanel() + ->setHeaderText(pht('Time Spent')) + ->setOrder(40000) + ->appendChild($status_view); + } + +} diff --git a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php index e3d28941bb..36c74f4a68 100644 --- a/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php @@ -55,7 +55,7 @@ final class PhrictionCreateConduitAPIMethod extends PhrictionConduitAPIMethod { $editor = id(new PhrictionTransactionEditor()) ->setActor($request->getUser()) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) ->setDescription($request->getValue('description')); diff --git a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php index f9c8c26a3e..e99a866529 100644 --- a/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionEditConduitAPIMethod.php @@ -50,7 +50,7 @@ final class PhrictionEditConduitAPIMethod extends PhrictionConduitAPIMethod { $editor = id(new PhrictionTransactionEditor()) ->setActor($request->getUser()) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) ->setDescription($request->getValue('description')); diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php index 11d0b40dfd..5b1a81f940 100644 --- a/src/applications/phriction/controller/PhrictionDiffController.php +++ b/src/applications/phriction/controller/PhrictionDiffController.php @@ -98,6 +98,7 @@ final class PhrictionDiffController extends PhrictionController { ->setRenderingReferences(array("{$l},{$r}")) ->setRenderURI('/phriction/diff/'.$document->getID().'/') ->setTitle(pht('Changes')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setParser($parser); require_celerity_resource('phriction-document-css'); @@ -121,11 +122,10 @@ final class PhrictionDiffController extends PhrictionController { $header = id(new PHUIHeaderView()) ->setHeader($title) - ->setTall(true); + ->setHeaderIcon('fa-history'); $crumbs->addTextCrumb($title, $request->getRequestURI()); - $comparison_table = $this->renderComparisonTable( array( $content_r, @@ -144,7 +144,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1), - 'class' => 'button simple', + 'class' => 'button grey', ), pht("\xC2\xAB Previous Change")); } else { @@ -163,7 +163,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1), - 'class' => 'button simple', + 'class' => 'button grey', ), pht("Next Change \xC2\xBB")); } else { @@ -185,7 +185,6 @@ final class PhrictionDiffController extends PhrictionController { ))); } - $output = hsprintf( '
'. '%s%s'. @@ -198,21 +197,25 @@ final class PhrictionDiffController extends PhrictionController { $revert_l, $revert_r); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('Edits')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($output); - return $this->buildApplicationPage( - array( - $crumbs, + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $object_box, $changes, - ), - array( - 'title' => pht('Document History'), )); + return $this->newPage() + ->setTitle(pht('Document History')) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function renderRevertButton( @@ -238,7 +241,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '/phriction/edit/'.$document_id.'/', - 'class' => 'button simple', + 'class' => 'button grey', ), pht('Edit Current Version')); } @@ -248,7 +251,7 @@ final class PhrictionDiffController extends PhrictionController { 'a', array( 'href' => '/phriction/edit/'.$document_id.'/?revert='.$version, - 'class' => 'button simple', + 'class' => 'button grey', ), pht('Revert to Version %s...', $version)); } diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index 360c0fbf3f..7601888987 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -151,7 +151,7 @@ final class PhrictionDocumentController array(), pht( 'This document has been moved. You can edit it to put new '. - 'contne here, or use history to revert to an earlier '. + 'content here, or use history to revert to an earlier '. 'version.'))); } @@ -230,16 +230,14 @@ final class PhrictionDocumentController $core_content, )); - return $this->buildApplicationPage( - array( - $crumbs->render(), + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($document->getPHID())) + ->appendChild(array( $page_content, $prop_list, $children, - ), - array( - 'pageObjects' => array($document->getPHID()), - 'title' => $page_title, )); } diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index 466d721a03..fd88bf9408 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -177,8 +177,9 @@ final class PhrictionEditController } if ($document->getID()) { - $panel_header = pht('Edit Phriction Document'); + $panel_header = pht('Edit Document: %s', $content->getTitle()); $page_title = pht('Edit Document'); + $header_icon = 'fa-pencil'; if ($overwrite) { $submit_button = pht('Overwrite Changes'); } else { @@ -188,6 +189,7 @@ final class PhrictionEditController $panel_header = pht('Create New Phriction Document'); $submit_button = pht('Create Document'); $page_title = pht('Create Document'); + $header_icon = 'fa-plus-square'; } $uri = $document->getSlug(); @@ -263,8 +265,9 @@ final class PhrictionEditController ->setValue($submit_button)); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($panel_header) + ->setHeaderText(pht('Document')) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $preview = id(new PHUIRemarkupPreviewPanel()) @@ -282,17 +285,25 @@ final class PhrictionEditController } else { $crumbs->addTextCrumb(pht('Create')); } + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($panel_header) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $draft_note, $form_box, $preview, - ), - array( - 'title' => $page_title, )); + + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phriction/controller/PhrictionHistoryController.php b/src/applications/phriction/controller/PhrictionHistoryController.php index 5ce6c7225c..9b0d3188a2 100644 --- a/src/applications/phriction/controller/PhrictionHistoryController.php +++ b/src/applications/phriction/controller/PhrictionHistoryController.php @@ -3,19 +3,17 @@ final class PhrictionHistoryController extends PhrictionController { - private $slug; - public function shouldAllowPublic() { return true; } public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $this->slug = $request->getURIData('slug'); + $slug = $request->getURIData('slug'); $document = id(new PhrictionDocumentQuery()) ->setViewer($viewer) - ->withSlugs(array(PhabricatorSlug::normalize($this->slug))) + ->withSlugs(array(PhabricatorSlug::normalize($slug))) ->needContent(true) ->executeOne(); if (!$document) { @@ -141,6 +139,7 @@ final class PhrictionHistoryController $crumbs->addTextCrumb( pht('History'), PhrictionDocument::getSlugURI($document->getSlug(), 'history')); + $crumbs->setBorder(true); $header = new PHUIHeaderView(); $header->setHeader(phutil_tag( @@ -150,23 +149,31 @@ final class PhrictionHistoryController $header->setSubheader(pht('Document History')); $obj_box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setObjectList($list); $pager = id(new PHUIBoxView()) ->addClass('ml') ->appendChild($pager); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Document History: %s', head($history)->getTitle())) + ->setHeaderIcon('fa-history'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $obj_box, $pager, - ), - array( - 'title' => pht('Document History'), )); + $title = pht('Document History'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/phriction/controller/PhrictionNewController.php b/src/applications/phriction/controller/PhrictionNewController.php index c5659be6cc..bcb8e317ab 100644 --- a/src/applications/phriction/controller/PhrictionNewController.php +++ b/src/applications/phriction/controller/PhrictionNewController.php @@ -16,8 +16,8 @@ final class PhrictionNewController extends PhrictionController { PhrictionDocumentStatus::STATUS_EXISTS; if ($document_exists && $prompt == 'no') { - $dialog = new AphrontDialogView(); - $dialog->setSubmitURI('/phriction/new/') + return $this->newDialog() + ->setSubmitURI('/phriction/new/') ->setTitle(pht('Edit Existing Document?')) ->setUser($viewer) ->appendChild(pht( @@ -27,8 +27,6 @@ final class PhrictionNewController extends PhrictionController { ->addHiddenInput('prompt', 'yes') ->addCancelButton('/w/') ->addSubmitButton(pht('Edit Document')); - - return id(new AphrontDialogResponse())->setDialog($dialog); } $uri = '/phriction/edit/?slug='.$slug; @@ -46,8 +44,7 @@ final class PhrictionNewController extends PhrictionController { ->setValue($slug) ->setName('slug')); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle(pht('New Document')) ->setSubmitURI('/phriction/new/') ->appendChild(phutil_tag('p', @@ -57,7 +54,6 @@ final class PhrictionNewController extends PhrictionController { ->addSubmitButton(pht('Create')) ->addCancelButton('/w/'); - return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 915cfed1eb..f931ac8dbe 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -194,9 +194,6 @@ final class PhrictionDocument extends PhrictionDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php b/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php index 4136c855d8..3af53802e5 100644 --- a/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php +++ b/src/applications/phurl/controller/PhabricatorPhurlURLEditController.php @@ -24,6 +24,7 @@ final class PhabricatorPhurlURLEditController $viewer); $submit_label = pht('Create'); $page_title = pht('Shorten URL'); + $header_icon = 'fa-plus-square'; $subscribers = array(); $cancel_uri = $this->getApplicationURI(); } else { @@ -42,7 +43,8 @@ final class PhabricatorPhurlURLEditController } $submit_label = pht('Update'); - $page_title = pht('Update URL'); + $page_title = pht('Edit URL: %s', $url->getName()); + $header_icon = 'fa-pencil'; $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $url->getPHID()); @@ -238,19 +240,27 @@ final class PhabricatorPhurlURLEditController } $crumbs->addTextCrumb($page_title); + $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText($page_title) + ->setHeaderText(pht('URL')) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); - return $this->buildApplicationPage( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($page_title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $object_box, - ), - array( - 'title' => $page_title, )); + + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php index dbc7fbcc79..946e35c854 100644 --- a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php +++ b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php @@ -24,24 +24,20 @@ final class PhabricatorPhurlURLViewController $title = $url->getMonogram(); $page_title = $title.' '.$url->getName(); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title, $url->getURI()); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $url, new PhabricatorPhurlURLTransactionQuery()); $header = $this->buildHeaderView($url); - $actions = $this->buildActionView($url); - $properties = $this->buildPropertyView($url); + $curtain = $this->buildCurtain($url); + $details = $this->buildPropertySectionView($url); - $properties->setActionList($actions); $url_error = id(new PHUIInfoView()) ->setErrors(array(pht('This URL is invalid due to a bad protocol.'))) ->setIsHidden($url->isValid()); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties) - ->setInfoView($url_error); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $add_comment_header = $is_serious @@ -58,71 +54,80 @@ final class PhabricatorPhurlURLViewController ->setAction($comment_uri) ->setSubmitButtonName(pht('Add Comment')); - return $this->buildApplicationPage( - array( - $crumbs, - $box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $url_error, + $details, $timeline, $add_comment_form, - ), - array( - 'title' => $page_title, - 'pageObjects' => array($url->getPHID()), )); + + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($url->getPHID())) + ->appendChild( + array( + $view, + )); + } private function buildHeaderView(PhabricatorPhurlURL $url) { $viewer = $this->getViewer(); - $icon = 'fa-compress'; - $color = 'green'; + $icon = 'fa-check'; + $color = 'bluegrey'; $status = pht('Active'); + $id = $url->getID(); + + $visit = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Visit URL')) + ->setIcon('fa-external-link') + ->setHref("u/{$id}") + ->setDisabled(!$url->isValid()); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($url->getDisplayName()) ->setStatus($icon, $color, $status) - ->setPolicyObject($url); + ->setPolicyObject($url) + ->setHeaderIcon('fa-compress') + ->addActionLink($visit); return $header; } - private function buildActionView(PhabricatorPhurlURL $url) { + private function buildCurtain(PhabricatorPhurlURL $url) { $viewer = $this->getViewer(); $id = $url->getID(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($url); + $curtain = $this->newCurtainView($url); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $url, PhabricatorPolicyCapability::CAN_EDIT); - $actions + $curtain ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("url/edit/{$id}/")) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)) - ->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Visit URL')) - ->setIcon('fa-external-link') - ->setHref("u/{$id}") - ->setDisabled(!$url->isValid())); + ->setWorkflow(!$can_edit)); - return $actions; + return $curtain; } - private function buildPropertyView(PhabricatorPhurlURL $url) { + private function buildPropertySectionView(PhabricatorPhurlURL $url) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($url); + ->setUser($viewer); $properties->addProperty( pht('Original URL'), @@ -132,18 +137,17 @@ final class PhabricatorPhurlURLViewController pht('Alias'), $url->getAlias()); - $properties->invokeWillRenderEvent(); - $description = $url->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); - $properties->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); + $properties->addSectionHeader(pht('Description')); $properties->addTextContent($description); } - return $properties; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); } } diff --git a/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php b/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php index aca7c14dc7..4ef59b300c 100644 --- a/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php +++ b/src/applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php @@ -18,7 +18,9 @@ final class PhabricatorPhurlLinkRemarkupRule extends PhutilRemarkupRule { public function markupLink(array $matches) { $engine = $this->getEngine(); $viewer = $engine->getConfig('viewer'); + $text_mode = $engine->isTextMode(); + $html_mode = $engine->isHTMLMailMode(); if (!$this->isFlatText($matches[0])) { return $matches[0]; @@ -28,46 +30,45 @@ final class PhabricatorPhurlLinkRemarkupRule extends PhutilRemarkupRule { $monogram = null; $is_monogram = '/^U(?P[1-9]\d*)/'; + $query = id(new PhabricatorPhurlURLQuery()) + ->setViewer($viewer); + if (preg_match($is_monogram, $ref, $monogram)) { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withIDs(array($monogram[1])) - ->execute(); + $query->withIDs(array($monogram[1])); } else if (ctype_digit($ref)) { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withIDs(array($ref)) - ->execute(); + $query->withIDs(array($ref)); } else { - $phurls = id(new PhabricatorPhurlURLQuery()) - ->setViewer($viewer) - ->withAliases(array($ref)) - ->execute(); + $query->withAliases(array($ref)); } - $phurl = head($phurls); + $phurl = $query->executeOne(); + if (!$phurl) { + return $matches[0]; + } - if ($phurl) { - if ($text_mode) { - return $phurl->getDisplayName(). - ' <'. - $phurl->getRedirectURI(). - '>'; - } + $uri = $phurl->getRedirectURI(); + $name = $phurl->getDisplayName(); + if ($text_mode || $html_mode) { + $uri = PhabricatorEnv::getProductionURI($uri); + } + + if ($text_mode) { + return pht( + '%s <%s>', + $name, + $uri); + } else { $link = phutil_tag( 'a', array( - 'href' => $phurl->getRedirectURI(), + 'href' => $uri, 'target' => '_blank', ), - $phurl->getDisplayName()); - - return $this->getEngine()->storeText($link); - } else { - return $matches[0]; + $name); } + + return $this->getEngine()->storeText($link); } - } diff --git a/src/applications/phurl/storage/PhabricatorPhurlURL.php b/src/applications/phurl/storage/PhabricatorPhurlURL.php index 30a3b0b93b..19d5968b0c 100644 --- a/src/applications/phurl/storage/PhabricatorPhurlURL.php +++ b/src/applications/phurl/storage/PhabricatorPhurlURL.php @@ -169,9 +169,6 @@ final class PhabricatorPhurlURL extends PhabricatorPhurlDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/ponder/controller/PonderAnswerEditController.php b/src/applications/ponder/controller/PonderAnswerEditController.php index ff2fd35920..3362e15834 100644 --- a/src/applications/ponder/controller/PonderAnswerEditController.php +++ b/src/applications/ponder/controller/PonderAnswerEditController.php @@ -91,10 +91,16 @@ final class PonderAnswerEditController extends PonderController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb("Q{$qid}", $answer_uri); $crumbs->addTextCrumb(pht('Edit Answer')); + $crumbs->setBorder(true); - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Edit Answer')) + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Answer')) + ->setHeaderIcon('fa-pencil'); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Answer')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $preview = id(new PHUIRemarkupPreviewPanel()) @@ -102,15 +108,17 @@ final class PonderAnswerEditController extends PonderController { ->setControlID($answer_content_id) ->setPreviewURI($this->getApplicationURI('preview/')); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, $preview, - ), - array( - 'title' => pht('Edit Answer'), )); + return $this->newPage() + ->setTitle(pht('Edit Answer')) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/ponder/controller/PonderAnswerHistoryController.php b/src/applications/ponder/controller/PonderAnswerHistoryController.php index 136cd8532e..9d7b96335a 100644 --- a/src/applications/ponder/controller/PonderAnswerHistoryController.php +++ b/src/applications/ponder/controller/PonderAnswerHistoryController.php @@ -18,7 +18,6 @@ final class PonderAnswerHistoryController extends PonderController { return new Aphront404Response(); } - $timeline = $this->buildTransactionTimeline( $answer, new PonderAnswerTransactionQuery()); @@ -32,15 +31,20 @@ final class PonderAnswerHistoryController extends PonderController { $crumbs->addTextCrumb("Q{$qid}", "/Q{$qid}"); $crumbs->addTextCrumb("A{$aid}", "/Q{$qid}#{$aid}"); $crumbs->addTextCrumb(pht('History')); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $timeline, - ), - array( - 'title' => pht('Answer History'), - )); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Answer History')) + ->setHeaderIcon('fa-history'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($timeline); + + return $this->newPage() + ->setTitle(pht('Answer History')) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/ponder/controller/PonderQuestionCommentController.php b/src/applications/ponder/controller/PonderQuestionCommentController.php index 4834d8040c..84c276cd5d 100644 --- a/src/applications/ponder/controller/PonderQuestionCommentController.php +++ b/src/applications/ponder/controller/PonderQuestionCommentController.php @@ -19,7 +19,6 @@ final class PonderQuestionCommentController extends PonderController { } $is_preview = $request->isPreviewRequest(); -// $draft = PhabricatorDraft::buildFromRequest($request); $qid = $question->getID(); $view_uri = "/Q{$qid}"; @@ -45,10 +44,6 @@ final class PonderQuestionCommentController extends PonderController { ->setException($ex); } -// if ($draft) { -// $draft->replaceOrDelete(); -// } - if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($viewer) diff --git a/src/applications/ponder/controller/PonderQuestionEditController.php b/src/applications/ponder/controller/PonderQuestionEditController.php index d31db7c157..907094c7e9 100644 --- a/src/applications/ponder/controller/PonderQuestionEditController.php +++ b/src/applications/ponder/controller/PonderQuestionEditController.php @@ -183,26 +183,37 @@ final class PonderQuestionEditController extends PonderController { $crumbs->addTextCrumb("Q{$id}", "/Q{$id}"); $crumbs->addTextCrumb(pht('Edit')); $title = pht('Edit Question'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); } else { $crumbs->addTextCrumb(pht('Ask Question')); $title = pht('Ask New Question'); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); } + $crumbs->setBorder(true); - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Question')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, $preview, $answer_preview, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/ponder/controller/PonderQuestionHistoryController.php b/src/applications/ponder/controller/PonderQuestionHistoryController.php index e7801ab4b1..21cc2ed064 100644 --- a/src/applications/ponder/controller/PonderQuestionHistoryController.php +++ b/src/applications/ponder/controller/PonderQuestionHistoryController.php @@ -29,15 +29,20 @@ final class PonderQuestionHistoryController extends PonderController { $crumbs->setBorder(true); $crumbs->addTextCrumb("Q{$qid}", "/Q{$qid}"); $crumbs->addTextCrumb(pht('History')); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $timeline, - ), - array( - 'title' => pht('Question History'), - )); + $header = id(new PHUIHeaderView()) + ->setHeader($question->getTitle()) + ->setHeaderIcon('fa-history'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($timeline); + + return $this->newPage() + ->setTitle(pht('Question History')) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 41819a34f0..a1105c6c8e 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -43,8 +43,7 @@ final class PonderQuestionViewController extends PonderController { $header->setStatus($icon, 'dark', $text); } - $properties = $this->buildPropertyListView($question); - $actions = $this->buildActionListView($question); + $curtain = $this->buildCurtain($question); $details = $this->buildPropertySectionView($question); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -118,29 +117,24 @@ final class PonderQuestionViewController extends PonderController { $ponder_view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) + ->setCurtain($curtain) ->setMainColumn($ponder_content) - ->setPropertyList($properties) ->addPropertySection(pht('DETAILS'), $details) - ->setActionList($actions) ->addClass('ponder-question-view'); $page_objects = array_merge( - array($question->getPHID()), - mpull($question->getAnswers(), 'getPHID')); + array($question->getPHID()), + mpull($question->getAnswers(), 'getPHID')); return $this->newPage() ->setTitle('Q'.$question->getID().' '.$question->getTitle()) ->setCrumbs($crumbs) ->setPageObjectPHIDs($page_objects) - ->appendChild( - array( - $ponder_view, - )); + ->appendChild($ponder_view); } - private function buildActionListView(PonderQuestion $question) { + private function buildCurtain(PonderQuestion $question) { $viewer = $this->getViewer(); - $request = $this->getRequest(); $id = $question->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -148,9 +142,7 @@ final class PonderQuestionViewController extends PonderController { $question, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($question); + $curtain = $this->newCurtainView($question); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $name = pht('Close Question'); @@ -160,7 +152,7 @@ final class PonderQuestionViewController extends PonderController { $icon = 'fa-square-o'; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Question')) @@ -168,7 +160,7 @@ final class PonderQuestionViewController extends PonderController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($name) ->setIcon($icon) @@ -176,26 +168,13 @@ final class PonderQuestionViewController extends PonderController { ->setDisabled(!$can_edit) ->setHref($this->getApplicationURI("/question/status/{$id}/"))); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-list') ->setName(pht('View History')) ->setHref($this->getApplicationURI("/question/history/{$id}/"))); - return $view; - } - - private function buildPropertyListView( - PonderQuestion $question) { - - $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($question); - - $view->invokeWillRenderEvent(); - - return $view; + return $curtain; } private function buildSubheaderView( diff --git a/src/applications/ponder/storage/PonderAnswer.php b/src/applications/ponder/storage/PonderAnswer.php index 025c020637..76c9057497 100644 --- a/src/applications/ponder/storage/PonderAnswer.php +++ b/src/applications/ponder/storage/PonderAnswer.php @@ -219,10 +219,6 @@ final class PonderAnswer extends PonderDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index 5ab719e3ac..99ce64c9f7 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -248,10 +248,6 @@ final class PonderQuestion extends PonderDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index 989837aa94..d958e9843e 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -18,7 +18,7 @@ final class PonderAddAnswerView extends AphrontView { public function render() { $question = $this->question; - $viewer = $this->user; + $viewer = $this->getViewer(); $authors = mpull($question->getAnswers(), null, 'getAuthorPHID'); if (isset($authors[$viewer->getPHID()])) { @@ -49,7 +49,7 @@ final class PonderAddAnswerView extends AphrontView { $form = new AphrontFormView(); $form - ->setUser($this->user) + ->setViewer($viewer) ->setAction($this->actionURI) ->setWorkflow(true) ->addHiddenInput('question_id', $question->getID()) @@ -59,7 +59,7 @@ final class PonderAddAnswerView extends AphrontView { ->setLabel(pht('Answer')) ->setError(true) ->setID('answer-content') - ->setUser($this->user)) + ->setViewer($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Add Answer'))); diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 39efcadb55..e2b2f9688d 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -1087,7 +1087,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $editor = id(new ManiphestTransactionEditor()) ->setActor($viewer) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->setContinueOnNoEffect(true) ->applyTransactions($task, $xactions); } @@ -1203,7 +1203,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $editor = id(new ManiphestTransactionEditor()) ->setActor($viewer) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->setContinueOnNoEffect(true) ->applyTransactions($task, $xactions); } @@ -1239,7 +1239,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $editor = id(new ManiphestTransactionEditor()) ->setActor($viewer) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->setContinueOnNoEffect(true) ->applyTransactions($task, $xactions); @@ -1464,7 +1464,7 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($user) - ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContentSource($this->newContentSource()) ->setContinueOnNoEffect(true) ->applyTransactions($project, $xactions); } diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php index a209fd9a40..aefade63a2 100644 --- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php @@ -71,7 +71,7 @@ final class ProjectCreateConduitAPIMethod extends ProjectConduitAPIMethod { $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($user) ->setContinueOnNoEffect(true) - ->setContentSourceFromConduitRequest($request); + ->setContentSource($request->newContentSource()); $editor->applyTransactions($project, $xactions); diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index 87420d1aa3..d84df87e93 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -30,12 +30,8 @@ final class PhabricatorProjectManageController $header->setStatus('fa-ban', 'red', pht('Archived')); } - $actions = $this->buildActionListView($project); - $properties = $this->buildPropertyListView($project, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $curtain = $this->buildCurtain($project); + $properties = $this->buildPropertyListView($project); $timeline = $this->buildTransactionTimeline( $project, @@ -47,6 +43,16 @@ final class PhabricatorProjectManageController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Manage')); + $crumbs->setBorder(true); + + $manage = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties) + ->setMainColumn( + array( + $timeline, + )); return $this->newPage() ->setNavigation($nav) @@ -58,26 +64,22 @@ final class PhabricatorProjectManageController )) ->appendChild( array( - $object_box, - $timeline, + $manage, )); } - private function buildActionListView(PhabricatorProject $project) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildCurtain(PhabricatorProject $project) { + $viewer = $this->getViewer(); $id = $project->getID(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain = $this->newCurtainView($project); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Details')) ->setIcon('fa-pencil') @@ -85,7 +87,7 @@ final class PhabricatorProjectManageController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Menu')) ->setIcon('fa-th-list') @@ -93,7 +95,7 @@ final class PhabricatorProjectManageController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Picture')) ->setIcon('fa-picture-o') @@ -102,7 +104,7 @@ final class PhabricatorProjectManageController ->setWorkflow(!$can_edit)); if ($project->isArchived()) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Project')) ->setIcon('fa-check') @@ -110,7 +112,7 @@ final class PhabricatorProjectManageController ->setDisabled(!$can_edit) ->setWorkflow(true)); } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Project')) ->setIcon('fa-ban') @@ -119,18 +121,15 @@ final class PhabricatorProjectManageController ->setWorkflow(true)); } - return $view; + return $curtain; } private function buildPropertyListView( - PhabricatorProject $project, - PhabricatorActionListView $actions) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + PhabricatorProject $project) { + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); + ->setUser($viewer); $view->addProperty( pht('Looks Like'), diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index f61ebd28d2..c9f3c2b9d3 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -91,7 +91,6 @@ final class PhabricatorProjectProfileController $home = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFluid(true) ->addClass('project-view-home') ->setMainColumn( array( diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index ee101cb2e4..760fad39d0 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -56,7 +56,7 @@ final class PhabricatorProjectEditEngine } protected function getObjectEditTitleText($object) { - return pht('Edit %s', $object->getName()); + return pht('Edit Project: %s', $object->getName()); } protected function getObjectEditShortText($object) { @@ -67,6 +67,10 @@ final class PhabricatorProjectEditEngine return pht('Create Project'); } + protected function getObjectName() { + return pht('Project'); + } + protected function getObjectViewURI($object) { if ($this->getIsCreate()) { return $object->getURI(); diff --git a/src/applications/project/engineextension/PhabricatorProjectsCurtainExtension.php b/src/applications/project/engineextension/PhabricatorProjectsCurtainExtension.php new file mode 100644 index 0000000000..c69e130275 --- /dev/null +++ b/src/applications/project/engineextension/PhabricatorProjectsCurtainExtension.php @@ -0,0 +1,91 @@ +getViewer(); + + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object->getPHID(), + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); + + $has_projects = (bool)$project_phids; + $project_phids = array_reverse($project_phids); + $handles = $viewer->loadHandles($project_phids); + + // If this object can appear on boards, build the workboard annotations. + // Some day, this might be a generic interface. For now, only tasks can + // appear on boards. + $can_appear_on_boards = ($object instanceof ManiphestTask); + + $annotations = array(); + if ($has_projects && $can_appear_on_boards) { + $engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($viewer) + ->setBoardPHIDs($project_phids) + ->setObjectPHIDs(array($object->getPHID())) + ->executeLayout(); + + // TDOO: Generalize this UI and move it out of Maniphest. + require_celerity_resource('maniphest-task-summary-css'); + + foreach ($project_phids as $project_phid) { + $handle = $handles[$project_phid]; + + $columns = $engine->getObjectColumns( + $project_phid, + $object->getPHID()); + + $annotation = array(); + foreach ($columns as $column) { + $project_id = $column->getProject()->getID(); + + $column_name = pht('(%s)', $column->getDisplayName()); + $column_link = phutil_tag( + 'a', + array( + 'href' => "/project/board/{$project_id}/", + 'class' => 'maniphest-board-link', + ), + $column_name); + + $annotation[] = $column_link; + } + + if ($annotation) { + $annotations[$project_phid] = array( + ' ', + phutil_implode_html(', ', $annotation), + ); + } + } + + } + + if ($has_projects) { + $list = id(new PHUIHandleTagListView()) + ->setHandles($handles) + ->setAnnotations($annotations) + ->setShowHovercards(true); + } else { + $list = phutil_tag('em', array(), pht('None')); + } + + return $this->newPanel() + ->setHeaderText(pht('Tags')) + ->setOrder(10000) + ->appendChild($list); + } + +} diff --git a/src/applications/project/view/PhabricatorProjectUserListView.php b/src/applications/project/view/PhabricatorProjectUserListView.php index e7f6631bfb..d590cbb559 100644 --- a/src/applications/project/view/PhabricatorProjectUserListView.php +++ b/src/applications/project/view/PhabricatorProjectUserListView.php @@ -45,7 +45,7 @@ abstract class PhabricatorProjectUserListView extends AphrontView { abstract protected function getHeaderText(); public function render() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $project = $this->getProject(); $user_phids = $this->getUserPHIDs(); diff --git a/src/applications/releeph/conduit/ReleephRequestConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephRequestConduitAPIMethod.php index 8473c0c7b5..0ac3caf15e 100644 --- a/src/applications/releeph/conduit/ReleephRequestConduitAPIMethod.php +++ b/src/applications/releeph/conduit/ReleephRequestConduitAPIMethod.php @@ -144,10 +144,7 @@ final class ReleephRequestConduitAPIMethod extends ReleephConduitAPIMethod { $editor = id(new ReleephRequestTransactionalEditor()) ->setActor($user) ->setContinueOnNoEffect(true) - ->setContentSource( - PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_CONDUIT, - array())); + ->setContentSource($request->newContentSource()); $editor->applyTransactions($releeph_request, $xactions); } diff --git a/src/applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php index d32249fe65..9cfbe72c99 100644 --- a/src/applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php +++ b/src/applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php @@ -65,10 +65,7 @@ final class ReleephWorkRecordConduitAPIMethod $editor = id(new ReleephRequestTransactionalEditor()) ->setActor($request->getUser()) ->setContinueOnNoEffect(true) - ->setContentSource( - PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_CONDUIT, - array())); + ->setContentSource($request->newContentSource()); $editor->applyTransactions($releeph_request, $xactions); } diff --git a/src/applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php b/src/applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php index ead3d9dd2a..5de0108a25 100644 --- a/src/applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php +++ b/src/applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php @@ -64,10 +64,7 @@ final class ReleephWorkRecordPickStatusConduitAPIMethod $editor = id(new ReleephRequestTransactionalEditor()) ->setActor($request->getUser()) ->setContinueOnNoEffect(true) - ->setContentSource( - PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_CONDUIT, - array())); + ->setContentSource($request->newContentSource()); $xactions = array(); diff --git a/src/applications/releeph/controller/ReleephController.php b/src/applications/releeph/controller/ReleephController.php index 83c94debe9..f48d2858e8 100644 --- a/src/applications/releeph/controller/ReleephController.php +++ b/src/applications/releeph/controller/ReleephController.php @@ -2,19 +2,6 @@ abstract class ReleephController extends PhabricatorController { - public function buildStandardPageResponse($view, array $data) { - $page = $this->buildStandardPageView(); - - $page->setApplicationName(pht('Releeph')); - $page->setBaseURI('/releeph/'); - $page->setTitle(idx($data, 'title')); - $page->setGlyph("\xD3\x82"); - $page->appendChild($view); - - $response = new AphrontWebpageResponse(); - return $response->setContent($page->render()); - } - public function buildSideNavView($for_app = false) { $user = $this->getRequest()->getUser(); diff --git a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php index d13383cc3b..e03e432d1f 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchCreateController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchCreateController.php @@ -105,20 +105,29 @@ final class ReleephBranchCreateController extends ReleephProductController { ->addCancelButton($product_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('New Branch')) + ->setHeaderText(pht('Branch')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('New Branch')); + $title = pht('New Branch'); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('New Branch'), - )); } } diff --git a/src/applications/releeph/controller/branch/ReleephBranchEditController.php b/src/applications/releeph/controller/branch/ReleephBranchEditController.php index 6d66f5d9d5..9d34e78668 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchEditController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchEditController.php @@ -86,23 +86,29 @@ final class ReleephBranchEditController extends ReleephBranchController { ->setValue(pht('Save Branch'))); $title = pht( - 'Edit Branch %s', + 'Edit Branch: %s', $branch->getDisplayNameWithDetail()); + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Branch')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($form); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit')); + $crumbs->setBorder(true); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->appendChild($form); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Branch')) + ->setHeaderIcon('fa-pencil'); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php b/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php index a77cdf8fb3..5a07a5c879 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchHistoryController.php @@ -27,15 +27,15 @@ final class ReleephBranchHistoryController extends ReleephBranchController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('History')); + $crumbs->setBorder(true); + + $title = pht('Branch History'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($timeline); - return $this->buildApplicationPage( - array( - $crumbs, - $timeline, - ), - array( - 'title' => pht('Branch History'), - )); } } diff --git a/src/applications/releeph/controller/product/ReleephProductCreateController.php b/src/applications/releeph/controller/product/ReleephProductCreateController.php index 2aedf8cdf1..12da2ea3f4 100644 --- a/src/applications/releeph/controller/product/ReleephProductCreateController.php +++ b/src/applications/releeph/controller/product/ReleephProductCreateController.php @@ -91,22 +91,30 @@ final class ReleephProductCreateController extends ReleephProductController { ->addCancelButton('/releeph/project/') ->setValue(pht('Create Release Product'))); - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Create New Product')) + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Product')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $title = pht('Create New Product'); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('New Product')); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => pht('Create New Product'), - )); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-plus-square'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function getRepositorySelectOptions() { diff --git a/src/applications/releeph/controller/product/ReleephProductEditController.php b/src/applications/releeph/controller/product/ReleephProductEditController.php index 6a58a39bd9..7938f0d930 100644 --- a/src/applications/releeph/controller/product/ReleephProductEditController.php +++ b/src/applications/releeph/controller/product/ReleephProductEditController.php @@ -195,22 +195,30 @@ final class ReleephProductEditController extends ReleephProductController { ->setValue(pht('Save'))); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Edit Releeph Product')) + ->setHeaderText(pht('Product')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); + $title = pht('Edit Product'); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Product')); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); - return $this->buildStandardPageResponse( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Edit Releeph Product'), - 'device' => true, - )); } private function getBranchHelpText() { diff --git a/src/applications/releeph/controller/product/ReleephProductHistoryController.php b/src/applications/releeph/controller/product/ReleephProductHistoryController.php index ebe9f15725..12d0d0b5c1 100644 --- a/src/applications/releeph/controller/product/ReleephProductHistoryController.php +++ b/src/applications/releeph/controller/product/ReleephProductHistoryController.php @@ -28,14 +28,12 @@ final class ReleephProductHistoryController extends ReleephProductController { $crumbs->addTextCrumb(pht('History')); $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $timeline, - ), - array( - 'title' => pht('Product History'), - )); + $title = pht('Product History'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($timeline); } } diff --git a/src/applications/releeph/controller/request/ReleephRequestEditController.php b/src/applications/releeph/controller/request/ReleephRequestEditController.php index d5f5187349..af7adc2c83 100644 --- a/src/applications/releeph/controller/request/ReleephRequestEditController.php +++ b/src/applications/releeph/controller/request/ReleephRequestEditController.php @@ -275,12 +275,14 @@ final class ReleephRequestEditController extends ReleephBranchController { if ($is_edit) { $title = pht('Edit Pull Request'); $submit_name = pht('Save'); + $header_icon = 'fa-pencil'; $crumbs->addTextCrumb($pull->getMonogram(), '/'.$pull->getMonogram()); $crumbs->addTextCrumb(pht('Edit')); } else { $title = pht('Create Pull Request'); $submit_name = pht('Create Pull Request'); + $header_icon = 'fa-plus-square'; $crumbs->addTextCrumb(pht('New Pull Request')); } @@ -291,18 +293,28 @@ final class ReleephRequestEditController extends ReleephBranchController { ->setValue($submit_name)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Request')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); - return $this->buildApplicationPage( - array( - $crumbs, + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $notice_view, $box, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/releeph/controller/request/ReleephRequestViewController.php b/src/applications/releeph/controller/request/ReleephRequestViewController.php index 694505cd20..c404e31579 100644 --- a/src/applications/releeph/controller/request/ReleephRequestViewController.php +++ b/src/applications/releeph/controller/request/ReleephRequestViewController.php @@ -76,17 +76,25 @@ final class ReleephRequestViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($pull->getMonogram(), '/'.$pull->getMonogram()); + $crumbs->setBorder(true); - return $this->buildStandardPageResponse( - array( - $crumbs, + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon('fa-flag-checkered'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $pull_box, $timeline, $add_comment_form, - ), - array( - 'title' => $title, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php index 26e708a00c..bdb63e6c9c 100644 --- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php +++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php @@ -301,8 +301,7 @@ final class DifferentialReleephRequestFieldSpecification extends Phobject { ->setContinueOnNoEffect(true) ->setContentSource( PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_UNKNOWN, - array())); + PhabricatorUnknownContentSource::SOURCECONST)); $editor->applyTransactions($releeph_request, $xactions); } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php index 83447fcc18..cc33f1021c 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php @@ -117,7 +117,7 @@ final class PhabricatorRepositoryManagementEditWorkflow pht('Specify one or more fields to edit!')); } - $content_source = PhabricatorContentSource::newConsoleSource(); + $content_source = $this->newContentSource(); $editor = id(new PhabricatorRepositoryEditor()) ->setActor($actor) diff --git a/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php b/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php new file mode 100644 index 0000000000..cd693be605 --- /dev/null +++ b/src/applications/repository/query/PhabricatorRepositoryGitLFSRefQuery.php @@ -0,0 +1,64 @@ +ids = $ids; + return $this; + } + + public function withRepositoryPHIDs(array $phids) { + $this->repositoryPHIDs = $phids; + return $this; + } + + public function withObjectHashes(array $hashes) { + $this->objectHashes = $hashes; + return $this; + } + + public function newResultObject() { + return new PhabricatorRepositoryGitLFSRef(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + if ($this->objectHashes !== null) { + $where[] = qsprintf( + $conn, + 'objectHash IN (%Ls)', + $this->objectHashes); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorDiffusionApplication'; + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 9896a8ef29..7068657acc 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1518,6 +1518,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return null; } + return $this->getRawHTTPCloneURIObject(); + } + + private function getRawHTTPCloneURIObject() { $uri = PhabricatorEnv::getProductionURI($this->getURI()); $uri = new PhutilURI($uri); @@ -1819,6 +1823,38 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return !$this->isSVN(); } + public function canUseGitLFS() { + if (!$this->isGit()) { + return false; + } + + if (!$this->isHosted()) { + return false; + } + + // TODO: Unprototype this feature. + if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + return false; + } + + return true; + } + + public function getGitLFSURI($path = null) { + if (!$this->canUseGitLFS()) { + throw new Exception( + pht( + 'This repository does not support Git LFS, so Git LFS URIs can '. + 'not be generated for it.')); + } + + $uri = $this->getRawHTTPCloneURIObject(); + $uri = (string)$uri; + $uri = $uri.'/'.$path; + + return $uri; + } + public function canMirror() { if ($this->isGit() || $this->isHg()) { return true; @@ -2399,6 +2435,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO $engine->destroyObject($atom); } + $lfs_refs = id(new PhabricatorRepositoryGitLFSRefQuery()) + ->setViewer($engine->getViewer()) + ->withRepositoryPHIDs(array($phid)) + ->execute(); + foreach ($lfs_refs as $ref) { + $engine->destroyObject($ref); + } + $this->saveTransaction(); } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 9fd218db9a..6e56cc711a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -10,6 +10,7 @@ final class PhabricatorRepositoryCommit PhabricatorSubscribableInterface, PhabricatorMentionableInterface, HarbormasterBuildableInterface, + HarbormasterCircleCIBuildableInterface, PhabricatorCustomFieldInterface, PhabricatorApplicationTransactionInterface, PhabricatorFulltextInterface { @@ -411,6 +412,52 @@ final class PhabricatorRepositoryCommit } +/* -( HarbormasterCircleCIBuildableInterface )----------------------------- */ + + + public function getCircleCIGitHubRepositoryURI() { + $repository = $this->getRepository(); + + $commit_phid = $this->getPHID(); + $repository_phid = $repository->getPHID(); + + if ($repository->isHosted()) { + throw new Exception( + pht( + 'This commit ("%s") is associated with a hosted repository '. + '("%s"). Repositories must be imported from GitHub to be built '. + 'with CircleCI.', + $commit_phid, + $repository_phid)); + } + + $remote_uri = $repository->getRemoteURI(); + $path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath( + $remote_uri); + if (!$path) { + throw new Exception( + pht( + 'This commit ("%s") is associated with a repository ("%s") that '. + 'with a remote URI ("%s") that does not appear to be hosted on '. + 'GitHub. Repositories must be hosted on GitHub to be built with '. + 'CircleCI.', + $commit_phid, + $repository_phid, + $remote_uri)); + } + + return $remote_uri; + } + + public function getCircleCIBuildIdentifierType() { + return 'revision'; + } + + public function getCircleCIBuildIdentifier() { + return $this->getCommitIdentifier(); + } + + /* -( PhabricatorCustomFieldInterface )------------------------------------ */ @@ -443,10 +490,6 @@ final class PhabricatorRepositoryCommit return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php b/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php new file mode 100644 index 0000000000..507af99cb4 --- /dev/null +++ b/src/applications/repository/storage/PhabricatorRepositoryGitLFSRef.php @@ -0,0 +1,72 @@ + array( + 'objectHash' => 'bytes64', + 'byteSize' => 'uint64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_hash' => array( + 'columns' => array('repositoryPHID', 'objectHash'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $file_phid = $this->getFilePHID(); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($engine->getViewer()) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if ($file) { + $engine->destroyObject($file); + } + + $this->delete(); + } + +} diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php index 0aa4dad187..9ad8ef2b66 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php @@ -30,9 +30,7 @@ final class PhabricatorRepositoryCommitHeraldWorker $commit->attachRepository($repository); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_DAEMON, - array()); + $content_source = $this->newContentSource(); $committer_phid = $data->getCommitDetail('committerPHID'); $author_phid = $data->getCommitDetail('authorPHID'); diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index dbb912eda4..25eedf0be2 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -221,9 +221,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker ->setViewer($actor) ->setAuthorPHID($acting_as_phid); - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_DAEMON, - array()); + $content_source = $this->newContentSource(); $update_data = $extraction_engine->updateRevisionWithCommit( $revision, @@ -337,9 +335,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $xactions[] = $edge_xaction; - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_DAEMON, - array()); + $content_source = $this->newContentSource(); $editor = id(new ManiphestTransactionEditor()) ->setActor($actor) diff --git a/src/applications/search/controller/PhabricatorSearchEditController.php b/src/applications/search/controller/PhabricatorSearchEditController.php index da89919611..c7f6c860e9 100644 --- a/src/applications/search/controller/PhabricatorSearchEditController.php +++ b/src/applications/search/controller/PhabricatorSearchEditController.php @@ -73,26 +73,35 @@ final class PhabricatorSearchEditController if ($named_query->getID()) { $title = pht('Edit Saved Query'); + $header_icon = 'fa-pencil'; } else { $title = pht('Save Query'); + $header_icon = 'fa-search'; } $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Query')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); } } diff --git a/src/applications/search/editor/PhabricatorProfilePanelEditEngine.php b/src/applications/search/editor/PhabricatorProfilePanelEditEngine.php index 3a2f725719..e5689c8e68 100644 --- a/src/applications/search/editor/PhabricatorProfilePanelEditEngine.php +++ b/src/applications/search/editor/PhabricatorProfilePanelEditEngine.php @@ -108,6 +108,10 @@ final class PhabricatorProfilePanelEditEngine return pht('Edit Menu Item'); } + protected function getObjectName() { + return pht('Menu Item'); + } + protected function getObjectCreateCancelURI($object) { return $this->getPanelEngine()->getConfigureURI(); } diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index f411c208bf..b5cd52471b 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -151,6 +151,7 @@ EOTEXT return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Builtin and Saved Queries')) ->setCollapsed(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) ->appendChild($table); } @@ -236,6 +237,7 @@ EOTEXT return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Custom Query Constraints')) ->setCollapsed(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) ->appendChild($table); } @@ -342,6 +344,7 @@ EOTEXT return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Result Ordering')) ->setCollapsed(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($orders_info)) ->appendChild($orders_table) ->appendChild($this->buildRemarkup($columns_info)) @@ -422,6 +425,7 @@ EOTEXT return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Object Fields')) ->setCollapsed(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) ->appendChild($table); } @@ -510,6 +514,7 @@ EOTEXT return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Attachments')) ->setCollapsed(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)) ->appendChild($table); } @@ -580,6 +585,7 @@ EOTEXT return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Paging and Limits')) ->setCollapsed(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($info)); } diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php index 5c41aba98b..4050cb70b5 100644 --- a/src/applications/settings/controller/PhabricatorSettingsMainController.php +++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php @@ -61,17 +61,15 @@ final class PhabricatorSettingsMainController '/p/'.$this->getUser()->getUsername().'/'); } $crumbs->addTextCrumb($panel->getPanelName()); - $nav->appendChild( - array( - $crumbs, - $response, - )); + $nav->appendChild($response); + + $title = $panel->getPanelName(); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setNavigation($nav); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $panel->getPanelName(), - )); } private function buildPanels($user) { diff --git a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php index 56b8fad76a..f2db373945 100644 --- a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php @@ -40,13 +40,14 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel { // the workflow from a password reset email. $key = $request->getStr('key'); + $password_type = PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE; + $token = null; if ($key) { $token = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($user) - ->withObjectPHIDs(array($user->getPHID())) - ->withTokenTypes( - array(PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE)) + ->withTokenResources(array($user->getPHID())) + ->withTokenTypes(array($password_type)) ->withTokenCodes(array(PhabricatorHash::digest($key))) ->withExpired(false) ->executeOne(); diff --git a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php index a92026333a..030ad37116 100644 --- a/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorTokensSettingsPanel.php @@ -23,7 +23,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel { $tokens = id(new PhabricatorAuthTemporaryTokenQuery()) ->setViewer($viewer) - ->withObjectPHIDs(array($viewer->getPHID())) + ->withTokenResources(array($viewer->getPHID())) ->execute(); $rows = array(); diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php index bd6dca4ce3..7354466772 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php @@ -136,21 +136,10 @@ final class PhabricatorSlowvoteEditController } } - $instructions = - phutil_tag( - 'p', - array( - 'class' => 'aphront-form-instructions', - ), - pht('Resolve issues and build consensus through '. - 'protracted deliberation.')); - $form = id(new AphrontFormView()) ->setUser($viewer) - ->appendChild($instructions) ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) + id(new AphrontFormTextControl()) ->setLabel(pht('Question')) ->setName('question') ->setValue($v_question) @@ -218,10 +207,12 @@ final class PhabricatorSlowvoteEditController $title = pht('Create Slowvote'); $button = pht('Create'); $cancel_uri = $this->getApplicationURI(); + $header_icon = 'fa-plus-square'; } else { - $title = pht('Edit %s', 'V'.$poll->getID()); + $title = pht('Edit Poll: %s', $poll->getQuestion()); $button = pht('Save Changes'); $cancel_uri = '/V'.$poll->getID(); + $header_icon = 'fa-pencil'; } $policies = id(new PhabricatorPolicyQuery()) @@ -259,18 +250,28 @@ final class PhabricatorSlowvoteEditController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) + ->setHeaderText(pht('Poll')) ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($form_box); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $form_box, + $view, )); } diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 2eb2c08c75..f788f5e94c 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -46,8 +46,7 @@ final class PhabricatorSlowvotePollController ->setPolicyObject($poll) ->setHeaderIcon('fa-bar-chart'); - $actions = $this->buildActionView($poll); - $properties = $this->buildPropertyView($poll); + $curtain = $this->buildCurtain($poll); $subheader = $this->buildSubheaderView($poll); $crumbs = $this->buildApplicationCrumbs(); @@ -68,37 +67,31 @@ final class PhabricatorSlowvotePollController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) - ->setMainColumn($poll_content) - ->setPropertyList($properties) - ->setActionList($actions); + ->setCurtain($curtain) + ->setMainColumn($poll_content); return $this->newPage() ->setTitle('V'.$poll->getID().' '.$poll->getQuestion()) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($poll->getPHID())) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } - private function buildActionView(PhabricatorSlowvotePoll $poll) { - $viewer = $this->getRequest()->getUser(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($poll); + private function buildCurtain(PhabricatorSlowvotePoll $poll) { + $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $poll, PhabricatorPolicyCapability::CAN_EDIT); + $curtain = $this->newCurtainView($poll); + $is_closed = $poll->getIsClosed(); $close_poll_text = $is_closed ? pht('Reopen Poll') : pht('Close Poll'); $close_poll_icon = $is_closed ? 'fa-play-circle-o' : 'fa-ban'; - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Poll')) ->setIcon('fa-pencil') @@ -106,7 +99,7 @@ final class PhabricatorSlowvotePollController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($close_poll_text) ->setIcon($close_poll_icon) @@ -114,19 +107,7 @@ final class PhabricatorSlowvotePollController ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $view; - } - - private function buildPropertyView( - PhabricatorSlowvotePoll $poll) { - - $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($poll); - $view->invokeWillRenderEvent(); - - return $view; + return $curtain; } private function buildSubheaderView( diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php index 2240c510aa..6896f7ba2f 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php @@ -179,10 +179,6 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php b/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php index f17d0f404a..4215ec96fe 100644 --- a/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php +++ b/src/applications/spaces/__tests__/PhabricatorSpacesTestCase.php @@ -215,7 +215,7 @@ final class PhabricatorSpacesTestCase extends PhabricatorTestCase { ->setNewValue($is_default); } - $content_source = PhabricatorContentSource::newConsoleSource(); + $content_source = $this->newContentSource(); $editor = id(new PhabricatorSpacesNamespaceEditor()) ->setActor($actor) diff --git a/src/applications/spaces/controller/PhabricatorSpacesEditController.php b/src/applications/spaces/controller/PhabricatorSpacesEditController.php index 4dfeeed7ed..3f7dae9e82 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesEditController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesEditController.php @@ -162,7 +162,8 @@ final class PhabricatorSpacesEditController ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($header_text) + ->setHeaderText(pht('Space')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setValidationException($validation_exception) ->appendChild($form); @@ -173,14 +174,21 @@ final class PhabricatorSpacesEditController $cancel_uri); } $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $header = id(new PHUIHeaderView()) + ->setHeader($header_text) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/spaces/controller/PhabricatorSpacesViewController.php b/src/applications/spaces/controller/PhabricatorSpacesViewController.php index 25bb70f646..8319f19a6e 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesViewController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesViewController.php @@ -18,9 +18,9 @@ final class PhabricatorSpacesViewController return new Aphront404Response(); } - $action_list = $this->buildActionListView($space); + $curtain = $this->buildCurtain($space); $property_list = $this->buildPropertyListView($space); - $property_list->setActionList($action_list); + $title = array($space->getMonogram(), $space->getNamespaceName()); $xactions = id(new PhabricatorSpacesNamespaceTransactionQuery()) ->setViewer($viewer) @@ -35,7 +35,8 @@ final class PhabricatorSpacesViewController $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($space->getNamespaceName()) - ->setPolicyObject($space); + ->setPolicyObject($space) + ->setHeaderIcon('fa-th-large'); if ($space->getIsArchived()) { $header->setStatus('fa-ban', 'red', pht('Archived')); @@ -44,21 +45,27 @@ final class PhabricatorSpacesViewController } $box = id(new PHUIObjectBoxView()) - ->setHeader($header) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($property_list); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($space->getMonogram()); + $crumbs->setBorder(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $box, + $timeline, + )) + ->setCurtain($curtain); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => array($space->getMonogram(), $space->getNamespaceName()), - )); } private function buildPropertyListView(PhabricatorSpacesNamespace $space) { @@ -93,18 +100,17 @@ final class PhabricatorSpacesViewController return $list; } - private function buildActionListView(PhabricatorSpacesNamespace $space) { + private function buildCurtain(PhabricatorSpacesNamespace $space) { $viewer = $this->getRequest()->getUser(); - $list = id(new PhabricatorActionListView()) - ->setUser($viewer); + $curtain = $this->newCurtainView($space); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $space, PhabricatorPolicyCapability::CAN_EDIT); - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Space')) ->setIcon('fa-pencil') @@ -115,7 +121,7 @@ final class PhabricatorSpacesViewController $id = $space->getID(); if ($space->getIsArchived()) { - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Space')) ->setIcon('fa-check') @@ -123,7 +129,7 @@ final class PhabricatorSpacesViewController ->setDisabled(!$can_edit) ->setWorkflow(true)); } else { - $list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Space')) ->setIcon('fa-ban') @@ -132,7 +138,7 @@ final class PhabricatorSpacesViewController ->setWorkflow(true)); } - return $list; + return $curtain; } } diff --git a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php new file mode 100644 index 0000000000..6467141de2 --- /dev/null +++ b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php @@ -0,0 +1,39 @@ +getViewer(); + $object_phid = $object->getPHID(); + + $subscriber_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( + $object_phid); + + $handles = $viewer->loadHandles($subscriber_phids); + + // TODO: This class can't accept a HandleList yet. + $handles = iterator_to_array($handles); + + $susbscribers_view = id(new SubscriptionListStringBuilder()) + ->setObjectPHID($object_phid) + ->setHandles($handles) + ->buildPropertyString(); + + return $this->newPanel() + ->setHeaderText(pht('Subscribers')) + ->setOrder(20000) + ->appendChild($susbscribers_view); + } + +} diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php index b69ce4243f..acbb978238 100644 --- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php +++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php @@ -96,11 +96,6 @@ final class PhabricatorSubscriptionsUIEventListener return; } - if (!$object->shouldShowSubscribersProperty()) { - // This object doesn't render subscribers in its property list. - return; - } - $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $object->getPHID()); if ($subscribers) { diff --git a/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php b/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php index e54e2cd42e..1af9e7107f 100644 --- a/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php +++ b/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php @@ -13,15 +13,6 @@ interface PhabricatorSubscribableInterface { */ public function isAutomaticallySubscribed($phid); - - /** - * Return `true` to indicate that "Subscribers:" should be shown when - * rendering property lists for this object, or `false` to omit the property. - * - * @return bool True to show the "Subscribers:" property. - */ - public function shouldShowSubscribersProperty(); - } // TEMPLATE IMPLEMENTATION ///////////////////////////////////////////////////// @@ -33,8 +24,4 @@ interface PhabricatorSubscribableInterface { return false; } - public function shouldShowSubscribersProperty() { - return true; - } - */ diff --git a/src/applications/tokens/conduit/TokenGiveConduitAPIMethod.php b/src/applications/tokens/conduit/TokenGiveConduitAPIMethod.php index 4e0a463a74..eb591025b2 100644 --- a/src/applications/tokens/conduit/TokenGiveConduitAPIMethod.php +++ b/src/applications/tokens/conduit/TokenGiveConduitAPIMethod.php @@ -22,7 +22,7 @@ final class TokenGiveConduitAPIMethod extends TokenConduitAPIMethod { } protected function execute(ConduitAPIRequest $request) { - $content_source = PhabricatorContentSource::newFromConduitRequest($request); + $content_source = $request->newContentSource(); $editor = id(new PhabricatorTokenGivenEditor()) ->setActor($request->getUser()) diff --git a/src/applications/tokens/controller/PhabricatorTokenGivenController.php b/src/applications/tokens/controller/PhabricatorTokenGivenController.php index 86318a885a..8352c35d69 100644 --- a/src/applications/tokens/controller/PhabricatorTokenGivenController.php +++ b/src/applications/tokens/controller/PhabricatorTokenGivenController.php @@ -71,12 +71,10 @@ final class PhabricatorTokenGivenController extends PhabricatorTokenController { $nav->appendChild($box); $nav->appendChild($pager); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); + } - } diff --git a/src/applications/tokens/controller/PhabricatorTokenLeaderController.php b/src/applications/tokens/controller/PhabricatorTokenLeaderController.php index 53cda0f484..bfef3cd8cf 100644 --- a/src/applications/tokens/controller/PhabricatorTokenLeaderController.php +++ b/src/applications/tokens/controller/PhabricatorTokenLeaderController.php @@ -55,11 +55,10 @@ final class PhabricatorTokenLeaderController $nav->appendChild($box); $nav->appendChild($pager); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->appendChild($nav); + } } diff --git a/src/applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php b/src/applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php new file mode 100644 index 0000000000..1d1ca2551c --- /dev/null +++ b/src/applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php @@ -0,0 +1,67 @@ +getViewer(); + + $tokens_given = id(new PhabricatorTokenGivenQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->execute(); + if (!$tokens_given) { + return null; + } + + $author_phids = mpull($tokens_given, 'getAuthorPHID'); + $handles = $viewer->loadHandles($author_phids); + + Javelin::initBehavior('phabricator-tooltips'); + + $list = array(); + foreach ($tokens_given as $token_given) { + $token = $token_given->getToken(); + + $aural = javelin_tag( + 'span', + array( + 'aural' => true, + ), + pht( + '"%s" token, awarded by %s.', + $token->getName(), + $handles[$token_given->getAuthorPHID()]->getName())); + + $list[] = javelin_tag( + 'span', + array( + 'sigil' => 'has-tooltip', + 'class' => 'token-icon', + 'meta' => array( + 'tip' => $handles[$token_given->getAuthorPHID()]->getName(), + ), + ), + array( + $aural, + $token->renderIcon(), + )); + } + + return $this->newPanel() + ->setHeaderText(pht('Tokens')) + ->setOrder(30000) + ->appendChild($list); + } + +} diff --git a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php index 224efff198..b4a86428c1 100644 --- a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php @@ -58,10 +58,12 @@ final class PhabricatorTokenGivenQuery } protected function willFilterPage(array $results) { + $viewer = $this->getViewer(); + $object_phids = mpull($results, 'getObjectPHID'); $objects = id(new PhabricatorObjectQuery()) - ->setViewer($this->getViewer()) + ->setViewer($viewer) ->withPHIDs($object_phids) ->execute(); $objects = mpull($objects, null, 'getPHID'); @@ -80,6 +82,31 @@ final class PhabricatorTokenGivenQuery unset($results[$key]); } + if (!$results) { + return $results; + } + + $token_phids = mpull($results, 'getTokenPHID'); + + $tokens = id(new PhabricatorTokenQuery()) + ->setViewer($viewer) + ->withPHIDs($token_phids) + ->execute(); + $tokens = mpull($tokens, null, 'getPHID'); + + foreach ($results as $key => $result) { + $token_phid = $result->getTokenPHID(); + + $token = idx($tokens, $token_phid); + if (!$token) { + $this->didRejectResult($result); + unset($results[$key]); + continue; + } + + $result->attachToken($token); + } + return $results; } diff --git a/src/applications/tokens/storage/PhabricatorTokenGiven.php b/src/applications/tokens/storage/PhabricatorTokenGiven.php index 59ffc819d1..44db1baaf5 100644 --- a/src/applications/tokens/storage/PhabricatorTokenGiven.php +++ b/src/applications/tokens/storage/PhabricatorTokenGiven.php @@ -8,6 +8,7 @@ final class PhabricatorTokenGiven extends PhabricatorTokenDAO protected $tokenPHID; private $object = self::ATTACHABLE; + private $token = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -35,6 +36,15 @@ final class PhabricatorTokenGiven extends PhabricatorTokenDAO return $this->assertAttached($this->object); } + public function attachToken(PhabricatorToken $token) { + $this->token = $token; + return $this; + } + + public function getToken() { + return $this->assertAttached($this->token); + } + public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php index 65b6282aa9..ae3fb7520c 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php @@ -37,9 +37,9 @@ final class PhabricatorApplicationTransactionCommentRawController $addendum = null; if ($request->getExists('email')) { $content_source = $xaction->getContentSource(); - $source_email = PhabricatorContentSource::SOURCE_EMAIL; + $source_email = PhabricatorEmailContentSource::SOURCECONST; if ($content_source->getSource() == $source_email) { - $source_id = $content_source->getParam('id'); + $source_id = $content_source->getContentSourceParameter('id'); if ($source_id) { $message = id(new PhabricatorMetaMTAReceivedMail())->loadOneWhere( 'id = %d', diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php index fa7f30c858..340431dd19 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php @@ -86,7 +86,7 @@ final class PhabricatorEditEngineConfigurationDefaultsController ->addCancelButton($cancel_uri)); $info = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors( array( pht('You are editing the default values for this form.'), @@ -94,18 +94,30 @@ final class PhabricatorEditEngineConfigurationDefaultsController $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setInfoView($info) + ->setHeaderText(pht('Form')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Form %d', $config->getID()), $cancel_uri); $crumbs->addTextCrumb(pht('Edit Defaults')); + $crumbs->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Edit Form Defaults')) + ->setHeaderIcon('fa-pencil'); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $info, + $box, + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild($box); + ->appendChild($view); } } diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php index 248abc79e5..23ccedd04d 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php @@ -17,27 +17,35 @@ final class PhabricatorEditEngineConfigurationViewController $is_concrete = (bool)$config->getID(); - $actions = $this->buildActionView($config); - - $properties = $this->buildPropertyView($config) - ->setActionList($actions); + $curtain = $this->buildCurtainView($config); + $properties = $this->buildPropertyView($config); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($config) - ->setHeader(pht('Edit Form: %s', $config->getDisplayName())); + ->setHeader(pht('Edit Form: %s', $config->getDisplayName())) + ->setHeaderIcon('fa-pencil'); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + if ($config->getIsDisabled()) { + $name = pht('Disabled'); + $icon = 'fa-ban'; + $color = 'indigo'; + } else { + $name = pht('Enabled'); + $icon = 'fa-check'; + $color = 'green'; + } + $header->setStatus($icon, $color, $name); $field_list = $this->buildFieldList($config); - $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); if ($is_concrete) { - $crumbs->addTextCrumb(pht('Form %d', $config->getID())); + $title = pht('Form %d', $config->getID()); + $crumbs->addTextCrumb($title); } else { + $title = pht('Builtin'); $crumbs->addTextCrumb(pht('Builtin')); } @@ -51,17 +59,21 @@ final class PhabricatorEditEngineConfigurationViewController $timeline = null; } + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $field_list, + $timeline, + )); + return $this->newPage() + ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - $field_list, - $timeline, - )); + ->appendChild($view); } - private function buildActionView( + private function buildCurtainView( PhabricatorEditEngineConfiguration $config) { $viewer = $this->getViewer(); $engine = $config->getEngine(); @@ -72,9 +84,7 @@ final class PhabricatorEditEngineConfigurationViewController $config, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - + $curtain = $this->newCurtainView($config); $form_key = $config->getIdentifier(); $base_uri = "/transactions/editengine/{$engine_key}"; @@ -83,7 +93,7 @@ final class PhabricatorEditEngineConfigurationViewController if (!$is_concrete) { $save_uri = "{$base_uri}/save/{$form_key}/"; - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Make Editable')) ->setIcon('fa-pencil') @@ -94,7 +104,7 @@ final class PhabricatorEditEngineConfigurationViewController $can_edit = false; } else { $edit_uri = "{$base_uri}/edit/{$form_key}/"; - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Form Configuration')) ->setIcon('fa-pencil') @@ -105,7 +115,7 @@ final class PhabricatorEditEngineConfigurationViewController $use_uri = $engine->getEditURI(null, "form/{$form_key}/"); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Use Form')) ->setIcon('fa-th-list') @@ -113,7 +123,7 @@ final class PhabricatorEditEngineConfigurationViewController $defaults_uri = "{$base_uri}/defaults/{$form_key}/"; - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Change Default Values')) ->setIcon('fa-paint-brush') @@ -123,7 +133,7 @@ final class PhabricatorEditEngineConfigurationViewController $reorder_uri = "{$base_uri}/reorder/{$form_key}/"; - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Change Field Order')) ->setIcon('fa-sort-alpha-asc') @@ -133,7 +143,7 @@ final class PhabricatorEditEngineConfigurationViewController $lock_uri = "{$base_uri}/lock/{$form_key}/"; - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Lock / Hide Fields')) ->setIcon('fa-lock') @@ -151,7 +161,7 @@ final class PhabricatorEditEngineConfigurationViewController $disable_icon = 'fa-ban'; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($disable_name) ->setIcon($disable_icon) @@ -169,7 +179,7 @@ final class PhabricatorEditEngineConfigurationViewController $defaultcreate_icon = 'fa-plus'; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($defaultcreate_name) ->setIcon($defaultcreate_icon) @@ -187,7 +197,7 @@ final class PhabricatorEditEngineConfigurationViewController $isedit_uri = "{$base_uri}/defaultedit/{$form_key}/"; - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($isedit_name) ->setIcon($isedit_icon) @@ -195,7 +205,7 @@ final class PhabricatorEditEngineConfigurationViewController ->setWorkflow(true) ->setDisabled(!$can_edit)); - return $view; + return $curtain; } private function buildPropertyView( @@ -203,8 +213,7 @@ final class PhabricatorEditEngineConfigurationViewController $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($config); + ->setUser($viewer); return $properties; } @@ -226,7 +235,7 @@ final class PhabricatorEditEngineConfigurationViewController } $info = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors( array( pht('This is a preview of the current form configuration.'), @@ -234,10 +243,10 @@ final class PhabricatorEditEngineConfigurationViewController $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Form Preview')) - ->setInfoView($info) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - return $box; + return array($info, $box); } } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 3f5e751bf3..8f85d8f618 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -188,6 +188,12 @@ abstract class PhabricatorEditEngine abstract protected function getObjectCreateShortText(); + /** + * @task text + */ + abstract protected function getObjectName(); + + /** * @task text */ @@ -821,7 +827,7 @@ abstract class PhabricatorEditEngine } private function buildCrumbs($object, $final = false) { - $controller = $this->getcontroller(); + $controller = $this->getController(); $crumbs = $controller->buildApplicationCrumbsForEditEngine(); if ($this->getIsCreate()) { @@ -988,8 +994,10 @@ abstract class PhabricatorEditEngine if ($this->getIsCreate()) { $header_text = $this->getFormHeaderText($object); + $header_icon = 'fa-plus-square'; } else { $header_text = $this->getObjectEditTitleText($object); + $header_icon = 'fa-pencil'; } $show_preview = !$request->isAjax(); @@ -1036,25 +1044,34 @@ abstract class PhabricatorEditEngine } $header = id(new PHUIHeaderView()) - ->setHeader($header_text); + ->setHeader($header_text) + ->setHeaderIcon($header_icon); if ($action_button) { $header->addActionLink($action_button); } $crumbs = $this->buildCrumbs($object, $final = true); + $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) ->setUser($viewer) - ->setHeader($header) + ->setHeaderText($this->getObjectName()) ->setValidationException($validation_exception) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($form); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( + $box, + $previews, + )); + return $controller->newPage() ->setTitle($header_text) ->setCrumbs($crumbs) - ->appendChild($box) - ->appendChild($previews); + ->appendChild($view); } protected function newEditResponse( @@ -1179,6 +1196,60 @@ abstract class PhabricatorEditEngine return $actions; } + + /** + * Test if the viewer could apply a certain type of change by using the + * normal "Edit" form. + * + * This method returns `true` if the user has access to an edit form and + * that edit form has a field which applied the specified transaction type, + * and that field is visible and editable for the user. + * + * For example, you can use it to test if a user is able to reassign tasks + * or not, prior to rendering dedicated UI for task reassingment. + * + * Note that this method does NOT test if the user can actually edit the + * current object, just if they have access to the related field. + * + * @param const Transaction type to test for. + * @return bool True if the user could "Edit" to apply the transaction type. + */ + final public function hasEditAccessToTransaction($xaction_type) { + $viewer = $this->getViewer(); + + $config = $this->loadDefaultEditConfiguration(); + if (!$config) { + return false; + } + + $object = $this->getTargetObject(); + if (!$object) { + $object = $this->newEditableObject(); + } + + $fields = $this->buildEditFields($object); + + $field = null; + foreach ($fields as $form_field) { + $field_xaction_type = $form_field->getTransactionType(); + if ($field_xaction_type === $xaction_type) { + $field = $form_field; + break; + } + } + + if (!$field) { + return false; + } + + if (!$field->shouldReadValueFromSubmit()) { + return false; + } + + return true; + } + + final public function addActionToCrumbs(PHUICrumbsView $crumbs) { $viewer = $this->getViewer(); @@ -1632,7 +1703,7 @@ abstract class PhabricatorEditEngine $editor = $object->getApplicationTransactionEditor() ->setActor($viewer) - ->setContentSourceFromConduitRequest($request) + ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true); if (!$this->getIsCreate()) { diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php index 3cf04aeca8..b6f95ecd1b 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php @@ -94,6 +94,7 @@ abstract class PhabricatorEditEngineAPIMethod $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Transaction Types')) ->setCollapsed(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($summary_info)) ->appendChild($summary_table); @@ -140,6 +141,7 @@ abstract class PhabricatorEditEngineAPIMethod $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Transaction Type: %s', $type->getEditType())) ->setCollapsed(true) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($section)) ->appendChild($type_table); } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index e418c7b54d..a257698ae3 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -733,16 +733,6 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorContentSource::newFromRequest($request)); } - public function setContentSourceFromConduitRequest( - ConduitAPIRequest $request) { - - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_CONDUIT, - array()); - - return $this->setContentSource($content_source); - } - public function getContentSource() { return $this->contentSource; } @@ -979,8 +969,7 @@ abstract class PhabricatorApplicationTransactionEditor // out from transcripts, but it would be cleaner if you didn't have to. $herald_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_HERALD, - array()); + PhabricatorHeraldContentSource::SOURCECONST); $herald_editor = newv(get_class($this), array()) ->setContinueOnNoEffect(true) diff --git a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php index 0305c19015..a9d96337ed 100644 --- a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php +++ b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php @@ -70,6 +70,10 @@ final class PhabricatorEditEngineConfigurationEditEngine return pht('Create Form'); } + protected function getObjectName() { + return pht('Form'); + } + protected function getObjectViewURI($object) { $id = $object->getID(); return $this->getURI("view/{$id}/"); diff --git a/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php b/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php index ca072f8172..52d48f528b 100644 --- a/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php +++ b/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php @@ -116,24 +116,27 @@ final class PhabricatorEditEngineConfigurationSearchEngine $id = $config->getID(); if ($id) { - $item->setObjectName(pht('Form %d', $id)); + $item->addIcon('fa-file-text-o bluegrey', pht('Form %d', $id)); $key = $id; } else { - $item->setObjectName(pht('Builtin')); + $item->addIcon('fa-file-text bluegrey', pht('Builtin')); $key = $config->getBuiltinKey(); } $item->setHref("/transactions/editengine/{$engine_key}/view/{$key}/"); if ($config->getIsDefault()) { - $item->addIcon('fa-plus', pht('Default')); + $item->addAttribute(pht('Default Create Form')); } if ($config->getIsEdit()) { - $item->addIcon('fa-pencil', pht('Edit Form')); + $item->addAttribute(pht('Edit Form')); } if ($config->getIsDisabled()) { - $item->addIcon('fa-ban', pht('Disabled')); + $item->setDisabled(true); + $item->setStatusIcon('fa-ban grey', pht('Disabled')); + } else { + $item->setStatusIcon('fa-file-text-o green', pht('Enabled')); } $list->addItem($item); diff --git a/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php b/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php index 560a750f00..5457708953 100644 --- a/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php +++ b/src/applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php @@ -18,11 +18,7 @@ abstract class PhabricatorApplicationTransactionReplyHandler } private function newEditor(PhabricatorMetaMTAReceivedMail $mail) { - $content_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_EMAIL, - array( - 'id' => $mail->getID(), - )); + $content_source = $mail->newContentSource(); $editor = $this->getMailReceiver() ->getApplicationTransactionEditor() diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 8084ea302f..ee8a7a47a3 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -605,24 +605,27 @@ abstract class PhabricatorApplicationTransaction break; } - // If a transaction publishes an inline comment: - // - // - Don't show it if there are other kinds of transactions. The - // rationale here is that application mail will make the presence - // of inline comments obvious enough by including them prominently - // in the body. We could change this in the future if the obviousness - // needs to be increased. - // - If there are only inline transactions, only show the first - // transaction. The rationale is that seeing multiple "added an inline - // comment" transactions is not useful. - if ($this->isInlineCommentTransaction()) { + $inlines = array(); + + // If there's a normal comment, we don't need to publish the inline + // transaction, since the normal comment covers things. foreach ($xactions as $xaction) { - if (!$xaction->isInlineCommentTransaction()) { + if ($xaction->isInlineCommentTransaction()) { + $inlines[] = $xaction; + continue; + } + + // We found a normal comment, so hide this inline transaction. + if ($xaction->hasComment()) { return true; } } - return ($this !== head($xactions)); + + // If there are several inline comments, only publish the first one. + if ($this !== head($inlines)) { + return true; + } } return $this->shouldHide(); diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php index da5f0520f6..4bae34e5eb 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php @@ -104,4 +104,47 @@ final class PhabricatorEditEngineConfigurationTransaction return parent::getTitle(); } + public function getColor() { + $author_phid = $this->getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $type = $this->getTransactionType(); + switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return 'green'; + case self::TYPE_DISABLE: + if ($new) { + return 'indigo'; + } else { + return 'green'; + } + } + + return parent::getColor(); + } + + public function getIcon() { + $author_phid = $this->getAuthorPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $type = $this->getTransactionType(); + switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return 'fa-plus'; + case self::TYPE_DISABLE: + if ($new) { + return 'fa-ban'; + } else { + return 'fa-check'; + } + } + + return parent::getIcon(); + } + + } diff --git a/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php b/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php index 1b017c224e..0369fbff54 100644 --- a/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php +++ b/src/applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php @@ -62,9 +62,7 @@ final class PhabricatorApplicationTransactionPublishWorker PhabricatorApplicationTransactionInterface $object) { $data = $this->getTaskData(); - $daemon_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_DAEMON, - array()); + $daemon_source = $this->newContentSource(); $viewer = PhabricatorUser::getOmnipotentUser(); $editor = $object->getApplicationTransactionEditor() diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php index a771bf0747..3084b2d434 100644 --- a/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php +++ b/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php @@ -126,21 +126,18 @@ final class PhabricatorTypeaheadFunctionHelpController $header = id(new PHUIHeaderView()) ->setHeader($title); - $document = id(new PHUIDocumentView()) + $document = id(new PHUIDocumentViewPro()) ->setHeader($header) ->appendChild($content_box); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Function Help')); + $crumbs->setBorder(true); - return $this->buildApplicationPage( - array( - $crumbs, - $document, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($document); } } diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php index b3687b85e8..15650a33fd 100644 --- a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php +++ b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php @@ -309,6 +309,7 @@ final class PhabricatorTypeaheadModularDatasourceController $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Token Query')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); $table = new AphrontTableView($content); @@ -329,17 +330,24 @@ final class PhabricatorTypeaheadModularDatasourceController $result_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Token Results (%s)', $class)) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($table); - return $this->buildApplicationPage( - array( + $title = pht('Typeahead Results'); + + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter(array( $form_box, $result_box, - ), - array( - 'title' => pht('Typeahead Results'), - 'device' => false, )); + + return $this->newPage() + ->setTitle($title) + ->appendChild($view); } } diff --git a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php index 8da553104f..6be978a6a9 100644 --- a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php +++ b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php @@ -50,11 +50,9 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController { $result, )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $example->getName(), - )); + return $this->newPage() + ->setTitle($example->getName()) + ->appendChild($nav); } } diff --git a/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php b/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php index 65ca5593f3..5faab70c7a 100644 --- a/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php +++ b/src/applications/xhprof/controller/PhabricatorXHProfSampleListController.php @@ -87,11 +87,12 @@ final class PhabricatorXHProfSampleListController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('XHProf Samples')); - return $this->buildApplicationPage( - array($crumbs, $list), - array( - 'title' => pht('XHProf Samples'), - )); + $title = pht('XHProf Samples'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($list); } } diff --git a/src/docs/contributor/bug_reports.diviner b/src/docs/contributor/bug_reports.diviner index 987fd1571f..91cfbce947 100644 --- a/src/docs/contributor/bug_reports.diviner +++ b/src/docs/contributor/bug_reports.diviner @@ -8,8 +8,9 @@ Include Reproduction Steps! IMPORTANT: When filing a bug report, you **MUST** include reproduction instructions. We can not help fix bugs we can not reproduce, and will not -accept reports which omit reproduction instructions. See below for details. +accept reports which omit reproduction instructions. +For help, see @{article:Providing Reproduction Steps}. Overview ======== @@ -69,12 +70,9 @@ To update Phabricator, use a script like the one described in @{article:Upgrading Phabricator}. **If you can not update** for some reason, please include the version of -Phabricator you are running when you file a report. You can find the version in -{nav Config > Versions} in the web UI. +Phabricator you are running when you file a report. -(The version is just the Git hash of your local HEAD, so you can also find it -by running `git show` in `phabricator/` and looking at the first line of -output.) +For help, see @{article:Providing Version Information}. Supported Issues @@ -86,6 +84,8 @@ support. **We can NOT help you with issues we can not reproduce.** It is critical that you explain how to reproduce the issue when filing a report. +For help, see @{article:Providing Reproduction Steps}. + **We do NOT support prototype applications.** If you're running into an issue with a prototype application, you're on your own. For more information about prototype applications, see @{article:User Guide: Prototype Applications}. @@ -144,39 +144,11 @@ reproduce the issue. What did you do? If you do it again, does it still break? Does it depend on a specific browser? Can you reproduce the issue on `secure.phabricator.com`? -Feel free to try to reproduce issues on the upstream install (which is kept near -HEAD), within reason -- it's okay to make a few test objects if you're having -trouble narrowing something down or want to check if updating might fix an -issue. - It is nearly impossible for us to resolve many issues if we can not reproduce them. We will not accept reports which do not contain the information required to reproduce problems. - -Unreproducible Problems -======================= - -Before we can fix a bug, we need to reproduce it. If we can't reproduce a -problem, we can't tell if we've fixed it and often won't be able to figure out -why it is occurring. - -Most problems reproduce easily, but some are more difficult to reproduce. We -will generally make a reasonable effort to reproduce problems, but sometimes -we will be unable to reproduce an issue. - -Many of these unreproducible issues turn out to be bizarre environmental -problems that are unique to one user's install, and figuring out what is wrong -takes a very long time with a lot of back and forth as we ask questions to -narrow down the cause of the problem. When we eventually figure it out and fix -it, few others benefit (in some cases, no one else). This sort of fishing -expedition is not a good use of anyone's time, and it's very hard for us to -prioritize solving these problems because they represent a huge effort for very -little benefit. - -We will make a reasonable effort to reproduce problems, but can not help with -issues which we can't reproduce. You can make sure we're able to help resolve -your issue by generating clear reproduction steps. +For help, see @{article:Providing Reproduction Steps}. Create a Task in Maniphest diff --git a/src/docs/contributor/reproduction_steps.diviner b/src/docs/contributor/reproduction_steps.diviner new file mode 100644 index 0000000000..d5d58913b7 --- /dev/null +++ b/src/docs/contributor/reproduction_steps.diviner @@ -0,0 +1,252 @@ +@title Providing Reproduction Steps +@group detail + +Describes how to provide reproduction steps. + +Overview +======== + +When you submit a bug report about Phabricator, you **MUST** include +reproduction steps. We can not help you with bugs we can not reproduce, and +will not accept reports which omit reproduction steps or have incomplete or +insufficient instructions. + +This document explains what we're looking for in good reproduction steps. +Briefly: + + - Reproduction steps must allow us to reproduce the problem locally on a + clean, up-to-date install of Phabricator. + - Reproduction should be as simple as possible. + +Good reproduction steps can take time to write out clearly, simplify, and +verify. As a reporter, we expect you to shoulder as much of this burden as you +can, to make make it easy for us to reproduce and resolve bugs. + +We do not have the resources to pursue reports with limited, inaccurate, or +incomplete reproduction steps, and will not accept them. These reports require +large amounts of our time and are often fruitless. + + +Example Reproduction Steps +========================== + +Here's an example of what good reproduction steps might look like: + +--- + +Reproduction steps: + + - Click "Create Event" in Calendar. + - Fill in the required fields with any text (name, description, etc). + - Set an invalid time for one of the dates, like the meaningless string + "Tea Time". This is not valid, so we're expecting an error when we + submit the form. + - Click "Create" to save the event. + +Expected result: + + - Form reloads with an error message about the specific mistake. + - All field values are retained. + +Actual result: + + - Form reloads with an error message about the specific mistake. + - Most values are discarded, so I have to re-type the name, description, etc. + +--- + +These steps are **complete** and **self-contained**: anyone can reproduce the +issue by following these steps. These steps are **clear** and **easy to +follow**. These steps are also simple and minimal: they don't include any +extra unnecessary steps. + +Finally, these steps explain what the reporter expected to happen, what they +observed, and how those behaviors differ. This isn't as important when the +observation is an obvious error like an exception, but can be important if a +behavior is merely odd or ambiguous. + + +Reliable Reproduction +===================== + +When you file a bug report, the first thing we do to fix it is to try to +reproduce the problem locally on an up-to-date install of Phabricator. We will +do this by following the steps you provide. If we can recreate the issue +locally, we can almost always resolve the problem (often very promptly). + +However, many reports do not have enough information, are missing important +steps, or rely on data (like commits, users, other projects, permission +settings, feed stories, etc) that we don't have access to. We either can't +follow these steps, or can't reproduce issues by following them. + +Reproduction steps must be complete and self-contained, and must allow +**anyone** to reproduce the issue on a new, empty install of Phabricator. If +the bug you're seeing depends on data or configuration which would not be +present on a new install, you need to include that information in your steps. + +For example, if you're seeing an issue which depends on a particular policy +setting or configuration setting, you need to include instructions for creating +the policy or adjusting the setting in your steps. + + +Getting Started +=============== + +To generate reproduction steps, first find a sequence of actions which +reproduce the issue you're seeing reliably. + +Next, write down everything you did as clearly as possible. Make sure each step +is self-contained: anyone should be able to follow your steps, without access +to private or proprietary data. + +Now, to verify that your steps provide a complete, self-contained way to +reproduce the issue, follow them yourself on a new, empty, up-to-date instance +of Phabricator. + +If you can't easily start an empty instance locally, you can launch an empty +instance on Phacility in about 60 seconds (see the "Resources" section for +details). + +If you can follow your steps and reproduce the issue on a clean instance, +we'll probably be able to follow them and reproduce the issue ourselves. + +If you can't follow your steps because they depend on information which is not +available on a clean instance (for example, a certain config setting), rewrite +them to include instrutions to create that information (for example, adjusting +the config to the problematic value). + +If you follow your instructions but the issue does not reproduce, the issue +might already be fixed. Make sure your install is up to date. + +If your install is up to date and the issue still doesn't reproduce on a clean +install, your reproduction steps are missing important information. You need to +figure out what key element differs between your install and the clean install. + +Once you have working reproduction steps, your steps may have details which +aren't actually necessary to reproduce the issue. You may be able to simplify +them by removing some steps or describing steps more narrowly. For help, see +"Simplifying Steps" below. + + +Resources +========= + +We provide some resources which can make it easier to start building steps, or +to simplify steps. + +**Phacility Test Instances**: You can launch a new, up-to-date instance of +Phabricator on Phacility in about a minute at . +These instances run `stable`. + +You can use these instances to make sure that issues haven't already been +fixed, that they reproduce on a clean install, or that your steps are really +complete, and that the root cause isn't custom code or local extensions. Using +a test instance will avoid disrupting other users on your install. + +**Test Repositories**: There are several test repositories on +`secure.phabricator.com` which you can push commits to if you need to build +an example to demonstrate a problem. + +For example, if you're seeing an issue with a certain filename but the commit +where the problem occurs is in a proprietary internal repository, push a commit +that affects a file with a similar name to a test repository, then reproduce +against the test data. This will allow you to generate steps which anyone can +follow. + + +Simplifying Steps +================= + +If you aren't sure how to simplify reproduction steps, this section has some +advice. + +In general, you'll simplify reproduction steps by first finding a scenario +where the issue reproduces reliably (a "bad" case) and a second, similar +situation where it does not reproduce (a "good" case). Once you have a "bad" +case and a "good" case, you'll change the scenarios step-by-step to be more +similar to each other, until you have two scenarios which differ only a very +small amount. This remaining difference usually points clearly at the root +cause of the issue. + +For example, suppose you notice that you get an error if you commit a file +named `A Banana.doc`, but not if you commit a file named `readme.md`. In +this case, `A Banana.doc` is your "bad" case and `readme.md` is your "good" +case. + +There are several differences between these file names, and any of them might +be causing the problem. To narrow this down, you can try making the scenarios +more similar. For example, do these files work? + + - `A_Banana.doc` - Problem with spaces? + - `A Banana.md` - File extension issue? + - `A Ban.doc` - Path length issue? + - `a banana.doc` - Issue with letter case? + +Some of these probably work, while others might not. These could lead you to a +smaller case which reproduces the problem, which might be something like this: + + - Files like `a b`, which contain spaces, do not work. + - Files like `a.doc`, which have the `.doc` extension, do not work. + - Files like `AAAAAAAAAA`, which have more than 9 letters, do not work. + - Files like `A`, which have uppercase letters, do not work. + +With a simpler reproduction scenario, you can simplify your steps to be more +tailored and mimimal. This will help us pointpoint the issue more quickly and +be more certain that we've understood and resolved it. + +It is more important that steps be complete than that they be simple, and it's +acceptable to submit complex instructions if you have difficulty simplifying +them, so long as they are complete, self-contained, and accurately reproduce +the issue. + + +Things to Avoid +=============== + +These are common mistakes when providing reproduction instructions: + +**Insufficient Information**: The most common issue we see is instructions +which do not have enough information: they are missing critical details which +are necessary in order to follow them on a clean install. + +**Inaccurate Steps**: The second most common issue we see is instructions +which do not actually reproduce a problem when followed on a clean, up-to-date +install. Verify your steps by testing them. + +**Private Information**: Some users provide reports which hinge on the +particulars of private commits in proprietary repositories we can not access. +This is not useful, because we can not examine the underlying commit to figure +out why it is causing an issue. + +Instead, reproduce the issue in a public repository. There are several test +repositories available which you can push commits to in order to construct a +reproduction case. + +**Screenshots**: Screenshots can be helpful to explain a set of steps or show +what you're seeing, but they usually aren't sufficient on their own because +they don't contain all the information we need to reproduce them. + +For example, a screenshot may show a particular policy or object, but not have +enough information for us rebuild a similar object locally. + + +Alternatives +============ + +If you have an issue which you can't build reproduction steps for, or which +only reproduces in your environment, or which you don't want to narrow down +to a minimal reproduction case, we can't accept it as a bug report. These +issues are tremendously time consuming for us to pursue and rarely benefit +more than one install. + +If the issue is important but falls outside the scope of permissible bug +reports, we're happy to provide more tailored support at consulting rates. See +[[ https://secure.phabricator.com/w/consulting/ | Consulting ]] for details. + + +Next Steps +========== + +Continue by: + + - returning to @{article:Contributing Bug Reports}. diff --git a/src/docs/contributor/using_oauthserver.diviner b/src/docs/contributor/using_oauthserver.diviner index b09f595a5a..b40496d7cf 100644 --- a/src/docs/contributor/using_oauthserver.diviner +++ b/src/docs/contributor/using_oauthserver.diviner @@ -110,11 +110,7 @@ the entire `Authorization Code Grant` flow. NOTE: See "Scopes" section below for more information on what data is currently exposed through the OAuth Server. -= Scopes = +Scopes +====== -There are only two scopes supported at this time. - -- **offline_access** - allows an access token to work indefinitely without - expiring. -- **whoami** - allows the client to access the results of Conduit.whoami on - behalf of the resource owner. +//This section has not been written yet.// diff --git a/src/docs/contributor/version.diviner b/src/docs/contributor/version.diviner new file mode 100644 index 0000000000..81b1c845ef --- /dev/null +++ b/src/docs/contributor/version.diviner @@ -0,0 +1,57 @@ +@title Providing Version Information +@group detail + +How to provide version information with reports made to the upstream. + +Overview +======== + +When you submit a bug report, we require that you include version information. + +Despite our insistence that users update before reporting issues, many reports +we receive describe issues which have already been resolved. Including version +information in your report allows us to quickly determine that you are out of +date and that updating will fix your issue. + +That said, your report must also include reproduction steps, and you should be +unable to generate valid reproduction steps for an issue which has already been +resolved because valid reproduction steps must also reproduce against a clean, +up-to-date install. See @{article:Providing Reproduction Steps} for details. + + +Phabricator Version +=================== + +To get Phabricator version information: + + - Go to the {nav Config} application. You can type "Config" into the global + search box, or navigate to `https://your.install.com/config/`. You must + be an administrator to access this application. + - Click {nav Versions} in the left menu. + - Copy and paste all of the information on the page into your report. + + +Arcanist Version +================ + +To get Arcanist version information: + + - Run `arc version`. + - Copy and paste all of the output into your report. + + +Other Versions +============== + +In general, we use `git` commit hashes as version identifiers, so you can +identify the version of something by running `git show` and copy/pasting the +hash from the output. This may be useful if you're encountering an issue which +prevents you from reaching the version reporting screen. + + +Next Steps +========== + +Continue by: + + - returning to @{article:Contributing Bug Reports}. diff --git a/src/docs/tech/celerity.diviner b/src/docs/tech/celerity.diviner index e1b744645b..8501b1b536 100644 --- a/src/docs/tech/celerity.diviner +++ b/src/docs/tech/celerity.diviner @@ -51,7 +51,7 @@ generate fewer resource requests, improving performance). It then generates These references point at `/res/` URIs, which are handled by @{class:CelerityResourceController}. It responds to these requests and delivers the relevant resources and packages, managing cache lifetimes and handling any -neessary preprocessing. It uses @{class:CelerityResourceMap} to locate resources +necessary preprocessing. It uses @{class:CelerityResourceMap} to locate resources and read packaging rules. The dependency and packaging maps are generated by `bin/celerity map`, diff --git a/src/docs/user/configuration/configuring_outbound_email.diviner b/src/docs/user/configuration/configuring_outbound_email.diviner index 55c51837c7..2a95f49bc3 100644 --- a/src/docs/user/configuration/configuring_outbound_email.diviner +++ b/src/docs/user/configuration/configuring_outbound_email.diviner @@ -135,6 +135,7 @@ To configure Phabricator to use Amazon SES, set these configuration keys: "PhabricatorMailImplementationAmazonSESAdapter". - **amazon-ses.access-key**: set to your Amazon SES access key. - **amazon-ses.secret-key**: set to your Amazon SES secret key. + - **amazon-ses.endpoint**: Set to your Amazon SES endpoint. NOTE: Amazon SES **requires you to verify your "From" address**. Configure which "From" address to use by setting "`metamta.default-address`" in your config, diff --git a/src/docs/user/userguide/almanac.diviner b/src/docs/user/userguide/almanac.diviner index b7c30cf77a..a6b04e1fd1 100644 --- a/src/docs/user/userguide/almanac.diviner +++ b/src/docs/user/userguide/almanac.diviner @@ -96,7 +96,7 @@ them from the service. Concepts ======== -The major concepts in Almanac are **devices*, **interfaces**, **services**, +The major concepts in Almanac are **devices**, **interfaces**, **services**, **bindings**, **networks**, and **namespaces**. **Devices**: Almanac devices represent physical or virtual devices. diff --git a/src/docs/user/userguide/remarkup.diviner b/src/docs/user/userguide/remarkup.diviner index c56600ebdf..379c09e5c3 100644 --- a/src/docs/user/userguide/remarkup.diviner +++ b/src/docs/user/userguide/remarkup.diviner @@ -532,10 +532,12 @@ escape HTML and preserve line breaks). Remarkup supports simple table syntax. For example, this: - | Fruit | Color | Price | Peel? - | ----- | ----- | ----- | ----- - | Apple | red | `$0.93` | no - | Banana | yellow | `$0.19` | **YES** +``` +| Fruit | Color | Price | Peel? +| ----- | ----- | ----- | ----- +| Apple | red | `$0.93` | no +| Banana | yellow | `$0.19` | **YES** +``` ...produces this: @@ -546,26 +548,28 @@ Remarkup supports simple table syntax. For example, this: Remarkup also supports a simplified HTML table syntax. For example, this: - - - - - - - - - - - - - - - - - - - -
FruitColorPricePeel?
Applered`$0.93`no
Bananayellow`$0.19`**YES**
+``` + + + + + + + + + + + + + + + + + + + +
FruitColorPricePeel?
Applered`$0.93`no
Bananayellow`$0.19`**YES**
+``` ...produces this: diff --git a/src/infrastructure/contentsource/PhabricatorConduitContentSource.php b/src/infrastructure/contentsource/PhabricatorConduitContentSource.php new file mode 100644 index 0000000000..64820a236c --- /dev/null +++ b/src/infrastructure/contentsource/PhabricatorConduitContentSource.php @@ -0,0 +1,16 @@ +getPhobjectClassConstant('SOURCECONST', 32); + } + + final public static function getAllContentSources() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getSourceTypeConstant') + ->execute(); + } + + /** + * Construct a new content source object. + * + * @param const The source type constant to build a source for. + * @param array Source parameters. + * @param bool True to suppress errors and force construction of a source + * even if the source type is not valid. + * @return PhabricatorContentSource New source object. + */ + final public static function newForSource( + $source, + array $params = array(), + $force = false) { + + $map = self::getAllContentSources(); + if (isset($map[$source])) { + $obj = clone $map[$source]; + } else { + if ($force) { + $obj = new PhabricatorUnknownContentSource(); + } else { + throw new Exception( + pht( + 'Content source type "%s" is not known to Phabricator!', + $source)); + } + } + + $obj->source = $source; + $obj->params = $params; + + return $obj; + } + + public static function newFromSerialized($serialized) { + $dict = json_decode($serialized, true); + if (!is_array($dict)) { + $dict = array(); + } + + $source = idx($dict, 'source'); + $params = idx($dict, 'params'); + if (!is_array($params)) { + $params = array(); + } + + return self::newForSource($source, $params, true); + } + + public static function newFromRequest(AphrontRequest $request) { + return self::newForSource( + PhabricatorWebContentSource::SOURCECONST); + } + + final public function serialize() { + return phutil_json_encode( + array( + 'source' => $this->getSource(), + 'params' => $this->params, + )); + } + + final public function getSource() { + return $this->source; + } + + final public function getContentSourceParameter($key, $default = null) { + return idx($this->params, $key, $default); + } + +} diff --git a/src/infrastructure/contentsource/PhabricatorContentSourceModule.php b/src/infrastructure/contentsource/PhabricatorContentSourceModule.php new file mode 100644 index 0000000000..c9bd3347f3 --- /dev/null +++ b/src/infrastructure/contentsource/PhabricatorContentSourceModule.php @@ -0,0 +1,51 @@ +getViewer(); + + $sources = PhabricatorContentSource::getAllContentSources(); + ksort($sources); + + $rows = array(); + foreach ($sources as $source) { + $rows[] = array( + $source->getSourceTypeConstant(), + get_class($source), + $source->getSourceName(), + $source->getSourceDescription(), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Key'), + pht('Class'), + pht('Source'), + pht('Description'), + )) + ->setColumnClasses( + array( + null, + null, + 'pri', + 'wide', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Content Sources')) + ->setTable($table); + } + +} diff --git a/src/applications/metamta/contentsource/PhabricatorContentSourceView.php b/src/infrastructure/contentsource/PhabricatorContentSourceView.php similarity index 71% rename from src/applications/metamta/contentsource/PhabricatorContentSourceView.php rename to src/infrastructure/contentsource/PhabricatorContentSourceView.php index f159b2059d..14bacb08d9 100644 --- a/src/applications/metamta/contentsource/PhabricatorContentSourceView.php +++ b/src/infrastructure/contentsource/PhabricatorContentSourceView.php @@ -9,15 +9,15 @@ final class PhabricatorContentSourceView extends AphrontView { return $this; } + public function getSourceName() { + return $this->contentSource->getSourceName(); + } + public function render() { require_celerity_resource('phabricator-content-source-view-css'); - $map = PhabricatorContentSource::getSourceNameMap(); - - $source = $this->contentSource->getSource(); - $type = idx($map, $source, null); - - if (!$type) { + $name = $this->getSourceName(); + if ($name === null) { return null; } @@ -26,7 +26,7 @@ final class PhabricatorContentSourceView extends AphrontView { array( 'class' => 'phabricator-content-source-view', ), - pht('Via %s', $type)); + pht('Via %s', $name)); } } diff --git a/src/infrastructure/contentsource/PhabricatorFaxContentSource.php b/src/infrastructure/contentsource/PhabricatorFaxContentSource.php new file mode 100644 index 0000000000..36da91365b --- /dev/null +++ b/src/infrastructure/contentsource/PhabricatorFaxContentSource.php @@ -0,0 +1,16 @@ +getSource(); + if (strlen($source)) { + return pht('Unknown ("%s")', $source); + } else { + return pht('Unknown'); + } + } + + public function getSourceDescription() { + return pht('Content with no known source.'); + } + +} diff --git a/src/infrastructure/contentsource/PhabricatorWebContentSource.php b/src/infrastructure/contentsource/PhabricatorWebContentSource.php new file mode 100644 index 0000000000..d788148e52 --- /dev/null +++ b/src/infrastructure/contentsource/PhabricatorWebContentSource.php @@ -0,0 +1,16 @@ +unlock(); $sleep_duration = $this->getSleepDuration(); + $sleep_duration = $this->runNuanceImportCursors($sleep_duration); $sleep_duration = $this->runGarbageCollection($sleep_duration); $this->sleep($sleep_duration); } while (!$this->shouldExit()); @@ -379,4 +384,75 @@ final class PhabricatorTriggerDaemon return false; } + +/* -( Nuance Importers )--------------------------------------------------- */ + + + private function runNuanceImportCursors($duration) { + $run_until = (PhabricatorTime::getNow() + $duration); + + do { + $more_data = $this->updateNuanceImportCursors(); + if (!$more_data) { + break; + } + } while (PhabricatorTime::getNow() <= $run_until); + + $remaining = max(0, $run_until - PhabricatorTime::getNow()); + + return $remaining; + } + + + private function updateNuanceImportCursors() { + $nuance_app = 'PhabricatorNuanceApplication'; + if (!PhabricatorApplication::isClassInstalled($nuance_app)) { + return false; + } + + // If we haven't loaded sources yet, load them first. + if (!$this->nuanceSources && !$this->nuanceCursors) { + $this->anyNuanceData = false; + + $sources = id(new NuanceSourceQuery()) + ->setViewer($this->getViewer()) + ->withIsDisabled(false) + ->withHasImportCursors(true) + ->execute(); + if (!$sources) { + return false; + } + + $this->nuanceSources = array_reverse($sources); + } + + // If we don't have any cursors, move to the next source and generate its + // cursors. + if (!$this->nuanceCursors) { + $source = array_pop($this->nuanceSources); + + $definition = $source->getDefinition() + ->setViewer($this->getViewer()) + ->setSource($source); + + $cursors = $definition->getImportCursors(); + $this->nuanceCursors = array_reverse($cursors); + } + + // Update the next cursor. + $cursor = array_pop($this->nuanceCursors); + if ($cursor) { + $more_data = $cursor->importFromSource(); + if ($more_data) { + $this->anyNuanceData = true; + } + } + + if (!$this->nuanceSources && !$this->nuanceCursors) { + return $this->anyNuanceData; + } + + return true; + } + } diff --git a/src/infrastructure/daemon/workers/PhabricatorWorker.php b/src/infrastructure/daemon/workers/PhabricatorWorker.php index ea59462740..b974f9989d 100644 --- a/src/infrastructure/daemon/workers/PhabricatorWorker.php +++ b/src/infrastructure/daemon/workers/PhabricatorWorker.php @@ -305,4 +305,9 @@ abstract class PhabricatorWorker extends Phobject { $epoch_ago); } + protected function newContentSource() { + return PhabricatorContentSource::newForSource( + PhabricatorDaemonContentSource::SOURCECONST); + } + } diff --git a/src/infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php b/src/infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php index 1f6644ae71..9117060da6 100644 --- a/src/infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php +++ b/src/infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php @@ -121,9 +121,7 @@ abstract class PhabricatorWorkerBulkJobWorker ->setTransactionType($type_status) ->setNewValue($status); - $daemon_source = PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_DAEMON, - array()); + $daemon_source = $this->newContentSource(); $app_phid = id(new PhabricatorDaemonsApplication())->getPHID(); diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php index 0b70268cb4..feb25a0128 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php @@ -102,7 +102,7 @@ final class PhabricatorWorkerBulkJob public function newContentSource() { return PhabricatorContentSource::newForSource( - PhabricatorContentSource::SOURCE_BULK, + PhabricatorBulkContentSource::SOURCECONST, array( 'jobID' => $this->getID(), )); @@ -218,10 +218,6 @@ final class PhabricatorWorkerBulkJob return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index aa37d29c72..1c92b167b0 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -305,13 +305,18 @@ abstract class PhabricatorInlineCommentController pht('Failed to load comment "%s".', $reply_phid)); } - // NOTE: It's fine to reply to a comment from a different changeset, so - // the reply comment may not appear on the same changeset that the new - // comment appears on. This is expected in the case of ghost comments. - // We currently put the new comment on the visible changeset, not the - // original comment's changeset. + // When replying, force the new comment into the same location as the + // old comment. If we don't do this, replying to a ghost comment from + // diff A while viewing diff B can end up placing the two comments in + // different places while viewing diff C, because the porting algorithm + // makes a different decision. Forcing the comments to bind to the same + // place makes sure they stick together no matter which diff is being + // viewed. See T10562 for discussion. + $this->changesetID = $reply_comment->getChangesetID(); $this->isNewFile = $reply_comment->getIsNewFile(); + $this->lineNumber = $reply_comment->getLineNumber(); + $this->lineLength = $reply_comment->getLineLength(); } } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index b58b1764a8..808fb5d067 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -176,7 +176,7 @@ final class PHUIDiffInlineCommentDetailView if ($inline->getHasReplies()) { $classes[] = 'inline-comment-has-reply'; } - // I think this is unused + if ($inline->getReplyToCommentPHID()) { $classes[] = 'inline-comment-is-reply'; } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php index 3a9bc616d9..a7c10b5b48 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php @@ -78,12 +78,11 @@ final class PHUIDiffInlineCommentEditView if (!$this->uri) { throw new PhutilInvalidStateException('setSubmitURI'); } - if (!$this->user) { - throw new PhutilInvalidStateException('setUser'); - } + + $viewer = $this->getViewer(); $content = phabricator_form( - $this->user, + $viewer, array( 'action' => $this->uri, 'method' => 'POST', diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index f032e34017..3330620148 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -4,6 +4,9 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { private $items = array(); private $authorityPackages; + private $header; + private $infoView; + private $background; public function addItem(PHUIDiffTableOfContentsItemView $item) { $this->items[] = $item; @@ -20,6 +23,21 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { return $this->authorityPackages; } + public function setBackground($background) { + $this->background = $background; + return $this; + } + + public function setHeader(PHUIHeaderView $header) { + $this->header = $header; + return $this; + } + + public function setInfoView(PHUIInfoView $infoview) { + $this->infoView = $infoview; + return $this; + } + public function render() { $this->requireResource('differential-core-view-css'); $this->requireResource('differential-table-of-contents-css'); @@ -142,11 +160,24 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { ->setAnchorName('toc') ->setNavigationMarker(true); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Table of Contents')) + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Table of Contents')); + + if ($this->header) { + $header = $this->header; + } + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground($this->background) ->setTable($table) ->appendChild($anchor) ->appendChild($buttons); + + if ($this->infoView) { + $box->setInfoView($this->infoView); + } + return $box; } } diff --git a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php index 4e256dd5ca..0473f2426d 100644 --- a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php +++ b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php @@ -17,7 +17,6 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants { static $class_map = array( PhabricatorPHIDConstants::PHID_TYPE_TOBJ => 'HarbormasterObject', - PhabricatorPHIDConstants::PHID_TYPE_XOBJ => 'DoorkeeperExternalObject', ); $class = idx($class_map, $phid_type); diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index 694c793e0a..421d7eca25 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -575,8 +575,8 @@ final class PhabricatorEnv extends Phobject { * @return void * @task uri */ - public static function requireValidRemoteURIForLink($uri) { - $uri = new PhutilURI($uri); + public static function requireValidRemoteURIForLink($raw_uri) { + $uri = new PhutilURI($raw_uri); $proto = $uri->getProtocol(); if (!strlen($proto)) { @@ -584,7 +584,7 @@ final class PhabricatorEnv extends Phobject { pht( 'URI "%s" is not a valid linkable resource. A valid linkable '. 'resource URI must specify a protocol.', - $uri)); + $raw_uri)); } $protocols = self::getEnvConfig('uri.allowed-protocols'); @@ -593,7 +593,7 @@ final class PhabricatorEnv extends Phobject { pht( 'URI "%s" is not a valid linkable resource. A valid linkable '. 'resource URI must use one of these protocols: %s.', - $uri, + $raw_uri, implode(', ', array_keys($protocols)))); } @@ -603,7 +603,7 @@ final class PhabricatorEnv extends Phobject { pht( 'URI "%s" is not a valid linkable resource. A valid linkable '. 'resource URI must specify a domain.', - $uri)); + $raw_uri)); } } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 93027ce5d6..c49069fcae 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1528,6 +1528,38 @@ final class PhabricatorUSEnglishTranslation '%s removed watchers for %3$s: %4$s.', ), ), + + '%s awarded this badge to %s recipient(s): %s.' => array( + array( + '%s awarded this badge to recipient: %3$s.', + '%s awarded this badge to recipients: %3$s.', + ), + ), + + '%s revoked this badge from %s recipient(s): %s.' => array( + array( + '%s revoked this badge from recipient: %3$s.', + '%s revoked this badge from recipients: %3$s.', + ), + ), + + '%s awarded %s to %s recipient(s): %s.' => array( + array( + array( + '%s awarded %s to recipient: %4$s.', + '%s awarded %s to recipients: %4$s.', + ), + ), + ), + + '%s revoked %s from %s recipient(s): %s.' => array( + array( + array( + '%s revoked %s from recipient: %4$s.', + '%s revoked %s from recipients: %4$s.', + ), + ), + ), ); } diff --git a/src/infrastructure/management/PhabricatorManagementWorkflow.php b/src/infrastructure/management/PhabricatorManagementWorkflow.php index a2305b8ff0..0cd8c0230e 100644 --- a/src/infrastructure/management/PhabricatorManagementWorkflow.php +++ b/src/infrastructure/management/PhabricatorManagementWorkflow.php @@ -26,4 +26,9 @@ abstract class PhabricatorManagementWorkflow extends PhutilArgumentWorkflow { return $epoch; } + protected function newContentSource() { + return PhabricatorContentSource::newForSource( + PhabricatorConsoleContentSource::SOURCECONST); + } + } diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index c3d9dab02a..38eb0c500e 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -494,6 +494,7 @@ final class PhabricatorMarkupEngine extends Phobject { $rules[] = new PhabricatorIconRemarkupRule(); $rules[] = new PhabricatorEmojiRemarkupRule(); + $rules[] = new PhabricatorHandleRemarkupRule(); $applications = PhabricatorApplication::getAllInstalledApplications(); foreach ($applications as $application) { @@ -516,7 +517,7 @@ final class PhabricatorMarkupEngine extends Phobject { $rules[] = new PhutilRemarkupHighlightRule(); foreach (self::loadCustomInlineRules() as $rule) { - $rules[] = $rule; + $rules[] = clone $rule; } $blocks = array(); diff --git a/src/infrastructure/markup/view/PHUIRemarkupView.php b/src/infrastructure/markup/view/PHUIRemarkupView.php index 8a8d9ddf7f..0a07e64a87 100644 --- a/src/infrastructure/markup/view/PHUIRemarkupView.php +++ b/src/infrastructure/markup/view/PHUIRemarkupView.php @@ -13,6 +13,7 @@ final class PHUIRemarkupView extends AphrontView { private $corpus; private $markupType; + private $contextObject; const DOCUMENT = 'document'; @@ -26,16 +27,27 @@ final class PHUIRemarkupView extends AphrontView { return $this; } + public function setContextObject($context_object) { + $this->contextObject = $context_object; + return $this; + } + + public function getContextObject() { + return $this->contextObject; + } + public function render() { $viewer = $this->getUser(); $corpus = $this->corpus; + $context = $this->getContextObject(); $content = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff()) ->setPreserveLinebreaks(true) ->setContent($corpus), 'default', - $viewer); + $viewer, + $context); if ($this->markupType == self::DOCUMENT) { return phutil_tag( diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php index 9512acbc22..42d8001ee2 100644 --- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php @@ -234,6 +234,9 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { if ($page) { $maybe_visible = $this->willFilterPage($page); + if ($maybe_visible) { + $maybe_visible = $this->applyWillFilterPageExtensions($maybe_visible); + } } else { $maybe_visible = array(); } @@ -672,4 +675,52 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { return PhabricatorApplication::isClassInstalledForViewer($class, $viewer); } + private function applyWillFilterPageExtensions(array $page) { + $bridges = array(); + foreach ($page as $key => $object) { + if ($object instanceof DoorkeeperBridgedObjectInterface) { + $bridges[$key] = $object; + } + } + + if ($bridges) { + $external_phids = array(); + foreach ($bridges as $bridge) { + $external_phid = $bridge->getBridgedObjectPHID(); + if ($external_phid) { + $external_phids[$key] = $external_phid; + } + } + + if ($external_phids) { + $external_objects = id(new DoorkeeperExternalObjectQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($external_phids) + ->execute(); + $external_objects = mpull($external_objects, null, 'getPHID'); + } else { + $external_objects = array(); + } + + foreach ($bridges as $key => $bridge) { + $external_phid = idx($external_phids, $key); + if (!$external_phid) { + $bridge->attachBridgedObject(null); + continue; + } + + $external_object = idx($external_objects, $external_phid); + if (!$external_object) { + $this->didRejectResult($bridge); + unset($page[$key]); + continue; + } + + $bridge->attachBridgedObject($external_object); + } + } + + return $page; + } + } diff --git a/src/infrastructure/testing/PhabricatorTestCase.php b/src/infrastructure/testing/PhabricatorTestCase.php index 068c2ed557..c9a6ba986c 100644 --- a/src/infrastructure/testing/PhabricatorTestCase.php +++ b/src/infrastructure/testing/PhabricatorTestCase.php @@ -229,5 +229,9 @@ abstract class PhabricatorTestCase extends PhutilTestCase { } } + protected function newContentSource() { + return PhabricatorContentSource::newForSource( + PhabricatorUnitTestContentSource::SOURCECONST); + } } diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php index f8bc3fa060..26dee0c398 100644 --- a/src/view/AphrontDialogView.php +++ b/src/view/AphrontDialogView.php @@ -236,11 +236,11 @@ final class AphrontDialogView $this->cancelText); } - if (!$this->user) { + if (!$this->hasViewer()) { throw new Exception( pht( 'You must call %s when rendering an %s.', - 'setUser()', + 'setViewer()', __CLASS__)); } @@ -308,7 +308,7 @@ final class AphrontDialogView if (!$this->renderAsForm) { $buttons = array( phabricator_form( - $this->user, + $this->getViewer(), $form_attributes, array_merge($hidden_inputs, $buttons)), ); @@ -376,7 +376,7 @@ final class AphrontDialogView if ($this->renderAsForm) { return phabricator_form( - $this->user, + $this->getViewer(), $form_attributes + $attributes, array($hidden_inputs, $content)); } else { diff --git a/src/view/AphrontView.php b/src/view/AphrontView.php index 91565ca6bd..c014d7d50c 100644 --- a/src/view/AphrontView.php +++ b/src/view/AphrontView.php @@ -6,7 +6,7 @@ abstract class AphrontView extends Phobject implements PhutilSafeHTMLProducerInterface { - protected $user; + private $viewer; protected $children = array(); @@ -14,19 +14,65 @@ abstract class AphrontView extends Phobject /** - * @task config + * Set the user viewing this element. + * + * @param PhabricatorUser Viewing user. + * @return this */ - public function setUser(PhabricatorUser $user) { - $this->user = $user; + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; return $this; } /** + * Get the user viewing this element. + * + * Throws an exception if no viewer has been set. + * + * @return PhabricatorUser Viewing user. + */ + public function getViewer() { + if (!$this->viewer) { + throw new PhutilInvalidStateException('setViewer'); + } + + return $this->viewer; + } + + + /** + * Test if a viewer has been set on this elmeent. + * + * @return bool True if a viewer is available. + */ + public function hasViewer() { + return (bool)$this->viewer; + } + + + /** + * Deprecated, use @{method:setViewer}. + * * @task config + * @deprecated + */ + public function setUser(PhabricatorUser $user) { + return $this->setViewer($user); + } + + + /** + * Deprecated, use @{method:getViewer}. + * + * @task config + * @deprecated */ protected function getUser() { - return $this->user; + if (!$this->hasViewer()) { + return null; + } + return $this->getViewer(); } diff --git a/src/view/extension/PHUICurtainExtension.php b/src/view/extension/PHUICurtainExtension.php new file mode 100644 index 0000000000..7f7693cbac --- /dev/null +++ b/src/view/extension/PHUICurtainExtension.php @@ -0,0 +1,124 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + abstract public function shouldEnableForObject($object); + abstract public function getExtensionApplication(); + + public function buildCurtainPanels($object) { + $panel = $this->buildCurtainPanel($object); + + if ($panel !== null) { + return array($panel); + } + + return array(); + } + + public function buildCurtainPanel($object) { + throw new PhutilMethodNotImplementedException(); + } + + final public function getExtensionKey() { + return $this->getPhobjectClassConstant('EXTENSIONKEY'); + } + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + + protected function newPanel() { + return new PHUICurtainPanelView(); + } + + final public static function buildExtensionPanels( + PhabricatorUser $viewer, + $object) { + + $extensions = self::getAllExtensions(); + foreach ($extensions as $extension) { + $extension->setViewer($viewer); + } + + foreach ($extensions as $key => $extension) { + $application = $extension->getExtensionApplication(); + if (!($application instanceof PhabricatorApplication)) { + throw new Exception( + pht( + 'Curtain extension ("%s", of class "%s") did not return an '. + 'application from method "%s". This method must return an '. + 'object of class "%s".', + $key, + get_class($extension), + 'getExtensionApplication()', + 'PhabricatorApplication')); + } + + $has_application = PhabricatorApplication::isClassInstalledForViewer( + get_class($application), + $viewer); + + if (!$has_application) { + unset($extensions[$key]); + } + } + + foreach ($extensions as $key => $extension) { + if (!$extension->shouldEnableForObject($object)) { + unset($extensions[$key]); + } + } + + $result = array(); + + foreach ($extensions as $key => $extension) { + $panels = $extension->buildCurtainPanels($object); + if (!is_array($panels)) { + throw new Exception( + pht( + 'Curtain extension ("%s", of class "%s") did not return a list of '. + 'curtain panels from method "%s". This method must return an '. + 'array, and each value in the array must be a "%s" object.', + $key, + get_class($extension), + 'buildCurtainPanels()', + 'PHUICurtainPanelView')); + } + + foreach ($panels as $panel_key => $panel) { + if (!($panel instanceof PHUICurtainPanelView)) { + throw new Exception( + pht( + 'Curtain extension ("%s", of class "%s") returned a list of '. + 'curtain panels from "%s" that contains an invalid value: '. + 'a value (with key "%s") is not an object of class "%s". '. + 'Each item in the returned array must be a panel.', + $key, + get_class($extension), + 'buildCurtainPanels()', + $panel_key, + 'PHUICurtainPanelView')); + } + + $result[] = $panel; + } + } + + return $result; + } + +} diff --git a/src/view/form/AphrontFormView.php b/src/view/form/AphrontFormView.php index 810208ffae..ecd4c1206e 100644 --- a/src/view/form/AphrontFormView.php +++ b/src/view/form/AphrontFormView.php @@ -85,13 +85,13 @@ final class AphrontFormView extends AphrontView { public function appendRemarkupInstructions($remarkup) { return $this->appendInstructions( - new PHUIRemarkupView($this->getUser(), $remarkup)); + new PHUIRemarkupView($this->getViewer(), $remarkup)); } public function buildLayoutView() { foreach ($this->controls as $control) { - $control->setUser($this->getUser()); + $control->setViewer($this->getViewer()); $control->willRender(); } @@ -123,7 +123,7 @@ final class AphrontFormView extends AphrontView { $layout = $this->buildLayoutView(); - if (!$this->user) { + if (!$this->hasViewer()) { throw new Exception( pht( 'You must pass the user to %s.', @@ -136,7 +136,7 @@ final class AphrontFormView extends AphrontView { } return phabricator_form( - $this->user, + $this->getViewer(), array( 'class' => $this->shaded ? 'phui-form-shaded' : null, 'action' => $this->action, diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php index 0da471574c..92481bc614 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/form/PHUIInfoView.php @@ -14,6 +14,7 @@ final class PHUIInfoView extends AphrontView { private $id; private $buttons = array(); private $isHidden; + private $flush; public function setTitle($title) { $this->title = $title; @@ -40,6 +41,11 @@ final class PHUIInfoView extends AphrontView { return $this; } + public function setFlush($flush) { + $this->flush = $flush; + return $this; + } + public function addButton(PHUIButtonView $button) { $this->buttons[] = $button; return $this; @@ -87,6 +93,9 @@ final class PHUIInfoView extends AphrontView { $classes[] = 'phui-info-view'; $classes[] = 'phui-info-severity-'.$this->severity; $classes[] = 'grouped'; + if ($this->flush) { + $classes[] = 'phui-info-view-flush'; + } $classes = implode(' ', $classes); $children = $this->renderChildren(); diff --git a/src/view/form/PHUIPagedFormView.php b/src/view/form/PHUIPagedFormView.php index b5f6ad7066..cfd18add58 100644 --- a/src/view/form/PHUIPagedFormView.php +++ b/src/view/form/PHUIPagedFormView.php @@ -263,6 +263,7 @@ final class PHUIPagedFormView extends AphrontView { $form->appendChild($submit); $box = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setFormErrors($errors) ->setForm($form); diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index 75398d688a..25dec8eef0 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -137,12 +137,12 @@ final class AphrontFormDateControl extends AphrontFormControl { } private function getTimeFormat() { - return $this->getUser() + return $this->getViewer() ->getPreference(PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT); } private function getDateFormat() { - return $this->getUser() + return $this->getViewer() ->getPreference(PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT); } @@ -153,7 +153,7 @@ final class AphrontFormDateControl extends AphrontFormControl { private function formatTime($epoch, $fmt) { return phabricator_format_local_time( $epoch, - $this->user, + $this->getViewer(), $fmt); } @@ -259,7 +259,7 @@ final class AphrontFormDateControl extends AphrontFormControl { ), $time_sel); - $preferences = $this->user->loadPreferences(); + $preferences = $this->getViewer()->loadPreferences(); $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; $week_start = $preferences->getPreference($pref_week_start, 0); @@ -300,12 +300,9 @@ final class AphrontFormDateControl extends AphrontFormControl { return $this->zone; } - $user = $this->getUser(); - if (!$this->getUser()) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); - $user_zone = $user->getTimezoneIdentifier(); + $user_zone = $viewer->getTimezoneIdentifier(); $this->zone = new DateTimeZone($user_zone); return $this->zone; } diff --git a/src/view/form/control/AphrontFormTokenizerControl.php b/src/view/form/control/AphrontFormTokenizerControl.php index 3f24dd1348..3d65c4e525 100644 --- a/src/view/form/control/AphrontFormTokenizerControl.php +++ b/src/view/form/control/AphrontFormTokenizerControl.php @@ -90,8 +90,8 @@ final class AphrontFormTokenizerControl extends AphrontFormControl { } $username = null; - if ($this->user) { - $username = $this->user->getUsername(); + if ($this->hasViewer()) { + $username = $this->getViewer()->getUsername(); } $datasource_uri = $datasource->getDatasourceURI(); diff --git a/src/view/layout/AphrontSideNavFilterView.php b/src/view/layout/AphrontSideNavFilterView.php index 3a267510f0..d59bf5ccac 100644 --- a/src/view/layout/AphrontSideNavFilterView.php +++ b/src/view/layout/AphrontSideNavFilterView.php @@ -199,9 +199,6 @@ final class AphrontSideNavFilterView extends AphrontView { } private function renderFlexNav() { - - $user = $this->user; - require_celerity_resource('phabricator-nav-view-css'); $nav_classes = array(); diff --git a/src/view/layout/PHUICurtainPanelView.php b/src/view/layout/PHUICurtainPanelView.php new file mode 100644 index 0000000000..238e56a374 --- /dev/null +++ b/src/view/layout/PHUICurtainPanelView.php @@ -0,0 +1,63 @@ +headerText = $header_text; + return $this; + } + + public function getHeaderText() { + return $this->headerText; + } + + public function setOrder($order) { + $this->order = $order; + return $this; + } + + public function getOrder() { + return $this->order; + } + + public function getOrderVector() { + return id(new PhutilSortVector()) + ->addInt($this->getOrder()); + } + + protected function getTagAttributes() { + return array( + 'class' => 'phui-curtain-panel', + ); + } + + protected function getTagContent() { + $header = null; + + $header_text = $this->getHeaderText(); + if (strlen($header_text)) { + $header = phutil_tag( + 'div', + array( + 'class' => 'phui-curtain-panel-header', + ), + $header_text); + } + + $body = phutil_tag( + 'div', + array( + 'class' => 'phui-curtain-panel-body', + ), + $this->renderChildren()); + + return array( + $header, + $body, + ); + } + +} diff --git a/src/view/layout/PHUICurtainView.php b/src/view/layout/PHUICurtainView.php new file mode 100644 index 0000000000..af02ceb932 --- /dev/null +++ b/src/view/layout/PHUICurtainView.php @@ -0,0 +1,63 @@ +getActionList()->addAction($action); + return $this; + } + + public function addPanel(PHUICurtainPanelView $curtain_panel) { + $this->panels[] = $curtain_panel; + return $this; + } + + public function newPanel() { + $panel = new PHUICurtainPanelView(); + $this->addPanel($panel); + + // By default, application panels go at the bottom of the curtain, below + // extension panels. + $panel->setOrder(100000); + + return $panel; + } + + public function setActionList(PhabricatorActionListView $action_list) { + $this->actionList = $action_list; + return $this; + } + + public function getActionList() { + return $this->actionList; + } + + protected function canAppendChild() { + return false; + } + + protected function getTagContent() { + $action_list = $this->actionList; + + require_celerity_resource('phui-curtain-view-css'); + + $panels = $this->renderPanels(); + + return id(new PHUIObjectBoxView()) + ->appendChild($action_list) + ->appendChild($panels) + ->addClass('phui-two-column-properties'); + } + + private function renderPanels() { + $panels = $this->panels; + $panels = msortv($panels, 'getOrderVector'); + + return $panels; + } + + +} diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php index 6c343af4fe..4965f02793 100644 --- a/src/view/layout/PhabricatorActionListView.php +++ b/src/view/layout/PhabricatorActionListView.php @@ -22,9 +22,7 @@ final class PhabricatorActionListView extends AphrontView { } public function render() { - if (!$this->user) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); $event = new PhabricatorEvent( PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS, @@ -32,7 +30,7 @@ final class PhabricatorActionListView extends AphrontView { 'object' => $this->object, 'actions' => $this->actions, )); - $event->setUser($this->user); + $event->setUser($viewer); PhutilEventEngine::dispatchEvent($event); $actions = $event->getValue('actions'); @@ -41,7 +39,7 @@ final class PhabricatorActionListView extends AphrontView { } foreach ($actions as $action) { - $action->setUser($this->user); + $action->setViewer($viewer); } require_celerity_resource('phabricator-action-list-view-css'); diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index b89c00daf5..3efa32071d 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -125,11 +125,11 @@ final class PhabricatorActionView extends AphrontView { $sigils = $sigils ? implode(' ', $sigils) : null; if ($this->renderAsForm) { - if (!$this->user) { + if (!$this->hasViewer()) { throw new Exception( pht( 'Call %s when rendering an action as a form.', - 'setUser()')); + 'setViewer()')); } $item = javelin_tag( @@ -140,7 +140,7 @@ final class PhabricatorActionView extends AphrontView { array($icon, $this->name)); $item = phabricator_form( - $this->user, + $this->getViewer(), array( 'action' => $this->getHref(), 'method' => 'POST', diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index e85abb2140..f38bf3c18b 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -490,7 +490,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $nav->appendFooter($footer); $content = phutil_implode_html('', array($nav->render())); } else { - $contnet = array(); + $content = array(); $crumbs = $this->getCrumbs(); if ($crumbs) { diff --git a/src/view/page/menu/PhabricatorMainMenuSearchView.php b/src/view/page/menu/PhabricatorMainMenuSearchView.php index a5c5653cc3..d3c7319f7d 100644 --- a/src/view/page/menu/PhabricatorMainMenuSearchView.php +++ b/src/view/page/menu/PhabricatorMainMenuSearchView.php @@ -24,7 +24,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView { } public function render() { - $user = $this->user; + $viewer = $this->getViewer(); $target_id = celerity_generate_unique_node_id(); $search_id = $this->getID(); @@ -86,7 +86,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView { $selector = $this->buildModeSelector($selector_id, $application_id); $form = phabricator_form( - $user, + $viewer, array( 'action' => '/search/', 'method' => 'POST', @@ -109,7 +109,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView { } private function buildModeSelector($selector_id, $application_id) { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $items = array(); $items[] = array( diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 5ce25fb3da..e804573ff0 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -24,7 +24,7 @@ final class PhabricatorMainMenuView extends AphrontView { } public function render() { - $user = $this->user; + $viewer = $this->getViewer(); require_celerity_resource('phabricator-main-menu-view'); @@ -35,7 +35,7 @@ final class PhabricatorMainMenuView extends AphrontView { $app_button = ''; $aural = null; - if ($user->isLoggedIn() && $user->isUserActivated()) { + if ($viewer->isLoggedIn() && $viewer->isUserActivated()) { list($menu, $dropdowns, $aural) = $this->renderNotificationMenu(); if (array_filter($menu)) { $alerts[] = $menu; @@ -77,10 +77,10 @@ final class PhabricatorMainMenuView extends AphrontView { $controller = $this->getController(); foreach ($applications as $application) { $app_actions = $application->buildMainMenuItems( - $user, + $viewer, $controller); $app_extra = $application->buildMainMenuExtraNodes( - $user, + $viewer, $controller); foreach ($app_actions as $action) { @@ -97,7 +97,7 @@ final class PhabricatorMainMenuView extends AphrontView { $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); foreach ($extensions as $extension) { - $extension->setViewer($user); + $extension->setViewer($viewer); $controller = $this->getController(); if ($controller) { @@ -158,7 +158,7 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderSearch() { - $user = $this->user; + $viewer = $this->getViewer(); $result = null; @@ -166,15 +166,15 @@ final class PhabricatorMainMenuView extends AphrontView { 'helpURI' => '/help/keyboardshortcut/', ); - if ($user->isLoggedIn()) { - $show_search = $user->isUserActivated(); + if ($viewer->isLoggedIn()) { + $show_search = $viewer->isUserActivated(); } else { $show_search = PhabricatorEnv::getEnvConfig('policy.allow-public'); } if ($show_search) { $search = new PhabricatorMainMenuSearchView(); - $search->setUser($user); + $search->setViewer($viewer); $application = null; $controller = $this->getController(); @@ -188,7 +188,7 @@ final class PhabricatorMainMenuView extends AphrontView { $result = $search; $pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT; - if ($user->loadPreferences()->getPreference($pref_shortcut, true)) { + if ($viewer->loadPreferences()->getPreference($pref_shortcut, true)) { $keyboard_config['searchID'] = $search->getID(); } } @@ -230,7 +230,7 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderApplicationMenu(array $bar_items) { - $user = $this->getUser(); + $viewer = $this->getViewer(); $view = $this->getApplicationMenu(); @@ -302,7 +302,7 @@ final class PhabricatorMainMenuView extends AphrontView { $logo_uri = $cache->getKey($cache_key_logo); if (!$logo_uri) { $file = id(new PhabricatorFileQuery()) - ->setViewer($this->getUser()) + ->setViewer($this->getViewer()) ->withPHIDs(array($custom_header)) ->executeOne(); if ($file) { @@ -355,7 +355,7 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderNotificationMenu() { - $user = $this->user; + $viewer = $this->getViewer(); require_celerity_resource('phabricator-notification-css'); require_celerity_resource('phabricator-notification-menu-css'); @@ -364,7 +364,7 @@ final class PhabricatorMainMenuView extends AphrontView { $aural = array(); $dropdown_query = id(new AphlictDropdownDataQuery()) - ->setViewer($user); + ->setViewer($viewer); $dropdown_data = $dropdown_query->execute(); $message_tag = ''; diff --git a/src/view/phui/PHUIBadgeMiniView.php b/src/view/phui/PHUIBadgeMiniView.php index 21f455b4d6..4e058ed29d 100644 --- a/src/view/phui/PHUIBadgeMiniView.php +++ b/src/view/phui/PHUIBadgeMiniView.php @@ -48,7 +48,9 @@ final class PHUIBadgeMiniView extends AphrontTagView { $classes = array(); $classes[] = 'phui-badge-mini'; if ($this->quality) { - $classes[] = 'phui-badge-mini-'.$this->quality; + $quality_color = PhabricatorBadgesQuality::getQualityColor( + $this->quality); + $classes[] = 'phui-badge-mini-'.$quality_color; } return array( diff --git a/src/view/phui/PHUIBadgeView.php b/src/view/phui/PHUIBadgeView.php index 7e9f310843..7594b6cb30 100644 --- a/src/view/phui/PHUIBadgeView.php +++ b/src/view/phui/PHUIBadgeView.php @@ -10,16 +10,6 @@ final class PHUIBadgeView extends AphrontTagView { private $subhead; private $bylines = array(); - // Yes, World of Warcraft Item Quality - const POOR = 'grey'; - const COMMON = 'white'; - const UNCOMMON = 'green'; - const RARE = 'blue'; - const EPIC = 'indigo'; - const LEGENDARY = 'orange'; - const HEIRLOOM = 'yellow'; - - public function setIcon($icon) { $this->icon = $icon; return $this; @@ -35,6 +25,14 @@ final class PHUIBadgeView extends AphrontTagView { return $this; } + private function getQualityColor() { + return PhabricatorBadgesQuality::getQualityColor($this->quality); + } + + private function getQualityName() { + return PhabricatorBadgesQuality::getQualityName($this->quality); + } + public function setSource($source) { $this->source = $source; return $this; @@ -55,26 +53,6 @@ final class PHUIBadgeView extends AphrontTagView { return $this; } - private function getQualityTitle() { - - switch ($this->quality) { - case self::POOR: - return pht('Poor'); - case self::COMMON: - return pht('Common'); - case self::UNCOMMON: - return pht('Uncommon'); - case self::RARE: - return pht('Rare'); - case self::EPIC: - return pht('Epic'); - case self::LEGENDARY: - return pht('Legendary'); - case self::HEIRLOOM: - return pht('Heirloom'); - } - } - protected function getTagName() { return 'span'; } @@ -86,7 +64,8 @@ final class PHUIBadgeView extends AphrontTagView { $classes = array(); $classes[] = 'phui-badge-view'; if ($this->quality) { - $classes[] = 'phui-badge-view-'.$this->quality; + $color = $this->getQualityColor(); + $classes[] = 'phui-badge-view-'.$color; } return array( @@ -131,7 +110,7 @@ final class PHUIBadgeView extends AphrontTagView { ), array($header, $subhead)); - $quality = phutil_tag_div('phui-badge-quality', $this->getQualityTitle()); + $quality = phutil_tag_div('phui-badge-quality', $this->getQualityName()); $source = phutil_tag_div('phui-badge-source', $this->source); $bylines = array(); diff --git a/src/view/phui/PHUIFeedStoryView.php b/src/view/phui/PHUIFeedStoryView.php index bacd266089..978e25062f 100644 --- a/src/view/phui/PHUIFeedStoryView.php +++ b/src/view/phui/PHUIFeedStoryView.php @@ -172,8 +172,8 @@ final class PHUIFeedStoryView extends AphrontView { if ($this->epoch) { // TODO: This is really bad; when rendering through Conduit and via // renderText() we don't have a user. - if ($this->user) { - $foot = phabricator_datetime($this->epoch, $this->user); + if ($this->hasViewer()) { + $foot = phabricator_datetime($this->epoch, $this->getViewer()); } else { $foot = null; } diff --git a/src/view/phui/PHUIHeadThingView.php b/src/view/phui/PHUIHeadThingView.php index 4ff0290578..dd788399d4 100644 --- a/src/view/phui/PHUIHeadThingView.php +++ b/src/view/phui/PHUIHeadThingView.php @@ -35,6 +35,9 @@ final class PHUIHeadThingView extends AphrontTagView { $classes = array(); $classes[] = 'phui-head-thing-view'; + if ($this->image) { + $classes[] = 'phui-head-has-image'; + } if ($this->size) { $classes[] = $this->size; @@ -57,8 +60,11 @@ final class PHUIHeadThingView extends AphrontTagView { 'href' => $this->imageHref, )); - return array($image, $this->content); - + if ($this->image) { + return array($image, $this->content); + } else { + return $this->content; + } } diff --git a/src/view/phui/PHUIHovercardView.php b/src/view/phui/PHUIHovercardView.php index e5c89bb8fd..115964f191 100644 --- a/src/view/phui/PHUIHovercardView.php +++ b/src/view/phui/PHUIHovercardView.php @@ -106,7 +106,7 @@ final class PHUIHovercardView extends AphrontTagView { $header->setHeader($title); if ($this->tags) { foreach ($this->tags as $tag) { - $header->addTag($tag); + $header->addActionItem($tag); } } diff --git a/src/view/phui/PHUIObjectItemListView.php b/src/view/phui/PHUIObjectItemListView.php index c21ea558a2..55c0c8aede 100644 --- a/src/view/phui/PHUIObjectItemListView.php +++ b/src/view/phui/PHUIObjectItemListView.php @@ -7,6 +7,7 @@ final class PHUIObjectItemListView extends AphrontTagView { private $pager; private $noDataString; private $flush; + private $simple; private $allowEmptyList; private $states; private $itemClass = 'phui-object-item-standard'; @@ -35,6 +36,11 @@ final class PHUIObjectItemListView extends AphrontTagView { return $this; } + public function setSimple($simple) { + $this->simple = $simple; + return $this; + } + public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; return $this; @@ -69,6 +75,9 @@ final class PHUIObjectItemListView extends AphrontTagView { if ($this->flush) { $classes[] = 'phui-object-list-flush'; } + if ($this->simple) { + $classes[] = 'phui-object-list-simple'; + } return array( 'class' => $classes, diff --git a/src/view/phui/PHUITimelineEventView.php b/src/view/phui/PHUITimelineEventView.php index ecd9329860..08b6cc2aaf 100644 --- a/src/view/phui/PHUITimelineEventView.php +++ b/src/view/phui/PHUITimelineEventView.php @@ -232,11 +232,12 @@ final class PHUITimelineEventView extends AphrontView { $fill_classes = array(); $fill_classes[] = 'phui-timeline-icon-fill'; if ($this->color) { + $fill_classes[] = 'fill-has-color'; $fill_classes[] = 'phui-timeline-icon-fill-'.$this->color; } $icon = id(new PHUIIconView()) - ->setIcon($this->icon.' white') + ->setIcon($this->icon) ->addClass('phui-timeline-icon'); $icon = phutil_tag( @@ -341,6 +342,8 @@ final class PHUITimelineEventView extends AphrontView { // Render "extra" information (timestamp, etc). $extra = $this->renderExtra($events); + $show_badges = false; + $group_titles = array(); $group_items = array(); $group_children = array(); @@ -357,6 +360,7 @@ final class PHUITimelineEventView extends AphrontView { if ($event->hasChildren()) { $group_children[] = $event->renderChildren(); + $show_badges = true; } } @@ -381,7 +385,7 @@ final class PHUITimelineEventView extends AphrontView { 'href' => $this->userHandle->getURI(), ), ''); - if ($this->badges) { + if ($this->badges && $show_badges) { $flex = new PHUIBadgeBoxView(); $flex->addItems($this->badges); $flex->setCollapsed(true); @@ -504,11 +508,12 @@ final class PHUITimelineEventView extends AphrontView { } $source = $this->getContentSource(); + $content_source = null; if ($source) { - $extra[] = id(new PhabricatorContentSourceView()) + $content_source = id(new PhabricatorContentSourceView()) ->setContentSource($source) - ->setUser($this->getUser()) - ->render(); + ->setUser($this->getUser()); + $content_source = pht('Via %s', $content_source->getSourceName()); } $date_created = null; @@ -528,6 +533,7 @@ final class PHUITimelineEventView extends AphrontView { $this->getUser()); if ($this->anchor) { Javelin::initBehavior('phabricator-watch-anchor'); + Javelin::initBehavior('phabricator-tooltips'); $anchor = id(new PhabricatorAnchorView()) ->setAnchorName($this->anchor) @@ -535,10 +541,14 @@ final class PHUITimelineEventView extends AphrontView { $date = array( $anchor, - phutil_tag( + javelin_tag( 'a', array( 'href' => '#'.$this->anchor, + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $content_source, + ), ), $date), ); @@ -615,9 +625,9 @@ final class PHUITimelineEventView extends AphrontView { )); $content_source = $this->getContentSource(); - $source_email = PhabricatorContentSource::SOURCE_EMAIL; + $source_email = PhabricatorEmailContentSource::SOURCECONST; if ($content_source->getSource() == $source_email) { - $source_id = $content_source->getParam('id'); + $source_id = $content_source->getContentSourceParameter('id'); if ($source_id) { $items[] = id(new PhabricatorActionView()) ->setIcon('fa-envelope-o') diff --git a/src/view/phui/PHUITimelineView.php b/src/view/phui/PHUITimelineView.php index 2718fbd9f2..fa3bd4a69f 100644 --- a/src/view/phui/PHUITimelineView.php +++ b/src/view/phui/PHUITimelineView.php @@ -224,12 +224,6 @@ final class PHUITimelineView extends AphrontView { $user_phids = array(); foreach ($events as $key => $event) { - if (!$event->hasChildren()) { - // This is a minor event, so we don't have space to show badges. - unset($events[$key]); - continue; - } - $author_phid = $event->getAuthorPHID(); if (!$author_phid) { unset($events[$key]); @@ -250,31 +244,30 @@ final class PHUITimelineView extends AphrontView { return; } - $edges = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($user_phids) - ->withEdgeTypes(array($badge_edge_type)); - $edges->execute(); - $badge_phids = $edges->getDestinationPHIDs(); - if (!$badge_phids) { - return; - } - - $all_badges = id(new PhabricatorBadgesQuery()) - ->setViewer($viewer) - ->withPHIDs($badge_phids) - ->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE)) + $awards = id(new PhabricatorBadgesAwardQuery()) + ->setViewer($this->getViewer()) + ->withRecipientPHIDs($user_phids) ->execute(); - $all_badges = mpull($all_badges, null, 'getPHID'); + + $awards = mgroup($awards, 'getRecipientPHID'); foreach ($events as $event) { - $author_phid = $event->getAuthorPHID(); - $event_phids = $edges->getDestinationPHIDs(array($author_phid)); - $badges = array_select_keys($all_badges, $event_phids); + + $author_awards = idx($awards, $event->getAuthorPHID(), array()); + + $badges = array(); + foreach ($author_awards as $award) { + $badge = $award->getBadge(); + if ($badge->getStatus() == PhabricatorBadgesBadge::STATUS_ACTIVE) { + $badges[$award->getBadgePHID()] = $badge; + } + } // TODO: Pick the "best" badges in some smart way. For now, just pick // the first two. $badges = array_slice($badges, 0, 2); + foreach ($badges as $badge) { $badge_view = id(new PHUIBadgeMiniView()) ->setIcon($badge->getIcon()) diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index fb27a1a50d..d174547559 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -8,9 +8,9 @@ final class PHUITwoColumnView extends AphrontTagView { private $fluid; private $header; private $subheader; + private $footer; private $propertySection = array(); - private $actionList; - private $propertyList; + private $curtain; const DISPLAY_LEFT = 'phui-side-column-left'; const DISPLAY_RIGHT = 'phui-side-column-right'; @@ -35,19 +35,23 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function setFooter($footer) { + $this->footer = $footer; + return $this; + } + public function addPropertySection($title, $section) { $this->propertySection[] = array($title, $section); return $this; } - public function setActionList(PhabricatorActionListView $list) { - $this->actionList = $list; + public function setCurtain(PHUICurtainView $curtain) { + $this->curtain = $curtain; return $this; } - public function setPropertyList(PHUIPropertyListView $list) { - $this->propertyList = $list; - return $this; + public function getCurtain() { + return $this->curtain; } public function setFluid($fluid) { @@ -91,6 +95,8 @@ final class PHUITwoColumnView extends AphrontTagView { $main = $this->buildMainColumn(); $side = $this->buildSideColumn(); + $footer = $this->buildFooter(); + $order = array($side, $main); $inner = phutil_tag_div('phui-two-column-row grouped', $order); @@ -98,9 +104,12 @@ final class PHUITwoColumnView extends AphrontTagView { $header = null; if ($this->header) { - if ($this->actionList) { - $this->header->setActionList($this->actionList); + $curtain = $this->getCurtain(); + if ($curtain) { + $action_list = $curtain->getActionList(); + $this->header->setActionList($action_list); } + $header = phutil_tag_div( 'phui-two-column-header', $this->header); } @@ -120,6 +129,7 @@ final class PHUITwoColumnView extends AphrontTagView { $header, $subheader, $table, + $footer, )); } @@ -151,20 +161,8 @@ final class PHUITwoColumnView extends AphrontTagView { } private function buildSideColumn() { - $property_list = $this->propertyList; - $action_list = $this->actionList; - $properties = null; - if ($property_list || $action_list) { - if ($property_list) { - $property_list->setStacked(true); - } - - $properties = id(new PHUIObjectBoxView()) - ->appendChild($action_list) - ->appendChild($property_list) - ->addClass('phui-two-column-properties'); - } + $curtain = $this->getCurtain(); return phutil_tag( 'div', @@ -172,8 +170,23 @@ final class PHUITwoColumnView extends AphrontTagView { 'class' => 'phui-side-column', ), array( - $properties, + $curtain, $this->sideColumn, )); } + + private function buildFooter() { + + $footer = $this->footer; + + return phutil_tag( + 'div', + array( + 'class' => 'phui-two-column-content phui-two-column-footer', + ), + array( + $footer, + )); + + } } diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 34299e39c3..d71554abe5 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -278,7 +278,7 @@ final class PHUICalendarDayView extends AphrontView { ->addClass('calendar-day-view-sidebar'); $list = id(new PHUICalendarListView()) - ->setUser($this->user) + ->setUser($this->getViewer()) ->setView('day'); if (count($events) == 0) { @@ -304,7 +304,7 @@ final class PHUICalendarDayView extends AphrontView { $box_start_time = clone $display_start_day; - $today_time = PhabricatorTime::getTodayMidnightDateTime($this->user); + $today_time = PhabricatorTime::getTodayMidnightDateTime($this->getViewer()); $tomorrow_time = clone $today_time; $tomorrow_time->modify('+1 day'); @@ -437,7 +437,7 @@ final class PHUICalendarDayView extends AphrontView { } private function getDateTime() { - $user = $this->user; + $user = $this->getViewer(); $timezone = new DateTimeZone($user->getTimezoneIdentifier()); $day = $this->day; diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 5efa4c1059..d40736494e 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -51,9 +51,7 @@ final class PHUICalendarMonthView extends AphrontView { } public function render() { - if (empty($this->user)) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); $events = msort($this->events, 'getEpochStart'); $days = $this->getDatesInMonth(); @@ -93,7 +91,7 @@ final class PHUICalendarMonthView extends AphrontView { $counter = 0; $list = new PHUICalendarListView(); - $list->setUser($this->user); + $list->setViewer($viewer); foreach ($all_day_events as $item) { if ($counter <= $max_daily) { $list->addEvent($item); @@ -495,9 +493,9 @@ final class PHUICalendarMonthView extends AphrontView { * @return list List of DateTimes, one for each day. */ private function getDatesInMonth() { - $user = $this->user; + $viewer = $this->getViewer(); - $timezone = new DateTimeZone($user->getTimezoneIdentifier()); + $timezone = new DateTimeZone($viewer->getTimezoneIdentifier()); $month = $this->month; $year = $this->year; @@ -575,7 +573,7 @@ final class PHUICalendarMonthView extends AphrontView { } private function getWeekStartAndEnd() { - $preferences = $this->user->loadPreferences(); + $preferences = $this->getViewer()->loadPreferences(); $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; $week_start = $preferences->getPreference($pref_week_start, 0); @@ -585,7 +583,7 @@ final class PHUICalendarMonthView extends AphrontView { } private function getDateTime() { - $user = $this->user; + $user = $this->getViewer(); $timezone = new DateTimeZone($user->getTimezoneIdentifier()); $month = $this->month; diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 5174767e28..6c50ed603e 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -83,6 +83,26 @@ final class PhabricatorStartup { * @task info */ public static function getRawInput() { + if (self::$rawInput === null) { + $stream = new AphrontRequestStream(); + + if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { + $encoding = trim($_SERVER['HTTP_CONTENT_ENCODING']); + $stream->setEncoding($encoding); + } + + $input = ''; + do { + $bytes = $stream->readData(); + if ($bytes === null) { + break; + } + $input .= $bytes; + } while (true); + + self::$rawInput = $input; + } + return self::$rawInput; } @@ -128,47 +148,6 @@ final class PhabricatorStartup { self::detectPostMaxSizeTriggered(); self::beginOutputCapture(); - - if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { - $encoding = trim($_SERVER['HTTP_CONTENT_ENCODING']); - } else { - $encoding = null; - } - - $input_stream = fopen('php://input', 'rb'); - if (!$input_stream) { - self::didFatal( - 'Unable to open "php://input" to read HTTP request body.'); - } - - if ($encoding === 'gzip') { - $ok = stream_filter_append( - $input_stream, - 'zlib.inflate', - STREAM_FILTER_READ, - array( - 'window' => 30, - )); - - if (!$ok) { - self::didFatal( - 'Failed to append gzip inflate filter to HTTP request body input '. - 'stream.'); - } - } - - $input_data = ''; - while (!feof($input_stream)) { - $read_bytes = fread($input_stream, 16 * 1024); - if ($read_bytes === false) { - self::didFatal( - 'Failed to read input bytes from HTTP request body.'); - } - $input_data .= $read_bytes; - } - fclose($input_stream); - - self::$rawInput = $input_data; } diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index b2efc8c3c0..af304c930a 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -2,8 +2,7 @@ * @provides aphront-table-view-css */ -.device-phone .aphront-table-wrap, -.device-tablet .aphront-table-wrap { +.aphront-table-wrap { overflow-x: auto; } diff --git a/webroot/rsrc/css/application/dashboard/dashboard.css b/webroot/rsrc/css/application/dashboard/dashboard.css index 045e63eb8f..d8f4d3450e 100644 --- a/webroot/rsrc/css/application/dashboard/dashboard.css +++ b/webroot/rsrc/css/application/dashboard/dashboard.css @@ -55,8 +55,8 @@ .aphront-multi-column-column.dashboard-column-empty .dashboard-panel-placeholder { display: block; - padding: 24px; - margin: 16px 16px 0px 16px; + padding: 20px; + margin: 0 0 12px 0; text-decoration: none; border: 1px {$greyborder} dashed; color: {$greytext}; @@ -73,3 +73,9 @@ .aphront-multi-column-column .phui-info-view { margin: 0; } + +.dashboard-preview-box { + border: 1px solid {$lightblueborder}; + border-radius: 3px; + background-color: rgba(255,255,255,.33); +} diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 71cfa3170b..85eb5ab437 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -6,7 +6,7 @@ .differential-changeset { position: relative; margin: 0; - padding-top: 32px; + padding-top: 16px; overflow-x: auto; /* Fixes what seems to be a layout bug in Firefox which causes scrollbars, @@ -265,7 +265,7 @@ td.cov-I { .differential-changeset h1 { font-size: {$biggestfontsize}; - padding: 2px 0 12px 12px; + padding: 2px 0 20px 12px; line-height: 20px; color: #000; } @@ -322,7 +322,7 @@ td.cov-I { .differential-changeset-buttons { float: right; - margin-right: 8px; + margin-right: 12px; } .device-phone .differential-changeset-buttons { @@ -362,3 +362,7 @@ tr.differential-inline-hidden { tr.differential-inline-loading { opacity: 0.5; } + +.differential-review-stage { + position: relative; +} diff --git a/webroot/rsrc/css/application/differential/core.css b/webroot/rsrc/css/application/differential/core.css index 7ccd633f3f..2dcc02bb18 100644 --- a/webroot/rsrc/css/application/differential/core.css +++ b/webroot/rsrc/css/application/differential/core.css @@ -3,7 +3,7 @@ */ .differential-primary-pane { - margin-bottom: 32px; + margin-top: -20px; } .differential-panel { @@ -23,3 +23,7 @@ -ms-user-select: none; user-select: none; } + +.differential-content-hidden { + margin: 0 0 24px 0; +} diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 0055f25699..f93b2c8d6c 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -61,7 +61,7 @@ /* Tighten up spacing on replies */ .differential-inline-comment.inline-comment-is-reply { - margin-top: -4px; + margin-top: -12px; } .differential-inline-comment .inline-head-right { diff --git a/webroot/rsrc/css/application/diffusion/diffusion-icons.css b/webroot/rsrc/css/application/diffusion/diffusion-icons.css index 087ce9362e..c735e3d842 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-icons.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-icons.css @@ -20,3 +20,11 @@ input.diffusion-clone-uri { .diffusion-link-icon + .diffusion-link-icon { margin-left: 6px; } + +.diffusion-search-boxen { + padding: 16px; +} + +.diffusion-search-boxen .phui-form-view { + padding: 0; +} diff --git a/webroot/rsrc/css/application/diffusion/diffusion-readme.css b/webroot/rsrc/css/application/diffusion/diffusion-readme.css index f7f8506e2d..bb661bb2c8 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-readme.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-readme.css @@ -2,6 +2,7 @@ * @provides diffusion-readme-css */ +.device .diffusion-readme-view .phui-document-fluid .phui-document-view, .device-desktop .diffusion-readme-view .phui-document-fluid .phui-document-view { margin: 0; diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index ca52d53a79..7f404e3b9b 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -4,7 +4,6 @@ .diffusion-source { width: 100%; - font: 10px/13px "Menlo", "Consolas", "Monaco", monospace; background: #fff; } @@ -21,13 +20,12 @@ } .diffusion-source td { - letter-spacing: 0.0083334px; - vertical-align: top; - white-space: pre-wrap; - padding-bottom: 1px; - padding-left: 8px; - line-height: 16px; - width: 100%; + vertical-align: top; + white-space: pre-wrap; + padding-top: 1px; + padding-bottom: 1px; + padding-left: 8px; + width: 100%; } .diffusion-browse-type-form { diff --git a/webroot/rsrc/css/application/harbormaster/harbormaster.css b/webroot/rsrc/css/application/harbormaster/harbormaster.css index a80c8b7418..4bfd2d6cbe 100644 --- a/webroot/rsrc/css/application/harbormaster/harbormaster.css +++ b/webroot/rsrc/css/application/harbormaster/harbormaster.css @@ -18,6 +18,8 @@ border: 1px solid {$yellow}; text-align: center; padding: 12px; + margin: 0 0 20px 0; + border-radius: 3px; color: {$darkgreytext}; } diff --git a/webroot/rsrc/css/application/herald/herald.css b/webroot/rsrc/css/application/herald/herald.css index 26cb9cfd71..17b09a1a86 100644 --- a/webroot/rsrc/css/application/herald/herald.css +++ b/webroot/rsrc/css/application/herald/herald.css @@ -44,7 +44,7 @@ .herald-list-description { color: {$bluetext}; font-weight: bold; - padding: 8px 0; + padding: 12px 0; } .herald-list-icon { @@ -52,6 +52,6 @@ } .herald-list-item { - padding-bottom: 20px; + padding-bottom: 4px; color: {$darkbluetext}; } diff --git a/webroot/rsrc/css/application/project/project-view.css b/webroot/rsrc/css/application/project/project-view.css index 2c0fd7d6d8..0af3ef3490 100644 --- a/webroot/rsrc/css/application/project/project-view.css +++ b/webroot/rsrc/css/application/project/project-view.css @@ -91,3 +91,7 @@ .profile-no-badges { padding: 24px 0; } + +.project-view-home .phabricator-remarkup .remarkup-code-block pre { + white-space: pre-wrap; +} diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 7b537f5d67..2313939f4c 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -182,6 +182,8 @@ } .phabricator-remarkup blockquote em { + /* In blockquote bodies, default text is italic so emphasized text should + be normal. */ font-style: normal; } @@ -190,6 +192,12 @@ padding-bottom: 4px; } +.phabricator-remarkup blockquote div.remarkup-reply-head em { + /* In blockquote headers, default text is normal so emphasized text should + be italic. See T10686. */ + font-style: italic; +} + .phabricator-remarkup blockquote div.remarkup-reply-head .phui-tag-core { background-color: transparent; diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 7eaca8b741..30328d0d38 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -66,6 +66,10 @@ padding: 0; } +.phui-box.phui-box-blue-property .phui-header-header .phui-header-icon { + margin-right: 6px; +} + .phui-box.phui-box-blue-property .phui-header-action-link { margin-top: 0; margin-bottom: 0; @@ -82,11 +86,30 @@ padding: 6px 16px; } -.device .phui-box.phui-box-blue-property .phui-header-shell { - padding: 6px 12px; +.device .phui-box.phui-box-blue-property .phui-header-shell, +.device .phui-box-blue-property.phui-object-box.phui-object-box-collapsed + .phui-header-shell { + padding: 6px 12px; } .phui-box.phui-box-blue-property .phui-header-header { font-size: 13px; color: {$bluetext}; } + +.phui-box-blue-property .phui-object-item-list-view { + padding: 2px 8px; +} + +body .phui-box-blue-property.phui-object-box.phui-object-box-collapsed { + padding: 0; +} + +body .phui-box-blue-property .phui-header-shell + .phui-object-box { + margin-bottom: 0; +} + +.phui-box-blue-property .phui-header-shell + .phui-object-box + .phui-header-shell { + background: #fff; +} diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css new file mode 100644 index 0000000000..b33369abe0 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -0,0 +1,41 @@ +/** + * @provides phui-curtain-view-css + */ + +.phui-curtain-panel { + padding: 16px 0; + margin: 0 4px; +} + +.device .phui-curtain-panel { + padding: 8px 0; + margin: 0; +} + +.device-desktop .phui-curtain-panel { + border-top: 1px solid rgba({$alphablue}, .1); +} + +.phui-curtain-panel-header { + padding: 0 0 4px; + color: {$bluetext}; + font-weight: bold; +} + +.phui-curtain-panel-body { + padding: 4px 0 0; +} + +.device .phui-curtain-panel-body { + padding: 0; +} + +/* Project tags */ + +.phui-curtain-panel-body .phabricator-handle-tag-list-item { + line-height: 21px; +} + +.phui-side-column .phui-curtain-panel-body .phui-tag-view { + white-space: pre-wrap; +} diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index c77ffa77e2..85f8150015 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -190,10 +190,6 @@ a.button.phui-document-toc { margin: 0; } -.phui-document-view-pro-box .phui-object-box .phui-form-view { - padding-bottom: 0; -} - .phui-document-view-pro-box .phui-object-box .remarkup-assist-textarea { height: 9em; } diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 2ff68ba4f3..9549110431 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -263,11 +263,10 @@ table.aphront-form-control-checkbox-layout th { } .phui-form-inset { - margin: 4px 0 8px; + margin: 12px 0; padding: 8px; background: #f7f9fd; border: 1px solid {$lightblueborder}; - border-bottom: 1px solid {$blueborder}; border-radius: 3px; } diff --git a/webroot/rsrc/css/phui/phui-head-thing.css b/webroot/rsrc/css/phui/phui-head-thing.css index bce67ef387..bf491c4f1f 100644 --- a/webroot/rsrc/css/phui/phui-head-thing.css +++ b/webroot/rsrc/css/phui/phui-head-thing.css @@ -6,11 +6,14 @@ height: 24px; line-height: 22px; color: {$greytext}; +} + +.phui-head-thing-view.phui-head-has-image { position: relative; padding-left: 32px; } -.device-phone .phui-head-thing-view { +.device-phone .phui-two-column-subheader .phui-head-thing-view { min-height: 24px; height: auto; line-height: inherit; diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index b75715a7ee..2a1731eb61 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -175,6 +175,10 @@ body .phui-header-shell.phui-bleed-header margin-top: 8px; } +.phui-header-subheader .phui-icon-view { + margin-right: 4px; +} + .phui-header-subheader .phui-tag-view .phui-icon-view, .phui-header-subheader .policy-header-callout .phui-icon-view { display: inline-block; @@ -320,3 +324,7 @@ body .phui-header-shell.phui-bleed-header .phui-profile-header .phui-header-col3 { vertical-align: top; } + +.phui-header-view .phui-tag-shade-indigo a { + color: {$sh-indigotext}; +} diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index 59405995a5..d3b12dd4fe 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -10,6 +10,10 @@ border-radius: 3px; } +.phui-info-view.phui-info-view-flush { + margin: 0 0 20px 0; +} + .device .phui-info-view { margin: 8px; } diff --git a/webroot/rsrc/css/phui/phui-object-box.css b/webroot/rsrc/css/phui/phui-object-box.css index f9f06e2d18..dee0525944 100644 --- a/webroot/rsrc/css/phui/phui-object-box.css +++ b/webroot/rsrc/css/phui/phui-object-box.css @@ -52,6 +52,16 @@ div.phui-object-box.phui-object-box-flush { margin: 8px 8px 0 8px; } +.phui-object-box .phui-header-header .phui-tag-view { + margin-left: 8px; +} + +.phui-object-box .phui-header-header .phui-tag-core { + border-color: transparent; + padding: 1px 6px; + font-size: {$normalfontsize}; +} + /* - Object Box Colors ------------------------------------------------------ */ .phui-box-border.phui-object-box-green { diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css index 7365df5ddc..d3eca4eba2 100644 --- a/webroot/rsrc/css/phui/phui-object-item-list-view.css +++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css @@ -794,3 +794,44 @@ ul.phui-object-item-list-view .phui-object-item-selected padding: 0 8px 8px; text-align: left; } + +/* - Simple List------------------------------------------------------------- */ + +.phui-object-list-simple .phui-object-item-with-image .phui-object-item-frame { + min-height: 26px; +} + +.phui-object-list-simple .phui-object-item-image { + height: 26px; + width: 26px; + margin: 0; +} + +.phui-object-list-simple .phui-object-item-with-image + .phui-object-item-content-box { + margin-left: 32px; +} + +.phui-object-list-simple .phui-object-item-name { + padding: 2px 0; +} + +.phui-object-list-simple .phui-object-item-name a { + color: {$darkbluetext}; +} + +.phui-object-item-list-view.phui-object-list-simple .phui-object-item-frame { + border: none; + margin-bottom: 4px; +} + +.phui-object-item-list-view.phui-object-list-simple li:last-child + .phui-object-item-frame { + margin: 0; +} + +.phui-object-list-simple .phui-object-item-actions { + top: 2px; + bottom: 2px; + right: 2px; +} diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css index 3558bf7a9e..ab250a3cc5 100644 --- a/webroot/rsrc/css/phui/phui-property-list-view.css +++ b/webroot/rsrc/css/phui/phui-property-list-view.css @@ -125,7 +125,7 @@ } .phui-property-list-text-content { - padding: 12px 4px; + padding: 16px 4px; overflow: hidden; } @@ -199,7 +199,6 @@ .phui-property-list-image-content img { margin: 20px auto; background: url('/rsrc/image/checker_light.png'); - border: 1px solid {$lightblueborder}; } .device-desktop .phui-property-list-image-content img:hover { diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index d69df93953..9778f1a58c 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -4,9 +4,9 @@ .phui-timeline-view { padding: 0 16px; - background-image: url('/rsrc/image/BFCFDA.png'); + background-image: url('/rsrc/image/d5d8e1.png'); background-repeat: repeat-y; - background-position: 94px; + background-position: 96px; } .device .phui-timeline-view { @@ -23,8 +23,8 @@ } .phui-timeline-major-event .phui-timeline-group { - border-left: 1px solid {$lightblueborder}; - border-right: 1px solid {$lightblueborder}; + border-left: 1px solid {$timeline}; + border-right: 1px solid {$timeline}; border-radius: 3px; } @@ -34,7 +34,7 @@ } .device-desktop .phui-timeline-event-view.phui-timeline-minor-event { - margin-left: 65px; + margin-left: 67px; } .device-desktop .phui-timeline-spacer { @@ -46,11 +46,11 @@ width: 9px; height: 9px; border-radius: 2px; - margin-left: 74px; + margin-left: 76px; } .device-desktop .phui-timeline-wedge { - border-bottom: 1px solid {$lightblueborder}; + border-bottom: 1px solid {$timeline}; position: absolute; width: 12px; } @@ -61,25 +61,25 @@ } .phui-timeline-major-event .phui-timeline-content { - border-top: 1px solid {$lightblueborder}; - border-bottom: 1px solid {$lightblueborder}; + border-top: 1px solid {$timeline}; + border-bottom: 1px solid {$timeline}; border-radius: 3px; } .phui-timeline-title { - line-height: 22px; + line-height: 24px; min-height: 19px; position: relative; - color: {$bluetext}; + color: {$greytext}; } .phui-timeline-minor-event .phui-timeline-title { - padding: 4px 8px 4px 33px; + padding: 1px 8px 4px 33px; } .phui-timeline-title a { font-weight: bold; - color: {$darkbluetext}; + color: {$darkgreytext}; } .device-desktop .phui-timeline-wedge { @@ -91,7 +91,7 @@ } .device-desktop .phui-timeline-minor-event .phui-timeline-wedge { - top: 13px; + top: 12px; left: -18px; width: 20px; } @@ -100,7 +100,6 @@ background-repeat: no-repeat; position: absolute; border-radius: 3px; - box-shadow: {$borderinset}; background-size: 100%; display: block; } @@ -113,22 +112,29 @@ } .device-desktop .phui-timeline-minor-event .phui-timeline-image { - width: 28px; - height: 28px; - background-size: 28px auto; + width: 26px; + height: 26px; + background-size: 26px auto; left: -41px; } .phui-timeline-major-event .phui-timeline-title { - background: {$lightbluebackground}; + background: {$lightgreybackground}; min-height: 22px; border-top-right-radius: 3px; + border-top-left-radius: 3px; } -.phui-timeline-title + .phui-timeline-title { +.phui-timeline-major-event .phui-timeline-title + .phui-timeline-title { border-radius: 0; + padding-top: 0; } +.phui-timeline-major-event .phui-timeline-title + .phui-timeline-title + .phui-timeline-icon-fill { + margin-top: 0; + } + .phui-timeline-title { padding: 5px 8px; overflow-x: auto; @@ -136,7 +142,7 @@ } .phui-timeline-title-with-icon { - padding-left: 38px; + padding-left: 36px; } .phui-timeline-title-with-menu { @@ -170,9 +176,10 @@ .phui-timeline-major-event .phui-timeline-content .phui-timeline-core-content { - padding: 16px 12px; + padding: 16px; line-height: 18px; background: #fff; + border-top: 1px solid rgba({$alphablue},.1); border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; } @@ -203,52 +210,49 @@ border-width: 0; } -.phui-timeline-spacer.phui-timeline-spacer-bold { - border-bottom: 4px solid {$lightblueborder}; - margin: 0; -} - -.phui-timeline-spacer-bold + .phui-timeline-spacer { - background-color: #ebecee; -} - .phui-timeline-icon-fill { position: absolute; - width: 32px; - height: 32px; - background-color: {$lightblueborder}; + width: 34px; + height: 34px; top: 0; left: 0; text-align: center; } -.phui-icon-view.phui-timeline-icon:before { - font-size: 15px; +.phui-timeline-icon { + color: {$sh-blueicon}; } -.phui-timeline-minor-event .phui-timeline-icon-fill { - height: 28px; - width: 28px; +.phui-icon-view.phui-timeline-icon { + font-size: 14px; +} + +.phui-timeline-icon-fill { + height: 26px; + width: 26px; border-radius: 3px; + background-color: #E6E9F1; +} + +.phui-timeline-major-event .phui-timeline-icon-fill { + margin: 4px; } .phui-timeline-icon-fill .phui-timeline-icon { - margin-top: 8px; -} - -.phui-timeline-minor-event .phui-timeline-icon-fill .phui-timeline-icon { - margin-top: 7px; + margin-top: 6px; } .phui-timeline-extra, .phui-timeline-extra .phabricator-content-source-view { - font-size: {$smallestfontsize}; + font-size: {$smallerfontsize}; font-weight: normal; - color: {$lightbluetext}; + color: {$lightgreytext}; + margin-left: 8px; } .phui-timeline-title .phui-timeline-extra a { font-weight: normal; + color: {$lightgreytext}; } .device-desktop .phui-timeline-extra { @@ -267,6 +271,10 @@ margin: 0; } +.phui-timeline-icon-fill.fill-has-color .phui-icon-view { + color: #fff; +} + .phui-timeline-icon-fill-red { background-color: {$red}; } @@ -304,7 +312,7 @@ } .phui-timeline-icon-fill-black { - background-color: #333; + background-color: #000; } .phui-timeline-shell.anchor-target { @@ -343,7 +351,7 @@ .phui-timeline-title .phui-timeline-extra-information a { font-weight: normal; - color: {$bluetext}; + color: {$greytext}; } .phui-timeline-comment-actions .phui-icon-view { @@ -359,11 +367,11 @@ right: 3px; top: 6px; width: 28px; - height: 22px; + height: 24px; text-align: center; line-height: 22px; - font-size: 15px; - border-left: 1px solid {$lightblueborder}; + font-size: 16px; + border-left: 1px solid {$thinblueborder}; } .phui-timeline-menu:focus { @@ -379,11 +387,13 @@ a.phui-timeline-menu .phui-icon-view { } .device-desktop a.phui-timeline-menu:hover .phui-icon-view { - color: {$darkgreytext}; + color: {$sky}; } .phui-timeline-menu.phuix-dropdown-open { - background: {$hovergrey}; + background: rgba({$alphablue},0.1); + border: none; + border-radius: 3px; } .phui-timeline-view + .phui-object-box { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 769eaf84e4..600945103d 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -79,15 +79,11 @@ margin: 0 0 20px 0; } -.phui-two-column-view .phui-object-box.phui-object-box-collapsed { - padding: 0; -} - /* Timeline */ .phui-two-column-view .phui-timeline-view { padding: 0; - background-position: 78px; + background-position: 80px; } .phui-two-column-view .phui-main-column .phui-object-box + .phui-timeline-view { @@ -107,16 +103,20 @@ border-top: 1px solid {$thinblueborder}; } +.device-phone .phui-main-column .phui-timeline-older-transactions-are-hidden { + margin: 0; +} + /* Main Column Properties */ .device-desktop .phui-main-column .phui-property-list-key { margin-left: 0; - width: 140px; + width: 160px; } .device-desktop .phui-main-column .phui-property-list-value { margin-left: 8px; - width: calc(100% - 180px); + width: calc(100% - 200px); } @@ -128,47 +128,27 @@ } .device-desktop .phui-two-column-view .phui-property-list-container { - padding: 12px 16px; + padding: 16px 0; +} + +.device-desktop .phui-two-column-view + .phui-property-list-properties-wrap.phui-property-list-stacked { + padding: 0 16px; } .device .phui-two-column-view .phui-property-list-container { padding: 12px 8px; } +.phui-two-column-view .phui-property-list-container + .keyboard-shortcuts-available { + display: none; +} + .phui-two-column-properties.phui-object-box { border: 1px solid rgba({$alphablue}, .2); } -.phui-two-column-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-key { - margin: 4px 0 8px 0; - padding: 20px 4px 0; - border-top: 1px solid rgba({$alphablue}, .2); -} - -.phui-two-column-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-value { - margin: 0 0 20px 0; - padding: 0 4px; -} - -.device .phui-two-column-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-value { - margin-bottom: 12px; -} - -.device-desktop .phui-two-column-properties .phui-property-list-container, -.device .phui-two-column-properties .phui-property-list-container { - padding: 0; -} - -.device .phui-two-column-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-key { - margin: 12px 0 4px 0; - padding: 0; - border: none; -} - .device .phui-two-column-content .phui-two-column-properties.phui-object-box { padding: 0 12px; } @@ -208,12 +188,17 @@ /* Info View */ -.phui-two-column-view .phui-two-column-content .phui-info-view { +.phui-two-column-view .phui-info-view { margin: 0 0 20px 0; padding: 16px; } -.phui-two-column-view .phui-two-column-content .phui-object-box +.phui-two-column-view .phui-two-column-row .phui-object-box .phui-info-view { margin: 0; } + +.phui-two-column-view .phui-box-blue-property + .phui-header-shell + .phui-info-view { + margin: 16px; +} diff --git a/webroot/rsrc/externals/javelin/lib/Workflow.js b/webroot/rsrc/externals/javelin/lib/Workflow.js index b640adecab..50716b1c07 100644 --- a/webroot/rsrc/externals/javelin/lib/Workflow.js +++ b/webroot/rsrc/externals/javelin/lib/Workflow.js @@ -192,7 +192,7 @@ JX.install('Workflow', { // Use more space if the dialog is large (at least roughly the size // of the viewport). var offset = Math.min(Math.max(20, (v.y - d.y) / 2), 100); - JX.$V((v.x - d.x) / 2, s.y + offset).setPos(this._root); + JX.$V(0, s.y + offset).setPos(this._root); try { JX.DOM.focus(JX.DOM.find(this._root, 'button', '__default__')); diff --git a/webroot/rsrc/image/d5d8e1.png b/webroot/rsrc/image/d5d8e1.png new file mode 100644 index 0000000000..352aeb61dc Binary files /dev/null and b/webroot/rsrc/image/d5d8e1.png differ diff --git a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js index ba01c9bef3..8c6b46ba44 100644 --- a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js +++ b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js @@ -323,7 +323,14 @@ JX.install('HeraldRuleEditor', { _newCondition : function(data) { var row = this._conditionsRowManager.addRow([]); var row_id = this._conditionsRowManager.getRowID(row); - this._config.conditions[row_id] = data || [null, null, '']; + + var default_condition = [ + this._config.default.field, + this._config.default.condition, + null + ]; + this._config.conditions[row_id] = data || default_condition; + var r = this._conditionsRowManager.updateRow( row_id, this._renderCondition(row_id)); @@ -369,7 +376,12 @@ JX.install('HeraldRuleEditor', { }, _newAction : function(data) { - data = data || []; + var default_action = [ + this._config.default.action, + null + ]; + + data = data || default_action; var temprow = this._actionsRowManager.addRow([]); var row_id = this._actionsRowManager.getRowID(temprow); this._config.actions[row_id] = data; diff --git a/webroot/rsrc/js/application/projects/WorkboardColumn.js b/webroot/rsrc/js/application/projects/WorkboardColumn.js index 738cf151c2..ced39dcd73 100644 --- a/webroot/rsrc/js/application/projects/WorkboardColumn.js +++ b/webroot/rsrc/js/application/projects/WorkboardColumn.js @@ -221,6 +221,7 @@ JX.install('WorkboardColumn', { var board = this.getBoard(); var points = {}; + var count = 0; for (var phid in cards) { var card = cards[phid]; @@ -238,6 +239,8 @@ JX.install('WorkboardColumn', { } points[status] += card_points; } + + count++; } var total_points = 0; @@ -254,6 +257,10 @@ JX.install('WorkboardColumn', { display_value = total_points; } + if (board.getPointsEnabled()) { + display_value = count + ' | ' + display_value; + } + var over_limit = ((limit !== null) && (total_points > limit)); var content_node = this.getPointsContentNode(); diff --git a/webroot/rsrc/js/application/transactions/behavior-comment-actions.js b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js index be28e0bdad..87cb80c74a 100644 --- a/webroot/rsrc/js/application/transactions/behavior-comment-actions.js +++ b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js @@ -148,10 +148,7 @@ JX.behavior('comment-actions', function(config) { } else { JX.DOM.setContent( JX.$(config.timelineID), - [ - JX.$H(response.spacer), - JX.$H(response.xactions.join(response.spacer)) - ]); + JX.$H(response.xactions.join(''))); JX.DOM.show(panel); } }