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 3d5f59abe7..edc6372e8d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '2d0339fc', + 'core.pkg.css' => '82cefddc', 'core.pkg.js' => 'e5484f37', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '7ba78475', @@ -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,7 +54,7 @@ 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' => '3e3b0b76', @@ -123,12 +123,12 @@ 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' => 'b2d49bae', + 'rsrc/css/phui/phui-box.css' => 'd909ea3d', '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-crumbs-view.css' => '1a1265d4', 'rsrc/css/phui/phui-curtain-view.css' => '7148ae25', - 'rsrc/css/phui/phui-document-pro.css' => '92d5b648', + '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', @@ -156,7 +156,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => '6e342216', - 'rsrc/css/phui/phui-two-column-view.css' => '691fec04', + 'rsrc/css/phui/phui-two-column-view.css' => 'b9538af1', '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', @@ -515,7 +515,7 @@ return array( 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', 'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca', - 'rsrc/js/phuix/PHUIXFormControl.js' => 'a7763e11', + 'rsrc/js/phuix/PHUIXFormControl.js' => 'e15869a8', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), 'symbols' => array( @@ -526,7 +526,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', @@ -753,7 +753,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', @@ -805,18 +805,18 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => 'b2d49bae', + 'phui-box-css' => 'd909ea3d', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-chart-css' => '6bf6f78e', - 'phui-crumbs-view-css' => '79d536e5', + 'phui-crumbs-view-css' => '1a1265d4', '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', @@ -846,7 +846,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '6e342216', - 'phui-two-column-view-css' => '691fec04', + 'phui-two-column-view-css' => 'b9538af1', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', @@ -855,7 +855,7 @@ return array( 'phuix-action-view' => '8cf6d262', 'phuix-autocomplete' => '9196fb06', 'phuix-dropdown-menu' => 'bd4c8dca', - 'phuix-form-control-view' => 'a7763e11', + 'phuix-form-control-view' => 'e15869a8', 'phuix-icon-view' => 'bff6884b', 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', @@ -1659,10 +1659,6 @@ return array( 'javelin-uri', 'phabricator-notification', ), - 'a7763e11' => array( - 'javelin-install', - 'javelin-dom', - ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1964,6 +1960,10 @@ return array( 'javelin-dom', 'phabricator-prefab', ), + 'e15869a8' => array( + 'javelin-install', + 'javelin-dom', + ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', 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/autopatches/20160406.badges.ngrams.php b/resources/sql/autopatches/20160406.badges.ngrams.php new file mode 100644 index 0000000000..ce8d8896ef --- /dev/null +++ b/resources/sql/autopatches/20160406.badges.ngrams.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/resources/sql/autopatches/20160406.badges.ngrams.sql b/resources/sql/autopatches/20160406.badges.ngrams.sql new file mode 100644 index 0000000000..14a03759c9 --- /dev/null +++ b/resources/sql/autopatches/20160406.badges.ngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_badges.badges_badgename_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/20160406.columns.1.php b/resources/sql/autopatches/20160406.columns.1.php new file mode 100644 index 0000000000..8be75ebbed --- /dev/null +++ b/resources/sql/autopatches/20160406.columns.1.php @@ -0,0 +1,84 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $xaction) { + $type = $xaction->getTransactionType(); + $id = $xaction->getID(); + + // This is an old ManiphestTransaction::TYPE_COLUMN. It did not do anything + // on its own and was hidden from the UI, so we're just going to remove it. + if ($type == 'column') { + queryfx( + $conn_w, + 'DELETE FROM %T WHERE id = %d', + $table->getTableName(), + $id); + continue; + } + + // This is an old ManiphestTransaction::TYPE_PROJECT_COLUMN. It moved + // tasks between board columns; we're going to replace it with a modern + // PhabricatorTransactions::TYPE_COLUMNS transaction. + if ($type == 'projectcolumn') { + try { + $new = $xaction->getNewValue(); + if (!$new || !is_array($new)) { + continue; + } + + $column_phids = idx($new, 'columnPHIDs'); + if (!is_array($column_phids) || !$column_phids) { + continue; + } + + $column_phid = head($column_phids); + if (!$column_phid) { + continue; + } + + $board_phid = idx($new, 'projectPHID'); + if (!$board_phid) { + continue; + } + + $before_phid = idx($new, 'beforePHID'); + $after_phid = idx($new, 'afterPHID'); + + $old = $xaction->getOldValue(); + if ($old && is_array($old)) { + $from_phids = idx($old, 'columnPHIDs'); + $from_phids = array_values($from_phids); + } else { + $from_phids = array(); + } + + $replacement = array( + 'columnPHID' => $column_phid, + 'boardPHID' => $board_phid, + 'fromColumnPHIDs' => $from_phids, + ); + + if ($before_phid) { + $replacement['beforePHID'] = $before_phid; + } else if ($after_phid) { + $replacement['afterPHID'] = $after_phid; + } + + queryfx( + $conn_w, + 'UPDATE %T SET transactionType = %s, oldValue = %s, newValue = %s + WHERE id = %d', + $table->getTableName(), + PhabricatorTransactions::TYPE_COLUMNS, + 'null', + phutil_json_encode(array($replacement)), + $id); + } catch (Exception $ex) { + // If anything went awry, just move on. + } + } + + +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c4bc803f52..596901a360 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -139,6 +139,7 @@ phutil_register_library_map(array( 'AphrontDefaultApplicationConfiguration' => 'aphront/configuration/AphrontDefaultApplicationConfiguration.php', 'AphrontDialogResponse' => 'aphront/response/AphrontDialogResponse.php', 'AphrontDialogView' => 'view/AphrontDialogView.php', + 'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php', 'AphrontException' => 'aphront/exception/AphrontException.php', 'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php', 'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php', @@ -253,6 +254,7 @@ phutil_register_library_map(array( 'ConduitBoolParameterType' => 'applications/conduit/parametertype/ConduitBoolParameterType.php', 'ConduitCall' => 'applications/conduit/call/ConduitCall.php', 'ConduitCallTestCase' => 'applications/conduit/call/__tests__/ConduitCallTestCase.php', + 'ConduitColumnsParameterType' => 'applications/conduit/parametertype/ConduitColumnsParameterType.php', 'ConduitConnectConduitAPIMethod' => 'applications/conduit/method/ConduitConnectConduitAPIMethod.php', 'ConduitEpochParameterType' => 'applications/conduit/parametertype/ConduitEpochParameterType.php', 'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php', @@ -1807,6 +1809,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', @@ -1847,6 +1850,8 @@ 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', @@ -1870,11 +1875,13 @@ phutil_register_library_map(array( 'PhabricatorBadgesAwardController' => 'applications/badges/controller/PhabricatorBadgesAwardController.php', 'PhabricatorBadgesAwardQuery' => 'applications/badges/query/PhabricatorBadgesAwardQuery.php', 'PhabricatorBadgesBadge' => 'applications/badges/storage/PhabricatorBadgesBadge.php', + 'PhabricatorBadgesBadgeNameNgrams' => 'applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.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', @@ -1889,6 +1896,7 @@ phutil_register_library_map(array( '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', @@ -1979,6 +1987,7 @@ phutil_register_library_map(array( 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', 'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php', + 'PhabricatorColumnsEditField' => 'applications/transactions/editfield/PhabricatorColumnsEditField.php', 'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php', 'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php', 'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php', @@ -2092,7 +2101,6 @@ phutil_register_library_map(array( 'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php', 'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php', 'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php', - 'PhabricatorCountdownCommentController' => 'applications/countdown/controller/PhabricatorCountdownCommentController.php', 'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php', 'PhabricatorCountdownCountdownPHIDType' => 'applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php', 'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php', @@ -2100,6 +2108,7 @@ phutil_register_library_map(array( 'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php', 'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php', 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', + 'PhabricatorCountdownEditEngine' => 'applications/countdown/editor/PhabricatorCountdownEditEngine.php', 'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php', 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 'PhabricatorCountdownMailReceiver' => 'applications/countdown/mail/PhabricatorCountdownMailReceiver.php', @@ -2183,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', @@ -2272,6 +2280,7 @@ phutil_register_library_map(array( 'PhabricatorEdgesDestructionEngineExtension' => 'infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php', 'PhabricatorEditEngine' => 'applications/transactions/editengine/PhabricatorEditEngine.php', 'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php', + 'PhabricatorEditEngineColumnsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php', 'PhabricatorEditEngineCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php', 'PhabricatorEditEngineConfiguration' => 'applications/transactions/storage/PhabricatorEditEngineConfiguration.php', 'PhabricatorEditEngineConfigurationDefaultCreateController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultCreateController.php', @@ -2317,6 +2326,7 @@ phutil_register_library_map(array( 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', + 'PhabricatorEpochEditField' => 'applications/transactions/editfield/PhabricatorEpochEditField.php', 'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php', 'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php', 'PhabricatorEventListener' => 'infrastructure/events/PhabricatorEventListener.php', @@ -2371,7 +2381,6 @@ 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', @@ -2702,10 +2711,11 @@ phutil_register_library_map(array( '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', @@ -2722,10 +2732,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', @@ -4250,6 +4263,7 @@ phutil_register_library_map(array( 'AphrontView', 'AphrontResponseProducerInterface', ), + 'AphrontEpochHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontException' => 'Exception', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', @@ -4372,6 +4386,7 @@ phutil_register_library_map(array( 'ConduitBoolParameterType' => 'ConduitListParameterType', 'ConduitCall' => 'Phobject', 'ConduitCallTestCase' => 'PhabricatorTestCase', + 'ConduitColumnsParameterType' => 'ConduitParameterType', 'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitEpochParameterType' => 'ConduitListParameterType', 'ConduitException' => 'Exception', @@ -6150,6 +6165,7 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthLoginHandler' => 'Phobject', + 'PhabricatorAuthLogoutConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod', 'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', @@ -6201,6 +6217,8 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhabricatorAuthSessionEngine' => 'Phobject', + 'PhabricatorAuthSessionEngineExtension' => 'Phobject', + 'PhabricatorAuthSessionEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck', @@ -6238,12 +6256,16 @@ phutil_register_library_map(array( 'PhabricatorTokenReceiverInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', + 'PhabricatorNgramsInterface', ), + 'PhabricatorBadgesBadgeNameNgrams' => 'PhabricatorSearchNgrams', 'PhabricatorBadgesCommentController' => 'PhabricatorBadgesController', 'PhabricatorBadgesController' => 'PhabricatorController', 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability', 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO', 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability', + 'PhabricatorBadgesEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController', @@ -6258,6 +6280,7 @@ phutil_register_library_map(array( 'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController', 'PhabricatorBadgesReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec', + 'PhabricatorBadgesSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorBadgesTransactionComment' => 'PhabricatorApplicationTransactionComment', @@ -6369,6 +6392,7 @@ phutil_register_library_map(array( 'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorColumnsEditField' => 'PhabricatorPHIDListEditField', 'PhabricatorCommentEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorCommentEditField' => 'PhabricatorEditField', 'PhabricatorCommentEditType' => 'PhabricatorEditType', @@ -6504,7 +6528,6 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', ), 'PhabricatorCountdownApplication' => 'PhabricatorApplication', - 'PhabricatorCountdownCommentController' => 'PhabricatorCountdownController', 'PhabricatorCountdownController' => 'PhabricatorController', 'PhabricatorCountdownCountdownPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', @@ -6512,6 +6535,7 @@ phutil_register_library_map(array( 'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', + 'PhabricatorCountdownEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownMailReceiver' => 'PhabricatorObjectMailReceiver', @@ -6604,7 +6628,6 @@ phutil_register_library_map(array( 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType', 'PhabricatorDashboardEditController' => 'PhabricatorDashboardController', - 'PhabricatorDashboardHistoryController' => 'PhabricatorDashboardController', 'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO', 'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController', 'PhabricatorDashboardLayoutConfig' => 'Phobject', @@ -6706,6 +6729,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'PhabricatorEditEngineAPIMethod' => 'ConduitAPIMethod', + 'PhabricatorEditEngineColumnsCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineCommentAction' => 'Phobject', 'PhabricatorEditEngineConfiguration' => array( 'PhabricatorSearchDAO', @@ -6755,6 +6779,7 @@ phutil_register_library_map(array( 'PhabricatorEmptyQueryException' => 'Exception', 'PhabricatorEnv' => 'Phobject', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', + 'PhabricatorEpochEditField' => 'PhabricatorEditField', 'PhabricatorEvent' => 'PhutilEvent', 'PhabricatorEventEngine' => 'Phobject', 'PhabricatorEventListener' => 'PhutilEventListener', @@ -6823,7 +6848,6 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), - 'PhabricatorFileAccessTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', 'PhabricatorFileBundleLoader' => 'Phobject', 'PhabricatorFileChunk' => array( 'PhabricatorFileDAO', @@ -7190,10 +7214,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', @@ -7205,6 +7230,7 @@ phutil_register_library_map(array( 'PhabricatorOAuthServerClient' => array( 'PhabricatorOAuthServerDAO', 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorOAuthServerClientAuthorizationPHIDType' => 'PhabricatorPHIDType', @@ -7214,10 +7240,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', diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index f8b01eb94c..3a95b0ebda 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -756,6 +756,7 @@ final class AphrontRequest extends Phobject { foreach ($_SERVER as $key => $value) { if (preg_match('/^HTTP_/', $key)) { // Unmangle the header as best we can. + $key = substr($key, strlen('HTTP_')); $key = str_replace('_', ' ', $key); $key = strtolower($key); $key = ucwords($key); diff --git a/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php new file mode 100644 index 0000000000..f1932bd872 --- /dev/null +++ b/src/aphront/httpparametertype/AphrontEpochHTTPParameterType.php @@ -0,0 +1,37 @@ +getExists($key) || + $request->getExists($key.'_d'); + } + + protected function getParameterValue(AphrontRequest $request, $key) { + return AphrontFormDateControlValue::newFromRequest($request, $key); + } + + protected function getParameterTypeName() { + return 'epoch'; + } + + protected function getParameterFormatDescriptions() { + return array( + pht('An epoch timestamp, as an integer.'), + pht('An absolute date, as a string.'), + pht('A relative date, as a string.'), + pht('Separate date and time inputs, as strings.'), + ); + } + + protected function getParameterExamples() { + return array( + 'v=1460050737', + 'v=2022-01-01', + 'v=yesterday', + 'v_d=2022-01-01&v_t=12:34', + ); + } + +} diff --git a/src/aphront/response/AphrontFileResponse.php b/src/aphront/response/AphrontFileResponse.php index bf58421c68..4688e3bc8f 100644 --- a/src/aphront/response/AphrontFileResponse.php +++ b/src/aphront/response/AphrontFileResponse.php @@ -11,7 +11,6 @@ final class AphrontFileResponse extends AphrontResponse { private $rangeMin; private $rangeMax; private $allowOrigins = array(); - private $fileToken; public function addAllowOrigin($origin) { $this->allowOrigins[] = $origin; @@ -76,15 +75,6 @@ final class AphrontFileResponse extends AphrontResponse { return $this; } - public function setTemporaryFileToken(PhabricatorAuthTemporaryToken $token) { - $this->fileToken = $token; - return $this; - } - - public function getTemporaryFileToken() { - return $this->fileToken; - } - public function getHeaders() { $headers = array( array('Content-Type', $this->getMimeType()), @@ -128,15 +118,4 @@ final class AphrontFileResponse extends AphrontResponse { return $headers; } - public function didCompleteWrite($aborted) { - if (!$aborted) { - $token = $this->getTemporaryFileToken(); - if ($token) { - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $token->delete(); - unset($unguarded); - } - } - } - } diff --git a/src/aphront/response/AphrontProxyResponse.php b/src/aphront/response/AphrontProxyResponse.php index 772c4adaab..eda9e9b719 100644 --- a/src/aphront/response/AphrontProxyResponse.php +++ b/src/aphront/response/AphrontProxyResponse.php @@ -39,6 +39,11 @@ abstract class AphrontProxyResponse return $this; } + public function setCanCDN($can_cdn) { + $this->getProxy()->setCanCDN($can_cdn); + return $this; + } + public function setLastModified($epoch_timestamp) { $this->getProxy()->setLastModified($epoch_timestamp); return $this; diff --git a/src/aphront/response/AphrontResponse.php b/src/aphront/response/AphrontResponse.php index 72dacf977e..dbd60d473d 100644 --- a/src/aphront/response/AphrontResponse.php +++ b/src/aphront/response/AphrontResponse.php @@ -4,6 +4,7 @@ abstract class AphrontResponse extends Phobject { private $request; private $cacheable = false; + private $canCDN; private $responseCode = 200; private $lastModified = null; @@ -66,6 +67,11 @@ abstract class AphrontResponse extends Phobject { return $this; } + public function setCanCDN($can_cdn) { + $this->canCDN = $can_cdn; + return $this; + } + public function setLastModified($epoch_timestamp) { $this->lastModified = $epoch_timestamp; return $this; @@ -186,6 +192,20 @@ abstract class AphrontResponse extends Phobject { public function getCacheHeaders() { $headers = array(); if ($this->cacheable) { + $cache_control = array(); + $cache_control[] = sprintf('max-age=%d', $this->cacheable); + + if ($this->canCDN) { + $cache_control[] = 'public'; + } else { + $cache_control[] = 'private'; + } + + $headers[] = array( + 'Cache-Control', + implode(', ', $cache_control), + ); + $headers[] = array( 'Expires', $this->formatEpochTimestampForHTTPHeader(time() + $this->cacheable), @@ -193,11 +213,7 @@ abstract class AphrontResponse extends Phobject { } else { $headers[] = array( 'Cache-Control', - 'private, no-cache, no-store, must-revalidate', - ); - $headers[] = array( - 'Pragma', - 'no-cache', + 'no-store', ); $headers[] = array( 'Expires', diff --git a/src/applications/almanac/controller/AlmanacBindingViewController.php b/src/applications/almanac/controller/AlmanacBindingViewController.php index ead3e1b4c1..d1ed2178d2 100644 --- a/src/applications/almanac/controller/AlmanacBindingViewController.php +++ b/src/applications/almanac/controller/AlmanacBindingViewController.php @@ -68,7 +68,7 @@ final class AlmanacBindingViewController $this->buildAlmanacPropertiesTable($binding), $timeline, )) - ->addPropertySection(pht('DETAILS'), $details); + ->addPropertySection(pht('Details'), $details); return $this->newPage() ->setTitle($title) diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php index c76fdf11b5..c23e99591d 100644 --- a/src/applications/almanac/controller/AlmanacController.php +++ b/src/applications/almanac/controller/AlmanacController.php @@ -158,7 +158,7 @@ abstract class AlmanacController ->setIcon('fa-plus'); $header = id(new PHUIHeaderView()) - ->setHeader(pht('PROPERTIES')) + ->setHeader(pht('Properties')) ->addActionLink($add_button); return id(new PHUIObjectBoxView()) diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php index 1036dc9e78..f4057ca32f 100644 --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -81,7 +81,7 @@ final class AlmanacServiceViewController $service->getServiceImplementation()->getServiceTypeShortName()); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) + ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($properties); } 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/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php index d591b6313f..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()) { diff --git a/src/applications/auth/controller/PhabricatorLogoutController.php b/src/applications/auth/controller/PhabricatorLogoutController.php index 2600a08313..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); diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php index 98b6a63b5a..1547d6bea8 100644 --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -297,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 )------------------------------------------------------ */ 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/provider/PhabricatorPhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php index 8ea5e71f43..8799c43c6f 100644 --- a/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorPhabricatorAuthProvider.php @@ -201,4 +201,9 @@ final class PhabricatorPhabricatorAuthProvider return true; } + public function getPhabricatorURI() { + $config = $this->getProviderConfig(); + return $config->getProperty(self::PROPERTY_PHABRICATOR_URI); + } + } 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/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 @@ +addPropertySection(pht('DESCRIPTION'), $details); + ->addPropertySection(pht('Description'), $details); return $this->newPage() ->setTitle($title) diff --git a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php index 8c569b5fbb..937dc1bd0e 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php @@ -81,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()) @@ -100,6 +102,8 @@ 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(PhabricatorBadgesQuality::getDropdownQualityMap()), @@ -107,6 +111,7 @@ final class PhabricatorBadgesEditEngine ->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 c8dc23df3d..e71dd622c8 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditor.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php @@ -11,6 +11,10 @@ final class PhabricatorBadgesEditor return pht('Badges'); } + protected function supportsSearch() { + return true; + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); diff --git a/src/applications/badges/query/PhabricatorBadgesQuery.php b/src/applications/badges/query/PhabricatorBadgesQuery.php index c5d8b2dd12..b6277c5f34 100644 --- a/src/applications/badges/query/PhabricatorBadgesQuery.php +++ b/src/applications/badges/query/PhabricatorBadgesQuery.php @@ -36,6 +36,12 @@ final class PhabricatorBadgesQuery return $this; } + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + id(new PhabricatorBadgesBadgeNameNgrams()), + $ngrams); + } + public function needRecipients($need_recipients) { $this->needRecipients = $need_recipients; return $this; @@ -45,6 +51,10 @@ final class PhabricatorBadgesQuery return $this->loadStandardPage($this->newResultObject()); } + protected function getPrimaryTableAlias() { + return 'badges'; + } + public function newResultObject() { return new PhabricatorBadgesBadge(); } @@ -73,28 +83,28 @@ final class PhabricatorBadgesQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'badges.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'badges.phid IN (%Ls)', $this->phids); } if ($this->qualities !== null) { $where[] = qsprintf( $conn, - 'quality IN (%Ls)', + 'badges.quality IN (%Ls)', $this->qualities); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ls)', + 'badges.status IN (%Ls)', $this->statuses); } diff --git a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php index 025ad3b53f..fc2bf7ef1e 100644 --- a/src/applications/badges/query/PhabricatorBadgesSearchEngine.php +++ b/src/applications/badges/query/PhabricatorBadgesSearchEngine.php @@ -15,22 +15,12 @@ final class PhabricatorBadgesSearchEngine return new PhabricatorBadgesQuery(); } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'statuses', - $this->readListFromRequest($request, 'statuses')); - - $saved->setParameter( - 'qualities', - $this->readListFromRequest($request, 'qualities')); - - return $saved; - } - protected function buildCustomSearchFields() { return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('name') + ->setDescription(pht('Search for badges by name substring.')), id(new PhabricatorSearchCheckboxesField()) ->setKey('qualities') ->setLabel(pht('Quality')) @@ -55,6 +45,10 @@ final class PhabricatorBadgesSearchEngine $query->withQualities($map['qualities']); } + if ($map['name'] !== null) { + $query->withNameNgrams($map['name']); + } + return $query; } diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php index c5b29df09d..91ed3cf34d 100644 --- a/src/applications/badges/storage/PhabricatorBadgesBadge.php +++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php @@ -7,7 +7,9 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO PhabricatorSubscribableInterface, PhabricatorTokenReceiverInterface, PhabricatorFlaggableInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface, + PhabricatorNgramsInterface { protected $name; protected $flavor; @@ -49,6 +51,8 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO ->setQuality(PhabricatorBadgesQuality::DEFAULT_QUALITY) ->setCreatorPHID($actor->getPHID()) ->setEditPolicy($edit_policy) + ->setFlavor('') + ->setDescription('') ->setStatus(self::STATUS_ACTIVE); } @@ -56,7 +60,7 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text255', + 'name' => 'sort255', 'flavor' => 'text255', 'description' => 'text', 'icon' => 'text255', @@ -190,4 +194,46 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO $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(); + } + +/* -( PhabricatorNgramInterface )------------------------------------------ */ + + + public function newNgrams() { + return array( + id(new PhabricatorBadgesBadgeNameNgrams()) + ->setValue($this->getName()), + ); + } + } diff --git a/src/applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.php b/src/applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.php new file mode 100644 index 0000000000..c5db47f42d --- /dev/null +++ b/src/applications/badges/storage/PhabricatorBadgesBadgeNameNgrams.php @@ -0,0 +1,18 @@ +badge; $handles = $this->handles; + $awards = mpull($badge->getAwards(), null, 'getRecipientPHID'); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -34,8 +35,17 @@ final class PhabricatorBadgesRecipientsListView extends AphrontView { $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()); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 5880db1210..7c3b34bc09 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -94,8 +94,8 @@ final class PhabricatorCalendarEventViewController $add_comment_form, )) ->setCurtain($curtain) - ->addPropertySection(pht('DETAILS'), $details) - ->addPropertySection(pht('DESCRIPTION'), $description); + ->addPropertySection(pht('Details'), $details) + ->addPropertySection(pht('Description'), $description); return $this->newPage() ->setTitle($page_title) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 06416e43a1..51cd0ea25e 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -90,24 +90,11 @@ final class PhabricatorCalendarEventQuery } protected function loadPage() { - $table = new PhabricatorCalendarEvent(); - $conn_r = $table->establishConnection('r'); + $events = $this->loadStandardPage($this->newResultObject()); + $viewer = $this->getViewer(); - - $data = queryfx_all( - $conn_r, - 'SELECT event.* FROM %T event %Q %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - $events = $table->loadAllFromArray($data); - foreach ($events as $event) { - $event->applyViewerTimezone($this->getViewer()); + $event->applyViewerTimezone($viewer); } if (!$this->generateGhosts) { @@ -115,6 +102,15 @@ final class PhabricatorCalendarEventQuery } $enforced_end = null; + $raw_limit = $this->getRawResultLimit(); + + if (!$raw_limit && !$this->rangeEnd) { + throw new Exception( + pht( + 'Event queries which generate ghost events must include either a '. + 'result limit or an end date, because they may otherwise generate '. + 'an infinite number of results. This query has neither.')); + } foreach ($events as $key => $event) { $sequence_start = 0; @@ -176,12 +172,12 @@ final class PhabricatorCalendarEventQuery $sequence_end++; $datetime->modify($modify_key); $date = $datetime->format('U'); - if ($sequence_end > $this->getRawResultLimit() + $sequence_start) { + if ($sequence_end > $raw_limit + $sequence_start) { break; } } } else { - $sequence_end = $this->getRawResultLimit() + $sequence_start; + $sequence_end = $raw_limit + $sequence_start; } $sequence_start = max(1, $sequence_start); @@ -190,10 +186,17 @@ final class PhabricatorCalendarEventQuery $events[] = $event->generateNthGhost($index, $viewer); } - if (count($events) >= $this->getRawResultLimit()) { - $events = msort($events, 'getDateFrom'); - $events = array_slice($events, 0, $this->getRawResultLimit(), true); - $enforced_end = last($events)->getDateFrom(); + // NOTE: We're slicing results every time because this makes it cheaper + // to generate future ghosts. If we already have 100 events that occur + // before July 1, we know we never need to generate ghosts after that + // because they couldn't possibly ever appear in the result set. + + if ($raw_limit) { + if (count($events) >= $raw_limit) { + $events = msort($events, 'getDateFrom'); + $events = array_slice($events, 0, $raw_limit, true); + $enforced_end = last($events)->getDateFrom(); + } } } } @@ -251,61 +254,61 @@ final class PhabricatorCalendarEventQuery return $parts; } - 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, 'event.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'event.phid IN (%Ls)', $this->phids); } if ($this->rangeBegin) { $where[] = qsprintf( - $conn_r, + $conn, 'event.dateTo >= %d OR event.isRecurring = 1', $this->rangeBegin); } if ($this->rangeEnd) { $where[] = qsprintf( - $conn_r, + $conn, 'event.dateFrom <= %d', $this->rangeEnd); } if ($this->inviteePHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'invitee.inviteePHID IN (%Ls)', $this->inviteePHIDs); } if ($this->creatorPHIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'event.userPHID IN (%Ls)', $this->creatorPHIDs); } if ($this->isCancelled !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'event.isCancelled = %d', (int)$this->isCancelled); } if ($this->eventsWithNoParent == true) { $where[] = qsprintf( - $conn_r, + $conn, 'event.instanceOfEventPHID IS NULL'); } @@ -314,20 +317,19 @@ final class PhabricatorCalendarEventQuery foreach ($this->instanceSequencePairs as $pair) { $sql[] = qsprintf( - $conn_r, + $conn, '(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)', $pair[0], $pair[1]); } + $where[] = qsprintf( - $conn_r, + $conn, '%Q', implode(' OR ', $sql)); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function getPrimaryTableAlias() { diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php index 01f6424382..4e6feeac6c 100644 --- a/src/applications/celerity/controller/CelerityResourceController.php +++ b/src/applications/celerity/controller/CelerityResourceController.php @@ -140,6 +140,7 @@ abstract class CelerityResourceController extends PhabricatorController { private function makeResponseCacheable(AphrontResponse $response) { $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); $response->setLastModified(time()); + $response->setCanCDN(true); return $response; } 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/parametertype/ConduitColumnsParameterType.php b/src/applications/conduit/parametertype/ConduitColumnsParameterType.php new file mode 100644 index 0000000000..c6669fae06 --- /dev/null +++ b/src/applications/conduit/parametertype/ConduitColumnsParameterType.php @@ -0,0 +1,38 @@ +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; diff --git a/src/applications/config/controller/PhabricatorConfigAllController.php b/src/applications/config/controller/PhabricatorConfigAllController.php index d805168eb1..e46db665de 100644 --- a/src/applications/config/controller/PhabricatorConfigAllController.php +++ b/src/applications/config/controller/PhabricatorConfigAllController.php @@ -58,18 +58,20 @@ final class PhabricatorConfigAllController $panel->setHeaderText(pht('Current Settings')); $panel->setTable($table); - $nav = $this->buildSideNavView(); $nav->selectFilter('all/'); - $nav->setCrumbs($crumbs); - $nav->appendChild($panel); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( + $panel, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } } diff --git a/src/applications/config/controller/PhabricatorConfigCacheController.php b/src/applications/config/controller/PhabricatorConfigCacheController.php index 67fbf6e120..ab9ddb3ad0 100644 --- a/src/applications/config/controller/PhabricatorConfigCacheController.php +++ b/src/applications/config/controller/PhabricatorConfigCacheController.php @@ -18,18 +18,17 @@ final class PhabricatorConfigCacheController $code_box = $this->renderCodeBox(); $data_box = $this->renderDataBox(); - $nav->appendChild( - array( - $crumbs, + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( $code_box, $data_box, - )); + )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function renderCodeBox() { diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php index 22df5104e4..0366b90b16 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseIssueController.php @@ -153,17 +153,17 @@ final class PhabricatorConfigDatabaseIssueController $nav = $this->buildSideNavView(); $nav->selectFilter('dbissue/'); - $nav->appendChild( - array( - $crumbs, - $table_box, - )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( + $table_box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php index d886e98d74..b03ce2a9fc 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -91,14 +91,16 @@ final class PhabricatorConfigDatabaseStatusController $crumbs->addTextCrumb(pht('Database Status')); } - $nav->setCrumbs($crumbs); - $nav->appendChild($body); + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( + $body, + )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } 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..2709041f94 100644 --- a/src/applications/config/controller/PhabricatorConfigHistoryController.php +++ b/src/applications/config/controller/PhabricatorConfigHistoryController.php @@ -31,22 +31,22 @@ final class PhabricatorConfigHistoryController $title = pht('Settings History'); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->setBorder(true); $crumbs->addTextCrumb('Config', $this->getApplicationURI()); $crumbs->addTextCrumb($title, '/config/history/'); $nav = $this->buildSideNavView(); $nav->selectFilter('history/'); - $nav->setCrumbs($crumbs); - $nav->appendChild($timeline); - return $this->buildApplicationPage( - array( - $nav, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( + $timeline, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } 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..02bec4e082 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -56,21 +56,22 @@ final class PhabricatorConfigIssueListController ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); } - $nav->appendChild($setup_issues); - $title = pht('Setup Issues'); $crumbs = $this ->buildApplicationCrumbs($nav) ->addTextCrumb(pht('Setup'), $this->getApplicationURI('issue/')); - $nav->setCrumbs($crumbs); + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( + $setup_issues, + )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } 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..8b0ff42e50 100644 --- a/src/applications/config/controller/PhabricatorConfigListController.php +++ b/src/applications/config/controller/PhabricatorConfigListController.php @@ -23,23 +23,21 @@ final class PhabricatorConfigListController ->setHeaderText(pht('Applications Configuration')) ->setObjectList($apps_list); - $nav->appendChild( - array( - $core, - $apps, - )); - $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb(pht('Config'), $this->getApplicationURI()); - $nav->setCrumbs($crumbs); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( + $core, + $apps, )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildConfigOptionsList(array $groups, $type) { diff --git a/src/applications/config/controller/PhabricatorConfigModuleController.php b/src/applications/config/controller/PhabricatorConfigModuleController.php index 76343ecc11..3a67a8cdb4 100644 --- a/src/applications/config/controller/PhabricatorConfigModuleController.php +++ b/src/applications/config/controller/PhabricatorConfigModuleController.php @@ -14,24 +14,24 @@ 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.'/'); - $nav->appendChild( - array( - $crumbs, + + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( $content, )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $name, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } } diff --git a/src/applications/config/controller/PhabricatorConfigWelcomeController.php b/src/applications/config/controller/PhabricatorConfigWelcomeController.php index e2d704e0b7..addf80e62f 100644 --- a/src/applications/config/controller/PhabricatorConfigWelcomeController.php +++ b/src/applications/config/controller/PhabricatorConfigWelcomeController.php @@ -15,14 +15,16 @@ final class PhabricatorConfigWelcomeController ->buildApplicationCrumbs() ->addTextCrumb(pht('Welcome')); - $nav->setCrumbs($crumbs); - $nav->appendChild($this->buildWelcomeScreen($request)); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn(array( + $this->buildWelcomeScreen($request), )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } public function buildWelcomeScreen(AphrontRequest $request) { 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/countdown/application/PhabricatorCountdownApplication.php b/src/applications/countdown/application/PhabricatorCountdownApplication.php index d6c62d7e1c..a446c88a88 100644 --- a/src/applications/countdown/application/PhabricatorCountdownApplication.php +++ b/src/applications/countdown/application/PhabricatorCountdownApplication.php @@ -46,9 +46,7 @@ final class PhabricatorCountdownApplication extends PhabricatorApplication { => 'PhabricatorCountdownViewController', 'comment/(?P[1-9]\d*)/' => 'PhabricatorCountdownCommentController', - 'edit/(?:(?P[1-9]\d*)/)?' - => 'PhabricatorCountdownEditController', - 'create/' + $this->getEditRoutePattern('edit/') => 'PhabricatorCountdownEditController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorCountdownDeleteController', diff --git a/src/applications/countdown/controller/PhabricatorCountdownCommentController.php b/src/applications/countdown/controller/PhabricatorCountdownCommentController.php deleted file mode 100644 index 03b2001289..0000000000 --- a/src/applications/countdown/controller/PhabricatorCountdownCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $countdown = id(new PhabricatorCountdownQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$countdown) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $view_uri = '/'.$countdown->getMonogram(); - - $xactions = array(); - $xactions[] = id(new PhabricatorCountdownTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new PhabricatorCountdownTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new PhabricatorCountdownEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($countdown, $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/countdown/controller/PhabricatorCountdownController.php b/src/applications/countdown/controller/PhabricatorCountdownController.php index 37b0e49a68..4d09d7ed5b 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownController.php @@ -7,16 +7,5 @@ abstract class PhabricatorCountdownController extends PhabricatorController { ->setSearchEngine(new PhabricatorCountdownSearchEngine()); } - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Countdown')) - ->setHref($this->getApplicationURI('create/')) - ->setIcon('fa-plus-square')); - - return $crumbs; - } } diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php index ea89c1591b..7bdd4236b7 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php @@ -4,205 +4,9 @@ final class PhabricatorCountdownEditController extends PhabricatorCountdownController { public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $countdown = id(new PhabricatorCountdownQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$countdown) { - return new Aphront404Response(); - } - $date_value = AphrontFormDateControlValue::newFromEpoch( - $viewer, - $countdown->getEpoch()); - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $countdown->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - $title = pht('Edit Countdown: %s', $countdown->getTitle()); - } else { - $title = pht('Create Countdown'); - $countdown = PhabricatorCountdown::initializeNewCountdown($viewer); - $date_value = AphrontFormDateControlValue::newFromEpoch( - $viewer, PhabricatorTime::getNow()); - $v_projects = array(); - } - - $errors = array(); - $e_text = true; - $e_epoch = null; - - $v_text = $countdown->getTitle(); - $v_desc = $countdown->getDescription(); - $v_space = $countdown->getSpacePHID(); - $v_view = $countdown->getViewPolicy(); - $v_edit = $countdown->getEditPolicy(); - - if ($request->isFormPost()) { - $v_text = $request->getStr('title'); - $v_desc = $request->getStr('description'); - $v_space = $request->getStr('spacePHID'); - $date_value = AphrontFormDateControlValue::newFromRequest( - $request, - 'epoch'); - $v_view = $request->getStr('viewPolicy'); - $v_edit = $request->getStr('editPolicy'); - $v_projects = $request->getArr('projects'); - - $type_title = PhabricatorCountdownTransaction::TYPE_TITLE; - $type_epoch = PhabricatorCountdownTransaction::TYPE_EPOCH; - $type_description = PhabricatorCountdownTransaction::TYPE_DESCRIPTION; - $type_space = PhabricatorTransactions::TYPE_SPACE; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new PhabricatorCountdownTransaction()) - ->setTransactionType($type_title) - ->setNewValue($v_text); - - $xactions[] = id(new PhabricatorCountdownTransaction()) - ->setTransactionType($type_epoch) - ->setNewValue($date_value); - - $xactions[] = id(new PhabricatorCountdownTransaction()) - ->setTransactionType($type_description) - ->setNewValue($v_desc); - - $xactions[] = id(new PhabricatorCountdownTransaction()) - ->setTransactionType($type_space) - ->setNewValue($v_space); - - $xactions[] = id(new PhabricatorCountdownTransaction()) - ->setTransactionType($type_view) - ->setNewValue($v_view); - - $xactions[] = id(new PhabricatorCountdownTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhabricatorCountdownTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new PhabricatorCountdownEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($countdown, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI('/'.$countdown->getMonogram()); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_title = $ex->getShortMessage($type_title); - $e_epoch = $ex->getShortMessage($type_epoch); - } - - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->setBorder(true); - - $cancel_uri = '/countdown/'; - if ($countdown->getID()) { - $cancel_uri = '/countdown/'.$countdown->getID().'/'; - $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()) - ->setViewer($viewer) - ->setObject($countdown) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->setAction($request->getRequestURI()->getPath()) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Title')) - ->setValue($v_text) - ->setName('title') - ->setError($e_text)) - ->appendControl( - id(new AphrontFormDateControl()) - ->setName('epoch') - ->setLabel(pht('End Date')) - ->setError($e_epoch) - ->setValue($date_value)) - ->appendControl( - id(new PhabricatorRemarkupControl()) - ->setName('description') - ->setLabel(pht('Description')) - ->setValue($v_desc)) - ->appendControl( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($countdown) - ->setPolicies($policies) - ->setSpacePHID($v_space) - ->setValue($v_view) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) - ->appendControl( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($countdown) - ->setPolicies($policies) - ->setValue($v_edit) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Projects')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($submit_label)); - - $form_box = id(new PHUIObjectBoxView()) - ->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($title) - ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + return id(new PhabricatorCountdownEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php index 382f1a0306..b06cfd0e7f 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownListController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php @@ -13,4 +13,14 @@ final class PhabricatorCountdownListController ->buildResponse(); } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhabricatorCountdownEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + } diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 6e259df555..56911bf43b 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -55,12 +55,15 @@ final class PhabricatorCountdownViewController $timeline = $this->buildTransactionTimeline( $countdown, new PhabricatorCountdownTransactionQuery()); - $add_comment = $this->buildCommentForm($countdown); + + $comment_view = id(new PhabricatorCountdownEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($countdown); $content = array( $countdown_view, $timeline, - $add_comment, + $comment_view, ); $view = id(new PHUITwoColumnView()) @@ -135,25 +138,4 @@ final class PhabricatorCountdownViewController ->setContent($content); } - private function buildCommentForm(PhabricatorCountdown $countdown) { - $viewer = $this->getViewer(); - - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $add_comment_header = $is_serious - ? pht('Add Comment') - : pht('Last Words'); - - $draft = PhabricatorDraft::newFromUserAndKey( - $viewer, $countdown->getPHID()); - - return id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($countdown->getPHID()) - ->setDraft($draft) - ->setHeaderText($add_comment_header) - ->setAction($this->getApplicationURI('/comment/'.$countdown->getID().'/')) - ->setSubmitButtonName(pht('Add Comment')); - } - } diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php new file mode 100644 index 0000000000..c1d5f6753a --- /dev/null +++ b/src/applications/countdown/editor/PhabricatorCountdownEditEngine.php @@ -0,0 +1,108 @@ +getViewer()); + } + + protected function newObjectQuery() { + return id(new PhabricatorCountdownQuery()); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Countdown'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Countdown'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Countdown: %s', $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Countdown'); + } + + protected function getObjectCreateShortText() { + return pht('Create Countdown'); + } + + protected function getObjectName() { + return pht('Countdown'); + } + + protected function getCommentViewHeaderText($object) { + return pht('Last Words'); + } + + protected function getCommentViewButtonText($object) { + return pht('Contemplate Infinity'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + $epoch_value = $object->getEpoch(); + if ($epoch_value === null) { + $epoch_value = PhabricatorTime::getNow(); + } + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setIsRequired(true) + ->setTransactionType(PhabricatorCountdownTransaction::TYPE_TITLE) + ->setDescription(pht('The countdown name.')) + ->setConduitDescription(pht('Rename the countdown.')) + ->setConduitTypeDescription(pht('New countdown name.')) + ->setValue($object->getTitle()), + id(new PhabricatorEpochEditField()) + ->setKey('epoch') + ->setLabel(pht('End Date')) + ->setTransactionType(PhabricatorCountdownTransaction::TYPE_EPOCH) + ->setDescription(pht('Date when the countdown ends.')) + ->setConduitDescription(pht('Change the end date of the countdown.')) + ->setConduitTypeDescription(pht('New countdown end date.')) + ->setValue($epoch_value), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Description')) + ->setTransactionType(PhabricatorCountdownTransaction::TYPE_DESCRIPTION) + ->setDescription(pht('Description of the countdown.')) + ->setConduitDescription(pht('Change the countdown description.')) + ->setConduitTypeDescription(pht('New description.')) + ->setValue($object->getDescription()), + ); + } + +} diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index e1eddf2270..37f23f6bd6 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -120,19 +120,27 @@ final class PhabricatorCountdownEditor } break; case PhabricatorCountdownTransaction::TYPE_EPOCH: - $date_value = AphrontFormDateControlValue::newFromEpoch( - $this->requireActor(), - $object->getEpoch()); - if (!$date_value->isValid()) { + if (!$object->getEpoch() && !$xactions) { $error = new PhabricatorApplicationTransactionValidationError( $type, - pht('Invalid'), - pht('You must give the countdown a valid end date.'), - nonempty(last($xactions), null)); - + pht('Required'), + pht('You must give the countdown an end date.'), + null); $error->setIsMissingFieldError(true); $errors[] = $error; } + + foreach ($xactions as $xaction) { + $value = $xaction->getNewValue(); + if (!$value->isValid()) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht('You must give the countdown a valid end date.'), + $xaction); + $errors[] = $error; + } + } break; } diff --git a/src/applications/countdown/query/PhabricatorCountdownQuery.php b/src/applications/countdown/query/PhabricatorCountdownQuery.php index 17fde126f8..e6c410ee49 100644 --- a/src/applications/countdown/query/PhabricatorCountdownQuery.php +++ b/src/applications/countdown/query/PhabricatorCountdownQuery.php @@ -74,4 +74,35 @@ final class PhabricatorCountdownQuery return 'PhabricatorCountdownApplication'; } + public function getBuiltinOrders() { + return array( + 'ending' => array( + 'vector' => array('-epoch', '-id'), + 'name' => pht('End Date (Past to Future)'), + ), + 'unending' => array( + 'vector' => array('epoch', 'id'), + 'name' => pht('End Date (Future to Past)'), + ), + ) + parent::getBuiltinOrders(); + } + + public function getOrderableColumns() { + return array( + 'epoch' => array( + 'table' => $this->getPrimaryTableAlias(), + 'column' => 'epoch', + 'type' => 'int', + ), + ) + parent::getOrderableColumns(); + } + + protected function getPagingValueMap($cursor, array $keys) { + $countdown = $this->loadCursorObject($cursor); + return array( + 'epoch' => $countdown->getEpoch(), + 'id' => $countdown->getID(), + ); + } + } diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php index 79b329385a..93aec9a037 100644 --- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -30,20 +30,18 @@ final class PhabricatorCountdownSearchEngine } protected function buildCustomSearchFields() { - return array( - id(new PhabricatorUsersSearchField()) - ->setLabel(pht('Authors')) - ->setKey('authorPHIDs') - ->setAliases(array('author', 'authors')), - - id(new PhabricatorSearchCheckboxesField()) - ->setKey('upcoming') - ->setOptions(array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Authors')) + ->setKey('authorPHIDs') + ->setAliases(array('author', 'authors')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('upcoming') + ->setOptions( + array( 'upcoming' => pht('Show only upcoming countdowns.'), )), - ); - + ); } protected function getURI($path) { diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php index 52a395f0c2..ad9b1b33aa 100644 --- a/src/applications/countdown/storage/PhabricatorCountdown.php +++ b/src/applications/countdown/storage/PhabricatorCountdown.php @@ -28,10 +28,13 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO $view_policy = $app->getPolicy( PhabricatorCountdownDefaultViewCapability::CAPABILITY); + $edit_policy = $app->getPolicy( + PhabricatorCountdownDefaultEditCapability::CAPABILITY); + return id(new PhabricatorCountdown()) ->setAuthorPHID($actor->getPHID()) ->setViewPolicy($view_policy) - ->setEpoch(PhabricatorTime::getNow()) + ->setEditPolicy($edit_policy) ->setSpacePHID($actor->getDefaultSpacePHID()); } @@ -43,6 +46,14 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO 'description' => 'text', 'mailKey' => 'bytes20', ), + self::CONFIG_KEY_SCHEMA => array( + 'key_epoch' => array( + 'columns' => array('epoch'), + ), + 'key_author' => array( + 'columns' => array('authorPHID', 'epoch'), + ), + ), ) + parent::getConfiguration(); } @@ -55,6 +66,10 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO return 'C'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php index 72303a369f..247466ffe1 100644 --- a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php +++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php @@ -33,42 +33,20 @@ final class PhabricatorCountdownTransaction $type = $this->getTransactionType(); switch ($type) { case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created this countdown.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s renamed this countdown from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $old, - $new); - } - break; + return pht( + '%s renamed this countdown from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); case self::TYPE_DESCRIPTION: - if ($old === null) { - return pht( - '%s set the description of this countdown.', - $this->renderHandleLink($author_phid)); - } else { - return pht( - '%s edited the description of this countdown.', - $this->renderHandleLink($author_phid)); - } - break; + return pht( + '%s edited the description of this countdown.', + $this->renderHandleLink($author_phid)); case self::TYPE_EPOCH: - if ($old === null) { - return pht( - '%s set this countdown to end on %s.', - $this->renderHandleLink($author_phid), - phabricator_datetime($new, $this->getViewer())); - } else if ($old != $new) { - return pht( - '%s updated this countdown to end on %s.', - $this->renderHandleLink($author_phid), - phabricator_datetime($new, $this->getViewer())); - } - break; + return pht( + '%s updated this countdown to end on %s.', + $this->renderHandleLink($author_phid), + phabricator_datetime($new, $this->getViewer())); } return parent::getTitle(); @@ -84,47 +62,20 @@ final class PhabricatorCountdownTransaction $type = $this->getTransactionType(); switch ($type) { case self::TYPE_TITLE: - if ($old === null) { - return pht( - '%s created %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s renamed %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; + return pht( + '%s renamed %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); case self::TYPE_DESCRIPTION: - if ($old === null) { - return pht( - '%s set the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s edited the description of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; + return pht( + '%s edited the description of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); case self::TYPE_EPOCH: - if ($old === null) { - return pht( - '%s set the end date of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - - } else { - return pht( - '%s edited the end date of %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - } - break; + return pht( + '%s edited the end date of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); } return parent::getTitleForFeed(); @@ -150,15 +101,6 @@ final class PhabricatorCountdownTransaction return $tags; } - public function shouldHide() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_DESCRIPTION: - return ($old === null); - } - return parent::shouldHide(); - } - public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: diff --git a/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php b/src/applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php index f32892cf4c..f794024591 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/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..4e31b2d2ae 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..9fd9e1840d 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/controller/DifferentialRevisionEditController.php b/src/applications/differential/controller/DifferentialRevisionEditController.php index a21e1b2592..eb3aa08521 100644 --- a/src/applications/differential/controller/DifferentialRevisionEditController.php +++ b/src/applications/differential/controller/DifferentialRevisionEditController.php @@ -172,13 +172,13 @@ final class DifferentialRevisionEditController if ($revision->getID()) { if ($diff) { $header_icon = 'fa-upload'; - $title = pht('Update Differential Revision'); + $title = pht('Update Revision'); $crumbs->addTextCrumb( 'D'.$revision->getID(), '/differential/diff/'.$diff->getID().'/'); } else { $header_icon = 'fa-pencil'; - $title = pht('Edit Differential Revision'); + $title = pht('Edit Revision: %s', $revision->getTitle()); $crumbs->addTextCrumb( 'D'.$revision->getID(), '/D'.$revision->getID()); diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 61fffd9b2a..ef333a4197 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -480,7 +480,7 @@ final class DifferentialRevisionViewController extends DifferentialController { } $header = id(new PHUIHeaderView()) - ->setHeader(pht('DETAILS')); + ->setHeader(pht('Details')); return id(new PHUIObjectBoxView()) ->setHeader($header) @@ -1033,7 +1033,7 @@ final class DifferentialRevisionViewController extends DifferentialController { } $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DIFF DETAIL')) + ->setHeaderText(pht('Diff Detail')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setUser($viewer); @@ -1117,7 +1117,7 @@ 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()) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index a79613cfa9..7616922aa8 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -310,7 +310,7 @@ final class DiffusionBrowseController extends DiffusionController { )); if ($properties) { - $view->addPropertySection(pht('DETAILS'), $properties); + $view->addPropertySection(pht('Details'), $properties); } $title = array($basename, $repository->getDisplayName()); @@ -413,7 +413,7 @@ final class DiffusionBrowseController extends DiffusionController { )); if ($details) { - $view->addPropertySection(pht('DETAILS'), $details); + $view->addPropertySection(pht('Details'), $details); } return $this->newPage() @@ -1409,7 +1409,7 @@ 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()) @@ -1426,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) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 890c512870..e2f0d51f4a 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -57,14 +57,13 @@ 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(); @@ -356,8 +355,8 @@ final class DiffusionCommitController extends DiffusionController { $change_list, $add_comment, )) - ->addPropertySection(pht('DESCRIPTION'), $detail_list) - ->addPropertySection(pht('DETAILS'), $details) + ->addPropertySection(pht('Description'), $detail_list) + ->addPropertySection(pht('Details'), $details) ->setCurtain($curtain); $page = $this->newPage() diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 792bcc4249..b9e8fa40c6 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -288,7 +288,7 @@ final class DiffusionRepositoryController extends DiffusionController { $description = new PHUIRemarkupView($viewer, $description); $view->addTextContent($description); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DESCRIPTION')) + ->setHeaderText(pht('Description')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($view); } @@ -354,7 +354,7 @@ final class DiffusionRepositoryController extends DiffusionController { } $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) + ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($view); diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 80fb224163..a5871ca074 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -991,11 +991,12 @@ final class DiffusionServeController extends DiffusionController { // $no_authorization = 'Basic '.base64_encode('none'); - $get_uri = $file->getCDNURIWithToken(); + $get_uri = $file->getCDNURI(); $actions['download'] = array( 'href' => $get_uri, 'header' => array( 'Authorization' => $no_authorization, + 'X-Phabricator-Request-Type' => 'git-lfs', ), ); } else { 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/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/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/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/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/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index 848560f099..dfae99d201 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -4,7 +4,6 @@ final class PhabricatorFileDataController extends PhabricatorFileController { private $phid; private $key; - private $token; private $file; public function shouldRequireLogin() { @@ -15,7 +14,6 @@ final class PhabricatorFileDataController extends PhabricatorFileController { $viewer = $request->getViewer(); $this->phid = $request->getURIData('phid'); $this->key = $request->getURIData('key'); - $this->token = $request->getURIData('token'); $alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain'); $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); @@ -24,99 +22,40 @@ final class PhabricatorFileDataController extends PhabricatorFileController { $req_domain = $request->getHost(); $main_domain = id(new PhutilURI($base_uri))->getDomain(); - $cache_response = true; - if (empty($alt) || $main_domain == $alt_domain) { - // Alternate files domain isn't configured or it's set - // to the same as the default domain - - $response = $this->loadFile($viewer); - if ($response) { - return $response; - } - $file = $this->getFile(); - - // when the file is not CDNable, don't allow cache - $cache_response = $file->getCanCDN(); + if (!strlen($alt) || $main_domain == $alt_domain) { + // No alternate domain. + $should_redirect = false; + $use_viewer = $viewer; + $is_alternate_domain = false; } else if ($req_domain != $alt_domain) { - // Alternate domain is configured but this request isn't using it + // Alternate domain, but this request is on the main domain. + $should_redirect = true; + $use_viewer = $viewer; + $is_alternate_domain = false; + } else { + // Alternate domain, and on the alternate domain. + $should_redirect = false; + $use_viewer = PhabricatorUser::getOmnipotentUser(); + $is_alternate_domain = true; + } - $response = $this->loadFile($viewer); - if ($response) { - return $response; - } - $file = $this->getFile(); + $response = $this->loadFile($use_viewer); + if ($response) { + return $response; + } - // if the user can see the file, generate a token; - // redirect to the alt domain with the token; - $token_uri = $file->getCDNURIWithToken(); - $token_uri = new PhutilURI($token_uri); - $token_uri = $this->addURIParameters($token_uri); + $file = $this->getFile(); + if ($should_redirect) { return id(new AphrontRedirectResponse()) ->setIsExternal(true) - ->setURI($token_uri); - - } else { - // We are using the alternate domain. We don't have authentication - // on this domain, so we bypass policy checks when loading the file. - - $bypass_policies = PhabricatorUser::getOmnipotentUser(); - $response = $this->loadFile($bypass_policies); - if ($response) { - return $response; - } - $file = $this->getFile(); - - $acquire_token_uri = id(new PhutilURI($file->getViewURI())) - ->setDomain($main_domain); - $acquire_token_uri = $this->addURIParameters($acquire_token_uri); - - if ($this->token) { - // validate the token, if it is valid, continue - $validated_token = $file->validateOneTimeToken($this->token); - - if (!$validated_token) { - $dialog = $this->newDialog() - ->setShortTitle(pht('Expired File')) - ->setTitle(pht('File Link Has Expired')) - ->appendParagraph( - pht( - 'The link you followed to view this file is invalid or '. - 'expired.')) - ->appendParagraph( - pht( - 'Continue to generate a new link to the file. You may be '. - 'required to log in.')) - ->addCancelButton( - $acquire_token_uri, - pht('Continue')); - - // Build an explicit response so we can respond with HTTP/403 instead - // of HTTP/200. - $response = id(new AphrontDialogResponse()) - ->setDialog($dialog) - ->setHTTPResponseCode(403); - - return $response; - } - // return the file data without cache headers - $cache_response = false; - } else if (!$file->getCanCDN()) { - // file cannot be served via cdn, and no token given - // redirect to the main domain to aquire a token - - // This is marked as an "external" URI because it is fully qualified. - return id(new AphrontRedirectResponse()) - ->setIsExternal(true) - ->setURI($acquire_token_uri); - } + ->setURI($file->getCDNURI()); } $response = new AphrontFileResponse(); - if ($cache_response) { - $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); - } + $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); + $response->setCanCDN($file->getCanCDN()); $begin = null; $end = null; @@ -138,19 +77,18 @@ final class PhabricatorFileDataController extends PhabricatorFileController { $response->setHTTPResponseCode(206); $response->setRange($begin, ($end - 1)); } - } else if (isset($validated_token)) { - // We set this on the response, and the response deletes it after the - // transfer completes. This allows transfers to be resumed, in theory. - $response->setTemporaryFileToken($validated_token); } $is_viewable = $file->isViewableInBrowser(); $force_download = $request->getExists('download'); + $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type'); + $is_lfs = ($request_type == 'git-lfs'); + if ($is_viewable && !$force_download) { $response->setMimeType($file->getViewableMimeType()); } else { - if (!$request->isHTTPPost() && !$alt_domain) { + if (!$request->isHTTPPost() && !$is_alternate_domain && !$is_lfs) { // NOTE: Require POST to download files from the primary domain. We'd // rather go full-bore and do a real CSRF check, but can't currently // authenticate users on the file domain. This should blunt any @@ -174,20 +112,6 @@ final class PhabricatorFileDataController extends PhabricatorFileController { return $response; } - /** - * Add passthrough parameters to the URI so they aren't lost when we - * redirect to acquire tokens. - */ - private function addURIParameters(PhutilURI $uri) { - $request = $this->getRequest(); - - if ($request->getBool('download')) { - $uri->setQueryParam('download', 1); - } - - return $uri; - } - private function loadFile(PhabricatorUser $viewer) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) @@ -198,22 +122,46 @@ final class PhabricatorFileDataController extends PhabricatorFileController { return new Aphront404Response(); } + // We may be on the CDN domain, so we need to use a fully-qualified URI + // here to make sure we end up back on the main domain. + $info_uri = PhabricatorEnv::getURI($file->getInfoURI()); + + if (!$file->validateSecretKey($this->key)) { - return new Aphront403Response(); + $dialog = $this->newDialog() + ->setTitle(pht('Invalid Authorization')) + ->appendParagraph( + pht( + 'The link you followed to access this file is no longer '. + 'valid. The visibility of the file may have changed after '. + 'the link was generated.')) + ->appendParagraph( + pht( + 'You can continue to the file detail page to get more '. + 'information and attempt to access the file.')) + ->addCancelButton($info_uri, pht('Continue')); + + return id(new AphrontDialogResponse()) + ->setDialog($dialog) + ->setHTTPResponseCode(404); } if ($file->getIsPartial()) { - // We may be on the CDN domain, so we need to use a fully-qualified URI - // here to make sure we end up back on the main domain. - $info_uri = PhabricatorEnv::getURI($file->getInfoURI()); - - return $this->newDialog() + $dialog = $this->newDialog() ->setTitle(pht('Partial Upload')) ->appendParagraph( pht( 'This file has only been partially uploaded. It must be '. 'uploaded completely before you can download it.')) - ->addCancelButton($info_uri); + ->appendParagraph( + pht( + 'You can continue to the file detail page to monitor the '. + 'upload progress of the file.')) + ->addCancelButton($info_uri, pht('Continue')); + + return id(new AphrontDialogResponse()) + ->setDialog($dialog) + ->setHTTPResponseCode(404); } $this->file = $file; 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/markup/PhabricatorEmbedFileRemarkupRule.php b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php index 7f57db02b1..ecb0e4fd09 100644 --- a/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php +++ b/src/applications/files/markup/PhabricatorEmbedFileRemarkupRule.php @@ -16,6 +16,10 @@ final class PhabricatorEmbedFileRemarkupRule $objects = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withIDs($ids) + ->needTransforms( + array( + PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW, + )) ->execute(); $phids_key = self::KEY_EMBED_FILE_PHIDS; @@ -109,7 +113,15 @@ final class PhabricatorEmbedFileRemarkupRule default: $preview_key = PhabricatorFileThumbnailTransform::TRANSFORM_PREVIEW; $xform = PhabricatorFileTransform::getTransformByKey($preview_key); - $attrs['src'] = $file->getURIForTransform($xform); + + $existing_xform = $file->getTransform($preview_key); + if ($existing_xform) { + $xform_uri = $existing_xform->getCDNURI(); + } else { + $xform_uri = $file->getURIForTransform($xform); + } + + $attrs['src'] = $xform_uri; $dimensions = $xform->getTransformedDimensions($file); if ($dimensions) { diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index ba72156061..03446d9154 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -15,6 +15,7 @@ final class PhabricatorFileQuery private $maxLength; private $names; private $isPartial; + private $needTransforms; public function withIDs(array $ids) { $this->ids = $ids; @@ -117,6 +118,11 @@ final class PhabricatorFileQuery return $this; } + public function needTransforms(array $transforms) { + $this->needTransforms = $transforms; + return $this; + } + public function newResultObject() { return new PhabricatorFile(); } @@ -218,6 +224,44 @@ final class PhabricatorFileQuery return $files; } + protected function didFilterPage(array $files) { + $xform_keys = $this->needTransforms; + if ($xform_keys !== null) { + $xforms = id(new PhabricatorTransformedFile())->loadAllWhere( + 'originalPHID IN (%Ls) AND transform IN (%Ls)', + mpull($files, 'getPHID'), + $xform_keys); + + if ($xforms) { + $xfiles = id(new PhabricatorFile())->loadAllWhere( + 'phid IN (%Ls)', + mpull($xforms, 'getTransformedPHID')); + $xfiles = mpull($xfiles, null, 'getPHID'); + } + + $xform_map = array(); + foreach ($xforms as $xform) { + $xfile = idx($xfiles, $xform->getTransformedPHID()); + if (!$xfile) { + continue; + } + $original_phid = $xform->getOriginalPHID(); + $xform_key = $xform->getTransform(); + $xform_map[$original_phid][$xform_key] = $xfile; + } + + $default_xforms = array_fill_keys($xform_keys, null); + + foreach ($files as $file) { + $file_xforms = idx($xform_map, $file->getPHID(), array()); + $file_xforms += $default_xforms; + $file->attachTransforms($file_xforms); + } + } + + return $files; + } + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $joins = parent::buildJoinClauseParts($conn); diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index f1d4c11bd7..ff2a4d27ac 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -56,6 +56,7 @@ final class PhabricatorFile extends PhabricatorFileDAO private $objects = self::ATTACHABLE; private $objectPHIDs = self::ATTACHABLE; private $originalFile = self::ATTACHABLE; + private $transforms = self::ATTACHABLE; public static function initializeNewFile() { $app = id(new PhabricatorApplicationQuery()) @@ -139,6 +140,10 @@ final class PhabricatorFile extends PhabricatorFileDAO return 'F'.$this->getID(); } + public function scrambleSecret() { + return $this->setSecretKey($this->generateSecretKey()); + } + public static function readUploadedFileData($spec) { if (!$spec) { throw new Exception(pht('No file was uploaded!')); @@ -693,10 +698,10 @@ final class PhabricatorFile extends PhabricatorFileDAO pht('You must save a file before you can generate a view URI.')); } - return $this->getCDNURI(null); + return $this->getCDNURI(); } - private function getCDNURI($token) { + public function getCDNURI() { $name = self::normalizeFileName($this->getName()); $name = phutil_escape_uri($name); @@ -716,9 +721,6 @@ final class PhabricatorFile extends PhabricatorFileDAO $parts[] = $this->getSecretKey(); $parts[] = $this->getPHID(); - if ($token) { - $parts[] = $token; - } $parts[] = $name; $path = '/'.implode('/', $parts); @@ -733,19 +735,6 @@ final class PhabricatorFile extends PhabricatorFileDAO } } - /** - * Get the CDN URI for this file, including a one-time-use security token. - * - */ - public function getCDNURIWithToken() { - if (!$this->getPHID()) { - throw new Exception( - pht('You must save a file before you can generate a CDN URI.')); - } - - return $this->getCDNURI($this->generateOneTimeToken()); - } - public function getInfoURI() { return '/'.$this->getMonogram(); @@ -1116,38 +1105,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return $this; } - protected function generateOneTimeToken() { - $key = Filesystem::readRandomCharacters(16); - $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE; - - // Save the new secret. - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $token = id(new PhabricatorAuthTemporaryToken()) - ->setTokenResource($this->getPHID()) - ->setTokenType($token_type) - ->setTokenExpires(time() + phutil_units('1 hour in seconds')) - ->setTokenCode(PhabricatorHash::digest($key)) - ->save(); - unset($unguarded); - - return $key; - } - - public function validateOneTimeToken($token_code) { - $token_type = PhabricatorFileAccessTemporaryTokenType::TOKENTYPE; - - $token = id(new PhabricatorAuthTemporaryTokenQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withTokenResources(array($this->getPHID())) - ->withTokenTypes(array($token_type)) - ->withExpired(false) - ->withTokenCodes(array(PhabricatorHash::digest($token_code))) - ->executeOne(); - - return $token; - } - - /** * Write the policy edge between this file and some object. * @@ -1252,6 +1209,15 @@ final class PhabricatorFile extends PhabricatorFileDAO ->setURI($uri); } + public function attachTransforms(array $map) { + $this->transforms = $map; + return $this; + } + + public function getTransform($key) { + return $this->assertAttachedKey($this->transforms, $key); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php index f6199c9a03..8aa22c6578 100644 --- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php +++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php @@ -8,6 +8,133 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase { ); } + public function testFileDirectScramble() { + // Changes to a file's view policy should scramble the file secret. + + $engine = new PhabricatorTestStorageEngine(); + $data = Filesystem::readRandomCharacters(64); + + $author = $this->generateNewTestUser(); + + $params = array( + 'name' => 'test.dat', + 'viewPolicy' => PhabricatorPolicies::POLICY_USER, + 'authorPHID' => $author->getPHID(), + 'storageEngines' => array( + $engine, + ), + ); + + $file = PhabricatorFile::newFromFileData($data, $params); + + $secret1 = $file->getSecretKey(); + + // First, change the name: this should not scramble the secret. + $xactions = array(); + $xactions[] = id(new PhabricatorFileTransaction()) + ->setTransactionType(PhabricatorFileTransaction::TYPE_NAME) + ->setNewValue('test.dat2'); + + $engine = id(new PhabricatorFileEditor()) + ->setActor($author) + ->setContentSource($this->newContentSource()) + ->applyTransactions($file, $xactions); + + $file = $file->reload(); + + $secret2 = $file->getSecretKey(); + + $this->assertEqual( + $secret1, + $secret2, + pht('No secret scramble on non-policy edit.')); + + // Now, change the view policy. This should scramble the secret. + $xactions = array(); + $xactions[] = id(new PhabricatorFileTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) + ->setNewValue($author->getPHID()); + + $engine = id(new PhabricatorFileEditor()) + ->setActor($author) + ->setContentSource($this->newContentSource()) + ->applyTransactions($file, $xactions); + + $file = $file->reload(); + $secret3 = $file->getSecretKey(); + + $this->assertTrue( + ($secret1 !== $secret3), + pht('Changing file view policy should scramble secret.')); + } + + public function testFileIndirectScramble() { + // When a file is attached to an object like a task and the task view + // policy changes, the file secret should be scrambled. This invalidates + // old URIs if tasks get locked down. + + $engine = new PhabricatorTestStorageEngine(); + $data = Filesystem::readRandomCharacters(64); + + $author = $this->generateNewTestUser(); + + $params = array( + 'name' => 'test.dat', + 'viewPolicy' => $author->getPHID(), + 'authorPHID' => $author->getPHID(), + 'storageEngines' => array( + $engine, + ), + ); + + $file = PhabricatorFile::newFromFileData($data, $params); + $secret1 = $file->getSecretKey(); + + $task = ManiphestTask::initializeNewTask($author); + + $xactions = array(); + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setNewValue(pht('File Scramble Test Task')); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setNewValue('{'.$file->getMonogram().'}'); + + id(new ManiphestTransactionEditor()) + ->setActor($author) + ->setContentSource($this->newContentSource()) + ->applyTransactions($task, $xactions); + + $file = $file->reload(); + $secret2 = $file->getSecretKey(); + + $this->assertEqual( + $secret1, + $secret2, + pht( + 'File policy should not scramble when attached to '. + 'newly created object.')); + + $xactions = array(); + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) + ->setNewValue($author->getPHID()); + + id(new ManiphestTransactionEditor()) + ->setActor($author) + ->setContentSource($this->newContentSource()) + ->applyTransactions($task, $xactions); + + $file = $file->reload(); + $secret3 = $file->getSecretKey(); + + $this->assertTrue( + ($secret1 !== $secret3), + pht('Changing attached object view policy should scramble secret.')); + } + + public function testFileVisibility() { $engine = new PhabricatorTestStorageEngine(); $data = Filesystem::readRandomCharacters(64); diff --git a/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php b/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php deleted file mode 100644 index 73b5cd8c09..0000000000 --- a/src/applications/files/temporarytoken/PhabricatorFileAccessTemporaryTokenType.php +++ /dev/null @@ -1,17 +0,0 @@ -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 18b123bf3a..4960e7aa35 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -62,7 +62,7 @@ final class FundInitiativeViewController $timeline, $add_comment, )) - ->addPropertySection(pht('DETAILS'), $details); + ->addPropertySection(pht('Details'), $details); return $this->newPage() ->setTitle($title) diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 320e94d642..6b0e7c1e0d 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -539,7 +539,7 @@ final class HarbormasterBuildViewController $this->getStatus($build)); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('PROPERTIES')) + ->setHeaderText(pht('Properties')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($properties); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index fe124367df..390ff2ec3d 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -178,7 +178,7 @@ final class HarbormasterBuildableViewController : pht('Automatic Buildable')); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('PROPERTIES')) + ->setHeaderText(pht('Properties')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($properties); } diff --git a/src/applications/harbormaster/controller/HarbormasterStepViewController.php b/src/applications/harbormaster/controller/HarbormasterStepViewController.php index 07be537fa9..a404b48e88 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepViewController.php @@ -104,7 +104,7 @@ final class HarbormasterStepViewController } return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('PROPERTIES')) + ->setHeaderText(pht('Properties')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($view); } 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/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index f5e058d3f3..818eb7560f 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -54,8 +54,8 @@ final class HeraldRuleViewController extends HeraldController { ->setHeader($header) ->setCurtain($curtain) ->setMainColumn($timeline) - ->addPropertySection(pht('DETAILS'), $details) - ->addPropertySection(pht('DESCRIPTION'), $description); + ->addPropertySection(pht('Details'), $details) + ->addPropertySection(pht('Description'), $description); return $this->newPage() ->setTitle($title) diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php index e53c4fac8d..950944188b 100644 --- a/src/applications/home/controller/PhabricatorHomeMainController.php +++ b/src/applications/home/controller/PhabricatorHomeMainController.php @@ -51,11 +51,10 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController { $content = $nav; } - return $this->buildApplicationPage( - $content, - array( - 'title' => 'Phabricator', - )); + return $this->newPage() + ->setTitle('Phabricator') + ->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..1a39f2c143 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/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/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 8c12e5528a..386029c7ce 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -72,8 +72,8 @@ final class PhabricatorMacroViewController $timeline, $add_comment_form, )) - ->addPropertySection(pht('MACRO'), $file) - ->addPropertySection(pht('DETAILS'), $details); + ->addPropertySection(pht('Macro'), $file) + ->addPropertySection(pht('Details'), $details); return $this->newPage() ->setTitle($title_short) 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 285f919ce8..1007672e23 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -88,8 +88,8 @@ final class ManiphestTaskDetailController extends ManiphestController { $timeline, $comment_view, )) - ->addPropertySection(pht('DESCRIPTION'), $description) - ->addPropertySection(pht('DETAILS'), $details); + ->addPropertySection(pht('Description'), $description) + ->addPropertySection(pht('Details'), $details); return $this->newPage() ->setTitle($title) diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index 273c0c5472..8e5893a9b8 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -81,6 +81,59 @@ final class ManiphestEditEngine $owner_value = array($this->getViewer()->getPHID()); } + $column_documentation = pht(<<getColumnMap($object); + $fields = array( id(new PhabricatorHandlesEditField()) ->setKey('parent') @@ -95,19 +148,23 @@ final class ManiphestEditEngine ->setIsReorderable(false) ->setIsDefaultable(false) ->setIsLockable(false), - id(new PhabricatorHandlesEditField()) + id(new PhabricatorColumnsEditField()) ->setKey('column') ->setLabel(pht('Column')) - ->setDescription(pht('Workboard column to create this task into.')) - ->setConduitDescription(pht('Create into a workboard column.')) - ->setConduitTypeDescription(pht('PHID of workboard column.')) - ->setAliases(array('columnPHID')) - ->setTransactionType(ManiphestTransaction::TYPE_COLUMN) - ->setSingleValue(null) - ->setIsInvisible(true) + ->setDescription(pht('Create a task in a workboard column.')) + ->setConduitDescription( + pht('Move a task to one or more workboard columns.')) + ->setConduitTypeDescription( + pht('List of columns to move the task to.')) + ->setConduitDocumentation($column_documentation) + ->setAliases(array('columnPHID', 'columns', 'columnPHIDs')) + ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) ->setIsReorderable(false) ->setIsDefaultable(false) - ->setIsLockable(false), + ->setIsLockable(false) + ->setCommentActionLabel(pht('Move on Workboard')) + ->setCommentActionOrder(2000) + ->setColumnMap($column_map), id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) @@ -318,5 +375,87 @@ final class ManiphestEditEngine ->buildResponse(); } + private function getColumnMap(ManiphestTask $task) { + $phid = $task->getPHID(); + if (!$phid) { + return array(); + } + + $board_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $phid, + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); + if (!$board_phids) { + return array(); + } + + $viewer = $this->getViewer(); + + $layout_engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($viewer) + ->setBoardPHIDs($board_phids) + ->setObjectPHIDs(array($task->getPHID())) + ->executeLayout(); + + $map = array(); + foreach ($board_phids as $board_phid) { + $in_columns = $layout_engine->getObjectColumns($board_phid, $phid); + $in_columns = mpull($in_columns, null, 'getPHID'); + + $all_columns = $layout_engine->getColumns($board_phid); + if (!$all_columns) { + // This could be a project with no workboard, or a project the viewer + // does not have permission to see. + continue; + } + + $board = head($all_columns)->getProject(); + + $options = array(); + foreach ($all_columns as $column) { + $name = $column->getDisplayName(); + + $is_hidden = $column->isHidden(); + $is_selected = isset($in_columns[$column->getPHID()]); + + // Don't show hidden, subproject or milestone columns in this map + // unless the object is currently in the column. + $skip_column = ($is_hidden || $column->getProxyPHID()); + if ($skip_column) { + if (!$is_selected) { + continue; + } + } + + if ($is_hidden) { + $name = pht('(%s)', $name); + } + + if ($is_selected) { + $name = pht("\xE2\x97\x8F %s", $name); + } else { + $name = pht("\xE2\x97\x8B %s", $name); + } + + $option = array( + 'key' => $column->getPHID(), + 'label' => $name, + 'selected' => (bool)$is_selected, + ); + + $options[] = $option; + } + + $map[] = array( + 'label' => $board->getDisplayName(), + 'options' => $options, + ); + } + + $map = isort($map, 'label'); + $map = array_values($map); + + return $map; + } + } diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index b88c5a5923..6c9a7d0089 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -3,6 +3,8 @@ final class ManiphestTransactionEditor extends PhabricatorApplicationTransactionEditor { + private $moreValidationErrors = array(); + public function getEditorApplicationClass() { return 'PhabricatorManiphestApplication'; } @@ -22,14 +24,13 @@ final class ManiphestTransactionEditor $types[] = ManiphestTransaction::TYPE_DESCRIPTION; $types[] = ManiphestTransaction::TYPE_OWNER; $types[] = ManiphestTransaction::TYPE_SUBPRIORITY; - $types[] = ManiphestTransaction::TYPE_PROJECT_COLUMN; $types[] = ManiphestTransaction::TYPE_MERGED_INTO; $types[] = ManiphestTransaction::TYPE_MERGED_FROM; $types[] = ManiphestTransaction::TYPE_UNBLOCK; $types[] = ManiphestTransaction::TYPE_PARENT; - $types[] = ManiphestTransaction::TYPE_COLUMN; $types[] = ManiphestTransaction::TYPE_COVER_IMAGE; $types[] = ManiphestTransaction::TYPE_POINTS; + $types[] = PhabricatorTransactions::TYPE_COLUMNS; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -63,9 +64,6 @@ final class ManiphestTransactionEditor return $object->getDescription(); case ManiphestTransaction::TYPE_OWNER: return nonempty($object->getOwnerPHID(), null); - case ManiphestTransaction::TYPE_PROJECT_COLUMN: - // These are pre-populated. - return $xaction->getOldValue(); case ManiphestTransaction::TYPE_SUBPRIORITY: return $object->getSubpriority(); case ManiphestTransaction::TYPE_COVER_IMAGE: @@ -80,7 +78,7 @@ final class ManiphestTransactionEditor case ManiphestTransaction::TYPE_MERGED_FROM: return null; case ManiphestTransaction::TYPE_PARENT: - case ManiphestTransaction::TYPE_COLUMN: + case PhabricatorTransactions::TYPE_COLUMNS: return null; } } @@ -98,14 +96,13 @@ final class ManiphestTransactionEditor case ManiphestTransaction::TYPE_TITLE: case ManiphestTransaction::TYPE_DESCRIPTION: case ManiphestTransaction::TYPE_SUBPRIORITY: - case ManiphestTransaction::TYPE_PROJECT_COLUMN: case ManiphestTransaction::TYPE_MERGED_INTO: case ManiphestTransaction::TYPE_MERGED_FROM: case ManiphestTransaction::TYPE_UNBLOCK: case ManiphestTransaction::TYPE_COVER_IMAGE: return $xaction->getNewValue(); case ManiphestTransaction::TYPE_PARENT: - case ManiphestTransaction::TYPE_COLUMN: + case PhabricatorTransactions::TYPE_COLUMNS: return $xaction->getNewValue(); case ManiphestTransaction::TYPE_POINTS: $value = $xaction->getNewValue(); @@ -127,12 +124,8 @@ final class ManiphestTransactionEditor $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_PROJECT_COLUMN: - $new_column_phids = $new['columnPHIDs']; - $old_column_phids = $old['columnPHIDs']; - sort($new_column_phids); - sort($old_column_phids); - return ($old !== $new); + case PhabricatorTransactions::TYPE_COLUMNS: + return (bool)$new; } return parent::transactionHasEffect($object, $xaction); @@ -175,9 +168,6 @@ final class ManiphestTransactionEditor case ManiphestTransaction::TYPE_SUBPRIORITY: $object->setSubpriority($xaction->getNewValue()); return; - case ManiphestTransaction::TYPE_PROJECT_COLUMN: - // these do external (edge) updates - return; case ManiphestTransaction::TYPE_MERGED_INTO: $object->setStatus(ManiphestTaskStatus::getDuplicateStatus()); return; @@ -212,7 +202,7 @@ final class ManiphestTransactionEditor return; case ManiphestTransaction::TYPE_MERGED_FROM: case ManiphestTransaction::TYPE_PARENT: - case ManiphestTransaction::TYPE_COLUMN: + case PhabricatorTransactions::TYPE_COLUMNS: return; } } @@ -231,125 +221,10 @@ final class ManiphestTransactionEditor ->addEdge($parent_phid, $parent_type, $task_phid) ->save(); break; - case ManiphestTransaction::TYPE_PROJECT_COLUMN: - $board_phid = idx($xaction->getNewValue(), 'projectPHID'); - if (!$board_phid) { - throw new Exception( - pht( - "Expected '%s' in column transaction.", - 'projectPHID')); + case PhabricatorTransactions::TYPE_COLUMNS: + foreach ($xaction->getNewValue() as $move) { + $this->applyBoardMove($object, $move); } - - $old_phids = idx($xaction->getOldValue(), 'columnPHIDs', array()); - $new_phids = idx($xaction->getNewValue(), 'columnPHIDs', array()); - if (count($new_phids) !== 1) { - throw new Exception( - pht( - "Expected exactly one '%s' in column transaction.", - 'columnPHIDs')); - } - - $before_phid = idx($xaction->getNewValue(), 'beforePHID'); - $after_phid = idx($xaction->getNewValue(), 'afterPHID'); - - if (!$before_phid && !$after_phid && ($old_phids == $new_phids)) { - // If we are not moving the object between columns and also not - // reordering the position, this is a move on some other order - // (like priority). We can leave the positions untouched and just - // bail, there's no work to be done. - return; - } - - // Otherwise, we're either moving between columns or adjusting the - // object's position in the "natural" ordering, so we do need to update - // some rows. - - $object_phid = $object->getPHID(); - - // We're doing layout with the ominpotent viewer to make sure we don't - // remove positions in columns that exist, but which the actual actor - // can't see. - $omnipotent_viewer = PhabricatorUser::getOmnipotentUser(); - - $select_phids = array($board_phid); - - $descendants = id(new PhabricatorProjectQuery()) - ->setViewer($omnipotent_viewer) - ->withAncestorProjectPHIDs($select_phids) - ->execute(); - foreach ($descendants as $descendant) { - $select_phids[] = $descendant->getPHID(); - } - - $board_tasks = id(new ManiphestTaskQuery()) - ->setViewer($omnipotent_viewer) - ->withEdgeLogicPHIDs( - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, - PhabricatorQueryConstraint::OPERATOR_ANCESTOR, - array($select_phids)) - ->execute(); - - $board_tasks = mpull($board_tasks, null, 'getPHID'); - $board_tasks[$object_phid] = $object; - - // Make sure tasks are sorted by ID, so we lay out new positions in - // a consistent way. - $board_tasks = msort($board_tasks, 'getID'); - - $object_phids = array_keys($board_tasks); - - $engine = id(new PhabricatorBoardLayoutEngine()) - ->setViewer($omnipotent_viewer) - ->setBoardPHIDs(array($board_phid)) - ->setObjectPHIDs($object_phids) - ->executeLayout(); - - // TODO: This logic needs to be revised if we legitimately support - // multiple column positions. - - // NOTE: When a task is newly created, it's implicitly added to the - // backlog but we don't currently record that in the "$old_phids". Just - // clean it up for now. - $columns = $engine->getObjectColumns($board_phid, $object_phid); - foreach ($columns as $column) { - $engine->queueRemovePosition( - $board_phid, - $column->getPHID(), - $object_phid); - } - - // Remove all existing column positions on the board. - foreach ($old_phids as $column_phid) { - $engine->queueRemovePosition( - $board_phid, - $column_phid, - $object_phid); - } - - // Add new positions. - foreach ($new_phids as $column_phid) { - if ($before_phid) { - $engine->queueAddPositionBefore( - $board_phid, - $column_phid, - $object_phid, - $before_phid); - } else if ($after_phid) { - $engine->queueAddPositionAfter( - $board_phid, - $column_phid, - $object_phid, - $after_phid); - } else { - $engine->queueAddPosition( - $board_phid, - $column_phid, - $object_phid); - } - } - - $engine->applyPositionUpdates(); - break; default: break; @@ -492,13 +367,12 @@ final class ManiphestTransactionEditor $board_phids = array(); - $type_column = ManiphestTransaction::TYPE_PROJECT_COLUMN; + $type_columns = PhabricatorTransactions::TYPE_COLUMNS; foreach ($xactions as $xaction) { - if ($xaction->getTransactionType() == $type_column) { - $new = $xaction->getNewValue(); - $project_phid = idx($new, 'projectPHID'); - if ($project_phid) { - $board_phids[] = $project_phid; + if ($xaction->getTransactionType() == $type_columns) { + $moves = $xaction->getNewValue(); + foreach ($moves as $move) { + $board_phids[] = $move['boardPHID']; } } } @@ -815,38 +689,6 @@ final class ManiphestTransactionEditor last($with_effect)); } break; - case ManiphestTransaction::TYPE_COLUMN: - $with_effect = array(); - foreach ($xactions as $xaction) { - $column_phid = $xaction->getNewValue(); - if (!$column_phid) { - continue; - } - - $with_effect[] = $xaction; - - $column = $this->loadProjectColumn($column_phid); - if (!$column) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'Column PHID "%s" does not identify a visible column.', - $column_phid), - $xaction); - } - } - - if ($with_effect && !$this->getIsNewObject()) { - $errors[] = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Invalid'), - pht( - 'You can only put a task into an initial column during task '. - 'creation.'), - last($with_effect)); - } - break; case ManiphestTransaction::TYPE_OWNER: foreach ($xactions as $xaction) { $old = $xaction->getOldValue(); @@ -938,6 +780,19 @@ final class ManiphestTransactionEditor return $errors; } + protected function validateAllTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + $errors = parent::validateAllTransactions($object, $xactions); + + if ($this->moreValidationErrors) { + $errors = array_merge($errors, $this->moreValidationErrors); + } + + return $errors; + } + protected function expandTransactions( PhabricatorLiskDAO $object, array $xactions) { @@ -1009,28 +864,36 @@ final class ManiphestTransactionEditor $results = parent::expandTransaction($object, $xaction); - switch ($xaction->getTransactionType()) { - case ManiphestTransaction::TYPE_COLUMN: - $column_phid = $xaction->getNewValue(); - if (!$column_phid) { - break; - } + $type = $xaction->getTransactionType(); + switch ($type) { + case PhabricatorTransactions::TYPE_COLUMNS: + try { + $this->buildMoveTransaction($object, $xaction); - // When a task is created into a column, we also generate a transaction - // to actually put it in that column. - $column = $this->loadProjectColumn($column_phid); - $results[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN) - ->setOldValue( - array( - 'projectPHID' => $column->getProjectPHID(), - 'columnPHIDs' => array(), - )) - ->setNewValue( - array( - 'projectPHID' => $column->getProjectPHID(), - 'columnPHIDs' => array($column->getPHID()), - )); + // Implicilty add the task to any boards that we're moving it + // on, since moves on a board the task isn't part of are not + // meaningful. + $board_phids = ipull($xaction->getNewValue(), 'boardPHID'); + if ($board_phids) { + $results[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setIgnoreOnNoEffect(true) + ->setNewValue( + array( + '+' => array_fuse($board_phids), + )); + } + } catch (Exception $ex) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + $ex->getMessage(), + $xaction); + $this->moreValidationErrors[] = $error; + } break; case ManiphestTransaction::TYPE_OWNER: // If this is a no-op update, don't expand it. @@ -1057,13 +920,6 @@ final class ManiphestTransactionEditor return $results; } - private function loadProjectColumn($column_phid) { - return id(new PhabricatorProjectColumnQuery()) - ->setViewer($this->getActor()) - ->withPHIDs(array($column_phid)) - ->executeOne(); - } - protected function extractFilePHIDsFromCustomTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -1078,5 +934,264 @@ final class ManiphestTransactionEditor return $phids; } + private function buildMoveTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + $new = $xaction->getNewValue(); + if (!is_array($new)) { + $this->validateColumnPHID($new); + $new = array($new); + } + + $nearby_phids = array(); + foreach ($new as $key => $value) { + if (!is_array($value)) { + $this->validateColumnPHID($value); + $value = array( + 'columnPHID' => $value, + ); + } + + PhutilTypeSpec::checkMap( + $value, + array( + 'columnPHID' => 'string', + 'beforePHID' => 'optional string', + 'afterPHID' => 'optional string', + )); + + $new[$key] = $value; + + if (!empty($value['beforePHID'])) { + $nearby_phids[] = $value['beforePHID']; + } + + if (!empty($value['afterPHID'])) { + $nearby_phids[] = $value['afterPHID']; + } + } + + if ($nearby_phids) { + $nearby_objects = id(new PhabricatorObjectQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($nearby_phids) + ->execute(); + $nearby_objects = mpull($nearby_objects, null, 'getPHID'); + } else { + $nearby_objects = array(); + } + + $column_phids = ipull($new, 'columnPHID'); + if ($column_phids) { + $columns = id(new PhabricatorProjectColumnQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($column_phids) + ->execute(); + $columns = mpull($columns, null, 'getPHID'); + } else { + $columns = array(); + } + + $board_phids = mpull($columns, 'getProjectPHID'); + $object_phid = $object->getPHID(); + + $object_phids = $nearby_phids; + + // Note that we may not have an object PHID if we're creating a new + // object. + if ($object_phid) { + $object_phids[] = $object_phid; + } + + if ($object_phids) { + $layout_engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($this->getActor()) + ->setBoardPHIDs($board_phids) + ->setObjectPHIDs($object_phids) + ->setFetchAllBoards(true) + ->executeLayout(); + } + + foreach ($new as $key => $spec) { + $column_phid = $spec['columnPHID']; + $column = idx($columns, $column_phid); + if (!$column) { + throw new Exception( + pht( + 'Column move transaction specifies column PHID "%s", but there '. + 'is no corresponding column with this PHID.', + $column_phid)); + } + + $board_phid = $column->getProjectPHID(); + + $nearby = array(); + + if (!empty($spec['beforePHID'])) { + $nearby['beforePHID'] = $spec['beforePHID']; + } + + if (!empty($spec['afterPHID'])) { + $nearby['afterPHID'] = $spec['afterPHID']; + } + + if (count($nearby) > 1) { + throw new Exception( + pht( + 'Column move transaction moves object to multiple positions. '. + 'Specify only "beforePHID" or "afterPHID", not both.')); + } + + foreach ($nearby as $where => $nearby_phid) { + if (empty($nearby_objects[$nearby_phid])) { + throw new Exception( + pht( + 'Column move transaction specifies object "%s" as "%s", but '. + 'there is no corresponding object with this PHID.', + $object_phid, + $where)); + } + + $nearby_columns = $layout_engine->getObjectColumns( + $board_phid, + $nearby_phid); + $nearby_columns = mpull($nearby_columns, null, 'getPHID'); + + if (empty($nearby_columns[$column_phid])) { + throw new Exception( + pht( + 'Column move transaction specifies object "%s" as "%s" in '. + 'column "%s", but this object is not in that column!', + $nearby_phid, + $where, + $column_phid)); + } + } + + if ($object_phid) { + $old_columns = $layout_engine->getObjectColumns( + $board_phid, + $object_phid); + $old_column_phids = mpull($old_columns, 'getPHID'); + } else { + $old_column_phids = array(); + } + + $spec += array( + 'boardPHID' => $board_phid, + 'fromColumnPHIDs' => $old_column_phids, + ); + + // Check if the object is already in this column, and isn't being moved. + // We can just drop this column change if it has no effect. + $from_map = array_fuse($spec['fromColumnPHIDs']); + $already_here = isset($from_map[$column_phid]); + $is_reordering = (bool)$nearby; + + if ($already_here && !$is_reordering) { + unset($new[$key]); + } else { + $new[$key] = $spec; + } + } + + $new = array_values($new); + $xaction->setNewValue($new); + } + + private function applyBoardMove($object, array $move) { + $board_phid = $move['boardPHID']; + $column_phid = $move['columnPHID']; + $before_phid = idx($move, 'beforePHID'); + $after_phid = idx($move, 'afterPHID'); + + $object_phid = $object->getPHID(); + + // We're doing layout with the ominpotent viewer to make sure we don't + // remove positions in columns that exist, but which the actual actor + // can't see. + $omnipotent_viewer = PhabricatorUser::getOmnipotentUser(); + + $select_phids = array($board_phid); + + $descendants = id(new PhabricatorProjectQuery()) + ->setViewer($omnipotent_viewer) + ->withAncestorProjectPHIDs($select_phids) + ->execute(); + foreach ($descendants as $descendant) { + $select_phids[] = $descendant->getPHID(); + } + + $board_tasks = id(new ManiphestTaskQuery()) + ->setViewer($omnipotent_viewer) + ->withEdgeLogicPHIDs( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + PhabricatorQueryConstraint::OPERATOR_ANCESTOR, + array($select_phids)) + ->execute(); + + $board_tasks = mpull($board_tasks, null, 'getPHID'); + $board_tasks[$object_phid] = $object; + + // Make sure tasks are sorted by ID, so we lay out new positions in + // a consistent way. + $board_tasks = msort($board_tasks, 'getID'); + + $object_phids = array_keys($board_tasks); + + $engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($omnipotent_viewer) + ->setBoardPHIDs(array($board_phid)) + ->setObjectPHIDs($object_phids) + ->executeLayout(); + + // TODO: This logic needs to be revised when we legitimately support + // multiple column positions. + $columns = $engine->getObjectColumns($board_phid, $object_phid); + foreach ($columns as $column) { + $engine->queueRemovePosition( + $board_phid, + $column->getPHID(), + $object_phid); + } + + if ($before_phid) { + $engine->queueAddPositionBefore( + $board_phid, + $column_phid, + $object_phid, + $before_phid); + } else if ($after_phid) { + $engine->queueAddPositionAfter( + $board_phid, + $column_phid, + $object_phid, + $after_phid); + } else { + $engine->queueAddPosition( + $board_phid, + $column_phid, + $object_phid); + } + + $engine->applyPositionUpdates(); + } + + + private function validateColumnPHID($value) { + if (phid_get_type($value) == PhabricatorProjectColumnPHIDType::TYPECONST) { + return; + } + + throw new Exception( + pht( + 'When moving objects between columns on a board, columns must '. + 'be identified by PHIDs. This transaction uses "%s" to identify '. + 'a column, but that is not a valid column PHID.', + $value)); + } + + } diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index caf57a3f71..79901d7ae6 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -10,12 +10,10 @@ final class ManiphestTransaction const TYPE_PRIORITY = 'priority'; const TYPE_EDGE = 'edge'; const TYPE_SUBPRIORITY = 'subpriority'; - const TYPE_PROJECT_COLUMN = 'projectcolumn'; const TYPE_MERGED_INTO = 'mergedinto'; const TYPE_MERGED_FROM = 'mergedfrom'; const TYPE_UNBLOCK = 'unblock'; const TYPE_PARENT = 'parent'; - const TYPE_COLUMN = 'column'; const TYPE_COVER_IMAGE = 'cover-image'; const TYPE_POINTS = 'points'; @@ -48,7 +46,6 @@ final class ManiphestTransaction public function shouldGenerateOldValue() { switch ($this->getTransactionType()) { - case self::TYPE_PROJECT_COLUMN: case self::TYPE_EDGE: case self::TYPE_UNBLOCK: return false; @@ -85,10 +82,6 @@ final class ManiphestTransaction $phids[] = $old; } break; - case self::TYPE_PROJECT_COLUMN: - $phids[] = $new['projectPHID']; - $phids[] = head($new['columnPHIDs']); - break; case self::TYPE_MERGED_INTO: $phids[] = $new; break; @@ -152,18 +145,7 @@ final class ManiphestTransaction break; case self::TYPE_SUBPRIORITY: case self::TYPE_PARENT: - case self::TYPE_COLUMN: return true; - case self::TYPE_PROJECT_COLUMN: - $old_cols = idx($this->getOldValue(), 'columnPHIDs'); - $new_cols = idx($this->getNewValue(), 'columnPHIDs'); - - $old_cols = array_values($old_cols); - $new_cols = array_values($new_cols); - sort($old_cols); - sort($new_cols); - - return ($old_cols === $new_cols); case self::TYPE_COVER_IMAGE: // At least for now, don't show these. return true; @@ -308,7 +290,7 @@ final class ManiphestTransaction return pht('Reassigned'); } - case self::TYPE_PROJECT_COLUMN: + case PhabricatorTransactions::TYPE_COLUMNS: return pht('Changed Project Column'); case self::TYPE_PRIORITY: @@ -378,7 +360,7 @@ final class ManiphestTransaction case self::TYPE_DESCRIPTION: return 'fa-pencil'; - case self::TYPE_PROJECT_COLUMN: + case PhabricatorTransactions::TYPE_COLUMNS: return 'fa-columns'; case self::TYPE_MERGED_INTO: @@ -616,16 +598,6 @@ final class ManiphestTransaction $this->renderHandleList($removed)); } - case self::TYPE_PROJECT_COLUMN: - $project_phid = $new['projectPHID']; - $column_phid = head($new['columnPHIDs']); - return pht( - '%s moved this task to %s on the %s workboard.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($column_phid), - $this->renderHandleLink($project_phid)); - break; - case self::TYPE_MERGED_INTO: return pht( '%s closed this task as a duplicate of %s.', @@ -887,16 +859,6 @@ final class ManiphestTransaction $this->renderHandleList($removed)); } - case self::TYPE_PROJECT_COLUMN: - $project_phid = $new['projectPHID']; - $column_phid = head($new['columnPHIDs']); - return pht( - '%s moved %s to %s on the %s workboard.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid), - $this->renderHandleLink($column_phid), - $this->renderHandleLink($project_phid)); - case self::TYPE_MERGED_INTO: return pht( '%s merged task %s into %s.', @@ -961,7 +923,7 @@ final class ManiphestTransaction case self::TYPE_UNBLOCK: $tags[] = self::MAILTAG_UNBLOCK; break; - case self::TYPE_PROJECT_COLUMN: + case PhabricatorTransactions::TYPE_COLUMNS: $tags[] = self::MAILTAG_COLUMN; break; case PhabricatorTransactions::TYPE_COMMENT: diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index f86f9eb4d1..54068e1e71 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -63,7 +63,7 @@ final class PhabricatorApplicationDetailViewController $policies, $panels, )) - ->addPropertySection(pht('DETAILS'), $details); + ->addPropertySection(pht('Details'), $details); return $this->newPage() ->setTitle($title) diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php index ec43312e23..fda471b79f 100644 --- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php +++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php @@ -23,7 +23,8 @@ final class PhabricatorMetaMTAMailViewController $header = id(new PHUIHeaderView()) ->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/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/controller/NuanceConsoleController.php b/src/applications/nuance/controller/NuanceConsoleController.php index 6416c19aaf..d4a8f6c9ff 100644 --- a/src/applications/nuance/controller/NuanceConsoleController.php +++ b/src/applications/nuance/controller/NuanceConsoleController.php @@ -35,19 +35,25 @@ final class NuanceConsoleController extends NuanceController { $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/NuanceItemManageController.php b/src/applications/nuance/controller/NuanceItemManageController.php index c86d2cd985..2b6b4c89b8 100644 --- a/src/applications/nuance/controller/NuanceItemManageController.php +++ b/src/applications/nuance/controller/NuanceItemManageController.php @@ -40,7 +40,7 @@ final class NuanceItemManageController extends NuanceController { $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) - ->addPropertySection(pht('DETAILS'), $properties) + ->addPropertySection(pht('Details'), $properties) ->setMainColumn($timeline); return $this->newPage() diff --git a/src/applications/nuance/controller/NuanceSourceViewController.php b/src/applications/nuance/controller/NuanceSourceViewController.php index af602bfd7e..93facde3c1 100644 --- a/src/applications/nuance/controller/NuanceSourceViewController.php +++ b/src/applications/nuance/controller/NuanceSourceViewController.php @@ -40,8 +40,8 @@ final class NuanceSourceViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) - ->addPropertySection(pht('DETAILS'), $properties) - ->addPropertySection(pht('ROUTING'), $routing_list) + ->addPropertySection(pht('Details'), $properties) + ->addPropertySection(pht('Routing'), $routing_list) ->setMainColumn($timeline); return $this->newPage() 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/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php b/src/applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php new file mode 100644 index 0000000000..4dd21e2609 --- /dev/null +++ b/src/applications/oauthserver/query/PhabricatorOAuthServerTransactionQuery.php @@ -0,0 +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/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/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 72c21a9e39..4aa687ddc1 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -311,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()) { @@ -332,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( @@ -350,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 db31964773..1bbea88ec6 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -36,7 +36,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setSubheader($subheader) ->setCurtain($curtain) ->setMainColumn($timeline) - ->addPropertySection(pht('PROPERTIES'), $content); + ->addPropertySection(pht('Properties'), $content); return $this->newPage() ->setTitle($title) diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php index 7916279f69..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) { diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php index bc0909cd45..0e8f497936 100644 --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -264,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 51cf79ecff..08b438eaa2 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php @@ -57,7 +57,7 @@ final class PhabricatorPeopleProfileManageController $manage = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) - ->addPropertySection(pht('DETAILS'), $properties) + ->addPropertySection(pht('Details'), $properties) ->setMainColumn( array( $timeline, 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 cb9c558d5a..f77933df90 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -181,26 +181,48 @@ 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(); - } else { + $awards = mpull($awards, null, 'getBadgePHID'); + $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) { - $item = id(new PHUIBadgeView()) - ->setIcon($badge->getIcon()) - ->setHeader($badge->getName()) - ->setSubhead($badge->getFlavor()) - ->setQuality($badge->getQuality()); - $flex->addItem($item); + 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()) diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 0bdb9fab2d..77feeb313a 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -412,6 +412,10 @@ final class PhabricatorPeopleQuery $min_range = PhabricatorTime::getNow(); $max_range = $min_range + phutil_units('72 hours in seconds'); + // NOTE: We don't need to generate ghosts here, because we only care if + // the user is attending, and you can't attend a ghost event: RSVP'ing + // to it creates a real event. + $events = id(new PhabricatorCalendarEventQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withInvitedPHIDs(array_keys($rebuild)) 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/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..6ad0fe22cd 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/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php index 59d9273eaa..a0e1100004 100644 --- a/src/applications/phortune/controller/PhortuneMerchantViewController.php +++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php @@ -131,7 +131,7 @@ final class PhortuneMerchantViewController $view->addProperty(pht('Status'), $status_view); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) + ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($view); } @@ -146,7 +146,7 @@ final class PhortuneMerchantViewController $description = new PHUIRemarkupView($viewer, $description); $view->addTextContent($description); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DESCRIPTION')) + ->setHeaderText(pht('Description')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($view); } diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php index 0bf022e373..a434a94534 100644 --- a/src/applications/phortune/controller/PhortuneProductViewController.php +++ b/src/applications/phortune/controller/PhortuneProductViewController.php @@ -38,7 +38,7 @@ final class PhortuneProductViewController extends PhortuneController { $product->getPriceAsCurrency()->formatForDisplay()); $object_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) + ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); 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/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php index 0e1bc55b62..0aea396136 100644 --- a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php +++ b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php @@ -82,7 +82,7 @@ final class PhortuneSubscriptionViewController extends PhortuneController { $autopay_method); $details = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) + ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($properties); 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/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 85bfa1a660..7601888987 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -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/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 946e35c854..4703adade5 100644 --- a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php +++ b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php @@ -145,7 +145,7 @@ final class PhabricatorPhurlURLViewController } return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) + ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($properties); } diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index a1105c6c8e..529c396328 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -119,7 +119,7 @@ final class PonderQuestionViewController extends PonderController { ->setSubheader($subheader) ->setCurtain($curtain) ->setMainColumn($ponder_content) - ->addPropertySection(pht('DETAILS'), $details) + ->addPropertySection(pht('Details'), $details) ->addClass('ponder-question-view'); $page_objects = array_merge( diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index e2b2f9688d..5ead896035 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -1072,18 +1072,13 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $options = array(); } + $value = array( + 'columnPHID' => $dst->getPHID(), + ) + $options; + $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN) - ->setOldValue( - array( - 'projectPHID' => $board->getPHID(), - 'columnPHIDs' => array($src->getPHID()), - )) - ->setNewValue( - array( - 'projectPHID' => $board->getPHID(), - 'columnPHIDs' => array($dst->getPHID()), - ) + $options); + ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) + ->setNewValue(array($value)); $editor = id(new ManiphestTransactionEditor()) ->setActor($viewer) diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index d84df87e93..c827f5abab 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -48,7 +48,7 @@ final class PhabricatorProjectManageController $manage = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) - ->addPropertySection(pht('DETAILS'), $properties) + ->addPropertySection(pht('Details'), $properties) ->setMainColumn( array( $timeline, diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index d3540a1781..e529fab61e 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -68,26 +68,22 @@ final class PhabricatorProjectMoveController $xactions = array(); + $order_params = array(); if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { - $order_params = array( - 'afterPHID' => $after_phid, - 'beforePHID' => $before_phid, - ); - } else { - $order_params = array(); + if ($after_phid) { + $order_params['afterPHID'] = $after_phid; + } else if ($before_phid) { + $order_params['beforePHID'] = $before_phid; + } } $xactions[] = id(new ManiphestTransaction()) - ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN) + ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) ->setNewValue( array( - 'columnPHIDs' => array($column->getPHID()), - 'projectPHID' => $column->getProjectPHID(), - ) + $order_params) - ->setOldValue( - array( - 'columnPHIDs' => $old_column_phids, - 'projectPHID' => $column->getProjectPHID(), + array( + 'columnPHID' => $column->getPHID(), + ) + $order_params, )); if ($order == PhabricatorProjectColumn::ORDER_PRIORITY) { diff --git a/src/applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php index cd481946b2..ce9fbf332c 100644 --- a/src/applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php +++ b/src/applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php @@ -50,6 +50,7 @@ final class PhabricatorProjectsEditEngineExtension ->setIsCopyable(true) ->setUseEdgeTransactions(true) ->setCommentActionLabel(pht('Change Project Tags')) + ->setCommentActionOrder(8000) ->setDescription(pht('Select project tags for the object.')) ->setTransactionType($edge_type) ->setMetadataValue('edge:type', $project_edge_type) diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 061886f6f0..2172156e38 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -573,7 +573,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO public function getDisplayColor() { if ($this->isMilestone()) { - return PhabricatorProjectIconSet::getDefaultColorKey(); + return $this->getParentProject()->getColor(); } return $this->getColor(); diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php index 28aed0f5a8..660aafcc1f 100644 --- a/src/applications/project/storage/PhabricatorProjectColumn.php +++ b/src/applications/project/storage/PhabricatorProjectColumn.php @@ -170,16 +170,14 @@ final class PhabricatorProjectColumn // Normal columns and subproject columns go first, in a user-controlled // order. - // All the milestone columns go last, in reverse order (newest on the - // left) so that you don't have to scroll across older milestones to get - // to the newest ones. + // All the milestone columns go last, in their sequential order. if (!$proxy || !$proxy->isMilestone()) { $group = 'A'; $sequence = $this->getSequence(); } else { $group = 'B'; - $sequence = (10000000 - $proxy->getMilestoneNumber()); + $sequence = $proxy->getMilestoneNumber(); } return sprintf('%s%012d', $group, $sequence); 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/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php index a754f3c3d4..9d987636b6 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php @@ -21,7 +21,7 @@ final class PhabricatorRepositoryManagementUpdateWorkflow ->setSynopsis( pht( 'Update __repository__. This performs the __pull__, __discover__, '. - '__ref__ and __mirror__ operations and is primarily an internal '. + '__refs__ and __mirror__ operations and is primarily an internal '. 'workflow.')) ->setArguments( array( 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/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 728e2131ad..bf9b171031 100644 --- a/src/applications/settings/controller/PhabricatorSettingsMainController.php +++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php @@ -61,17 +61,18 @@ final class PhabricatorSettingsMainController '/p/'.$this->getUser()->getUsername().'/'); } $crumbs->addTextCrumb($panel->getPanelName()); - $nav->appendChild( - array( - $crumbs, - $response, - )); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $panel->getPanelName(), - )); + $title = $panel->getPanelName(); + + $view = id(new PHUITwoColumnView()) + ->setNavigation($nav) + ->setMainColumn($response); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } private function buildPanels() { diff --git a/src/applications/spaces/controller/PhabricatorSpacesViewController.php b/src/applications/spaces/controller/PhabricatorSpacesViewController.php index 8319f19a6e..495a0c8dee 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesViewController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesViewController.php @@ -45,7 +45,7 @@ final class PhabricatorSpacesViewController } $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) + ->setHeaderText(pht('Details')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->addPropertyList($property_list); diff --git a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php index 19b9281d02..a0a6cbf7be 100644 --- a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php +++ b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php @@ -45,6 +45,7 @@ final class PhabricatorSubscriptionsEditEngineExtension ->setIsCopyable(true) ->setUseEdgeTransactions(true) ->setCommentActionLabel(pht('Change Subscribers')) + ->setCommentActionOrder(9000) ->setDescription(pht('Choose subscribers.')) ->setTransactionType($subscribers_type) ->setValue($sub_phids); diff --git a/src/applications/system/controller/PhabricatorRobotsController.php b/src/applications/system/controller/PhabricatorRobotsController.php index bfc548fc04..e413984f5a 100644 --- a/src/applications/system/controller/PhabricatorRobotsController.php +++ b/src/applications/system/controller/PhabricatorRobotsController.php @@ -32,6 +32,7 @@ final class PhabricatorRobotsController extends PhabricatorController { return id(new AphrontPlainTextResponse()) ->setContent($content) - ->setCacheDurationInSeconds(phutil_units('2 hours in seconds')); + ->setCacheDurationInSeconds(phutil_units('2 hours in seconds')) + ->setCanCDN(true); } } 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/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php b/src/applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php new file mode 100644 index 0000000000..bc68b7b642 --- /dev/null +++ b/src/applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php @@ -0,0 +1,27 @@ +columnMap = $column_map; + return $this; + } + + public function getColumnMap() { + return $this->columnMap; + } + + public function getPHUIXControlType() { + return 'optgroups'; + } + + public function getPHUIXControlSpecification() { + return array( + 'groups' => $this->getColumnMap(), + ); + } + +} diff --git a/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php b/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php index 548039ce15..dc676630ba 100644 --- a/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php +++ b/src/applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php @@ -6,6 +6,7 @@ abstract class PhabricatorEditEngineCommentAction extends Phobject { private $label; private $value; private $initialValue; + private $order; abstract public function getPHUIXControlType(); abstract public function getPHUIXControlSpecification(); @@ -37,6 +38,20 @@ abstract class PhabricatorEditEngineCommentAction extends Phobject { return $this->value; } + public function setOrder($order) { + $this->order = $order; + return $this; + } + + public function getOrder() { + return $this->order; + } + + public function getSortVector() { + return id(new PhutilSortVector()) + ->addInt($this->getOrder()); + } + public function setInitialValue($initial_value) { $this->initialValue = $initial_value; return $this; diff --git a/src/applications/transactions/constants/PhabricatorTransactions.php b/src/applications/transactions/constants/PhabricatorTransactions.php index 172f80ba31..7d8e8afb1a 100644 --- a/src/applications/transactions/constants/PhabricatorTransactions.php +++ b/src/applications/transactions/constants/PhabricatorTransactions.php @@ -14,6 +14,7 @@ final class PhabricatorTransactions extends Phobject { const TYPE_INLINESTATE = 'core:inlinestate'; const TYPE_SPACE = 'core:space'; const TYPE_CREATE = 'core:create'; + const TYPE_COLUMNS = 'core:columns'; const COLOR_RED = 'red'; const COLOR_ORANGE = 'orange'; diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 9655a83e65..185ac13a5a 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1382,6 +1382,8 @@ abstract class PhabricatorEditEngine $comment_actions[$key] = $comment_action; } + $comment_actions = msortv($comment_actions, 'getSortVector'); + $view->setCommentActions($comment_actions); return $view; 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/editfield/PhabricatorColumnsEditField.php b/src/applications/transactions/editfield/PhabricatorColumnsEditField.php new file mode 100644 index 0000000000..fef51e2864 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorColumnsEditField.php @@ -0,0 +1,42 @@ +columnMap = $column_map; + return $this; + } + + public function getColumnMap() { + return $this->columnMap; + } + + protected function newControl() { + $control = id(new AphrontFormHandlesControl()); + $control->setIsInvisible(true); + + return $control; + } + + protected function newHTTPParameterType() { + return new AphrontPHIDListHTTPParameterType(); + } + + protected function newConduitParameterType() { + return new ConduitColumnsParameterType(); + } + + protected function newCommentAction() { + $column_map = $this->getColumnMap(); + if (!$column_map) { + return null; + } + + return id(new PhabricatorEditEngineColumnsCommentAction()) + ->setColumnMap($this->getColumnMap()); + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index 1216370819..3b4c4e264b 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -24,6 +24,7 @@ abstract class PhabricatorEditField extends Phobject { private $commentActionLabel; private $commentActionValue; + private $commentActionOrder = 1000; private $hasCommentActionValue; private $isLocked; @@ -243,6 +244,15 @@ abstract class PhabricatorEditField extends Phobject { return $this->commentActionLabel; } + public function setCommentActionOrder($order) { + $this->commentActionOrder = $order; + return $this; + } + + public function getCommentActionOrder() { + return $this->commentActionOrder; + } + public function setCommentActionValue($comment_action_value) { $this->hasCommentActionValue = true; $this->commentActionValue = $comment_action_value; @@ -686,7 +696,8 @@ abstract class PhabricatorEditField extends Phobject { $action ->setKey($this->getKey()) ->setLabel($label) - ->setValue($this->getValueForCommentAction($value)); + ->setValue($this->getValueForCommentAction($value)) + ->setOrder($this->getCommentActionOrder()); return $action; } diff --git a/src/applications/transactions/editfield/PhabricatorEpochEditField.php b/src/applications/transactions/editfield/PhabricatorEpochEditField.php new file mode 100644 index 0000000000..c5dabe6171 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorEpochEditField.php @@ -0,0 +1,21 @@ +setViewer($this->getViewer()); + } + + protected function newHTTPParameterType() { + return new AphrontEpochHTTPParameterType(); + } + + protected function newConduitParameterType() { + // TODO: This isn't correct, but we don't have any methods which use this + // yet. + return new ConduitIntParameterType(); + } + +} diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index d7c0bbf5f6..3f51ca7296 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -678,6 +678,10 @@ abstract class PhabricatorApplicationTransactionEditor $editor->save(); break; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_SPACE: + $this->scrambleFileSecrets($object); + break; } } @@ -914,7 +918,6 @@ abstract class PhabricatorApplicationTransactionEditor } $xactions = $this->applyFinalEffects($object, $xactions); - if ($read_locking) { $object->endReadLocking(); $read_locking = false; @@ -3471,4 +3474,64 @@ abstract class PhabricatorApplicationTransactionEditor return $phids; } + /** + * When the view policy for an object is changed, scramble the secret keys + * for attached files to invalidate existing URIs. + */ + private function scrambleFileSecrets($object) { + // If this is a newly created object, we don't need to scramble anything + // since it couldn't have been previously published. + if ($this->getIsNewObject()) { + return; + } + + // If the object is a file itself, scramble it. + if ($object instanceof PhabricatorFile) { + if ($this->shouldScramblePolicy($object->getViewPolicy())) { + $object->scrambleSecret(); + $object->save(); + } + } + + $phid = $object->getPHID(); + + $attached_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $phid, + PhabricatorObjectHasFileEdgeType::EDGECONST); + if (!$attached_phids) { + return; + } + + $omnipotent_viewer = PhabricatorUser::getOmnipotentUser(); + + $files = id(new PhabricatorFileQuery()) + ->setViewer($omnipotent_viewer) + ->withPHIDs($attached_phids) + ->execute(); + foreach ($files as $file) { + $view_policy = $file->getViewPolicy(); + if ($this->shouldScramblePolicy($view_policy)) { + $file->scrambleSecret(); + $file->save(); + } + } + } + + + /** + * Check if a policy is strong enough to justify scrambling. Objects which + * are set to very open policies don't need to scramble their files, and + * files with very open policies don't need to be scrambled when associated + * objects change. + */ + private function shouldScramblePolicy($policy) { + switch ($policy) { + case PhabricatorPolicies::POLICY_PUBLIC: + case PhabricatorPolicies::POLICY_USER: + return false; + } + + return true; + } + } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index ee8a7a47a3..e3fe7707f6 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -256,6 +256,15 @@ abstract class PhabricatorApplicationTransaction $phids[] = ipull($old, 'dst'); $phids[] = ipull($new, 'dst'); break; + case PhabricatorTransactions::TYPE_COLUMNS: + foreach ($new as $move) { + $phids[] = array( + $move['columnPHID'], + $move['boardPHID'], + ); + $phids[] = $move['fromColumnPHIDs']; + } + break; case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: @@ -407,6 +416,8 @@ abstract class PhabricatorApplicationTransaction return 'fa-trophy'; case PhabricatorTransactions::TYPE_SPACE: return 'fa-th-large'; + case PhabricatorTransactions::TYPE_COLUMNS: + return 'fa-columns'; } return 'fa-pencil'; @@ -501,9 +512,10 @@ abstract class PhabricatorApplicationTransaction return true; } - if (!strlen($old)) { + if (!is_array($old) && !strlen($old)) { return true; } + break; } } @@ -549,6 +561,9 @@ abstract class PhabricatorApplicationTransaction if ($field) { return $field->shouldHideInApplicationTransactions($this); } + break; + case PhabricatorTransactions::TYPE_COLUMNS: + return !$this->getInterestingMoves($this->getNewValue()); case PhabricatorTransactions::TYPE_EDGE: $edge_type = $this->getMetadataValue('edge:type'); switch ($edge_type) { @@ -712,6 +727,10 @@ abstract class PhabricatorApplicationTransaction return pht('This object is already in that space.'); case PhabricatorTransactions::TYPE_EDGE: return pht('Edges already exist; transaction has no effect.'); + case PhabricatorTransactions::TYPE_COLUMNS: + return pht( + 'You have not moved this object to any columns it is not '. + 'already in.'); } return pht( @@ -930,6 +949,44 @@ abstract class PhabricatorApplicationTransaction } break; + case PhabricatorTransactions::TYPE_COLUMNS: + $moves = $this->getInterestingMoves($new); + if (count($moves) == 1) { + $move = head($moves); + $from_columns = $move['fromColumnPHIDs']; + $to_column = $move['columnPHID']; + $board_phid = $move['boardPHID']; + if (count($from_columns) == 1) { + return pht( + '%s moved this task from %s to %s on the %s board.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink(head($from_columns)), + $this->renderHandleLink($to_column), + $this->renderHandleLink($board_phid)); + } else { + return pht( + '%s moved this task to %s on the %s board.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($to_column), + $this->renderHandleLink($board_phid)); + } + } else { + $fragments = array(); + foreach ($moves as $move) { + $fragments[] = pht( + '%s (%s)', + $this->renderHandleLink($board_phid), + $this->renderHandleLink($to_column)); + } + + return pht( + '%s moved this task on %s board(s): %s.', + $this->renderHandleLink($author_phid), + phutil_count($moves), + phutil_implode_html(', ', $fragments)); + } + break; + default: return pht( '%s edited this %s.', @@ -1058,6 +1115,47 @@ abstract class PhabricatorApplicationTransaction return null; } + case PhabricatorTransactions::TYPE_COLUMNS: + $moves = $this->getInterestingMoves($new); + if (count($moves) == 1) { + $move = head($moves); + $from_columns = $move['fromColumnPHIDs']; + $to_column = $move['columnPHID']; + $board_phid = $move['boardPHID']; + if (count($from_columns) == 1) { + return pht( + '%s moved %s from %s to %s on the %s board.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + $this->renderHandleLink(head($from_columns)), + $this->renderHandleLink($to_column), + $this->renderHandleLink($board_phid)); + } else { + return pht( + '%s moved %s to %s on the %s board.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + $this->renderHandleLink($to_column), + $this->renderHandleLink($board_phid)); + } + } else { + $fragments = array(); + foreach ($moves as $move) { + $fragments[] = pht( + '%s (%s)', + $this->renderHandleLink($board_phid), + $this->renderHandleLink($to_column)); + } + + return pht( + '%s moved %s on %s board(s): %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + phutil_count($moves), + phutil_implode_html(', ', $fragments)); + } + break; + } return $this->getTitle(); @@ -1364,6 +1462,18 @@ abstract class PhabricatorApplicationTransaction return true; } + private function getInterestingMoves(array $moves) { + // Remove moves which only shift the position of a task within a column. + foreach ($moves as $key => $move) { + $from_phids = array_fuse($move['fromColumnPHIDs']); + if (isset($from_phids[$move['columnPHID']])) { + unset($moves[$key]); + } + } + + return $moves; + } + /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ 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/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/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/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/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index 25dec8eef0..38420748d8 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -130,10 +130,13 @@ final class AphrontFormDateControl extends AphrontFormControl { $date_format = $this->getDateFormat(); $timezone = $this->getTimezone(); - $datetime = new DateTime($this->valueDate, $timezone); - $date = $datetime->format($date_format); + try { + $datetime = new DateTime($this->valueDate, $timezone); + } catch (Exception $ex) { + return $this->valueDate; + } - return $date; + return $datetime->format($date_format); } private function getTimeFormat() { diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index f533bcaddb..114340006e 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -84,10 +84,33 @@ final class AphrontFormDateControlValue extends Phobject { $value = new AphrontFormDateControlValue(); $value->viewer = $request->getViewer(); - list($value->valueDate, $value->valueTime) = - $value->getFormattedDateFromDate( - $request->getStr($key.'_d'), - $request->getStr($key.'_t')); + $datetime = $request->getStr($key); + if (strlen($datetime)) { + $date = $datetime; + $time = null; + } else { + $date = $request->getStr($key.'_d'); + $time = $request->getStr($key.'_t'); + } + + // If this looks like an epoch timestamp, prefix it with "@" so that + // DateTime() reads it as one. Assume small numbers are a "Ymd" digit + // string instead of an epoch timestamp for a time in 1970. + if (ctype_digit($date) && ($date > 30000000)) { + $date = '@'.$date; + $time = null; + } + + $value->valueDate = $date; + $value->valueTime = $time; + + $formatted = $value->getFormattedDateFromDate( + $value->valueDate, + $value->valueTime); + + if ($formatted) { + list($value->valueDate, $value->valueTime) = $formatted; + } $value->valueEnabled = $request->getStr($key.'_e'); return $value; @@ -96,6 +119,11 @@ final class AphrontFormDateControlValue extends Phobject { public static function newFromEpoch(PhabricatorUser $viewer, $epoch) { $value = new AphrontFormDateControlValue(); $value->viewer = $viewer; + + if (!$epoch) { + return $value; + } + $readable = $value->formatTime($epoch, 'Y!m!d!g:i A'); $readable = explode('!', $readable, 4); @@ -120,10 +148,16 @@ final class AphrontFormDateControlValue extends Phobject { $value = new AphrontFormDateControlValue(); $value->viewer = $viewer; - list($value->valueDate, $value->valueTime) = - $value->getFormattedDateFromDate( - idx($dictionary, 'd'), - idx($dictionary, 't')); + $value->valueDate = idx($dictionary, 'd'); + $value->valueTime = idx($dictionary, 't'); + + $formatted = $value->getFormattedDateFromDate( + $value->valueDate, + $value->valueTime); + + if ($formatted) { + list($value->valueDate, $value->valueTime) = $formatted; + } $value->valueEnabled = idx($dictionary, 'e'); @@ -170,37 +204,12 @@ final class AphrontFormDateControlValue extends Phobject { return null; } - $date = $this->valueDate; - $time = $this->valueTime; - $zone = $this->getTimezone(); - - if (!strlen($time)) { + $datetime = $this->newDateTime($this->valueDate, $this->valueTime); + if (!$datetime) { return null; } - $colloquial = array( - 'elevenses' => '11:00 AM', - 'morning tea' => '11:00 AM', - 'noon' => '12:00 PM', - 'high noon' => '12:00 PM', - 'lunch' => '12:00 PM', - 'tea time' => '3:00 PM', - 'witching hour' => '12:00 AM', - 'midnight' => '12:00 AM', - ); - - $normalized = phutil_utf8_strtolower($time); - if (isset($colloquial[$normalized])) { - $time = $colloquial[$normalized]; - } - - try { - $datetime = new DateTime("{$date} {$time}", $zone); - $value = $datetime->format('U'); - } catch (Exception $ex) { - $value = null; - } - return $value; + return $datetime->format('U'); } private function getTimeFormat() { @@ -214,25 +223,34 @@ final class AphrontFormDateControlValue extends Phobject { } private function getFormattedDateFromDate($date, $time) { - $original_input = $date; - $zone = $this->getTimezone(); - $separator = $this->getFormatSeparator(); - $parts = preg_split('@[,./:-]@', $date); - $date = implode($separator, $parts); - $date = id(new DateTime($date, $zone)); - - if ($date) { - $date = $date->format($this->getDateFormat()); - } else { - $date = $original_input; + $datetime = $this->newDateTime($date, $time); + if (!$datetime) { + return null; } - $date = id(new DateTime("{$date} {$time}", $zone)); - return array( - $date->format($this->getDateFormat()), - $date->format($this->getTimeFormat()), + $datetime->format($this->getDateFormat()), + $datetime->format($this->getTimeFormat()), ); + + return array($date, $time); + } + + private function newDateTime($date, $time) { + $date = $this->getStandardDateFormat($date); + $time = $this->getStandardTimeFormat($time); + try { + $datetime = new DateTime("{$date} {$time}"); + } catch (Exception $ex) { + return null; + } + + // Set the timezone explicitly because it is ignored in the constructor + // if the date is an epoch timestamp. + $zone = $this->getTimezone(); + $datetime->setTimezone($zone); + + return $datetime; } private function getFormattedDateFromParts( @@ -261,16 +279,7 @@ final class AphrontFormDateControlValue extends Phobject { } public function getDateTime() { - $epoch = $this->getEpoch(); - $date = null; - - if ($epoch) { - $zone = $this->getTimezone(); - $date = new DateTime('@'.$epoch); - $date->setTimeZone($zone); - } - - return $date; + return $this->newDateTime(); } private function getTimezone() { @@ -283,5 +292,56 @@ final class AphrontFormDateControlValue extends Phobject { return $this->zone; } + private function getStandardDateFormat($date) { + $colloquial = array( + 'newyear' => 'January 1', + 'valentine' => 'February 14', + 'pi' => 'March 14', + 'christma' => 'December 25', + ); + + // Lowercase the input, then remove punctuation, a "day" suffix, and an + // "s" if one is present. This allows all of these to match. This allows + // variations like "New Year's Day" and "New Year" to both match. + $normalized = phutil_utf8_strtolower($date); + $normalized = preg_replace('/[^a-z]/', '', $normalized); + $normalized = preg_replace('/day\z/', '', $normalized); + $normalized = preg_replace('/s\z/', '', $normalized); + + if (isset($colloquial[$normalized])) { + return $colloquial[$normalized]; + } + + $separator = $this->getFormatSeparator(); + $parts = preg_split('@[,./:-]@', $date); + return implode($separator, $parts); + } + + private function getStandardTimeFormat($time) { + $colloquial = array( + 'crack of dawn' => '5:00 AM', + 'dawn' => '6:00 AM', + 'early' => '7:00 AM', + 'morning' => '8:00 AM', + 'elevenses' => '11:00 AM', + 'morning tea' => '11:00 AM', + 'noon' => '12:00 PM', + 'high noon' => '12:00 PM', + 'lunch' => '12:00 PM', + 'afternoon' => '2:00 PM', + 'tea time' => '3:00 PM', + 'evening' => '7:00 PM', + 'late' => '11:00 PM', + 'witching hour' => '12:00 AM', + 'midnight' => '12:00 AM', + ); + + $normalized = phutil_utf8_strtolower($time); + if (isset($colloquial[$normalized])) { + $time = $colloquial[$normalized]; + } + + return $time; + } } diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index d174547559..819e726309 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -4,6 +4,7 @@ final class PHUITwoColumnView extends AphrontTagView { private $mainColumn; private $sideColumn = null; + private $navigation; private $display; private $fluid; private $header; @@ -25,6 +26,12 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function setNavigation($nav) { + $this->navigation = $nav; + $this->display = self::DISPLAY_LEFT; + return $this; + } + public function setHeader(PHUIHeaderView $header) { $this->header = $header; return $this; @@ -162,14 +169,24 @@ final class PHUITwoColumnView extends AphrontTagView { private function buildSideColumn() { + $classes = array(); + $classes[] = 'phui-side-column'; + $navigation = null; + if ($this->navigation) { + $classes[] = 'side-has-nav'; + $navigation = id(new PHUIObjectBoxView()) + ->appendChild($this->navigation); + } + $curtain = $this->getCurtain(); return phutil_tag( 'div', array( - 'class' => 'phui-side-column', + 'class' => implode($classes, ' '), ), array( + $navigation, $curtain, $this->sideColumn, )); 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/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 35f5067fec..7fed4a535d 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -93,11 +93,11 @@ } .phui-box.phui-box-blue-property .phui-header-header { - font-size: 13px; + font-size: {$biggerfontsize}; color: {$bluetext}; } -.phui-box-blue-property .phui-object-item-list-view.phui-object-list-flush { +.phui-box-blue-property .phui-object-item-list-view { padding: 2px 8px; } diff --git a/webroot/rsrc/css/phui/phui-crumbs-view.css b/webroot/rsrc/css/phui/phui-crumbs-view.css index 47fd0a4da9..6e99b70e19 100644 --- a/webroot/rsrc/css/phui/phui-crumbs-view.css +++ b/webroot/rsrc/css/phui/phui-crumbs-view.css @@ -5,7 +5,7 @@ .phui-crumbs-view { overflow: hidden; vertical-align: top; - padding: 0 8px 0 16px; + padding: 0 20px 0 28px; /* TODO: Position this over the slider for Differential's file tree view. Remove this once that gets sorted out. */ position: relative; 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-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 600945103d..4d71c3d5ba 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -70,6 +70,16 @@ width: 300px; } +.device-desktop .phui-two-column-view.phui-side-column-left .phui-main-column { + float: right; + width: calc(100% - 280px); +} + +.device-desktop .phui-two-column-view.phui-side-column-left .phui-side-column { + float: left; + width: 260px; +} + .device .phui-side-column { margin-bottom: 20px; } @@ -202,3 +212,22 @@ .phui-header-shell + .phui-info-view { margin: 16px; } + +/* Navigation */ + +.phui-two-column-view .side-has-nav .phabricator-nav-local { + width: auto; + position: static; + margin: 0; +} + +.device .phui-two-column-view .side-has-nav { + display: none; +} + +/* Document View */ + +.phui-two-column-view .phui-two-column-content .phui-document-fluid + .phui-document-view { + margin: 0 0 20px 0; +} diff --git a/webroot/rsrc/js/phuix/PHUIXFormControl.js b/webroot/rsrc/js/phuix/PHUIXFormControl.js index cd004b0e8f..5a53bafd7b 100644 --- a/webroot/rsrc/js/phuix/PHUIXFormControl.js +++ b/webroot/rsrc/js/phuix/PHUIXFormControl.js @@ -38,6 +38,9 @@ JX.install('PHUIXFormControl', { case 'points': input = this._newPoints(spec); break; + case 'optgroups': + input = this._newOptgroups(spec); + break; default: // TODO: Default or better error? JX.$E('Bad Input Type'); @@ -171,6 +174,38 @@ JX.install('PHUIXFormControl', { var node = JX.$N('input', attrs); + return { + node: node, + get: function() { + return node.value; + }, + set: function(value) { + node.value = value; + } + }; + }, + + _newOptgroups: function(spec) { + var value = spec.value || null; + + var optgroups = []; + for (var ii = 0; ii < spec.groups.length; ii++) { + var group = spec.groups[ii]; + var options = []; + for (var jj = 0; jj < group.options.length; jj++) { + var option = group.options[jj]; + options.push(JX.$N('option', {value: option.key}, option.label)); + + if (option.selected && (value === null)) { + value = option.key; + } + } + optgroups.push(JX.$N('optgroup', {label: group.label}, options)); + } + + var node = JX.$N('select', {}, optgroups); + node.value = value; + return { node: node, get: function() {